diff --git a/.gitignore b/.gitignore index 9e7abee..edb9b2a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,8 @@ docs/ # Dotenv file .env -.DS_Store \ No newline at end of file +.DS_Store + +# Create2 build files +.tmp +create2 diff --git a/.gitmodules b/.gitmodules index a4badc1..22e283c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "lib/openzeppelin-contracts"] - path = lib/openzeppelin-contracts - url = https://github.com/OpenZeppelin/openzeppelin-contracts -[submodule "lib/solady"] - path = lib/solady - url = https://github.com/Vectorized/solady [submodule "lib/LayerZero-v2"] path = lib/LayerZero-v2 url = https://github.com/clustersxyz/LayerZero-v2 @@ -17,3 +11,15 @@ path = lib/devtools url = https://github.com/0xfoobar/devtools branch = patch-1 +[submodule "lib/soledge"] + path = lib/soledge + url = https://github.com/Vectorized/soledge +[submodule "lib/multicaller"] + path = lib/multicaller + url = https://github.com/vectorized/multicaller +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/openzeppelin/openzeppelin-contracts +[submodule "lib/solady"] + path = lib/solady + url = https://github.com/vectorized/solady diff --git a/build_create2_deployments.sh b/build_create2_deployments.sh new file mode 100755 index 0000000..bd4b7ab --- /dev/null +++ b/build_create2_deployments.sh @@ -0,0 +1,57 @@ +# Run the formatter. +forge fmt; + +# Create the create2 deployments directory. +mkdir create2 > /dev/null 2>&1; + +# Build the Solidity files. +forge build --out="out" --root="."; + +# Use a temporary directory. +mkdir .tmp > /dev/null 2>&1; +rm -r .tmp/out > /dev/null 2>&1; +cp -r out .tmp/out > /dev/null 2>&1; + +# Go into the temporary directory. +cd .tmp; + +# Install some files for computing the initcodehash. +echo '{ "devDependencies": { "@ethersproject/keccak256": "5.7.0" } }' > package.json; +if [ ! -f package-lock.json ]; then npm install; fi + +# Create the deployments directory in the temporary directory. +mkdir create2 > /dev/null 2>&1; + +# Function to generate the deployment files. +generateDeployment() { + rm -rf "create2/$1" > /dev/null 2>&1; + mkdir "create2/$1" > /dev/null 2>&1; + # Generate the js file to do the hard work. + echo " + const fs = require(\"fs\"), + rfs = s => fs.readFileSync(s, { encoding: \"utf8\", flag: \"r\" }); + const solcOutput = JSON.parse(rfs(\"out/$1.sol/$1.json\")); + const initcode = solcOutput[\"bytecode\"][\"object\"].slice(2); + const d = \"create2/$1\"; + fs.writeFileSync(d + \"/initcode.txt\", initcode); + const t = solcOutput[\"metadata\"][\"settings\"][\"compilationTarget\"], k = Object.keys(t)[0]; + fs.writeFileSync(d + \"/t\", k + \":\" + t[k]); + fs.writeFileSync(d + \"/initcodehash.txt\", require(\"@ethersproject/keccak256\").keccak256(\"0x\" + initcode)); + " > "extract_$1.js"; + # Run the js file. + node "extract_$1.js"; + # Generate the standard json verification file. + forge verify-contract $(cast --address-zero) "$( "create2/$1/input.json"; + # Remove the temporary files. + rm "create2/$1/t" > /dev/null 2>&1; + rm "extract_$1.js" > /dev/null 2>&1; + # Move the directory over to the actual deployment directory. + rm -rf "../create2/$1" > /dev/null 2>&1; + cp -r "create2/$1" "../create2/$1"; + rm -rf "create2/$1" > /dev/null 2>&1; +} + +# Generate the deployments. +generateDeployment "ClustersCommunityHubBeta"; +generateDeployment "ClustersCommunityInitiatorBeta"; +generateDeployment "TestERC20"; diff --git a/lib/devtools b/lib/devtools index f2d2684..cb9dcb9 160000 --- a/lib/devtools +++ b/lib/devtools @@ -1 +1 @@ -Subproject commit f2d2684f9b57ccfc10afcb9f4290c12bc12162f0 +Subproject commit cb9dcb9f5982f9f086f0ce9fc0b5fdf1f60f84b0 diff --git a/lib/forge-std b/lib/forge-std index 6e05729..bf66061 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 6e05729b76f1ae0d437e74951aef1ca987788ab3 +Subproject commit bf6606142994b1e47e2882ce0cd477c020d77623 diff --git a/lib/multicaller b/lib/multicaller new file mode 160000 index 0000000..356350c --- /dev/null +++ b/lib/multicaller @@ -0,0 +1 @@ +Subproject commit 356350c2954d9e117ec839be37bddcdc773e04ac diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index dc62599..72c152d 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit dc625992575ecb3089acc35f5475bedfcb7e6be3 +Subproject commit 72c152dc1c41f23d7c504e175f5b417fccc89426 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable index 1bf98f4..22489db 160000 --- a/lib/openzeppelin-contracts-upgradeable +++ b/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit 1bf98f4edcf9d9e757314a49354ea98a3a771db4 +Subproject commit 22489db15621b9a42ebddb1facade6962034e9b9 diff --git a/lib/solady b/lib/solady index 74769c2..d223d43 160000 --- a/lib/solady +++ b/lib/solady @@ -1 +1 @@ -Subproject commit 74769c21b5759e897e90fb25026f24d889fe4b26 +Subproject commit d223d4347d0866cc7692747edf9c8a7be0d3d047 diff --git a/lib/soledge b/lib/soledge new file mode 160000 index 0000000..641f4fc --- /dev/null +++ b/lib/soledge @@ -0,0 +1 @@ +Subproject commit 641f4fc39f6ca9b387115985a099b4dfd26135fb diff --git a/remappings.txt b/remappings.txt index 0a67d8b..b74bc46 100644 --- a/remappings.txt +++ b/remappings.txt @@ -5,6 +5,8 @@ forge-std/=lib/forge-std/src/ openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/ solmate/=lib/solmate/src/ solady/=lib/solady/src/ +multicaller/=lib/multicaller/src/ +soledge/=lib/soledge/src/ clusters/=src/ solidity-bytes-utils/=lib/LayerZero-v2/oapp/node_modules/solidity-bytes-utils/ @openzeppelin/contracts/=lib/LayerZero-v2/oapp/node_modules/@openzeppelin/contracts/ diff --git a/src/beta/ClustersCommunityBaseBeta.sol b/src/beta/ClustersCommunityBaseBeta.sol new file mode 100644 index 0000000..cbe69ff --- /dev/null +++ b/src/beta/ClustersCommunityBaseBeta.sol @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {UUPSUpgradeable} from "solady/utils/UUPSUpgradeable.sol"; +import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol"; +import {EnumerableRoles} from "solady/auth/EnumerableRoles.sol"; +import {LibClone} from "solady/utils/LibClone.sol"; +import {EfficientHashLib} from "solady/utils/EfficientHashLib.sol"; + +/// @title ClustersCommunityBaseVaultBeta +/// @notice A contract to hold ERC20 and native tokens for a recipient of a community bid. +/// We'll just include this within the same file as ClustersCommunityBaseBeta, +/// since it is so tightly coupled. +contract ClustersCommunityBaseVaultBeta { + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* ERRORS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev The caller must be the mothership, and the mothership must be called by the owner. + error Unauthorized(); + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* WITHDRAW FUNCTIONS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Allows the owner to withdraw ERC20 tokens. + function withdrawERC20(address mothershipCaller, address token, address to, uint256 amount) public { + _checkMothership(mothershipCaller); + SafeTransferLib.safeTransfer(token, to, amount); + } + + /// @dev Allows the owner to withdraw native currency. + function withdrawNative(address mothershipCaller, address to, uint256 amount) public { + _checkMothership(mothershipCaller); + SafeTransferLib.safeTransferETH(to, amount); + } + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* GUARDS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Ensures that the caller is the mothership, + /// and that the caller of the mothership is the owner of the vault. + function _checkMothership(address mothershipCaller) internal view { + bytes32 h = abi.decode(LibClone.argsOnClone(address(this), 0x00, 0x20), (bytes32)); + if (EfficientHashLib.hash(uint160(msg.sender), uint160(mothershipCaller)) != h) revert Unauthorized(); + } + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* RECEIVE */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev For vault to receive native currency. + receive() external payable {} +} + +/// @title ClustersCommunityBaseBeta +/// @notice The base class for the ClustersCommunityHubBeta and ClustersCommunityInitiatorBeta. +/// Having a base class helps with conciseness, since both classes share a lot of +/// common logic. +contract ClustersCommunityBaseBeta is EnumerableRoles, UUPSUpgradeable { + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* STRUCTS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev A struct for passing in a community bid. + struct BidConfig { + // The token. If this is `address(0)`, the token will be the native token. + address token; + // The amount of token. + uint256 amount; + // The recipient of the token payment. + address paymentRecipient; + // The community name. + bytes32 communityName; + // The wallet name, + bytes32 walletName; + // This is the referral address. We use bytes32, as it might be multichain. + bytes32 referralAddress; + } + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* CONSTANTS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Admin role. + uint256 public constant ADMIN_ROLE = 0; + + /// @dev Withdrawer role. + uint256 public constant WITHDRAWER_ROLE = 1; + + /// @dev Max role. + uint256 public constant MAX_ROLE = 1; + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* IMMUTABLES */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Address of the vault implementation. + address internal immutable _vaultImplementation = address(new ClustersCommunityBaseVaultBeta()); + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* VAULT FUNCTIONS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Returns the deterministic address of the vault of `vaultOwner`. + function vaultOf(address vaultOwner) public view returns (address) { + bytes memory args = abi.encode(EfficientHashLib.hash(uint160(address(this)), uint160(vaultOwner))); + bytes32 salt; + return LibClone.predictDeterministicAddress(_vaultImplementation, args, salt, address(this)); + } + + /// @dev Creates a vault for `vaultOwner` if one does not exist yet. + function createVault(address vaultOwner) public returns (address instance) { + bytes memory args = abi.encode(EfficientHashLib.hash(uint160(address(this)), uint160(vaultOwner))); + bytes32 salt; + (, instance) = LibClone.createDeterministicClone(_vaultImplementation, args, salt); + } + + /// @dev Allows the `vaultOwner` to withdraw ERC20 tokens. + function withdrawERC20OnVault(address vaultOwner, address token, address to, uint256 amount) public { + ClustersCommunityBaseVaultBeta(payable(vaultOf(vaultOwner))).withdrawERC20(msg.sender, token, to, amount); + } + + /// @dev Allows the owner to withdraw native currency. + function withdrawNativeOnVault(address vaultOwner, address to, uint256 amount) public { + ClustersCommunityBaseVaultBeta(payable(vaultOf(vaultOwner))).withdrawNative(msg.sender, to, amount); + } + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* WITHDRAW FUNCTIONS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Allows the owner to withdraw ERC20 tokens. + function withdrawERC20(address token, address to, uint256 amount) + public + onlyOwnerOrRoles(abi.encode(ADMIN_ROLE, WITHDRAWER_ROLE)) + { + SafeTransferLib.safeTransfer(token, to, amount); + } + + /// @dev Allows the owner to withdraw native currency. + function withdrawNative(address to, uint256 amount) + public + onlyOwnerOrRoles(abi.encode(ADMIN_ROLE, WITHDRAWER_ROLE)) + { + SafeTransferLib.safeTransferETH(to, amount); + } + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* INTERNAL HELPERS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Transfers the native currency or ERC20 for the bid. + function _transferBidPayment(BidConfig calldata bidConfig) internal returns (uint256 nativeAmount) { + address vault = createVault(bidConfig.paymentRecipient); + if (bidConfig.token == address(0)) { + nativeAmount = bidConfig.amount; + SafeTransferLib.safeTransferETH(vault, nativeAmount); + } else { + SafeTransferLib.safeTransferFrom(bidConfig.token, msg.sender, vault, bidConfig.amount); + } + } + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* OVERRIDES */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Allow admins to set roles too. + function _authorizeSetRole(address, uint256, bool) internal override onlyOwnerOrRoles(abi.encode(ADMIN_ROLE)) {} + + /// @dev For UUPSUpgradeable. + function _authorizeUpgrade(address newImplementation) internal override onlyOwnerOrRoles(abi.encode(ADMIN_ROLE)) {} +} diff --git a/src/beta/ClustersCommunityHubBeta.sol b/src/beta/ClustersCommunityHubBeta.sol new file mode 100644 index 0000000..52dfb03 --- /dev/null +++ b/src/beta/ClustersCommunityHubBeta.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol"; +import {ReentrancyGuard} from "soledge/utils/ReentrancyGuard.sol"; +import {ClustersCommunityBaseBeta} from "clusters/beta/ClustersCommunityBaseBeta.sol"; +import {Origin, OAppReceiverUpgradeable} from "layerzero-oapp/contracts/oapp-upgradeable/OAppReceiverUpgradeable.sol"; + +/// @title ClustersCommunityHubBeta +/// @notice The hub contract for clusters community sales. +contract ClustersCommunityHubBeta is OAppReceiverUpgradeable, ReentrancyGuard, ClustersCommunityBaseBeta { + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* TRANSIENT STORAGE */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Transient storage slot to denote if the context is in the middle of a `_lzReceive`. + uint256 internal constant _IN_LZ_RECEIVE_TRANSIENT_SLOT = 0; + + /// @dev Transient storage slot for the sender. + uint256 internal constant _SENDER_TRANSIENT_SLOT = 1; + + /// @dev Transient storage slot for the message's origin chain ID. + uint256 internal constant _CHAIN_ID_TRANSIENT_SLOT = 2; + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* EVENTS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Emitted when a bid is received. + event Bid( + bytes32 from, + uint256 tokenChainId, + address token, + uint256 amount, + address paymentRecipient, + bytes32 communityName, + bytes32 walletName, + bytes32 referralAddress + ); + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* ERRORS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Insufficient native payment. + error InsufficientNativePayment(); + + /// @dev The input arrays must have the same length. + error ArrayLengthsMismatch(); + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* INITIALIZER */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Initializes the contract. + function initialize(address endpoint_, address owner_) public initializer onlyProxy { + _initializeOAppCore(endpoint_, owner_); + } + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* PUBLIC WRITE FUNCTIONS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Places a bid. + /// If any token is `address(0)`, it is treated as the native token. + function placeBid(BidConfig calldata bidConfig) public payable nonReentrant { + (bytes32 sender, uint256 chainId) = _senderAndChainId(); + if (chainId == block.chainid) { + if (msg.value < _transferBidPayment(bidConfig)) revert InsufficientNativePayment(); + } + emit Bid( + sender, + chainId, + bidConfig.token, + bidConfig.amount, + bidConfig.paymentRecipient, + bidConfig.communityName, + bidConfig.walletName, + bidConfig.referralAddress + ); + } + + /// @dev Places multiple bids. + /// If any token is `address(0)`, it is treated as the native token. + function placeBids(BidConfig[] calldata bidConfigs) public payable nonReentrant { + (bytes32 sender, uint256 chainId) = _senderAndChainId(); + uint256 requiredNativeValue; + for (uint256 i; i < bidConfigs.length; ++i) { + BidConfig calldata bidConfig = bidConfigs[i]; + if (chainId == block.chainid) { + requiredNativeValue += _transferBidPayment(bidConfig); + } + emit Bid( + sender, + chainId, + bidConfig.token, + bidConfig.amount, + bidConfig.paymentRecipient, + bidConfig.communityName, + bidConfig.walletName, + bidConfig.referralAddress + ); + } + if (msg.value < requiredNativeValue) revert InsufficientNativePayment(); + } + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* LAYERZERO */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev For receiving a bid. + function _lzReceive( + Origin calldata, /* origin */ + bytes32, /* guid */ + bytes calldata message, + address, /* executor */ + bytes calldata /* extraData */ + ) internal override { + assembly ("memory-safe") { + let chainId := calldataload(add(message.offset, 0x00)) + let sender := calldataload(add(message.offset, 0x20)) + + let o := add(message.offset, calldataload(add(message.offset, 0x40))) + let dataLength := calldataload(o) + let dataOffset := add(o, 0x20) + // Check that all of the data is within bounds. + if or(lt(message.length, 0x60), gt(add(dataOffset, dataLength), add(message.offset, message.length))) { + invalid() + } + + tstore(_IN_LZ_RECEIVE_TRANSIENT_SLOT, address()) + tstore(_CHAIN_ID_TRANSIENT_SLOT, chainId) + tstore(_SENDER_TRANSIENT_SLOT, sender) + + let m := mload(0x40) + calldatacopy(m, dataOffset, dataLength) + // Self-delegatecall with `data`. + if iszero(delegatecall(gas(), address(), m, dataLength, 0x00, 0x00)) { + // Bubble up the revert if the self-delegatecall fails. + returndatacopy(m, 0x00, returndatasize()) + revert(m, returndatasize()) + } + + tstore(_SENDER_TRANSIENT_SLOT, 0) + tstore(_CHAIN_ID_TRANSIENT_SLOT, 0) + tstore(_IN_LZ_RECEIVE_TRANSIENT_SLOT, 0) + } + } + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* INTERNAL HELPERS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Returns the sender and the chain ID, + /// using the values passed in via Layerzero if they are provided. + function _senderAndChainId() internal view returns (bytes32 sender, uint256 chainId) { + assembly ("memory-safe") { + sender := caller() + chainId := chainid() + if tload(_IN_LZ_RECEIVE_TRANSIENT_SLOT) { + sender := tload(_SENDER_TRANSIENT_SLOT) + chainId := tload(_CHAIN_ID_TRANSIENT_SLOT) + // Just for extra safety, in case some rogue L2 makes their chain ID the same. + if eq(chainId, chainid()) { invalid() } + } + } + } +} diff --git a/src/beta/ClustersCommunityInitiatorBeta.sol b/src/beta/ClustersCommunityInitiatorBeta.sol new file mode 100644 index 0000000..bcacab2 --- /dev/null +++ b/src/beta/ClustersCommunityInitiatorBeta.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol"; +import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol"; +import {ClustersCommunityBaseBeta} from "clusters/beta/ClustersCommunityBaseBeta.sol"; +import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; +import { + OAppSenderUpgradeable, MessagingFee +} from "layerzero-oapp/contracts/oapp-upgradeable/OAppSenderUpgradeable.sol"; + +/// @title ClustersCommunityHubBeta +/// @notice The initiator contract for clusters community sales. +contract ClustersCommunityInitiatorBeta is OAppSenderUpgradeable, ReentrancyGuard, ClustersCommunityBaseBeta { + using OptionsBuilder for bytes; + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* STORAGE */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev The storage struct for the contract. + struct ClustersCommunityInitiatorBetaStorage { + uint32 dstEid; + } + + /// @dev Returns the storage struct for the contract. + function _getClustersCommunityInitiatorBetaStorage() + internal + pure + returns (ClustersCommunityInitiatorBetaStorage storage $) + { + assembly ("memory-safe") { + // `uint72(bytes9(keccak256("Clusters.ClustersCommunityInitiatorBetaStorage")))`. + $.slot := 0xc3d7966f8edf259843 // Truncate to 9 bytes to reduce bytecode size. + } + } + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* EVENTS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Emitted when the destination endpoint ID is set. + event DstEidSet(uint32 eid); + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* ERRORS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Insufficient native payment. + error InsufficientNativePayment(); + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* INITIALIZER */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Initializes the contract. + function initialize(address endpoint_, address owner_) public initializer onlyProxy { + _initializeOAppCore(endpoint_, owner_); + } + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* PUBLIC WRITE FUNCTIONS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Places a bid. + /// If any token is `address(0)`, it is treated as the native token. + /// All tokens will not be bridged. + function placeBid(BidConfig calldata bidConfig, uint256 gas) public payable nonReentrant { + _sendBids(_encodeBidMessage(bidConfig), _transferBidPayment(bidConfig), gas); + } + + /// @dev Places multiple bids. + /// If any token is `address(0)`, it is treated as the native token. + /// All tokens will not be bridged. + function placeBids(BidConfig[] calldata bidConfigs, uint256 gas) public payable nonReentrant { + uint256 requiredNativeValue; + for (uint256 i; i < bidConfigs.length; ++i) { + requiredNativeValue += _transferBidPayment(bidConfigs[i]); + } + _sendBids(_encodeBidsMessage(bidConfigs), requiredNativeValue, gas); + } + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* PUBLIC VIEW FUNCTIONS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Returns the Layerzero destination endpoint ID. + function dstEid() public view returns (uint32) { + return _getClustersCommunityInitiatorBetaStorage().dstEid; + } + + /// @dev Returns the amount of native gas fee required to place a bid. + function quoteForBid(BidConfig calldata bidConfig, uint256 gas) public view returns (uint256) { + return _quoteNativeFee(_encodeBidMessage(bidConfig), gas); + } + + /// @dev Returns the amount of native gas fee required to place the bids. + function quoteForBids(BidConfig[] calldata bidConfigs, uint256 gas) public view returns (uint256) { + return _quoteNativeFee(_encodeBidsMessage(bidConfigs), gas); + } + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* ADMIN FUNCTIONS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Enables the owner to set the destination endpoint ID. + function setDstEid(uint32 eid) public onlyOwnerOrRoles(abi.encode(ADMIN_ROLE)) { + _getClustersCommunityInitiatorBetaStorage().dstEid = eid; + emit DstEidSet(eid); + } + + /*«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-«-*/ + /* INTERNAL HELPERS */ + /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + + /// @dev Used by `placeBid` and `placeBids`. + function _sendBids(bytes memory encoded, uint256 requiredNativeValue, uint256 gas) internal { + uint256 nativeFee = _quoteNativeFee(encoded, gas); + uint256 requiredNativeTotal = nativeFee + requiredNativeValue; + if (msg.value < requiredNativeTotal) revert InsufficientNativePayment(); + _lzSend(dstEid(), encoded, _defaultOptions(gas), MessagingFee(nativeFee, 0), payable(msg.sender)); + if (msg.value > requiredNativeTotal) { + SafeTransferLib.forceSafeTransferETH(msg.sender, msg.value - requiredNativeTotal); + } + } + + /// @dev Override to remove the `if (msg.value != nativeFee) revert()`. + function _payNative(uint256 nativeFee) internal virtual override returns (uint256) { + return nativeFee; + } + + /// @dev Returns the default options that encodes `gas`. + function _defaultOptions(uint256 gas) internal pure returns (bytes memory) { + if (gas >= 1 << 128) revert(); + return OptionsBuilder.newOptions().addExecutorLzReceiveOption(uint128(gas), 0); + } + + /// @dev Returns the required native fee for Layerzero. + function _quoteNativeFee(bytes memory encoded, uint256 gas) internal view returns (uint256) { + return _quote(dstEid(), encoded, _defaultOptions(gas), false).nativeFee; + } + + /// @dev Encodes the bid calldata. + function _encodeBidMessage(BidConfig calldata bidConfig) internal view returns (bytes memory) { + return abi.encode( + block.chainid, + msg.sender, + abi.encodeWithSignature("placeBid((address,uint256,address,bytes32,bytes32,bytes32))", bidConfig) + ); + } + + /// @dev Encodes the bids calldata. + function _encodeBidsMessage(BidConfig[] calldata bidConfigs) internal view returns (bytes memory) { + return abi.encode( + block.chainid, + msg.sender, + abi.encodeWithSignature("placeBids((address,uint256,address,bytes32,bytes32,bytes32)[])", bidConfigs) + ); + } +} diff --git a/src/beta/TestERC20.sol b/src/beta/TestERC20.sol new file mode 100644 index 0000000..310f64b --- /dev/null +++ b/src/beta/TestERC20.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {ERC20} from "solady/tokens/ERC20.sol"; + +contract TestERC20 is ERC20 { + function name() public pure override returns (string memory) { + return "Test"; + } + + function symbol() public pure override returns (string memory) { + return "TEST"; + } + + function mintHundredToSelf() public { + _mint(msg.sender, 100 * 10 ** 18); + } +} diff --git a/test/utils/Brutalizer.sol b/test/utils/Brutalizer.sol new file mode 100644 index 0000000..8bc8165 --- /dev/null +++ b/test/utils/Brutalizer.sol @@ -0,0 +1,885 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @dev WARNING! This mock is strictly intended for testing purposes only. +/// Do NOT copy anything here into production code unless you really know what you are doing. +contract Brutalizer { + /// @dev Multiplier for a mulmod Lehmer psuedorandom number generator. + /// Prime, and a primitive root of `_LPRNG_MODULO`. + uint256 private constant _LPRNG_MULTIPLIER = 0x100000000000000000000000000000051; + + /// @dev Modulo for a mulmod Lehmer psuedorandom number generator. (prime) + uint256 private constant _LPRNG_MODULO = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43; + + /// @dev Fills the memory with junk, for more robust testing of inline assembly + /// which reads/write to the memory. + function _brutalizeMemory() internal view { + // To prevent a solidity 0.8.13 bug. + // See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug + // Basically, we need to access a solidity variable from the assembly to + // tell the compiler that this assembly block is not in isolation. + uint256 zero; + /// @solidity memory-safe-assembly + assembly { + let offset := mload(0x40) // Start the offset at the free memory pointer. + calldatacopy(add(offset, 0x20), zero, calldatasize()) + mstore(offset, add(caller(), gas())) + + // Fill the 64 bytes of scratch space with garbage. + let r := keccak256(offset, add(calldatasize(), 0x40)) + mstore(zero, r) + mstore(0x20, keccak256(zero, 0x40)) + r := mulmod(mload(0x10), _LPRNG_MULTIPLIER, _LPRNG_MODULO) + + let cSize := add(codesize(), iszero(codesize())) + if iszero(lt(cSize, 32)) { cSize := sub(cSize, and(mload(0x02), 0x1f)) } + let start := mod(mload(0x10), cSize) + let size := mul(sub(cSize, start), gt(cSize, start)) + let times := div(0x7ffff, cSize) + if iszero(lt(times, 128)) { times := 128 } + + // Occasionally offset the offset by a pseudorandom large amount. + // Can't be too large, or we will easily get out-of-gas errors. + offset := add(offset, mul(iszero(and(r, 0xf00000000)), and(shr(64, r), 0xfffff))) + + // Fill the free memory with garbage. + // prettier-ignore + for { let w := not(0) } 1 {} { + mstore(offset, mload(0x00)) + mstore(add(offset, 0x20), mload(0x20)) + offset := add(offset, 0x40) + // We use codecopy instead of the identity precompile + // to avoid polluting the `forge test -vvvv` output with tons of junk. + codecopy(offset, start, size) + codecopy(add(offset, size), 0x00, start) + offset := add(offset, cSize) + times := add(times, w) // `sub(times, 1)`. + if iszero(times) { break } + } + // With a 1/16 chance, copy the contract's code to the scratch space. + if iszero(and(0xf00, r)) { + codecopy(0x00, mod(shr(128, r), add(codesize(), codesize())), 0x40) + mstore8(and(r, 0x3f), iszero(and(0x100000, r))) + } + } + } + + /// @dev Fills the scratch space with junk, for more robust testing of inline assembly + /// which reads/write to the memory. + function _brutalizeScratchSpace() internal view { + // To prevent a solidity 0.8.13 bug. + // See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug + // Basically, we need to access a solidity variable from the assembly to + // tell the compiler that this assembly block is not in isolation. + uint256 zero; + /// @solidity memory-safe-assembly + assembly { + let offset := mload(0x40) // Start the offset at the free memory pointer. + calldatacopy(add(offset, 0x20), zero, calldatasize()) + mstore(offset, add(caller(), gas())) + + // Fill the 64 bytes of scratch space with garbage. + let r := keccak256(offset, add(calldatasize(), 0x40)) + mstore(zero, r) + mstore(0x20, keccak256(zero, 0x40)) + r := mulmod(mload(0x10), _LPRNG_MULTIPLIER, _LPRNG_MODULO) + if iszero(and(0xf00, r)) { + codecopy(0x00, mod(shr(128, r), add(codesize(), codesize())), 0x40) + mstore8(and(r, 0x3f), iszero(and(0x100000, r))) + } + } + } + + /// @dev Fills the lower memory with junk, for more robust testing of inline assembly + /// which reads/write to the memory. + /// For efficiency, this only fills a small portion of the free memory. + function _brutalizeLowerMemory() internal view { + // To prevent a solidity 0.8.13 bug. + // See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug + // Basically, we need to access a solidity variable from the assembly to + // tell the compiler that this assembly block is not in isolation. + uint256 zero; + /// @solidity memory-safe-assembly + assembly { + let offset := mload(0x40) // Start the offset at the free memory pointer. + calldatacopy(add(offset, 0x20), zero, calldatasize()) + mstore(offset, add(caller(), gas())) + + // Fill the 64 bytes of scratch space with garbage. + let r := keccak256(offset, add(calldatasize(), 0x40)) + mstore(zero, r) + mstore(0x20, keccak256(zero, 0x40)) + r := mulmod(mload(0x10), _LPRNG_MULTIPLIER, _LPRNG_MODULO) + + for {} 1 {} { + if iszero(and(0x7000, r)) { + let x := keccak256(zero, 0x40) + mstore(offset, x) + mstore(add(0x20, offset), x) + mstore(add(0x40, offset), x) + mstore(add(0x60, offset), x) + mstore(add(0x80, offset), x) + mstore(add(0xa0, offset), x) + mstore(add(0xc0, offset), x) + mstore(add(0xe0, offset), x) + mstore(add(0x100, offset), x) + mstore(add(0x120, offset), x) + mstore(add(0x140, offset), x) + mstore(add(0x160, offset), x) + mstore(add(0x180, offset), x) + mstore(add(0x1a0, offset), x) + mstore(add(0x1c0, offset), x) + mstore(add(0x1e0, offset), x) + mstore(add(0x200, offset), x) + mstore(add(0x220, offset), x) + mstore(add(0x240, offset), x) + mstore(add(0x260, offset), x) + break + } + codecopy(offset, byte(0, r), codesize()) + break + } + if iszero(and(0x300, r)) { + codecopy(0x00, mod(shr(128, r), add(codesize(), codesize())), 0x40) + mstore8(and(r, 0x3f), iszero(and(0x100000, r))) + } + } + } + + /// @dev Fills the memory with junk, for more robust testing of inline assembly + /// which reads/write to the memory. + modifier brutalizeMemory() { + _brutalizeMemory(); + _; + _checkMemory(); + } + + /// @dev Fills the scratch space with junk, for more robust testing of inline assembly + /// which reads/write to the memory. + modifier brutalizeScratchSpace() { + _brutalizeScratchSpace(); + _; + _checkMemory(); + } + + /// @dev Fills the lower memory with junk, for more robust testing of inline assembly + /// which reads/write to the memory. + modifier brutalizeLowerMemory() { + _brutalizeLowerMemory(); + _; + _checkMemory(); + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalized(address value) internal pure returns (address result) { + uint256 r = uint256(uint160(value)); + r = (__brutalizerRandomness(r) << 160) ^ r; + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint8(uint8 value) internal pure returns (uint8 result) { + uint256 r = (__brutalizerRandomness(value) << 8) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes1(bytes1 value) internal pure returns (bytes1 result) { + bytes32 r = __brutalizedBytesN(value, 8); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint16(uint16 value) internal pure returns (uint16 result) { + uint256 r = (__brutalizerRandomness(value) << 16) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes2(bytes2 value) internal pure returns (bytes2 result) { + bytes32 r = __brutalizedBytesN(value, 16); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint24(uint24 value) internal pure returns (uint24 result) { + uint256 r = (__brutalizerRandomness(value) << 24) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes3(bytes3 value) internal pure returns (bytes3 result) { + bytes32 r = __brutalizedBytesN(value, 24); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint32(uint32 value) internal pure returns (uint32 result) { + uint256 r = (__brutalizerRandomness(value) << 32) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes4(bytes4 value) internal pure returns (bytes4 result) { + bytes32 r = __brutalizedBytesN(value, 32); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint40(uint40 value) internal pure returns (uint40 result) { + uint256 r = (__brutalizerRandomness(value) << 40) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes5(bytes5 value) internal pure returns (bytes5 result) { + bytes32 r = __brutalizedBytesN(value, 40); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint48(uint48 value) internal pure returns (uint48 result) { + uint256 r = (__brutalizerRandomness(value) << 48) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes6(bytes6 value) internal pure returns (bytes6 result) { + bytes32 r = __brutalizedBytesN(value, 48); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint56(uint56 value) internal pure returns (uint56 result) { + uint256 r = (__brutalizerRandomness(value) << 56) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes7(bytes7 value) internal pure returns (bytes7 result) { + bytes32 r = __brutalizedBytesN(value, 56); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint64(uint64 value) internal pure returns (uint64 result) { + uint256 r = (__brutalizerRandomness(value) << 64) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes8(bytes8 value) internal pure returns (bytes8 result) { + bytes32 r = __brutalizedBytesN(value, 64); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint72(uint72 value) internal pure returns (uint72 result) { + uint256 r = (__brutalizerRandomness(value) << 72) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes9(bytes9 value) internal pure returns (bytes9 result) { + bytes32 r = __brutalizedBytesN(value, 72); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint80(uint80 value) internal pure returns (uint80 result) { + uint256 r = (__brutalizerRandomness(value) << 80) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes10(bytes10 value) internal pure returns (bytes10 result) { + bytes32 r = __brutalizedBytesN(value, 80); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint88(uint88 value) internal pure returns (uint88 result) { + uint256 r = (__brutalizerRandomness(value) << 88) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes11(bytes11 value) internal pure returns (bytes11 result) { + bytes32 r = __brutalizedBytesN(value, 88); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint96(uint96 value) internal pure returns (uint96 result) { + uint256 r = (__brutalizerRandomness(value) << 96) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes12(bytes12 value) internal pure returns (bytes12 result) { + bytes32 r = __brutalizedBytesN(value, 96); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint104(uint104 value) internal pure returns (uint104 result) { + uint256 r = (__brutalizerRandomness(value) << 104) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes13(bytes13 value) internal pure returns (bytes13 result) { + bytes32 r = __brutalizedBytesN(value, 104); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint112(uint112 value) internal pure returns (uint112 result) { + uint256 r = (__brutalizerRandomness(value) << 112) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes14(bytes14 value) internal pure returns (bytes14 result) { + bytes32 r = __brutalizedBytesN(value, 112); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint120(uint120 value) internal pure returns (uint120 result) { + uint256 r = (__brutalizerRandomness(value) << 120) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes15(bytes15 value) internal pure returns (bytes15 result) { + bytes32 r = __brutalizedBytesN(value, 120); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint128(uint128 value) internal pure returns (uint128 result) { + uint256 r = (__brutalizerRandomness(value) << 128) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes16(bytes16 value) internal pure returns (bytes16 result) { + bytes32 r = __brutalizedBytesN(value, 128); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint136(uint136 value) internal pure returns (uint136 result) { + uint256 r = (__brutalizerRandomness(value) << 136) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes17(bytes17 value) internal pure returns (bytes17 result) { + bytes32 r = __brutalizedBytesN(value, 136); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint144(uint144 value) internal pure returns (uint144 result) { + uint256 r = (__brutalizerRandomness(value) << 144) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes18(bytes18 value) internal pure returns (bytes18 result) { + bytes32 r = __brutalizedBytesN(value, 144); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint152(uint152 value) internal pure returns (uint152 result) { + uint256 r = (__brutalizerRandomness(value) << 152) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes19(bytes19 value) internal pure returns (bytes19 result) { + bytes32 r = __brutalizedBytesN(value, 152); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint160(uint160 value) internal pure returns (uint160 result) { + uint256 r = (__brutalizerRandomness(value) << 160) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes20(bytes20 value) internal pure returns (bytes20 result) { + bytes32 r = __brutalizedBytesN(value, 160); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint168(uint168 value) internal pure returns (uint168 result) { + uint256 r = (__brutalizerRandomness(value) << 168) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes21(bytes21 value) internal pure returns (bytes21 result) { + bytes32 r = __brutalizedBytesN(value, 168); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint176(uint176 value) internal pure returns (uint176 result) { + uint256 r = (__brutalizerRandomness(value) << 176) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes22(bytes22 value) internal pure returns (bytes22 result) { + bytes32 r = __brutalizedBytesN(value, 176); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint184(uint184 value) internal pure returns (uint184 result) { + uint256 r = (__brutalizerRandomness(value) << 184) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes23(bytes23 value) internal pure returns (bytes23 result) { + bytes32 r = __brutalizedBytesN(value, 184); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint192(uint192 value) internal pure returns (uint192 result) { + uint256 r = (__brutalizerRandomness(value) << 192) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes24(bytes24 value) internal pure returns (bytes24 result) { + bytes32 r = __brutalizedBytesN(value, 192); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint200(uint200 value) internal pure returns (uint200 result) { + uint256 r = (__brutalizerRandomness(value) << 200) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes25(bytes25 value) internal pure returns (bytes25 result) { + bytes32 r = __brutalizedBytesN(value, 200); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint208(uint208 value) internal pure returns (uint208 result) { + uint256 r = (__brutalizerRandomness(value) << 208) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes26(bytes26 value) internal pure returns (bytes26 result) { + bytes32 r = __brutalizedBytesN(value, 208); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint216(uint216 value) internal pure returns (uint216 result) { + uint256 r = (__brutalizerRandomness(value) << 216) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes27(bytes27 value) internal pure returns (bytes27 result) { + bytes32 r = __brutalizedBytesN(value, 216); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint224(uint224 value) internal pure returns (uint224 result) { + uint256 r = (__brutalizerRandomness(value) << 224) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes28(bytes28 value) internal pure returns (bytes28 result) { + bytes32 r = __brutalizedBytesN(value, 224); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint232(uint232 value) internal pure returns (uint232 result) { + uint256 r = (__brutalizerRandomness(value) << 232) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes29(bytes29 value) internal pure returns (bytes29 result) { + bytes32 r = __brutalizedBytesN(value, 232); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint240(uint240 value) internal pure returns (uint240 result) { + uint256 r = (__brutalizerRandomness(value) << 240) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes30(bytes30 value) internal pure returns (bytes30 result) { + bytes32 r = __brutalizedBytesN(value, 240); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalizedUint248(uint248 value) internal pure returns (uint248 result) { + uint256 r = (__brutalizerRandomness(value) << 248) ^ uint256(value); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the lower bits dirtied. + function _brutalizedBytes31(bytes31 value) internal pure returns (bytes31 result) { + bytes32 r = __brutalizedBytesN(value, 248); + /// @solidity memory-safe-assembly + assembly { + result := r + } + } + + /// @dev Returns the result with the upper bits dirtied. + function _brutalized(bool value) internal pure returns (bool result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + calldatacopy(result, 0x00, calldatasize()) + mstore(0x20, keccak256(result, calldatasize())) + mstore(0x10, xor(value, mload(0x10))) + let r := keccak256(0x00, 0x88) + mstore(0x10, r) + result := mul(iszero(iszero(value)), r) + if iszero(and(1, shr(128, mulmod(r, _LPRNG_MULTIPLIER, _LPRNG_MODULO)))) { + result := iszero(iszero(result)) + } + } + } + + /// @dev Returns a brutalizer randomness. + function __brutalizedBytesN(bytes32 x, uint256 s) private pure returns (bytes32) { + return bytes32(uint256((__brutalizerRandomness(uint256(x)) >> s) ^ uint256(x))); + } + + /// @dev Returns a brutalizer randomness. + function __brutalizerRandomness(uint256 seed) private pure returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + calldatacopy(result, 0x00, calldatasize()) + mstore(0x20, keccak256(result, calldatasize())) + mstore(0x10, xor(seed, mload(0x10))) + result := keccak256(0x00, 0x88) + mstore(0x10, result) + if iszero(and(7, shr(128, mulmod(result, _LPRNG_MULTIPLIER, _LPRNG_MODULO)))) { result := 0 } + } + } + + /// @dev Misaligns the free memory pointer. + /// The free memory pointer has a 1/32 chance to be aligned. + function _misalignFreeMemoryPointer() internal pure { + uint256 twoWords = 0x40; + /// @solidity memory-safe-assembly + assembly { + let m := mload(twoWords) + m := add(m, mul(and(keccak256(0x00, twoWords), 0x1f), iszero(and(m, 0x1f)))) + mstore(twoWords, m) + } + } + + /// @dev Check if the free memory pointer and the zero slot are not contaminated. + /// Useful for cases where these slots are used for temporary storage. + function _checkMemory() internal pure { + bool zeroSlotIsNotZero; + bool freeMemoryPointerOverflowed; + /// @solidity memory-safe-assembly + assembly { + // Write ones to the free memory, to make subsequent checks fail if + // insufficient memory is allocated. + mstore(mload(0x40), not(0)) + // Test at a lower, but reasonable limit for more safety room. + if gt(mload(0x40), 0xffffffff) { freeMemoryPointerOverflowed := 1 } + // Check the value of the zero slot. + zeroSlotIsNotZero := mload(0x60) + } + if (freeMemoryPointerOverflowed) revert("`0x40` overflowed!"); + if (zeroSlotIsNotZero) revert("`0x60` is not zero!"); + } + + /// @dev Check if `s`: + /// - Has sufficient memory allocated. + /// - Is zero right padded (cuz some frontends like Etherscan has issues + /// with decoding non-zero-right-padded strings). + function _checkMemory(bytes memory s) internal pure { + bool notZeroRightPadded; + bool insufficientMalloc; + /// @solidity memory-safe-assembly + assembly { + // Write ones to the free memory, to make subsequent checks fail if + // insufficient memory is allocated. + mstore(mload(0x40), not(0)) + let length := mload(s) + let lastWord := mload(add(add(s, 0x20), and(length, not(0x1f)))) + let remainder := and(length, 0x1f) + if remainder { if shl(mul(8, remainder), lastWord) { notZeroRightPadded := 1 } } + // Check if the memory allocated is sufficient. + if length { if gt(add(add(s, 0x20), length), mload(0x40)) { insufficientMalloc := 1 } } + } + if (notZeroRightPadded) revert("Not zero right padded!"); + if (insufficientMalloc) revert("Insufficient memory allocation!"); + _checkMemory(); + } + + /// @dev For checking the memory allocation for string `s`. + function _checkMemory(string memory s) internal pure { + _checkMemory(bytes(s)); + } + + /// @dev Check if `a`: + /// - Has sufficient memory allocated. + function _checkMemory(uint256[] memory a) internal pure { + bool insufficientMalloc; + /// @solidity memory-safe-assembly + assembly { + // Write ones to the free memory, to make subsequent checks fail if + // insufficient memory is allocated. + mstore(mload(0x40), not(0)) + // Check if the memory allocated is sufficient. + insufficientMalloc := gt(add(add(a, 0x20), shl(5, mload(a))), mload(0x40)) + } + if (insufficientMalloc) revert("Insufficient memory allocation!"); + _checkMemory(); + } + + /// @dev Check if `a`: + /// - Has sufficient memory allocated. + function _checkMemory(bytes32[] memory a) internal pure { + uint256[] memory casted; + /// @solidity memory-safe-assembly + assembly { + casted := a + } + _checkMemory(casted); + } + + /// @dev Check if `a`: + /// - Has sufficient memory allocated. + function _checkMemory(address[] memory a) internal pure { + uint256[] memory casted; + /// @solidity memory-safe-assembly + assembly { + casted := a + } + _checkMemory(casted); + } + + /// @dev Check if `a`: + /// - Has sufficient memory allocated. + function _checkMemory(bool[] memory a) internal pure { + uint256[] memory casted; + /// @solidity memory-safe-assembly + assembly { + casted := a + } + _checkMemory(casted); + } +} diff --git a/test/utils/SoladyTest.sol b/test/utils/SoladyTest.sol new file mode 100644 index 0000000..21c29f6 --- /dev/null +++ b/test/utils/SoladyTest.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "forge-std/Test.sol"; +import "./TestPlus.sol"; + +contract SoladyTest is Test, TestPlus { + /// @dev Alias for `_hem`. + function _bound(uint256 x, uint256 min, uint256 max) internal pure virtual override returns (uint256) { + return _hem(x, min, max); + } +} diff --git a/test/utils/TestPlus.sol b/test/utils/TestPlus.sol new file mode 100644 index 0000000..52ec513 --- /dev/null +++ b/test/utils/TestPlus.sol @@ -0,0 +1,686 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {Brutalizer} from "./Brutalizer.sol"; + +contract TestPlus is Brutalizer { + event LogString(string name, string value); + event LogString(string value); + event LogBytes(string name, bytes value); + event LogBytes(bytes value); + event LogUint(string name, uint256 value); + event LogUint(uint256 value); + event LogBytes32(string name, bytes32 value); + event LogBytes32(bytes32 value); + event LogInt(string name, int256 value); + event LogInt(int256 value); + event LogAddress(string name, address value); + event LogAddress(address value); + event LogBool(string name, bool value); + event LogBool(bool value); + + event LogStringArray(string name, string[] value); + event LogStringArray(string[] value); + event LogBytesArray(string name, bytes[] value); + event LogBytesArray(bytes[] value); + event LogUintArray(string name, uint256[] value); + event LogUintArray(uint256[] value); + event LogBytes32Array(string name, bytes32[] value); + event LogBytes32Array(bytes32[] value); + event LogIntArray(string name, int256[] value); + event LogIntArray(int256[] value); + event LogAddressArray(string name, address[] value); + event LogAddressArray(address[] value); + event LogBoolArray(string name, bool[] value); + event LogBoolArray(bool[] value); + + /// @dev `address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))`. + address private constant _VM_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; + + /// @dev This is the keccak256 of a very long string I randomly mashed on my keyboard. + uint256 private constant _TESTPLUS_RANDOMNESS_SLOT = + 0xd715531fe383f818c5f158c342925dcf01b954d24678ada4d07c36af0f20e1ee; + + /// @dev The maximum private key. + uint256 private constant _PRIVATE_KEY_MAX = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140; + + /// @dev Some constant to brutalize the upper bits of addresses. + uint256 private constant _ADDRESS_BRUTALIZER = 0xc0618c2bfd481dcf3e31738f; + + /// @dev Multiplier for a mulmod Lehmer psuedorandom number generator. + /// Prime, and a primitive root of `_LPRNG_MODULO`. + uint256 private constant _LPRNG_MULTIPLIER = 0x100000000000000000000000000000051; + + /// @dev Modulo for a mulmod Lehmer psuedorandom number generator. (prime) + uint256 private constant _LPRNG_MODULO = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff43; + + /// @dev Returns whether the `value` has been generated for `typeId` and `groupId` before. + function __markAsGenerated(bytes32 typeId, bytes32 groupId, uint256 value) private returns (bool isSet) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x00, value) + mstore(0x20, groupId) + mstore(0x40, typeId) + mstore(0x60, _TESTPLUS_RANDOMNESS_SLOT) + let s := keccak256(0x00, 0x80) + isSet := sload(s) + sstore(s, 1) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero pointer. + } + } + + /// @dev Returns a pseudorandom random number from [0 .. 2**256 - 1] (inclusive). + /// For usage in fuzz tests, please ensure that the function has an unnamed uint256 argument. + /// e.g. `testSomething(uint256) public`. + /// This function may return a previously returned result. + function _random() internal returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + result := _TESTPLUS_RANDOMNESS_SLOT + let sValue := sload(result) + mstore(0x20, sValue) + let r := keccak256(0x20, 0x40) + // If the storage is uninitialized, initialize it to the keccak256 of the calldata. + if iszero(sValue) { + sValue := result + calldatacopy(mload(0x40), 0x00, calldatasize()) + r := keccak256(mload(0x40), calldatasize()) + } + sstore(result, add(r, 1)) + + // Do some biased sampling for more robust tests. + // prettier-ignore + for {} 1 {} { + let y := mulmod(r, _LPRNG_MULTIPLIER, _LPRNG_MODULO) + // With a 1/256 chance, randomly set `r` to any of 0,1,2,3. + if iszero(byte(19, y)) { + r := and(byte(11, y), 3) + break + } + let d := byte(17, y) + // With a 1/2 chance, set `r` to near a random power of 2. + if iszero(and(2, d)) { + // Set `t` either `not(0)` or `xor(sValue, r)`. + let t := or(xor(sValue, r), sub(0, and(1, d))) + // Set `r` to `t` shifted left or right. + // prettier-ignore + for {} 1 {} { + if iszero(and(8, d)) { + if iszero(and(16, d)) { t := 1 } + if iszero(and(32, d)) { + r := add(shl(shl(3, and(byte(7, y), 31)), t), sub(3, and(7, r))) + break + } + r := add(shl(byte(7, y), t), sub(511, and(1023, r))) + break + } + if iszero(and(16, d)) { t := shl(255, 1) } + if iszero(and(32, d)) { + r := add(shr(shl(3, and(byte(7, y), 31)), t), sub(3, and(7, r))) + break + } + r := add(shr(byte(7, y), t), sub(511, and(1023, r))) + break + } + // With a 1/2 chance, negate `r`. + r := xor(sub(0, shr(7, d)), r) + break + } + // Otherwise, just set `r` to `xor(sValue, r)`. + r := xor(sValue, r) + break + } + result := r + } + } + + /// @dev Returns a pseudorandom random number from [0 .. 2**256 - 1] (inclusive). + /// For usage in fuzz tests, please ensure that the function has an unnamed uint256 argument. + /// e.g. `testSomething(uint256) public`. + function _randomUnique(uint256 groupId) internal returns (uint256 result) { + result = _randomUnique(bytes32(groupId)); + } + + /// @dev Returns a pseudorandom random number from [0 .. 2**256 - 1] (inclusive). + /// For usage in fuzz tests, please ensure that the function has an unnamed uint256 argument. + /// e.g. `testSomething(uint256) public`. + function _randomUnique(bytes32 groupId) internal returns (uint256 result) { + do { + result = _random(); + } while (__markAsGenerated("uint256", groupId, result)); + } + + /// @dev Returns a pseudorandom random number from [0 .. 2**256 - 1] (inclusive). + /// For usage in fuzz tests, please ensure that the function has an unnamed uint256 argument. + /// e.g. `testSomething(uint256) public`. + function _randomUnique() internal returns (uint256 result) { + result = _randomUnique(""); + } + + /// @dev Returns a pseudorandom number, uniformly distributed in [0 .. 2**256 - 1] (inclusive). + function _randomUniform() internal returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + result := _TESTPLUS_RANDOMNESS_SLOT + // prettier-ignore + for { let sValue := sload(result) } 1 {} { + // If the storage is uninitialized, initialize it to the keccak256 of the calldata. + if iszero(sValue) { + calldatacopy(mload(0x40), 0x00, calldatasize()) + sValue := keccak256(mload(0x40), calldatasize()) + sstore(result, sValue) + result := sValue + break + } + mstore(0x1f, sValue) + sValue := keccak256(0x20, 0x40) + sstore(result, sValue) + result := sValue + break + } + } + } + + /// @dev Returns a boolean with an approximately 1/n chance of being true. + /// This function may return a previously returned result. + function _randomChance(uint256 n) internal returns (bool result) { + uint256 r = _randomUniform(); + /// @solidity memory-safe-assembly + assembly { + result := iszero(mod(r, n)) + } + } + + /// @dev Returns a random private key that can be used for ECDSA signing. + /// This function may return a previously returned result. + function _randomPrivateKey() internal returns (uint256 result) { + result = _randomUniform(); + /// @solidity memory-safe-assembly + assembly { + for {} 1 {} { + if iszero(and(result, 0x10)) { + if iszero(and(result, 0x20)) { + result := add(and(result, 0xf), 1) + break + } + result := sub(_PRIVATE_KEY_MAX, and(result, 0xf)) + break + } + result := shr(1, result) + break + } + } + } + + /// @dev Returns a random private key that can be used for ECDSA signing. + function _randomUniquePrivateKey(uint256 groupId) internal returns (uint256 result) { + result = _randomUniquePrivateKey(bytes32(groupId)); + } + + /// @dev Returns a random private key that can be used for ECDSA signing. + function _randomUniquePrivateKey(bytes32 groupId) internal returns (uint256 result) { + do { + result = _randomPrivateKey(); + } while (__markAsGenerated("uint256", groupId, result)); + } + + /// @dev Returns a random private key that can be used for ECDSA signing. + function _randomUniquePrivateKey() internal returns (uint256 result) { + result = _randomUniquePrivateKey(""); + } + + /// @dev Private helper function to get the signer from a private key. + function __getSigner(uint256 privateKey) private view returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0xffa18649) // `addr(uint256)`. + mstore(0x20, privateKey) + result := mload(staticcall(gas(), _VM_ADDRESS, 0x1c, 0x24, 0x01, 0x20)) + } + } + + /// @dev Private helper to ensure an address is brutalized. + function __toBrutalizedAddress(address a) private pure returns (address result) { + /// @solidity memory-safe-assembly + assembly { + result := keccak256(0x00, 0x88) + result := xor(shl(160, xor(result, _ADDRESS_BRUTALIZER)), a) + mstore(0x10, result) + } + } + + /// @dev Private helper to ensure an address is brutalized. + function __toBrutalizedAddress(uint256 a) private pure returns (address result) { + /// @solidity memory-safe-assembly + assembly { + result := keccak256(0x00, 0x88) + result := xor(shl(160, xor(result, _ADDRESS_BRUTALIZER)), a) + mstore(0x10, result) + } + } + + /// @dev Returns a pseudorandom signer and its private key. + /// This function may return a previously returned result. + /// The signer may have dirty upper 96 bits. + function _randomSigner() internal returns (address signer, uint256 privateKey) { + privateKey = _randomPrivateKey(); + signer = __toBrutalizedAddress(__getSigner(privateKey)); + } + + /// @dev Returns a pseudorandom signer and its private key. + /// The signer may have dirty upper 96 bits. + function _randomUniqueSigner(uint256 groupId) internal returns (address signer, uint256 privateKey) { + (signer, privateKey) = _randomUniqueSigner(bytes32(groupId)); + } + + /// @dev Returns a pseudorandom signer and its private key. + /// The signer may have dirty upper 96 bits. + function _randomUniqueSigner(bytes32 groupId) internal returns (address signer, uint256 privateKey) { + privateKey = _randomUniquePrivateKey(groupId); + signer = __toBrutalizedAddress(__getSigner(privateKey)); + } + + /// @dev Returns a pseudorandom signer and its private key. + /// The signer may have dirty upper 96 bits. + function _randomUniqueSigner() internal returns (address signer, uint256 privateKey) { + (signer, privateKey) = _randomUniqueSigner(""); + } + + /// @dev Returns a pseudorandom address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + /// This function may return a previously returned result. + function _randomAddress() internal returns (address result) { + uint256 r = _randomUniform(); + /// @solidity memory-safe-assembly + assembly { + result := xor(shl(158, r), and(sub(7, shr(252, r)), r)) + } + } + + /// @dev Returns a pseudorandom address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + function _randomUniqueAddress(uint256 groupId) internal returns (address result) { + result = _randomUniqueAddress(bytes32(groupId)); + } + + /// @dev Returns a pseudorandom address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + function _randomUniqueAddress(bytes32 groupId) internal returns (address result) { + do { + result = _randomAddress(); + } while (__markAsGenerated("address", groupId, uint160(result))); + } + + /// @dev Returns a pseudorandom address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + function _randomUniqueAddress() internal returns (address result) { + result = _randomUniqueAddress(""); + } + + /// @dev Returns a pseudorandom non-zero address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + /// This function may return a previously returned result. + function _randomNonZeroAddress() internal returns (address result) { + uint256 r = _randomUniform(); + /// @solidity memory-safe-assembly + assembly { + result := xor(shl(158, r), and(sub(7, shr(252, r)), r)) + if iszero(shl(96, result)) { + mstore(0x00, result) + result := keccak256(0x00, 0x30) + } + } + } + + /// @dev Returns a pseudorandom non-zero address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + function _randomUniqueNonZeroAddress(uint256 groupId) internal returns (address result) { + result = _randomUniqueNonZeroAddress(bytes32(groupId)); + } + + /// @dev Returns a pseudorandom non-zero address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + function _randomUniqueNonZeroAddress(bytes32 groupId) internal returns (address result) { + do { + result = _randomNonZeroAddress(); + } while (__markAsGenerated("address", groupId, uint160(result))); + } + + /// @dev Returns a pseudorandom non-zero address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + function _randomUniqueNonZeroAddress() internal returns (address result) { + result = _randomUniqueNonZeroAddress(""); + } + + /// @dev Cleans the upper 96 bits of the address. + /// This is included so that CI passes for older solc versions with --via-ir. + function _cleaned(address a) internal pure returns (address result) { + /// @solidity memory-safe-assembly + assembly { + result := shr(96, shl(96, a)) + } + } + + /// @dev Returns a pseudorandom address. + /// The result may have dirty upper 96 bits. + /// This function may return a previously returned result. + function _randomAddressWithVmVars() internal returns (address result) { + if (_randomChance(8)) result = __toBrutalizedAddress(_randomVmVar()); + else result = _randomAddress(); + } + + /// @dev Returns a pseudorandom non-zero address. + /// The result may have dirty upper 96 bits. + /// This function may return a previously returned result. + function _randomNonZeroAddressWithVmVars() internal returns (address result) { + do { + if (_randomChance(8)) result = __toBrutalizedAddress(_randomVmVar()); + else result = _randomAddress(); + } while (result == address(0)); + } + + /// @dev Returns a random variable in the virtual machine. + function _randomVmVar() internal returns (uint256 result) { + uint256 r = _randomUniform(); + uint256 t = r % 11; + if (t <= 4) { + if (t == 0) return uint160(address(this)); + if (t == 1) return uint160(tx.origin); + if (t == 2) return uint160(msg.sender); + if (t == 3) return uint160(_VM_ADDRESS); + if (t == 4) return uint160(0x000000000000000000636F6e736F6c652e6c6f67); + } + uint256 y = r >> 32; + if (t == 5) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, r) + codecopy(0x00, mod(and(y, 0xffff), add(codesize(), 0x20)), 0x20) + result := mload(0x00) + } + return result; + } + if (t == 6) { + /// @solidity memory-safe-assembly + assembly { + calldatacopy(0x00, mod(and(y, 0xffff), add(calldatasize(), 0x20)), 0x20) + result := mload(0x00) + } + return result; + } + if (t == 7) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + returndatacopy(m, 0x00, returndatasize()) + result := mload(add(m, mod(and(y, 0xffff), add(returndatasize(), 0x20)))) + } + return result; + } + if (t == 8) { + /// @solidity memory-safe-assembly + assembly { + result := sload(and(y, 0xff)) + } + return result; + } + if (t == 9) { + /// @solidity memory-safe-assembly + assembly { + result := mload(mod(y, add(mload(0x40), 0x40))) + } + return result; + } + result = __getSigner(_randomPrivateKey()); + } + + /// @dev Returns a pseudorandom hashed address. + /// The result may have dirty upper 96 bits. + /// This function will not return an existing contract. + /// This function will not return a precompile address. + /// This function will not return a zero address. + /// This function may return a previously returned result. + function _randomHashedAddress() internal returns (address result) { + uint256 r = _randomUniform(); + /// @solidity memory-safe-assembly + assembly { + mstore(0x1f, and(sub(7, shr(252, r)), r)) + calldatacopy(0x00, 0x00, 0x24) + result := keccak256(0x00, 0x3f) + } + } + + /// @dev Returns a pseudorandom address. + function _randomUniqueHashedAddress(uint256 groupId) internal returns (address result) { + result = _randomUniqueHashedAddress(bytes32(groupId)); + } + + /// @dev Returns a pseudorandom address. + function _randomUniqueHashedAddress(bytes32 groupId) internal returns (address result) { + do { + result = _randomHashedAddress(); + } while (__markAsGenerated("address", groupId, uint160(result))); + } + + /// @dev Returns a pseudorandom address. + function _randomUniqueHashedAddress() internal returns (address result) { + result = _randomUniqueHashedAddress(""); + } + + /// @dev Private helper function for returning random bytes. + function __randomBytes(bool zeroRightPad) private returns (bytes memory result) { + uint256 r = _randomUniform(); + /// @solidity memory-safe-assembly + assembly { + let n := and(r, 0x1ffff) + let t := shr(24, r) + for {} 1 {} { + // With a 1/256 chance, just return the zero pointer as the result. + if iszero(and(t, 0xff0)) { + result := 0x60 + break + } + result := mload(0x40) + // With a 15/16 chance, set the length to be + // exponentially distributed in the range [0,255] (inclusive). + if shr(252, r) { n := shr(and(t, 0x7), byte(5, r)) } + // Store some fixed word at the start of the string. + // We want this function to sometimes return duplicates. + mstore(add(result, 0x20), xor(calldataload(0x00), _TESTPLUS_RANDOMNESS_SLOT)) + // With a 1/2 chance, copy the contract code to the start and end. + if iszero(and(t, 0x1000)) { + // Copy to the start. + if iszero(and(t, 0x2000)) { codecopy(result, byte(1, r), codesize()) } + // Copy to the end. + codecopy(add(result, n), byte(2, r), 0x40) + } + // With a 1/16 chance, randomize the start and end. + if iszero(and(t, 0xf0000)) { + let y := mulmod(r, _LPRNG_MULTIPLIER, _LPRNG_MODULO) + mstore(add(result, 0x20), y) + mstore(add(result, n), xor(r, y)) + } + // With a 1/256 chance, make the result entirely zero bytes. + if iszero(byte(4, r)) { codecopy(result, codesize(), add(n, 0x20)) } + // Skip the zero-right-padding if not required. + if iszero(zeroRightPad) { + mstore(0x40, add(n, add(0x40, result))) // Allocate memory. + mstore(result, n) // Store the length. + break + } + mstore(add(add(result, 0x20), n), 0) // Zeroize the word after the result. + mstore(0x40, add(n, add(0x60, result))) // Allocate memory. + mstore(result, n) // Store the length. + break + } + } + } + + /// @dev Returns a random bytes string from 0 to 131071 bytes long. + /// This random bytes string may NOT be zero-right-padded. + /// This is intentional for memory robustness testing. + /// This function may return a previously returned result. + function _randomBytes() internal returns (bytes memory result) { + result = __randomBytes(false); + } + + /// @dev Returns a random bytes string from 0 to 131071 bytes long. + /// This function may return a previously returned result. + function _randomBytesZeroRightPadded() internal returns (bytes memory result) { + result = __randomBytes(true); + } + + /// @dev Truncate the bytes to `n` bytes. + /// Returns the result for function chaining. + function _truncateBytes(bytes memory b, uint256 n) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + if gt(mload(b), n) { mstore(b, n) } + result := b + } + } + + /// @dev Returns the free memory pointer. + function _freeMemoryPointer() internal pure returns (uint256 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + } + } + + /// @dev Increments the free memory pointer by a world. + function _incrementFreeMemoryPointer() internal pure { + uint256 word = 0x20; + /// @solidity memory-safe-assembly + assembly { + mstore(0x40, add(mload(0x40), word)) + } + } + + /// @dev Adapted from `bound`: + /// https://github.com/foundry-rs/forge-std/blob/ff4bf7db008d096ea5a657f2c20516182252a3ed/src/StdUtils.sol#L10 + /// Differentially fuzzed tested against the original implementation. + function _hem(uint256 x, uint256 min, uint256 max) internal pure virtual returns (uint256 result) { + require(min <= max, "Max is less than min."); + /// @solidity memory-safe-assembly + assembly { + // prettier-ignore + for {} 1 {} { + // If `x` is between `min` and `max`, return `x` directly. + // This is to ensure that dictionary values + // do not get shifted if the min is nonzero. + // More info: https://github.com/foundry-rs/forge-std/issues/188 + if iszero(or(lt(x, min), gt(x, max))) { + result := x + break + } + let size := add(sub(max, min), 1) + if lt(gt(x, 3), gt(size, x)) { + result := add(min, x) + break + } + if lt(lt(x, not(3)), gt(size, not(x))) { + result := sub(max, not(x)) + break + } + // Otherwise, wrap x into the range [min, max], + // i.e. the range is inclusive. + if iszero(lt(x, max)) { + let d := sub(x, max) + let r := mod(d, size) + if iszero(r) { + result := max + break + } + result := sub(add(min, r), 1) + break + } + let d := sub(min, x) + let r := mod(d, size) + if iszero(r) { + result := min + break + } + result := add(sub(max, r), 1) + break + } + } + } + + /// @dev Deploys a contract via 0age's immutable create 2 factory for testing. + function _safeCreate2(uint256 payableAmount, bytes32 salt, bytes memory initializationCode) + internal + returns (address deploymentAddress) + { + // Canonical address of 0age's immutable create 2 factory. + address c2f = 0x0000000000FFe8B47B3e2130213B802212439497; + uint256 c2fCodeLength; + /// @solidity memory-safe-assembly + assembly { + c2fCodeLength := extcodesize(c2f) + } + if (c2fCodeLength == 0) { + bytes memory ic2fBytecode = + hex"60806040526004361061003f5760003560e01c806308508b8f1461004457806364e030871461009857806385cf97ab14610138578063a49a7c90146101bc575b600080fd5b34801561005057600080fd5b506100846004803603602081101561006757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101ec565b604080519115158252519081900360200190f35b61010f600480360360408110156100ae57600080fd5b813591908101906040810160208201356401000000008111156100d057600080fd5b8201836020820111156100e257600080fd5b8035906020019184600183028401116401000000008311171561010457600080fd5b509092509050610217565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561014457600080fd5b5061010f6004803603604081101561015b57600080fd5b8135919081019060408101602082013564010000000081111561017d57600080fd5b82018360208201111561018f57600080fd5b803590602001918460018302840111640100000000831117156101b157600080fd5b509092509050610592565b3480156101c857600080fd5b5061010f600480360360408110156101df57600080fd5b508035906020013561069e565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205460ff1690565b600083606081901c33148061024c57507fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116155b6102a1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260458152602001806107746045913960600191505060405180910390fd5b606084848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920182905250604051855195965090943094508b93508692506020918201918291908401908083835b6020831061033557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016102f8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff018019909216911617905260408051929094018281037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00183528085528251928201929092207fff000000000000000000000000000000000000000000000000000000000000008383015260609890981b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016602183015260358201969096526055808201979097528251808203909701875260750182525084519484019490942073ffffffffffffffffffffffffffffffffffffffff81166000908152938490529390922054929350505060ff16156104a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603f815260200180610735603f913960400191505060405180910390fd5b81602001825188818334f5955050508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461053a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260468152602001806107b96046913960600191505060405180910390fd5b50505073ffffffffffffffffffffffffffffffffffffffff8116600090815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790559392505050565b6000308484846040516020018083838082843760408051919093018181037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001825280845281516020928301207fff000000000000000000000000000000000000000000000000000000000000008383015260609990991b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000166021820152603581019790975260558088019890985282518088039098018852607590960182525085519585019590952073ffffffffffffffffffffffffffffffffffffffff81166000908152948590529490932054939450505060ff909116159050610697575060005b9392505050565b604080517fff000000000000000000000000000000000000000000000000000000000000006020808301919091523060601b6021830152603582018590526055808301859052835180840390910181526075909201835281519181019190912073ffffffffffffffffffffffffffffffffffffffff81166000908152918290529190205460ff161561072e575060005b9291505056fe496e76616c696420636f6e7472616374206372656174696f6e202d20636f6e74726163742068617320616c7265616479206265656e206465706c6f7965642e496e76616c69642073616c74202d206669727374203230206279746573206f66207468652073616c74206d757374206d617463682063616c6c696e6720616464726573732e4661696c656420746f206465706c6f7920636f6e7472616374207573696e672070726f76696465642073616c7420616e6420696e697469616c697a6174696f6e20636f64652ea265627a7a723058202bdc55310d97c4088f18acf04253db593f0914059f0c781a9df3624dcef0d1cf64736f6c634300050a0032"; + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, 0xb4d6c782) // `etch(address,bytes)`. + mstore(add(m, 0x20), c2f) + mstore(add(m, 0x40), 0x40) + let n := mload(ic2fBytecode) + mstore(add(m, 0x60), n) + for { let i := 0 } lt(i, n) { i := add(0x20, i) } { + mstore(add(add(m, 0x80), i), mload(add(add(ic2fBytecode, 0x20), i))) + } + pop(call(gas(), _VM_ADDRESS, 0, add(m, 0x1c), add(n, 0x64), 0x00, 0x00)) + } + } + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(initializationCode) + mstore(m, 0x64e03087) // `safeCreate2(bytes32,bytes)`. + mstore(add(m, 0x20), salt) + mstore(add(m, 0x40), 0x40) + mstore(add(m, 0x60), n) + // prettier-ignore + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(m, 0x80), i), mload(add(add(initializationCode, 0x20), i))) + } + if iszero(call(gas(), c2f, payableAmount, add(m, 0x1c), add(n, 0x64), m, 0x20)) { + returndatacopy(m, m, returndatasize()) + revert(m, returndatasize()) + } + deploymentAddress := mload(m) + } + } + + /// @dev Deploys a contract via 0age's immutable create 2 factory for testing. + function _safeCreate2(bytes32 salt, bytes memory initializationCode) internal returns (address deploymentAddress) { + deploymentAddress = _safeCreate2(0, salt, initializationCode); + } + + /// @dev This function will make forge's gas output display the approximate codesize of + /// the test contract as the amount of gas burnt. Useful for quick guess checking if + /// certain optimizations actually compiles to similar bytecode. + function test__codesize() external view { + /// @solidity memory-safe-assembly + assembly { + // If the caller is the contract itself (i.e. recursive call), burn all the gas. + if eq(caller(), address()) { invalid() } + mstore(0x00, 0xf09ff470) // Store the function selector of `test__codesize()`. + pop(staticcall(codesize(), address(), 0x1c, 0x04, 0x00, 0x00)) + } + } +}