diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8e7b9b4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "solidity.remappings": [ + "ds-test/=lib/forge-std/lib/ds-test/src/", + "forge-std/=lib/forge-std/src", + "src/=./src/", + "permit2/=lib/permit2/", + "solmate/=lib/solmate/" + ] +} diff --git a/foundry.toml b/foundry.toml index 25b918f..a661e69 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,4 +3,13 @@ src = "src" out = "out" libs = ["lib"] +optimizer = true +optimizer_runs = 10_000 + +# Configs to support CREATE2 +solc = "0.8.29" # Pin solidity version +evm_version = "paris" # Because PUSH0 may not be supported everywhere +bytecode_hash = "none" # Avoid bytecode hash to prevent changes to bytecode +cbor_metadata = false # Disable CBOR metadata + # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index cdc1fe9..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterScript is Script { - Counter public counter; - - function setUp() public {} - - function run() public { - vm.startBroadcast(); - - counter = new Counter(); - - vm.stopBroadcast(); - } -} diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol new file mode 100644 index 0000000..63788b7 --- /dev/null +++ b/script/Deploy.s.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8; + +import {Script, console} from "forge-std/Script.sol"; +import {BungeeApproveAndBridge} from "src/BungeeApproveAndBridge.sol"; + +contract DeployScript is Script { + BungeeApproveAndBridge public bungeeApproveAndBridge; + + address constant SOCKET_GATEWAY = 0x3a23F943181408EAC424116Af7b7790c94Cb97a5; + + function run() public { + address txOrigin = msg.sender; + address create2Deployer = 0x4e59b44847b379578588920cA78FbF26c0B4956C; // foundry uses this contract by default + bytes32 salt = keccak256(abi.encode(uint256(9999999999999999999999))); // change this before deploying if needed + console.log("Deployer: ", txOrigin); + console.log("Create2 Deployer: ", create2Deployer); + console.logBytes32(salt); + + address computedAddress = vm.computeCreate2Address( + salt, + keccak256(abi.encodePacked(type(BungeeApproveAndBridge).creationCode, abi.encode(SOCKET_GATEWAY))), + create2Deployer + ); + console.log("Computed address: ", computedAddress); + + bungeeApproveAndBridge = BungeeApproveAndBridge(deploy(salt)); + console.log("Deployed BungeeApproveAndBridge at: ", address(bungeeApproveAndBridge)); + } + + function deploy(bytes32 salt) public returns (address addr) { + vm.broadcast(); + BungeeApproveAndBridge _bungeeApproveAndBridge = new BungeeApproveAndBridge{salt: salt}(SOCKET_GATEWAY); + addr = address(_bungeeApproveAndBridge); + console.log("Deployed contract to: %s", address(addr)); + + return addr; + } +} diff --git a/src/BungeeApproveAndBridge.sol b/src/BungeeApproveAndBridge.sol new file mode 100644 index 0000000..6f932c2 --- /dev/null +++ b/src/BungeeApproveAndBridge.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8; + +import {ApproveAndBridge, IERC20} from "./mixin/ApproveAndBridge.sol"; +import {Math} from "./vendored/Math.sol"; + +/// ! @dev UNAUDITED UNTESTED Do not use in production +/// @dev Performs two steps before bridging via SocketGateway: +/// 1. Modify input amount in calldata +/// 2. Modify output amount in calldata +/// 3. Call SocketGateway.fallback() with the modified calldata +contract BungeeApproveAndBridge is ApproveAndBridge { + error InvalidInput(); + error PositionOutOfBounds(); + error BridgeFailed(); + + /// @dev ModifyCalldataParams is a struct that contains information required to modify SocketGateway calldata + /// @dev the input amount index, modify output flag, and output amount index + struct ModifyCalldataParams { + uint256 inputAmountIdx; + bool modifyOutput; + uint256 outputAmountIdx; + } + + /// @dev routeIds on SocketGateway are 4 bytes + uint8 private constant ROUTE_ID_BYTES_LENGTH = 4; + /// @dev there are 3 params in ModifyCalldataParams + uint8 private constant MODIFY_CALLDATA_PARAMS_COUNT = 3; + /// @dev each ModifyCalldataParams is 32 bytes + uint8 private constant MODIFY_CALLDATA_LENGTH_BYTES = 32; + /// @dev total length of the modify calldata bytes + uint8 private constant MODIFY_CALLDATA_LENGTH = MODIFY_CALLDATA_PARAMS_COUNT * MODIFY_CALLDATA_LENGTH_BYTES; + /// @dev minimum length of the data payload + /// @dev should atleast include the routeId and the ModifyCalldataParams + uint8 private constant MIN_DATA_LENGTH = ROUTE_ID_BYTES_LENGTH + MODIFY_CALLDATA_LENGTH; + + /// @dev SocketGateway address + address public immutable SOCKET_GATEWAY; + + constructor(address socketGateway_) { + require(socketGateway_.code.length > 0, "Socket gateway contract not deployed"); + + SOCKET_GATEWAY = socketGateway_; + } + + /** + * @notice Approval should be given to the SocketGateway address + * @dev Returns the SocketGateway address + */ + function bridgeApprovalTarget() public view override returns (address) { + return address(SOCKET_GATEWAY); + } + + /** + * @notice Bridge the token via SocketGateway + * @dev Modifies SocketGateway calldata to modify the input and output amounts before bridging + * @param token The token to bridge + * @param amount The amount of token to bridge + * @param nativeTokenExtraFee extra fee in native token, if any + * @param data encoded bytes including SocketGateway calldata and ModifyCalldataParams + */ + function bridge(IERC20 token, uint256 amount, uint256 nativeTokenExtraFee, bytes calldata data) internal override { + // decode & parse data to find positions in calldata to modify + bytes memory modifiedCalldata = _parseAndModifyCalldata(amount, data); + + // execute using the modified calldata via SocketGateway.fallback() + (bool success,) = address(token) == NATIVE_TOKEN_ADDRESS + ? address(SOCKET_GATEWAY).call{value: amount + nativeTokenExtraFee}(modifiedCalldata) + : address(SOCKET_GATEWAY).call{value: nativeTokenExtraFee}(modifiedCalldata); + if (!success) revert BridgeFailed(); + } + + /** + * @dev Parses and modifies the calldata to modify the input and output amounts before bridging + * @param amount Updated input amount to use to modify the calldata + * @param data encoded bytes including SocketGateway calldata and ModifyCalldataParams + * @return modifiedCalldata The modified calldata + */ + function _parseAndModifyCalldata(uint256 amount, bytes calldata data) internal pure returns (bytes memory) { + // Parse the data into route calldata and ModifyCalldataParams + (bytes memory routeCalldata, ModifyCalldataParams memory modifyCalldataParams) = _parseCalldata(data); + + // Read the original input amount from the calldata + // before modifying input amount + uint256 originalInput = _readUint256({_data: routeCalldata, _index: modifyCalldataParams.inputAmountIdx}); + + // Replace the input amount in the calldata + bytes memory modifiedCalldata = + _replaceUint256({_original: routeCalldata, _start: modifyCalldataParams.inputAmountIdx, _amount: amount}); + + // Optionally replace the output amount if required + // in case of bridges like Across, need to modify both input and output amounts + // - decode current input and output amounts from calldata + // - calculate and apply the percentage diff bw new and old input amount on the old output amount + // - replace the output amount at the index with the new amount + // - assumes output amount is always uint256 in SocketGateway impls + if (modifyCalldataParams.modifyOutput) { + uint256 originalOutput = _readUint256({_data: routeCalldata, _index: modifyCalldataParams.outputAmountIdx}); + uint256 newOutput = _applyPctDiff({_base: originalInput, _compare: amount, _target: originalOutput}); + modifiedCalldata = _replaceUint256({ + _original: modifiedCalldata, + _start: modifyCalldataParams.outputAmountIdx, + _amount: newOutput + }); + } + + return modifiedCalldata; + } + + /** + * @dev Parses the calldata to extract the route calldata and ModifyCalldataParams + * @param _data The calldata to parse + * @return routeCalldata The SocketGateway route calldata + * @return modifyCalldataParams The ModifyCalldataParams + */ + function _parseCalldata(bytes calldata _data) internal pure returns (bytes memory, ModifyCalldataParams memory) { + // calldata should have minimum of routeId and ModifyCalldataParams + if (_data.length < MIN_DATA_LENGTH) revert InvalidInput(); + uint256 routeCalldataLength = _data.length - MODIFY_CALLDATA_LENGTH; + + // Extract the route execution calldata + bytes memory routeCalldata = _data[:routeCalldataLength]; + + // Extract the ModifyCalldataParams + ModifyCalldataParams memory modifyCalldataParams; + (modifyCalldataParams.inputAmountIdx, modifyCalldataParams.modifyOutput, modifyCalldataParams.outputAmountIdx) = + abi.decode(_data[routeCalldataLength:], (uint256, bool, uint256)); + + return (routeCalldata, modifyCalldataParams); + } + + /** + * @dev Replaces a uint256 at a given position in a bytes data with a new uint256 + * @dev Directly modifies the original bytes data in-place without creating a new copy + */ + function _replaceUint256(bytes memory _original, uint256 _start, uint256 _amount) + internal + pure + returns (bytes memory) + { + // check if the _start is out of bounds + if (_start + 32 > _original.length) revert PositionOutOfBounds(); + + // Directly modify externalData in-place without creating a new copy + assembly { + // Calculate position in memory where we need to write the new amount + // Write the amount at that position + mstore(add(add(_original, 32), _start), _amount) + } + + return _original; + } + + /** + * @dev Reads a uint256 at a given byte index in a bytes array + */ + function _readUint256(bytes memory _data, uint256 _index) internal pure returns (uint256 value) { + if (_data.length < _index + 32) revert PositionOutOfBounds(); + assembly { + value := mload(add(add(_data, 0x20), _index)) + } + } + + /** + * @dev Applies a percentage difference to a target number + */ + function _applyPctDiff(uint256 _base, uint256 _compare, uint256 _target) internal pure returns (uint256) { + if (_base == 0) revert InvalidInput(); + return Math.mulDiv({x: _target, y: _compare, denominator: _base}); + } +} diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/src/interface/IApproveAndBridge.sol b/src/interface/IApproveAndBridge.sol new file mode 100644 index 0000000..daba599 --- /dev/null +++ b/src/interface/IApproveAndBridge.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8; + +import {IERC20} from "../vendored/IERC20.sol"; + +interface IApproveAndBridge { + function approveAndBridge(IERC20 token, uint256 minAmount, uint256 nativeTokenExtraFee, bytes calldata data) + external; + + function bridgeApprovalTarget() external view returns (address); +} diff --git a/src/mixin/ApproveAndBridge.sol b/src/mixin/ApproveAndBridge.sol new file mode 100644 index 0000000..e91110c --- /dev/null +++ b/src/mixin/ApproveAndBridge.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8; + +import {IApproveAndBridge} from "../interface/IApproveAndBridge.sol"; +import {IERC20} from "../vendored/IERC20.sol"; +import {SafeERC20} from "../vendored/SafeERC20.sol"; + +abstract contract ApproveAndBridge is IApproveAndBridge { + using SafeERC20 for IERC20; + + error MinAmountNotMet(); + + /// @dev Address used to represent the native token + address public constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + /** + * @dev This function isn't intended to be called directly, it should be delegatecalled instead. + * @param token The token to bridge + * @param minAmount The minimum amount of tokens to bridge. minAmount should not be too small if the sell amount is big + * @param nativeTokenExtraFee The extra fee to pay in native tokens + * @param data The data to pass to the bridge + */ + function approveAndBridge(IERC20 token, uint256 minAmount, uint256 nativeTokenExtraFee, bytes calldata data) + external + { + // get the balance of the token + uint256 balance = address(token) == NATIVE_TOKEN_ADDRESS + // if native token, reduce the extra fee from balance + // if not enough balance, it will underflow and revert + ? address(this).balance - nativeTokenExtraFee + : token.balanceOf(address(this)); + + // check if the balance is greater than the minAmount + if (balance < minAmount) revert MinAmountNotMet(); + + // approve the bridgeApprovalTarget if ERC20 + if (address(token) != NATIVE_TOKEN_ADDRESS) { + token.forceApprove(bridgeApprovalTarget(), balance); + } + + // bridge the token + bridge(token, balance, nativeTokenExtraFee, data); + } + + /** + * @dev Returns the address of the contract that should be approved to bridge the token + */ + function bridgeApprovalTarget() public view virtual returns (address); + + /** + * @dev Bridges the token + */ + function bridge(IERC20 token, uint256 amount, uint256 nativeTokenExtraFee, bytes calldata data) internal virtual; +} diff --git a/src/vendored/IERC20.sol b/src/vendored/IERC20.sol new file mode 100644 index 0000000..748f1ac --- /dev/null +++ b/src/vendored/IERC20.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT + +// Vendored from OpenZeppelin contracts with minor modifications: +// - Formatted code +// +// Note: v5.2 points to commit acd4ff74de833399287ed6b31b4debf6b2b35527. + +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.20; + +/** + * @dev Interface of the ERC-20 standard as defined in the ERC. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool); +} diff --git a/src/vendored/Math.sol b/src/vendored/Math.sol new file mode 100644 index 0000000..7940e33 --- /dev/null +++ b/src/vendored/Math.sol @@ -0,0 +1,690 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/Math.sol) + +// Vendored from OpenZeppelin contracts +// +// Note: v5.2 points to commit acd4ff74de833399287ed6b31b4debf6b2b35527. + +pragma solidity ^0.8.20; + +import {Panic} from "./Panic.sol"; +import {SafeCast} from "./SafeCast.sol"; + +/** + * @dev Standard math utilities missing in the Solidity language. + */ +library Math { + enum Rounding { + Floor, // Toward negative infinity + Ceil, // Toward positive infinity + Trunc, // Toward zero + Expand // Away from zero + + } + + /** + * @dev Returns the addition of two unsigned integers, with an success flag (no overflow). + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the subtraction of two unsigned integers, with an success flag (no overflow). + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an success flag (no overflow). + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a success flag (no division by zero). + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero). + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant. + * + * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone. + * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute + * one branch when needed, making this function more expensive. + */ + function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) { + unchecked { + // branchless ternary works because: + // b ^ (a ^ b) == a + // b ^ 0 == b + return b ^ ((a ^ b) * SafeCast.toUint(condition)); + } + } + + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return ternary(a > b, a, b); + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return ternary(a < b, a, b); + } + + /** + * @dev Returns the average of two numbers. The result is rounded towards + * zero. + */ + function average(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b) / 2 can overflow. + return (a & b) + (a ^ b) / 2; + } + + /** + * @dev Returns the ceiling of the division of two numbers. + * + * This differs from standard division with `/` in that it rounds towards infinity instead + * of rounding towards zero. + */ + function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { + if (b == 0) { + // Guarantee the same behavior as in a regular Solidity division. + Panic.panic(Panic.DIVISION_BY_ZERO); + } + + // The following calculation ensures accurate ceiling division without overflow. + // Since a is non-zero, (a - 1) / b will not overflow. + // The largest possible result occurs when (a - 1) / b is type(uint256).max, + // but the largest value we can obtain is type(uint256).max - 1, which happens + // when a = type(uint256).max and b = 1. + unchecked { + return SafeCast.toUint(a > 0) * ((a - 1) / b + 1); + } + } + + /** + * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or + * denominator == 0. + * + * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by + * Uniswap Labs also under MIT license. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use + // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2²⁵⁶ + prod0. + uint256 prod0 = x * y; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(x, y, not(0)) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division. + if (prod1 == 0) { + // Solidity will revert if denominator == 0, unlike the div opcode on its own. + // The surrounding unchecked block does not change this fact. + // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. + return prod0 / denominator; + } + + // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0. + if (denominator <= prod1) { + Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW)); + } + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0]. + uint256 remainder; + assembly { + // Compute remainder using mulmod. + remainder := mulmod(x, y, denominator) + + // Subtract 256 bit number from 512 bit number. + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator and compute largest power of two divisor of denominator. + // Always >= 1. See https://cs.stackexchange.com/q/138556/92363. + + uint256 twos = denominator & (0 - denominator); + assembly { + // Divide denominator by twos. + denominator := div(denominator, twos) + + // Divide [prod1 prod0] by twos. + prod0 := div(prod0, twos) + + // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one. + twos := add(div(sub(0, twos), twos), 1) + } + + // Shift in bits from prod1 into prod0. + prod0 |= prod1 * twos; + + // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such + // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for + // four bits. That is, denominator * inv ≡ 1 mod 2⁴. + uint256 inverse = (3 * denominator) ^ 2; + + // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also + // works in modular arithmetic, doubling the correct bits in each step. + inverse *= 2 - denominator * inverse; // inverse mod 2⁸ + inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶ + inverse *= 2 - denominator * inverse; // inverse mod 2³² + inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴ + inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸ + inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶ + + // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. + // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is + // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inverse; + return result; + } + } + + /** + * @dev Calculates x * y / denominator with full precision, following the selected rounding direction. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { + return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0); + } + + /** + * @dev Calculate the modular multiplicative inverse of a number in Z/nZ. + * + * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0. + * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible. + * + * If the input value is not inversible, 0 is returned. + * + * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the + * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}. + */ + function invMod(uint256 a, uint256 n) internal pure returns (uint256) { + unchecked { + if (n == 0) return 0; + + // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version) + // Used to compute integers x and y such that: ax + ny = gcd(a, n). + // When the gcd is 1, then the inverse of a modulo n exists and it's x. + // ax + ny = 1 + // ax = 1 + (-y)n + // ax ≡ 1 (mod n) # x is the inverse of a modulo n + + // If the remainder is 0 the gcd is n right away. + uint256 remainder = a % n; + uint256 gcd = n; + + // Therefore the initial coefficients are: + // ax + ny = gcd(a, n) = n + // 0a + 1n = n + int256 x = 0; + int256 y = 1; + + while (remainder != 0) { + uint256 quotient = gcd / remainder; + + (gcd, remainder) = ( + // The old remainder is the next gcd to try. + remainder, + // Compute the next remainder. + // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd + // where gcd is at most n (capped to type(uint256).max) + gcd - remainder * quotient + ); + + (x, y) = ( + // Increment the coefficient of a. + y, + // Decrement the coefficient of n. + // Can overflow, but the result is casted to uint256 so that the + // next value of y is "wrapped around" to a value between 0 and n - 1. + x - y * int256(quotient) + ); + } + + if (gcd != 1) return 0; // No inverse exists. + return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative. + } + } + + /** + * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`. + * + * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is + * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that + * `a**(p-2)` is the modular multiplicative inverse of a in Fp. + * + * NOTE: this function does NOT check that `p` is a prime greater than `2`. + */ + function invModPrime(uint256 a, uint256 p) internal view returns (uint256) { + unchecked { + return Math.modExp(a, p - 2, p); + } + } + + /** + * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m) + * + * Requirements: + * - modulus can't be zero + * - underlying staticcall to precompile must succeed + * + * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make + * sure the chain you're using it on supports the precompiled contract for modular exponentiation + * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, + * the underlying function will succeed given the lack of a revert, but the result may be incorrectly + * interpreted as 0. + */ + function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) { + (bool success, uint256 result) = tryModExp(b, e, m); + if (!success) { + Panic.panic(Panic.DIVISION_BY_ZERO); + } + return result; + } + + /** + * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m). + * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying + * to operate modulo 0 or if the underlying precompile reverted. + * + * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain + * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in + * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack + * of a revert, but the result may be incorrectly interpreted as 0. + */ + function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) { + if (m == 0) return (false, 0); + assembly ("memory-safe") { + let ptr := mload(0x40) + // | Offset | Content | Content (Hex) | + // |-----------|------------|--------------------------------------------------------------------| + // | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 | + // | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 | + // | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 | + // | 0x60:0x7f | value of b | 0x<.............................................................b> | + // | 0x80:0x9f | value of e | 0x<.............................................................e> | + // | 0xa0:0xbf | value of m | 0x<.............................................................m> | + mstore(ptr, 0x20) + mstore(add(ptr, 0x20), 0x20) + mstore(add(ptr, 0x40), 0x20) + mstore(add(ptr, 0x60), b) + mstore(add(ptr, 0x80), e) + mstore(add(ptr, 0xa0), m) + + // Given the result < m, it's guaranteed to fit in 32 bytes, + // so we can use the memory scratch space located at offset 0. + success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20) + result := mload(0x00) + } + } + + /** + * @dev Variant of {modExp} that supports inputs of arbitrary length. + */ + function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) { + (bool success, bytes memory result) = tryModExp(b, e, m); + if (!success) { + Panic.panic(Panic.DIVISION_BY_ZERO); + } + return result; + } + + /** + * @dev Variant of {tryModExp} that supports inputs of arbitrary length. + */ + function tryModExp(bytes memory b, bytes memory e, bytes memory m) + internal + view + returns (bool success, bytes memory result) + { + if (_zeroBytes(m)) return (false, new bytes(0)); + + uint256 mLen = m.length; + + // Encode call args in result and move the free memory pointer + result = abi.encodePacked(b.length, e.length, mLen, b, e, m); + + assembly ("memory-safe") { + let dataPtr := add(result, 0x20) + // Write result on top of args to avoid allocating extra memory. + success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen) + // Overwrite the length. + // result.length > returndatasize() is guaranteed because returndatasize() == m.length + mstore(result, mLen) + // Set the memory pointer after the returned data. + mstore(0x40, add(dataPtr, mLen)) + } + } + + /** + * @dev Returns whether the provided byte array is zero. + */ + function _zeroBytes(bytes memory byteArray) private pure returns (bool) { + for (uint256 i = 0; i < byteArray.length; ++i) { + if (byteArray[i] != 0) { + return false; + } + } + return true; + } + + /** + * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded + * towards zero. + * + * This method is based on Newton's method for computing square roots; the algorithm is restricted to only + * using integer operations. + */ + function sqrt(uint256 a) internal pure returns (uint256) { + unchecked { + // Take care of easy edge cases when a == 0 or a == 1 + if (a <= 1) { + return a; + } + + // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a + // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between + // the current value as `ε_n = | x_n - sqrt(a) |`. + // + // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root + // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is + // bigger than any uint256. + // + // By noticing that + // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)` + // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar + // to the msb function. + uint256 aa = a; + uint256 xn = 1; + + if (aa >= (1 << 128)) { + aa >>= 128; + xn <<= 64; + } + if (aa >= (1 << 64)) { + aa >>= 64; + xn <<= 32; + } + if (aa >= (1 << 32)) { + aa >>= 32; + xn <<= 16; + } + if (aa >= (1 << 16)) { + aa >>= 16; + xn <<= 8; + } + if (aa >= (1 << 8)) { + aa >>= 8; + xn <<= 4; + } + if (aa >= (1 << 4)) { + aa >>= 4; + xn <<= 2; + } + if (aa >= (1 << 2)) { + xn <<= 1; + } + + // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1). + // + // We can refine our estimation by noticing that the middle of that interval minimizes the error. + // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2). + // This is going to be our x_0 (and ε_0) + xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2) + + // From here, Newton's method give us: + // x_{n+1} = (x_n + a / x_n) / 2 + // + // One should note that: + // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a + // = ((x_n² + a) / (2 * x_n))² - a + // = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a + // = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²) + // = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²) + // = (x_n² - a)² / (2 * x_n)² + // = ((x_n² - a) / (2 * x_n))² + // ≥ 0 + // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n + // + // This gives us the proof of quadratic convergence of the sequence: + // ε_{n+1} = | x_{n+1} - sqrt(a) | + // = | (x_n + a / x_n) / 2 - sqrt(a) | + // = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) | + // = | (x_n - sqrt(a))² / (2 * x_n) | + // = | ε_n² / (2 * x_n) | + // = ε_n² / | (2 * x_n) | + // + // For the first iteration, we have a special case where x_0 is known: + // ε_1 = ε_0² / | (2 * x_0) | + // ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2))) + // ≤ 2**(2*e-4) / (3 * 2**(e-1)) + // ≤ 2**(e-3) / 3 + // ≤ 2**(e-3-log2(3)) + // ≤ 2**(e-4.5) + // + // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n: + // ε_{n+1} = ε_n² / | (2 * x_n) | + // ≤ (2**(e-k))² / (2 * 2**(e-1)) + // ≤ 2**(2*e-2*k) / 2**e + // ≤ 2**(e-2*k) + xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above + xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5 + xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9 + xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18 + xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36 + xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72 + + // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision + // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either + // sqrt(a) or sqrt(a) + 1. + return xn - SafeCast.toUint(xn > a / xn); + } + } + + /** + * @dev Calculates sqrt(a), following the selected rounding direction. + */ + function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = sqrt(a); + return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a); + } + } + + /** + * @dev Return the log in base 2 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log2(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + uint256 exp; + unchecked { + exp = 128 * SafeCast.toUint(value > (1 << 128) - 1); + value >>= exp; + result += exp; + + exp = 64 * SafeCast.toUint(value > (1 << 64) - 1); + value >>= exp; + result += exp; + + exp = 32 * SafeCast.toUint(value > (1 << 32) - 1); + value >>= exp; + result += exp; + + exp = 16 * SafeCast.toUint(value > (1 << 16) - 1); + value >>= exp; + result += exp; + + exp = 8 * SafeCast.toUint(value > (1 << 8) - 1); + value >>= exp; + result += exp; + + exp = 4 * SafeCast.toUint(value > (1 << 4) - 1); + value >>= exp; + result += exp; + + exp = 2 * SafeCast.toUint(value > (1 << 2) - 1); + value >>= exp; + result += exp; + + result += SafeCast.toUint(value > 1); + } + return result; + } + + /** + * @dev Return the log in base 2, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log2(value); + return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value); + } + } + + /** + * @dev Return the log in base 10 of a positive value rounded towards zero. + * Returns 0 if given 0. + */ + function log10(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >= 10 ** 64) { + value /= 10 ** 64; + result += 64; + } + if (value >= 10 ** 32) { + value /= 10 ** 32; + result += 32; + } + if (value >= 10 ** 16) { + value /= 10 ** 16; + result += 16; + } + if (value >= 10 ** 8) { + value /= 10 ** 8; + result += 8; + } + if (value >= 10 ** 4) { + value /= 10 ** 4; + result += 4; + } + if (value >= 10 ** 2) { + value /= 10 ** 2; + result += 2; + } + if (value >= 10 ** 1) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 10, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log10(value); + return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value); + } + } + + /** + * @dev Return the log in base 256 of a positive value rounded towards zero. + * Returns 0 if given 0. + * + * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. + */ + function log256(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + uint256 isGt; + unchecked { + isGt = SafeCast.toUint(value > (1 << 128) - 1); + value >>= isGt * 128; + result += isGt * 16; + + isGt = SafeCast.toUint(value > (1 << 64) - 1); + value >>= isGt * 64; + result += isGt * 8; + + isGt = SafeCast.toUint(value > (1 << 32) - 1); + value >>= isGt * 32; + result += isGt * 4; + + isGt = SafeCast.toUint(value > (1 << 16) - 1); + value >>= isGt * 16; + result += isGt * 2; + + result += SafeCast.toUint(value > (1 << 8) - 1); + } + return result; + } + + /** + * @dev Return the log in base 256, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log256(value); + return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value); + } + } + + /** + * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. + */ + function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { + return uint8(rounding) % 2 == 1; + } +} diff --git a/src/vendored/Panic.sol b/src/vendored/Panic.sol new file mode 100644 index 0000000..90f50f3 --- /dev/null +++ b/src/vendored/Panic.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol) + +// Vendored from OpenZeppelin contracts +// +// Note: v5.2 points to commit acd4ff74de833399287ed6b31b4debf6b2b35527. + +pragma solidity ^0.8.20; + +/** + * @dev Helper library for emitting standardized panic codes. + * + * ```solidity + * contract Example { + * using Panic for uint256; + * + * // Use any of the declared internal constants + * function foo() { Panic.GENERIC.panic(); } + * + * // Alternatively + * function foo() { Panic.panic(Panic.GENERIC); } + * } + * ``` + * + * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil]. + * + * _Available since v5.1._ + */ +// slither-disable-next-line unused-state +library Panic { + /// @dev generic / unspecified error + uint256 internal constant GENERIC = 0x00; + /// @dev used by the assert() builtin + uint256 internal constant ASSERT = 0x01; + /// @dev arithmetic underflow or overflow + uint256 internal constant UNDER_OVERFLOW = 0x11; + /// @dev division or modulo by zero + uint256 internal constant DIVISION_BY_ZERO = 0x12; + /// @dev enum conversion error + uint256 internal constant ENUM_CONVERSION_ERROR = 0x21; + /// @dev invalid encoding in storage + uint256 internal constant STORAGE_ENCODING_ERROR = 0x22; + /// @dev empty array pop + uint256 internal constant EMPTY_ARRAY_POP = 0x31; + /// @dev array out of bounds access + uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32; + /// @dev resource error (too large allocation or too large array) + uint256 internal constant RESOURCE_ERROR = 0x41; + /// @dev calling invalid internal function + uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51; + + /// @dev Reverts with a panic code. Recommended to use with + /// the internal constants with predefined codes. + function panic(uint256 code) internal pure { + assembly ("memory-safe") { + mstore(0x00, 0x4e487b71) + mstore(0x20, code) + revert(0x1c, 0x24) + } + } +} \ No newline at end of file diff --git a/src/vendored/SafeCast.sol b/src/vendored/SafeCast.sol new file mode 100644 index 0000000..fc189fc --- /dev/null +++ b/src/vendored/SafeCast.sol @@ -0,0 +1,1166 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol) +// This file was procedurally generated from scripts/generate/templates/SafeCast.js. + +// Vendored from OpenZeppelin contracts +// +// Note: v5.2 points to commit acd4ff74de833399287ed6b31b4debf6b2b35527. + +pragma solidity ^0.8.20; + +/** + * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow + * checks. + * + * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can + * easily result in undesired exploitation or bugs, since developers usually + * assume that overflows raise errors. `SafeCast` restores this intuition by + * reverting the transaction when such an operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeCast { + /** + * @dev Value doesn't fit in an uint of `bits` size. + */ + error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); + + /** + * @dev An int value doesn't fit in an uint of `bits` size. + */ + error SafeCastOverflowedIntToUint(int256 value); + + /** + * @dev Value doesn't fit in an int of `bits` size. + */ + error SafeCastOverflowedIntDowncast(uint8 bits, int256 value); + + /** + * @dev An uint value doesn't fit in an int of `bits` size. + */ + error SafeCastOverflowedUintToInt(uint256 value); + + /** + * @dev Returns the downcasted uint248 from uint256, reverting on + * overflow (when the input is greater than largest uint248). + * + * Counterpart to Solidity's `uint248` operator. + * + * Requirements: + * + * - input must fit into 248 bits + */ + function toUint248(uint256 value) internal pure returns (uint248) { + if (value > type(uint248).max) { + revert SafeCastOverflowedUintDowncast(248, value); + } + return uint248(value); + } + + /** + * @dev Returns the downcasted uint240 from uint256, reverting on + * overflow (when the input is greater than largest uint240). + * + * Counterpart to Solidity's `uint240` operator. + * + * Requirements: + * + * - input must fit into 240 bits + */ + function toUint240(uint256 value) internal pure returns (uint240) { + if (value > type(uint240).max) { + revert SafeCastOverflowedUintDowncast(240, value); + } + return uint240(value); + } + + /** + * @dev Returns the downcasted uint232 from uint256, reverting on + * overflow (when the input is greater than largest uint232). + * + * Counterpart to Solidity's `uint232` operator. + * + * Requirements: + * + * - input must fit into 232 bits + */ + function toUint232(uint256 value) internal pure returns (uint232) { + if (value > type(uint232).max) { + revert SafeCastOverflowedUintDowncast(232, value); + } + return uint232(value); + } + + /** + * @dev Returns the downcasted uint224 from uint256, reverting on + * overflow (when the input is greater than largest uint224). + * + * Counterpart to Solidity's `uint224` operator. + * + * Requirements: + * + * - input must fit into 224 bits + */ + function toUint224(uint256 value) internal pure returns (uint224) { + if (value > type(uint224).max) { + revert SafeCastOverflowedUintDowncast(224, value); + } + return uint224(value); + } + + /** + * @dev Returns the downcasted uint216 from uint256, reverting on + * overflow (when the input is greater than largest uint216). + * + * Counterpart to Solidity's `uint216` operator. + * + * Requirements: + * + * - input must fit into 216 bits + */ + function toUint216(uint256 value) internal pure returns (uint216) { + if (value > type(uint216).max) { + revert SafeCastOverflowedUintDowncast(216, value); + } + return uint216(value); + } + + /** + * @dev Returns the downcasted uint208 from uint256, reverting on + * overflow (when the input is greater than largest uint208). + * + * Counterpart to Solidity's `uint208` operator. + * + * Requirements: + * + * - input must fit into 208 bits + */ + function toUint208(uint256 value) internal pure returns (uint208) { + if (value > type(uint208).max) { + revert SafeCastOverflowedUintDowncast(208, value); + } + return uint208(value); + } + + /** + * @dev Returns the downcasted uint200 from uint256, reverting on + * overflow (when the input is greater than largest uint200). + * + * Counterpart to Solidity's `uint200` operator. + * + * Requirements: + * + * - input must fit into 200 bits + */ + function toUint200(uint256 value) internal pure returns (uint200) { + if (value > type(uint200).max) { + revert SafeCastOverflowedUintDowncast(200, value); + } + return uint200(value); + } + + /** + * @dev Returns the downcasted uint192 from uint256, reverting on + * overflow (when the input is greater than largest uint192). + * + * Counterpart to Solidity's `uint192` operator. + * + * Requirements: + * + * - input must fit into 192 bits + */ + function toUint192(uint256 value) internal pure returns (uint192) { + if (value > type(uint192).max) { + revert SafeCastOverflowedUintDowncast(192, value); + } + return uint192(value); + } + + /** + * @dev Returns the downcasted uint184 from uint256, reverting on + * overflow (when the input is greater than largest uint184). + * + * Counterpart to Solidity's `uint184` operator. + * + * Requirements: + * + * - input must fit into 184 bits + */ + function toUint184(uint256 value) internal pure returns (uint184) { + if (value > type(uint184).max) { + revert SafeCastOverflowedUintDowncast(184, value); + } + return uint184(value); + } + + /** + * @dev Returns the downcasted uint176 from uint256, reverting on + * overflow (when the input is greater than largest uint176). + * + * Counterpart to Solidity's `uint176` operator. + * + * Requirements: + * + * - input must fit into 176 bits + */ + function toUint176(uint256 value) internal pure returns (uint176) { + if (value > type(uint176).max) { + revert SafeCastOverflowedUintDowncast(176, value); + } + return uint176(value); + } + + /** + * @dev Returns the downcasted uint168 from uint256, reverting on + * overflow (when the input is greater than largest uint168). + * + * Counterpart to Solidity's `uint168` operator. + * + * Requirements: + * + * - input must fit into 168 bits + */ + function toUint168(uint256 value) internal pure returns (uint168) { + if (value > type(uint168).max) { + revert SafeCastOverflowedUintDowncast(168, value); + } + return uint168(value); + } + + /** + * @dev Returns the downcasted uint160 from uint256, reverting on + * overflow (when the input is greater than largest uint160). + * + * Counterpart to Solidity's `uint160` operator. + * + * Requirements: + * + * - input must fit into 160 bits + */ + function toUint160(uint256 value) internal pure returns (uint160) { + if (value > type(uint160).max) { + revert SafeCastOverflowedUintDowncast(160, value); + } + return uint160(value); + } + + /** + * @dev Returns the downcasted uint152 from uint256, reverting on + * overflow (when the input is greater than largest uint152). + * + * Counterpart to Solidity's `uint152` operator. + * + * Requirements: + * + * - input must fit into 152 bits + */ + function toUint152(uint256 value) internal pure returns (uint152) { + if (value > type(uint152).max) { + revert SafeCastOverflowedUintDowncast(152, value); + } + return uint152(value); + } + + /** + * @dev Returns the downcasted uint144 from uint256, reverting on + * overflow (when the input is greater than largest uint144). + * + * Counterpart to Solidity's `uint144` operator. + * + * Requirements: + * + * - input must fit into 144 bits + */ + function toUint144(uint256 value) internal pure returns (uint144) { + if (value > type(uint144).max) { + revert SafeCastOverflowedUintDowncast(144, value); + } + return uint144(value); + } + + /** + * @dev Returns the downcasted uint136 from uint256, reverting on + * overflow (when the input is greater than largest uint136). + * + * Counterpart to Solidity's `uint136` operator. + * + * Requirements: + * + * - input must fit into 136 bits + */ + function toUint136(uint256 value) internal pure returns (uint136) { + if (value > type(uint136).max) { + revert SafeCastOverflowedUintDowncast(136, value); + } + return uint136(value); + } + + /** + * @dev Returns the downcasted uint128 from uint256, reverting on + * overflow (when the input is greater than largest uint128). + * + * Counterpart to Solidity's `uint128` operator. + * + * Requirements: + * + * - input must fit into 128 bits + */ + function toUint128(uint256 value) internal pure returns (uint128) { + if (value > type(uint128).max) { + revert SafeCastOverflowedUintDowncast(128, value); + } + return uint128(value); + } + + /** + * @dev Returns the downcasted uint120 from uint256, reverting on + * overflow (when the input is greater than largest uint120). + * + * Counterpart to Solidity's `uint120` operator. + * + * Requirements: + * + * - input must fit into 120 bits + */ + function toUint120(uint256 value) internal pure returns (uint120) { + if (value > type(uint120).max) { + revert SafeCastOverflowedUintDowncast(120, value); + } + return uint120(value); + } + + /** + * @dev Returns the downcasted uint112 from uint256, reverting on + * overflow (when the input is greater than largest uint112). + * + * Counterpart to Solidity's `uint112` operator. + * + * Requirements: + * + * - input must fit into 112 bits + */ + function toUint112(uint256 value) internal pure returns (uint112) { + if (value > type(uint112).max) { + revert SafeCastOverflowedUintDowncast(112, value); + } + return uint112(value); + } + + /** + * @dev Returns the downcasted uint104 from uint256, reverting on + * overflow (when the input is greater than largest uint104). + * + * Counterpart to Solidity's `uint104` operator. + * + * Requirements: + * + * - input must fit into 104 bits + */ + function toUint104(uint256 value) internal pure returns (uint104) { + if (value > type(uint104).max) { + revert SafeCastOverflowedUintDowncast(104, value); + } + return uint104(value); + } + + /** + * @dev Returns the downcasted uint96 from uint256, reverting on + * overflow (when the input is greater than largest uint96). + * + * Counterpart to Solidity's `uint96` operator. + * + * Requirements: + * + * - input must fit into 96 bits + */ + function toUint96(uint256 value) internal pure returns (uint96) { + if (value > type(uint96).max) { + revert SafeCastOverflowedUintDowncast(96, value); + } + return uint96(value); + } + + /** + * @dev Returns the downcasted uint88 from uint256, reverting on + * overflow (when the input is greater than largest uint88). + * + * Counterpart to Solidity's `uint88` operator. + * + * Requirements: + * + * - input must fit into 88 bits + */ + function toUint88(uint256 value) internal pure returns (uint88) { + if (value > type(uint88).max) { + revert SafeCastOverflowedUintDowncast(88, value); + } + return uint88(value); + } + + /** + * @dev Returns the downcasted uint80 from uint256, reverting on + * overflow (when the input is greater than largest uint80). + * + * Counterpart to Solidity's `uint80` operator. + * + * Requirements: + * + * - input must fit into 80 bits + */ + function toUint80(uint256 value) internal pure returns (uint80) { + if (value > type(uint80).max) { + revert SafeCastOverflowedUintDowncast(80, value); + } + return uint80(value); + } + + /** + * @dev Returns the downcasted uint72 from uint256, reverting on + * overflow (when the input is greater than largest uint72). + * + * Counterpart to Solidity's `uint72` operator. + * + * Requirements: + * + * - input must fit into 72 bits + */ + function toUint72(uint256 value) internal pure returns (uint72) { + if (value > type(uint72).max) { + revert SafeCastOverflowedUintDowncast(72, value); + } + return uint72(value); + } + + /** + * @dev Returns the downcasted uint64 from uint256, reverting on + * overflow (when the input is greater than largest uint64). + * + * Counterpart to Solidity's `uint64` operator. + * + * Requirements: + * + * - input must fit into 64 bits + */ + function toUint64(uint256 value) internal pure returns (uint64) { + if (value > type(uint64).max) { + revert SafeCastOverflowedUintDowncast(64, value); + } + return uint64(value); + } + + /** + * @dev Returns the downcasted uint56 from uint256, reverting on + * overflow (when the input is greater than largest uint56). + * + * Counterpart to Solidity's `uint56` operator. + * + * Requirements: + * + * - input must fit into 56 bits + */ + function toUint56(uint256 value) internal pure returns (uint56) { + if (value > type(uint56).max) { + revert SafeCastOverflowedUintDowncast(56, value); + } + return uint56(value); + } + + /** + * @dev Returns the downcasted uint48 from uint256, reverting on + * overflow (when the input is greater than largest uint48). + * + * Counterpart to Solidity's `uint48` operator. + * + * Requirements: + * + * - input must fit into 48 bits + */ + function toUint48(uint256 value) internal pure returns (uint48) { + if (value > type(uint48).max) { + revert SafeCastOverflowedUintDowncast(48, value); + } + return uint48(value); + } + + /** + * @dev Returns the downcasted uint40 from uint256, reverting on + * overflow (when the input is greater than largest uint40). + * + * Counterpart to Solidity's `uint40` operator. + * + * Requirements: + * + * - input must fit into 40 bits + */ + function toUint40(uint256 value) internal pure returns (uint40) { + if (value > type(uint40).max) { + revert SafeCastOverflowedUintDowncast(40, value); + } + return uint40(value); + } + + /** + * @dev Returns the downcasted uint32 from uint256, reverting on + * overflow (when the input is greater than largest uint32). + * + * Counterpart to Solidity's `uint32` operator. + * + * Requirements: + * + * - input must fit into 32 bits + */ + function toUint32(uint256 value) internal pure returns (uint32) { + if (value > type(uint32).max) { + revert SafeCastOverflowedUintDowncast(32, value); + } + return uint32(value); + } + + /** + * @dev Returns the downcasted uint24 from uint256, reverting on + * overflow (when the input is greater than largest uint24). + * + * Counterpart to Solidity's `uint24` operator. + * + * Requirements: + * + * - input must fit into 24 bits + */ + function toUint24(uint256 value) internal pure returns (uint24) { + if (value > type(uint24).max) { + revert SafeCastOverflowedUintDowncast(24, value); + } + return uint24(value); + } + + /** + * @dev Returns the downcasted uint16 from uint256, reverting on + * overflow (when the input is greater than largest uint16). + * + * Counterpart to Solidity's `uint16` operator. + * + * Requirements: + * + * - input must fit into 16 bits + */ + function toUint16(uint256 value) internal pure returns (uint16) { + if (value > type(uint16).max) { + revert SafeCastOverflowedUintDowncast(16, value); + } + return uint16(value); + } + + /** + * @dev Returns the downcasted uint8 from uint256, reverting on + * overflow (when the input is greater than largest uint8). + * + * Counterpart to Solidity's `uint8` operator. + * + * Requirements: + * + * - input must fit into 8 bits + */ + function toUint8(uint256 value) internal pure returns (uint8) { + if (value > type(uint8).max) { + revert SafeCastOverflowedUintDowncast(8, value); + } + return uint8(value); + } + + /** + * @dev Converts a signed int256 into an unsigned uint256. + * + * Requirements: + * + * - input must be greater than or equal to 0. + */ + function toUint256(int256 value) internal pure returns (uint256) { + if (value < 0) { + revert SafeCastOverflowedIntToUint(value); + } + return uint256(value); + } + + /** + * @dev Returns the downcasted int248 from int256, reverting on + * overflow (when the input is less than smallest int248 or + * greater than largest int248). + * + * Counterpart to Solidity's `int248` operator. + * + * Requirements: + * + * - input must fit into 248 bits + */ + function toInt248(int256 value) internal pure returns (int248 downcasted) { + downcasted = int248(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(248, value); + } + } + + /** + * @dev Returns the downcasted int240 from int256, reverting on + * overflow (when the input is less than smallest int240 or + * greater than largest int240). + * + * Counterpart to Solidity's `int240` operator. + * + * Requirements: + * + * - input must fit into 240 bits + */ + function toInt240(int256 value) internal pure returns (int240 downcasted) { + downcasted = int240(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(240, value); + } + } + + /** + * @dev Returns the downcasted int232 from int256, reverting on + * overflow (when the input is less than smallest int232 or + * greater than largest int232). + * + * Counterpart to Solidity's `int232` operator. + * + * Requirements: + * + * - input must fit into 232 bits + */ + function toInt232(int256 value) internal pure returns (int232 downcasted) { + downcasted = int232(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(232, value); + } + } + + /** + * @dev Returns the downcasted int224 from int256, reverting on + * overflow (when the input is less than smallest int224 or + * greater than largest int224). + * + * Counterpart to Solidity's `int224` operator. + * + * Requirements: + * + * - input must fit into 224 bits + */ + function toInt224(int256 value) internal pure returns (int224 downcasted) { + downcasted = int224(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(224, value); + } + } + + /** + * @dev Returns the downcasted int216 from int256, reverting on + * overflow (when the input is less than smallest int216 or + * greater than largest int216). + * + * Counterpart to Solidity's `int216` operator. + * + * Requirements: + * + * - input must fit into 216 bits + */ + function toInt216(int256 value) internal pure returns (int216 downcasted) { + downcasted = int216(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(216, value); + } + } + + /** + * @dev Returns the downcasted int208 from int256, reverting on + * overflow (when the input is less than smallest int208 or + * greater than largest int208). + * + * Counterpart to Solidity's `int208` operator. + * + * Requirements: + * + * - input must fit into 208 bits + */ + function toInt208(int256 value) internal pure returns (int208 downcasted) { + downcasted = int208(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(208, value); + } + } + + /** + * @dev Returns the downcasted int200 from int256, reverting on + * overflow (when the input is less than smallest int200 or + * greater than largest int200). + * + * Counterpart to Solidity's `int200` operator. + * + * Requirements: + * + * - input must fit into 200 bits + */ + function toInt200(int256 value) internal pure returns (int200 downcasted) { + downcasted = int200(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(200, value); + } + } + + /** + * @dev Returns the downcasted int192 from int256, reverting on + * overflow (when the input is less than smallest int192 or + * greater than largest int192). + * + * Counterpart to Solidity's `int192` operator. + * + * Requirements: + * + * - input must fit into 192 bits + */ + function toInt192(int256 value) internal pure returns (int192 downcasted) { + downcasted = int192(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(192, value); + } + } + + /** + * @dev Returns the downcasted int184 from int256, reverting on + * overflow (when the input is less than smallest int184 or + * greater than largest int184). + * + * Counterpart to Solidity's `int184` operator. + * + * Requirements: + * + * - input must fit into 184 bits + */ + function toInt184(int256 value) internal pure returns (int184 downcasted) { + downcasted = int184(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(184, value); + } + } + + /** + * @dev Returns the downcasted int176 from int256, reverting on + * overflow (when the input is less than smallest int176 or + * greater than largest int176). + * + * Counterpart to Solidity's `int176` operator. + * + * Requirements: + * + * - input must fit into 176 bits + */ + function toInt176(int256 value) internal pure returns (int176 downcasted) { + downcasted = int176(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(176, value); + } + } + + /** + * @dev Returns the downcasted int168 from int256, reverting on + * overflow (when the input is less than smallest int168 or + * greater than largest int168). + * + * Counterpart to Solidity's `int168` operator. + * + * Requirements: + * + * - input must fit into 168 bits + */ + function toInt168(int256 value) internal pure returns (int168 downcasted) { + downcasted = int168(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(168, value); + } + } + + /** + * @dev Returns the downcasted int160 from int256, reverting on + * overflow (when the input is less than smallest int160 or + * greater than largest int160). + * + * Counterpart to Solidity's `int160` operator. + * + * Requirements: + * + * - input must fit into 160 bits + */ + function toInt160(int256 value) internal pure returns (int160 downcasted) { + downcasted = int160(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(160, value); + } + } + + /** + * @dev Returns the downcasted int152 from int256, reverting on + * overflow (when the input is less than smallest int152 or + * greater than largest int152). + * + * Counterpart to Solidity's `int152` operator. + * + * Requirements: + * + * - input must fit into 152 bits + */ + function toInt152(int256 value) internal pure returns (int152 downcasted) { + downcasted = int152(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(152, value); + } + } + + /** + * @dev Returns the downcasted int144 from int256, reverting on + * overflow (when the input is less than smallest int144 or + * greater than largest int144). + * + * Counterpart to Solidity's `int144` operator. + * + * Requirements: + * + * - input must fit into 144 bits + */ + function toInt144(int256 value) internal pure returns (int144 downcasted) { + downcasted = int144(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(144, value); + } + } + + /** + * @dev Returns the downcasted int136 from int256, reverting on + * overflow (when the input is less than smallest int136 or + * greater than largest int136). + * + * Counterpart to Solidity's `int136` operator. + * + * Requirements: + * + * - input must fit into 136 bits + */ + function toInt136(int256 value) internal pure returns (int136 downcasted) { + downcasted = int136(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(136, value); + } + } + + /** + * @dev Returns the downcasted int128 from int256, reverting on + * overflow (when the input is less than smallest int128 or + * greater than largest int128). + * + * Counterpart to Solidity's `int128` operator. + * + * Requirements: + * + * - input must fit into 128 bits + */ + function toInt128(int256 value) internal pure returns (int128 downcasted) { + downcasted = int128(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(128, value); + } + } + + /** + * @dev Returns the downcasted int120 from int256, reverting on + * overflow (when the input is less than smallest int120 or + * greater than largest int120). + * + * Counterpart to Solidity's `int120` operator. + * + * Requirements: + * + * - input must fit into 120 bits + */ + function toInt120(int256 value) internal pure returns (int120 downcasted) { + downcasted = int120(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(120, value); + } + } + + /** + * @dev Returns the downcasted int112 from int256, reverting on + * overflow (when the input is less than smallest int112 or + * greater than largest int112). + * + * Counterpart to Solidity's `int112` operator. + * + * Requirements: + * + * - input must fit into 112 bits + */ + function toInt112(int256 value) internal pure returns (int112 downcasted) { + downcasted = int112(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(112, value); + } + } + + /** + * @dev Returns the downcasted int104 from int256, reverting on + * overflow (when the input is less than smallest int104 or + * greater than largest int104). + * + * Counterpart to Solidity's `int104` operator. + * + * Requirements: + * + * - input must fit into 104 bits + */ + function toInt104(int256 value) internal pure returns (int104 downcasted) { + downcasted = int104(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(104, value); + } + } + + /** + * @dev Returns the downcasted int96 from int256, reverting on + * overflow (when the input is less than smallest int96 or + * greater than largest int96). + * + * Counterpart to Solidity's `int96` operator. + * + * Requirements: + * + * - input must fit into 96 bits + */ + function toInt96(int256 value) internal pure returns (int96 downcasted) { + downcasted = int96(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(96, value); + } + } + + /** + * @dev Returns the downcasted int88 from int256, reverting on + * overflow (when the input is less than smallest int88 or + * greater than largest int88). + * + * Counterpart to Solidity's `int88` operator. + * + * Requirements: + * + * - input must fit into 88 bits + */ + function toInt88(int256 value) internal pure returns (int88 downcasted) { + downcasted = int88(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(88, value); + } + } + + /** + * @dev Returns the downcasted int80 from int256, reverting on + * overflow (when the input is less than smallest int80 or + * greater than largest int80). + * + * Counterpart to Solidity's `int80` operator. + * + * Requirements: + * + * - input must fit into 80 bits + */ + function toInt80(int256 value) internal pure returns (int80 downcasted) { + downcasted = int80(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(80, value); + } + } + + /** + * @dev Returns the downcasted int72 from int256, reverting on + * overflow (when the input is less than smallest int72 or + * greater than largest int72). + * + * Counterpart to Solidity's `int72` operator. + * + * Requirements: + * + * - input must fit into 72 bits + */ + function toInt72(int256 value) internal pure returns (int72 downcasted) { + downcasted = int72(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(72, value); + } + } + + /** + * @dev Returns the downcasted int64 from int256, reverting on + * overflow (when the input is less than smallest int64 or + * greater than largest int64). + * + * Counterpart to Solidity's `int64` operator. + * + * Requirements: + * + * - input must fit into 64 bits + */ + function toInt64(int256 value) internal pure returns (int64 downcasted) { + downcasted = int64(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(64, value); + } + } + + /** + * @dev Returns the downcasted int56 from int256, reverting on + * overflow (when the input is less than smallest int56 or + * greater than largest int56). + * + * Counterpart to Solidity's `int56` operator. + * + * Requirements: + * + * - input must fit into 56 bits + */ + function toInt56(int256 value) internal pure returns (int56 downcasted) { + downcasted = int56(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(56, value); + } + } + + /** + * @dev Returns the downcasted int48 from int256, reverting on + * overflow (when the input is less than smallest int48 or + * greater than largest int48). + * + * Counterpart to Solidity's `int48` operator. + * + * Requirements: + * + * - input must fit into 48 bits + */ + function toInt48(int256 value) internal pure returns (int48 downcasted) { + downcasted = int48(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(48, value); + } + } + + /** + * @dev Returns the downcasted int40 from int256, reverting on + * overflow (when the input is less than smallest int40 or + * greater than largest int40). + * + * Counterpart to Solidity's `int40` operator. + * + * Requirements: + * + * - input must fit into 40 bits + */ + function toInt40(int256 value) internal pure returns (int40 downcasted) { + downcasted = int40(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(40, value); + } + } + + /** + * @dev Returns the downcasted int32 from int256, reverting on + * overflow (when the input is less than smallest int32 or + * greater than largest int32). + * + * Counterpart to Solidity's `int32` operator. + * + * Requirements: + * + * - input must fit into 32 bits + */ + function toInt32(int256 value) internal pure returns (int32 downcasted) { + downcasted = int32(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(32, value); + } + } + + /** + * @dev Returns the downcasted int24 from int256, reverting on + * overflow (when the input is less than smallest int24 or + * greater than largest int24). + * + * Counterpart to Solidity's `int24` operator. + * + * Requirements: + * + * - input must fit into 24 bits + */ + function toInt24(int256 value) internal pure returns (int24 downcasted) { + downcasted = int24(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(24, value); + } + } + + /** + * @dev Returns the downcasted int16 from int256, reverting on + * overflow (when the input is less than smallest int16 or + * greater than largest int16). + * + * Counterpart to Solidity's `int16` operator. + * + * Requirements: + * + * - input must fit into 16 bits + */ + function toInt16(int256 value) internal pure returns (int16 downcasted) { + downcasted = int16(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(16, value); + } + } + + /** + * @dev Returns the downcasted int8 from int256, reverting on + * overflow (when the input is less than smallest int8 or + * greater than largest int8). + * + * Counterpart to Solidity's `int8` operator. + * + * Requirements: + * + * - input must fit into 8 bits + */ + function toInt8(int256 value) internal pure returns (int8 downcasted) { + downcasted = int8(value); + if (downcasted != value) { + revert SafeCastOverflowedIntDowncast(8, value); + } + } + + /** + * @dev Converts an unsigned uint256 into a signed int256. + * + * Requirements: + * + * - input must be less than or equal to maxInt256. + */ + function toInt256(uint256 value) internal pure returns (int256) { + // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive + if (value > uint256(type(int256).max)) { + revert SafeCastOverflowedUintToInt(value); + } + return int256(value); + } + + /** + * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump. + */ + function toUint(bool b) internal pure returns (uint256 u) { + assembly ("memory-safe") { + u := iszero(iszero(b)) + } + } +} diff --git a/src/vendored/SafeERC20.sol b/src/vendored/SafeERC20.sol new file mode 100644 index 0000000..1e3e27a --- /dev/null +++ b/src/vendored/SafeERC20.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT + +// Vendored from OpenZeppelin contracts with minor modifications: +// - Formatted code. +// - Replaced imports with vendored versions. +// - Removed functions, errors, and imports not needed to implement +// `forceApprove`. +// +// Note: v5.2 points to commit acd4ff74de833399287ed6b31b4debf6b2b35527. + +pragma solidity ^0.8.20; + +import {IERC20} from "./IERC20.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC-20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + /** + * @dev An operation with an ERC-20 token failed. + */ + error SafeERC20FailedOperation(address token); + + /** + * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval + * to be set to zero before setting it to a non-zero value, such as USDT. + * + * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function + * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being + * set here. + */ + function forceApprove(IERC20 token, address spender, uint256 value) internal { + bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value)); + + if (!_callOptionalReturnBool(token, approvalCall)) { + _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0))); + _callOptionalReturn(token, approvalCall); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + * + * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements. + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + uint256 returnSize; + uint256 returnValue; + assembly ("memory-safe") { + let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20) + // bubble errors + if iszero(success) { + let ptr := mload(0x40) + returndatacopy(ptr, 0, returndatasize()) + revert(ptr, returndatasize()) + } + returnSize := returndatasize() + returnValue := mload(0) + } + + if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + * + * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead. + */ + function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { + bool success; + uint256 returnSize; + uint256 returnValue; + assembly ("memory-safe") { + success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20) + returnSize := returndatasize() + returnValue := mload(0) + } + return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1); + } +} diff --git a/test/BungeeApproveAndBridge.t.sol b/test/BungeeApproveAndBridge.t.sol new file mode 100644 index 0000000..b78736a --- /dev/null +++ b/test/BungeeApproveAndBridge.t.sol @@ -0,0 +1,1194 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8; + +import {Test} from "forge-std/Test.sol"; + +import {IERC20} from "../src/vendored/IERC20.sol"; +import {MockERC20} from "./mocks/MockERC20.sol"; +import {MockFailingBridge} from "./mocks/MockBridge.sol"; + +import {BungeeApproveAndBridge, ApproveAndBridge} from "src/BungeeApproveAndBridge.sol"; + +contract PublicBungeeApproveAndBridge is BungeeApproveAndBridge { + constructor(address _socketGateway) BungeeApproveAndBridge(_socketGateway) {} + + function applyPctDiff(uint256 _base, uint256 _compare, uint256 _target) public view returns (uint256) { + return super._applyPctDiff(_base, _compare, _target); + } + + function replaceUint256(bytes memory _original, uint256 _start, uint256 _amount) + public + pure + returns (bytes memory original, bytes memory modified) + { + return (_original, super._replaceUint256(_original, _start, _amount)); + } + + function readUint256(bytes memory _data, uint256 _index) public pure returns (uint256) { + return super._readUint256(_data, _index); + } + + function parseCalldata(bytes calldata _data) + public + pure + returns (bytes memory, BungeeApproveAndBridge.ModifyCalldataParams memory) + { + return super._parseCalldata(_data); + } + + function parseAndModifyCalldata(uint256 amount, bytes calldata data) public pure returns (bytes memory) { + return super._parseAndModifyCalldata(amount, data); + } +} + +contract BungeeApproveAndBridgeTest is Test { + PublicBungeeApproveAndBridge public bungeeApproveAndBridge; + + MockERC20 public mockToken; + MockFailingBridge public failingBridge; + + address public constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address constant SOCKET_GATEWAY = 0x3a23F943181408EAC424116Af7b7790c94Cb97a5; + + function setUp() public { + // bypass the code size check + vm.etch(SOCKET_GATEWAY, bytes("Hello, World!")); + + bungeeApproveAndBridge = new PublicBungeeApproveAndBridge(SOCKET_GATEWAY); + mockToken = new MockERC20(1000e18); + failingBridge = new MockFailingBridge(); + } + + function test_constructor() public { + assertEq(bungeeApproveAndBridge.SOCKET_GATEWAY(), SOCKET_GATEWAY); + } + + function test_constructor_shouldRevert_nonContractAddress() public { + // Should revert when passing non-contract address + address nonContract = address(0x123); + vm.expectRevert("Socket gateway contract not deployed"); + new PublicBungeeApproveAndBridge(nonContract); + + // Should revert when passing address with no code + address emptyContract = address(0x456); + vm.etch(emptyContract, ""); // Empty code + vm.expectRevert("Socket gateway contract not deployed"); + new PublicBungeeApproveAndBridge(emptyContract); + } + + function test_bridgeApprovalTarget() public { + assertEq(bungeeApproveAndBridge.bridgeApprovalTarget(), SOCKET_GATEWAY); + } + + /*////////////////////////////////////////////////////////////// + _applyPctDiff() + //////////////////////////////////////////////////////////////*/ + function test_applyPctDiff_basic() public { + // Basic percentage calculations + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 100, _compare: 110, _target: 100}), 110); + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 100, _compare: 90, _target: 100}), 90); + + // 20 + 10% = 22 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 100, _compare: 110, _target: 20}), 22); + // 20 - 10% = 18 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 100, _compare: 90, _target: 20}), 18); + } + + function test_applyPctDiff_equal() public { + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 100, _compare: 100, _target: 100}), 100); + } + + function test_applyPctDiff_shouldRevert_InvalidInput_zeroBase() public { + // When _base is 0, should revert (handled by InvalidInput error) + vm.expectRevert(BungeeApproveAndBridge.InvalidInput.selector); + bungeeApproveAndBridge.applyPctDiff({_base: 0, _compare: 100, _target: 100}); + } + + function test_applyPctDiff_zeroValues() public { + // Edge cases with zero values + // When _target is 0, result should be 0 regardless of other values + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 100, _compare: 200, _target: 0}), 0); + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1000, _compare: 500, _target: 0}), 0); + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1, _compare: 1, _target: 0}), 0); + + // When _compare is 0, result should be 0 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 100, _compare: 0, _target: 100}), 0); + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1000, _compare: 0, _target: 500}), 0); + } + + function test_applyPctDiff_largeNumbers() public { + uint256 maxUint = type(uint256).max; + + // Test with various _target values beyond 100 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 100, _compare: 150, _target: 1000}), 1500); // 1000 + 50% = 1500 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 100, _compare: 50, _target: 1000}), 500); // 1000 - 50% = 500 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1000, _compare: 2000, _target: 100}), 200); // 100 + 100% = 200 + + // Large numbers and precision testing + // Test with large values + assertEq( + bungeeApproveAndBridge.applyPctDiff({_base: 100000e18, _compare: 200000e18, _target: 100000e18}), 200000e18 + ); + assertEq( + bungeeApproveAndBridge.applyPctDiff({_base: 200000e18, _compare: 100000e18, _target: 200000e18}), 100000e18 + ); + + // Test with very large _target values + // maxUint - 1, and not maxUint since there will be integer match truncation + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 100, _compare: 200, _target: maxUint / 2}), maxUint - 1); + assertEq( + bungeeApproveAndBridge.applyPctDiff({_base: 100, _compare: 50, _target: maxUint / 2}), ((maxUint / 2) / 2) + ); + } + + function test_applyPctDiff_smallNumbers() public { + // Test with very small values + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1, _compare: 1, _target: 1}), 1); + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1, _compare: 2, _target: 1}), 2); + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 2, _compare: 1, _target: 2}), 1); + } + + function test_applyPctDiff_maxUint256() public { + // Test with maximum uint256 values + uint256 maxUint = type(uint256).max; + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: maxUint, _compare: maxUint, _target: 100}), 100); + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: maxUint, _compare: maxUint, _target: maxUint}), maxUint); + // Test 1: When _target is maxUint and we scale it down + assertEq( + bungeeApproveAndBridge.applyPctDiff({_base: maxUint, _compare: maxUint / 2, _target: maxUint}), maxUint / 2 + ); + // Test 2: When _target is maxUint and we scale it up (should overflow/revert or handle gracefully) + // This tests if the function can handle cases where result would exceed maxUint + vm.expectRevert(); // or handle gracefully depending on implementation + bungeeApproveAndBridge.applyPctDiff({_base: maxUint / 2, _compare: maxUint, _target: maxUint}); + // Test 3: Test with maxUint as _compare but different _base and _target + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 100, _compare: maxUint, _target: 100}), maxUint); + // Test 4: Test with maxUint as _base but different _compare and _target + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: maxUint, _compare: 100, _target: maxUint}), 100); + + uint256 nearMax = maxUint - 1000; + // Test 1: Test with different ratios using nearMax values + assertEq( + bungeeApproveAndBridge.applyPctDiff({_base: nearMax, _compare: nearMax / 2, _target: nearMax}), nearMax / 2 + ); + // Test 2: Test scaling up with nearMax (should cause overflow) + vm.expectRevert(); // Should revert when result would exceed maxUint + bungeeApproveAndBridge.applyPctDiff({_base: nearMax / 2, _compare: nearMax, _target: nearMax}); + // Test 3: Test with nearMax and small values + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: nearMax, _compare: 100, _target: nearMax}), 100); + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 100, _compare: nearMax, _target: 100}), nearMax); + // Test 4: Test with nearMax and values that might cause precision loss + assertEq( + bungeeApproveAndBridge.applyPctDiff({_base: nearMax, _compare: nearMax - 1, _target: nearMax}), nearMax - 1 + ); + // Test 5: Test with nearMax and values that might cause intermediate overflow + vm.expectRevert(); // Should revert when _target * _compare > uint256.max + bungeeApproveAndBridge.applyPctDiff({_base: 1, _compare: nearMax, _target: 2}); + // Test 6: Test with values just below the overflow threshold + uint256 safeValue = 2 ** 255 - 1; // Just below overflow threshold + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1, _compare: 2, _target: safeValue}), safeValue * 2); + // Test 7: Test with nearMax and values that test rounding behavior + assertEq( + bungeeApproveAndBridge.applyPctDiff({_base: nearMax, _compare: nearMax / 3, _target: nearMax}), nearMax / 3 + ); + // Test 8: Test with values that are very close to maxUint + uint256 veryNearMax = maxUint - 1; + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: veryNearMax, _compare: veryNearMax, _target: 100}), 100); + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: veryNearMax, _compare: 1, _target: veryNearMax}), 1); + } + + function test_applyPctDiff_rounding() public { + // Rounding behavior tests + // Test cases where division might result in rounding + // 100 * 3 / 7 = 42.857... should round down to 42 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 7, _compare: 3, _target: 100}), 42); + // 100 * 5 / 3 = 166.666... should round down to 166 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 3, _compare: 5, _target: 100}), 166); + } + + function test_applyPctDiff_specialNumbers() public { + // Test with prime numbers and coprime values + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 17, _compare: 19, _target: 17}), 19); + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 19, _compare: 17, _target: 19}), 17); + // Case 1: Different target values to test actual ratio calculations + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 17, _compare: 19, _target: 34}), 38); // 34 * 19/17 = 38 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 19, _compare: 17, _target: 38}), 34); // 38 * 17/19 = 34 + // Case 2: Test with prime numbers that don't divide evenly + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 17, _compare: 19, _target: 100}), 111); // 100 * 19/17 = 111.76... rounds to 111 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 19, _compare: 17, _target: 100}), 89); // 100 * 17/19 = 89.47... rounds to 89 + // Case 3: Test with larger prime numbers + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 23, _compare: 29, _target: 46}), 58); // 46 * 29/23 = 58 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 29, _compare: 23, _target: 58}), 46); // 58 * 23/29 = 46 + + // Test with powers of 2 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 256, _compare: 512, _target: 256}), 512); + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 512, _compare: 256, _target: 512}), 256); + } + + function test_applyPctDiff_intermediateOverflow() public { + // Test with values that might cause intermediate overflow + // These should cause intermediate overflow in _target * _compare but final result is valid + + // Test 1: Intermediate overflow with large _target and _compare, but large _base + // _target = 2^200, _compare = 2^56, _base = 2^200 + // Intermediate: 2^200 * 2^56 = 2^256 (overflows) + // Final: 2^256 / 2^200 = 2^56 (valid) + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 2 ** 200, _compare: 2 ** 56, _target: 2 ** 200}), 2 ** 56); + + // Test 2: Intermediate overflow with maxUint values + // _target = maxUint, _compare = 2, _base = maxUint + // Intermediate: maxUint * 2 (overflows) + // Final: (maxUint * 2) / maxUint = 2 (valid) + assertEq( + bungeeApproveAndBridge.applyPctDiff({_base: type(uint256).max, _compare: 2, _target: type(uint256).max}), 2 + ); + + // Test 3: Intermediate overflow with values close to overflow threshold + // _target = 2^255, _compare = 2, _base = 2^255 + // Intermediate: 2^255 * 2 = 2^256 (overflows) + // Final: 2^256 / 2^255 = 2 (valid) + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 2 ** 255, _compare: 2, _target: 2 ** 255}), 2); + + // Test 4: Intermediate overflow with large values that would result in small final result + // _target = 2^200, _compare = 2^100, _base = 2^250 + // Intermediate: 2^200 * 2^100 = 2^300 (overflows) + // Final: 2^300 / 2^250 = 2^50 (valid) + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 2 ** 250, _compare: 2 ** 100, _target: 2 ** 200}), 2 ** 50); + + // Test 5: Test values just below intermediate overflow threshold (should work) + // _target = 2^255 - 1, _compare = 2, _base = 2^255 - 1 + // Intermediate: (2^255 - 1) * 2 = 2^256 - 2 (just below overflow) + // Final: (2^256 - 2) / (2^255 - 1) ≈ 2 (valid) + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 2 ** 255 - 1, _compare: 2, _target: 2 ** 255 - 1}), 2); + + // Test 6: Intermediate overflow with very large values + // _target = maxUint, _compare = maxUint, _base = maxUint + // Intermediate: maxUint * maxUint (massive overflow) + // Final: (maxUint * maxUint) / maxUint = maxUint (valid) + assertEq( + bungeeApproveAndBridge.applyPctDiff({ + _base: type(uint256).max, + _compare: type(uint256).max, + _target: type(uint256).max + }), + type(uint256).max + ); + + // Test 7: Cases where final result WOULD overflow (should revert) + // _target = 2^255, _compare = 2^255, _base = 1 + // Final: 2^255 * 2^255 / 1 = 2^510 (overflows) + vm.expectRevert(); // Should revert when final result > uint256.max + bungeeApproveAndBridge.applyPctDiff({_base: 1, _compare: 2 ** 255, _target: 2 ** 255}); + + // Test 8: Cases where final result is exactly at the limit + // _target = 2^255, _compare = 2^255, _base = 2^255 + // Final: 2^255 * 2^255 / 2^255 = 2^255 (valid) + assertEq( + bungeeApproveAndBridge.applyPctDiff({_base: 2 ** 255, _compare: 2 ** 255, _target: 2 ** 255}), 2 ** 255 + ); + } + + function test_applyPctDiff_underflow() public { + uint256 maxUint = type(uint256).max; + // Test with values that might cause underflow + // These should not underflow due to Math.mulDiv's protection + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 0, _target: 1e18}), 0); + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: maxUint, _compare: 0, _target: maxUint}), 0); + } + + function test_applyPctDiff_extremeRatios() public { + // Test with very small ratios + // Test 1: Very small ratio where _compare << _base + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 1, _target: 100}), 0); // 100 * 1/1e18 = 0 (rounds down) + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 1, _target: 1e18}), 1); // 1e18 * 1/1e18 = 1 + // Test 2: Small ratio with different target values + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 1e17, _target: 100}), 10); // 100 * 1e17/1e18 = 10 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 1e17, _target: 1e18}), 1e17); // 1e18 * 1e17/1e18 = 1e17 + // Test 3: Very small ratio with large target + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 1, _target: 1e20}), 100); // 1e20 * 1/1e18 = 100 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 1, _target: 1e16}), 0); // 1e16 * 1/1e18 = 0 (rounds down) + // Test 4: Extremely small ratios + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 1, _target: 1e15}), 0); // 1e15 * 1/1e18 = 0.001 (rounds to 0) + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 1, _target: 1e17}), 0); // 1e17 * 1/1e18 = 0.1 (rounds to 0) + // Test 5: Small ratio with precision testing + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 1e16, _target: 1e18}), 1e16); // 1e18 * 1e16/1e18 = 1e16 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 1e16, _target: 100}), 1); // 100 * 1e16/1e18 = 1 + // Test 6: Small ratio with values that test rounding behavior + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 1e17, _target: 1e17}), 1e16); // 1e17 * 1e17/1e18 = 1e16 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 1e17, _target: 1e16}), 1e15); // 1e16 * 1e17/1e18 = 1e15 + // Test with values that might cause precision loss + // 1 * 1e18 / 1e18 = 1 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 1e18, _target: 1}), 1); + // 1e18 * 1 / 1e18 = 1 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e18, _compare: 1, _target: 1e18}), 1); + + // Test with very large ratios + // Test 1: Very large ratio where _compare >> _base + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1, _compare: 1e18, _target: 100}), 100e18); // 100 * 1e18/1 = 100e18 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1, _compare: 1e18, _target: 1e18}), 1e36); // 1e18 * 1e18/1 = 1e36 + // Test 2: Large ratio with different target values + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e17, _compare: 1e18, _target: 100}), 1000); // 100 * 1e18/1e17 = 1000 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e17, _compare: 1e18, _target: 1e18}), 1e19); // 1e18 * 1e18/1e17 = 1e19 + // Test 3: Very large ratio with small target + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1, _compare: 1e20, _target: 1}), 1e20); // 1 * 1e20/1 = 1e20 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1, _compare: 1e20, _target: 100}), 100e20); // 100 * 1e20/1 = 100e20 + // Test 4: Large ratio + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1, _compare: 1e18, _target: 1e18}), 1e36); // 1e18 * 1e18/1 = 1e36 + // Test 5: Large ratio with values that test overflow thresholds + vm.expectRevert(); // Should revert when _target * _compare > uint256.max + bungeeApproveAndBridge.applyPctDiff({_base: 1, _compare: 2 ** 255, _target: 2}); // 2 * 2^255/1 = 2^256 (overflows) + // Test 6: Large ratio with values just below overflow threshold + uint256 safeValue = 2 ** 255 - 1; // Just below overflow threshold + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1, _compare: 2, _target: safeValue}), safeValue * 2); + // Test 7: Large ratio with precision testing + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e16, _compare: 1e18, _target: 1e16}), 1e18); // 1e16 * 1e18/1e16 = 1e18 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 1e16, _compare: 1e18, _target: 100}), 100e2); // 100 * 1e18/1e16 = 100e2 + // Test 8: Large ratio with values that test rounding behavior + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 3, _compare: 10, _target: 100}), 333); // 100 * 10/3 = 333.33... rounds to 333 + assertEq(bungeeApproveAndBridge.applyPctDiff({_base: 7, _compare: 22, _target: 100}), 314); // 100 * 22/7 = 314.28... rounds to 314 + } + + /*////////////////////////////////////////////////////////////// + _replaceUint256() + //////////////////////////////////////////////////////////////*/ + function test_replaceUint256_emptyBytes() public { + // Test with empty bytes - should revert with PositionOutOfBounds + bytes memory emptyBytes = ""; + vm.expectRevert(BungeeApproveAndBridge.PositionOutOfBounds.selector); + bungeeApproveAndBridge.replaceUint256(emptyBytes, 0, 123); + } + + function test_replaceUint256_exactly32Bytes() public { + // Test with exactly 32 bytes + bytes memory data = new bytes(32); + // Fill with some initial data + for (uint256 i = 0; i < 32; i++) { + data[i] = bytes1(uint8(i)); + } + + uint256 newAmount = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + + // Replace at start (offset 0) + (, bytes memory result) = bungeeApproveAndBridge.replaceUint256(data, 0, newAmount); + + // Verify the replacement worked + uint256 readValue = bungeeApproveAndBridge.readUint256(result, 0); + assertEq(readValue, newAmount); + + // Test that trying to replace at offset 1 should revert (would exceed bounds) + vm.expectRevert(BungeeApproveAndBridge.PositionOutOfBounds.selector); + bungeeApproveAndBridge.replaceUint256(data, 1, newAmount); + } + + function test_replaceUint256_largerPayload() public { + // Test with a larger payload (64 bytes) + bytes memory data = new bytes(64); + // Fill with some initial data + for (uint256 i = 0; i < 64; i++) { + data[i] = bytes1(uint8(i)); + } + + uint256 newAmount = 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef; + + // Test replacement at start (offset 0) + (, bytes memory result) = bungeeApproveAndBridge.replaceUint256(data, 0, newAmount); + uint256 readValue = bungeeApproveAndBridge.readUint256(result, 0); + assertEq(readValue, newAmount); + + // Test replacement at middle (offset 32) + (, result) = bungeeApproveAndBridge.replaceUint256(data, 32, newAmount); + readValue = bungeeApproveAndBridge.readUint256(result, 32); + assertEq(readValue, newAmount); + + // Test that trying to replace at offset 33 should revert (would exceed bounds) + vm.expectRevert(BungeeApproveAndBridge.PositionOutOfBounds.selector); + bungeeApproveAndBridge.replaceUint256(data, 33, newAmount); + } + + function test_replaceUint256_veryLargePayload() public { + // Test with a very large payload (128 bytes) + bytes memory data = new bytes(128); + // Fill with some initial data + for (uint256 i = 0; i < 128; i++) { + data[i] = bytes1(uint8(i)); + } + + uint256 newAmount = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; + + // Test replacement at start (offset 0) + (, bytes memory result) = bungeeApproveAndBridge.replaceUint256(data, 0, newAmount); + uint256 readValue = bungeeApproveAndBridge.readUint256(result, 0); + assertEq(readValue, newAmount); + + // Test replacement at middle (offset 64) + (, result) = bungeeApproveAndBridge.replaceUint256(data, 64, newAmount); + readValue = bungeeApproveAndBridge.readUint256(result, 64); + assertEq(readValue, newAmount); + + // Test replacement at end (offset 96) + (, result) = bungeeApproveAndBridge.replaceUint256(data, 96, newAmount); + readValue = bungeeApproveAndBridge.readUint256(result, 96); + assertEq(readValue, newAmount); + + // Test that trying to replace at offset 97 should revert (would exceed bounds) + vm.expectRevert(BungeeApproveAndBridge.PositionOutOfBounds.selector); + bungeeApproveAndBridge.replaceUint256(data, 97, newAmount); + } + + function test_replaceUint256_multipleReplacements() public { + // Test multiple replacements on the same data + bytes memory data = new bytes(96); // 3 * 32 bytes + // Fill with some initial data + for (uint256 i = 0; i < 96; i++) { + data[i] = bytes1(uint8(i)); + } + + uint256 amount1 = 0x1111111111111111111111111111111111111111111111111111111111111111; + uint256 amount2 = 0x2222222222222222222222222222222222222222222222222222222222222222; + uint256 amount3 = 0x3333333333333333333333333333333333333333333333333333333333333333; + + // Replace at offset 0 + (, bytes memory result) = bungeeApproveAndBridge.replaceUint256(data, 0, amount1); + assertEq(bungeeApproveAndBridge.readUint256(result, 0), amount1); + + // Replace at offset 32 + (, result) = bungeeApproveAndBridge.replaceUint256(result, 32, amount2); + assertEq(bungeeApproveAndBridge.readUint256(result, 0), amount1); // First value unchanged + assertEq(bungeeApproveAndBridge.readUint256(result, 32), amount2); // Second value changed + + // Replace at offset 64 + (, result) = bungeeApproveAndBridge.replaceUint256(result, 64, amount3); + assertEq(bungeeApproveAndBridge.readUint256(result, 0), amount1); // First value unchanged + assertEq(bungeeApproveAndBridge.readUint256(result, 32), amount2); // Second value unchanged + assertEq(bungeeApproveAndBridge.readUint256(result, 64), amount3); // Third value changed + } + + function test_replaceUint256_edgeCases() public { + // Test edge cases with different amounts + bytes memory data = new bytes(64); + for (uint256 i = 0; i < 64; i++) { + data[i] = bytes1(uint8(i)); + } + + // Test with zero amount + (, bytes memory result) = bungeeApproveAndBridge.replaceUint256(data, 0, 0); + assertEq(bungeeApproveAndBridge.readUint256(result, 0), 0); + + // Test with maximum uint256 + uint256 maxUint = type(uint256).max; + (, result) = bungeeApproveAndBridge.replaceUint256(data, 32, maxUint); + assertEq(bungeeApproveAndBridge.readUint256(result, 32), maxUint); + + // Test with a specific pattern + uint256 pattern = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + (, result) = bungeeApproveAndBridge.replaceUint256(data, 0, pattern); + assertEq(bungeeApproveAndBridge.readUint256(result, 0), pattern); + } + + function test_replaceUint256_boundsChecking() public { + // Test various bounds checking scenarios + bytes memory data = new bytes(64); + + // Test with _start = 0 (valid) + bungeeApproveAndBridge.replaceUint256(data, 0, 123); + + // Test with _start = 32 (valid) + bungeeApproveAndBridge.replaceUint256(data, 32, 123); + + // Test with _start = 33 (invalid - would exceed bounds) + vm.expectRevert(BungeeApproveAndBridge.PositionOutOfBounds.selector); + bungeeApproveAndBridge.replaceUint256(data, 33, 123); + + // Test with _start = 64 (invalid - would exceed bounds) + vm.expectRevert(BungeeApproveAndBridge.PositionOutOfBounds.selector); + bungeeApproveAndBridge.replaceUint256(data, 64, 123); + + // Test with very large _start value + // will revert since start + 32 will overflow + vm.expectRevert(); + bungeeApproveAndBridge.replaceUint256(data, type(uint256).max, 123); + } + + function test_replaceUint256_memorySafety() public { + // Test memory safety by ensuring the function doesn't corrupt memory + bytes memory data = new bytes(96); + + // Fill with a specific pattern + for (uint256 i = 0; i < 96; i++) { + data[i] = bytes1(uint8(i % 256)); + } + + uint256 newAmount = 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef; + + // Replace only the middle 32 bytes + (, bytes memory result) = bungeeApproveAndBridge.replaceUint256(data, 32, newAmount); + + // Verify that only the target location was modified + assertEq(bungeeApproveAndBridge.readUint256(result, 32), newAmount); + + // Verify that other locations remain unchanged + // Check first 32 bytes are unchanged + for (uint256 i = 0; i < 32; i++) { + assertEq(result[i], data[i]); + } + + // Check last 32 bytes are unchanged + for (uint256 i = 64; i < 96; i++) { + assertEq(result[i], data[i]); + } + } + + function test_replaceUint256_inPlaceModification() public { + // Test that the function modifies the original data in-place + bytes memory data = new bytes(64); + for (uint256 i = 0; i < 64; i++) { + data[i] = bytes1(uint8(i)); + } + + uint256 originalValue = bungeeApproveAndBridge.readUint256(data, 0); + uint256 newAmount = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + + // Replace the value + (bytes memory original, bytes memory result) = bungeeApproveAndBridge.replaceUint256(data, 0, newAmount); + + // Verify the result is the same as the original data (in-place modification) + assertEq(result.length, data.length); + assertEq(bungeeApproveAndBridge.readUint256(result, 0), newAmount); + // this will not modify the original data variable, but a copy of it since data is outside the scope of the function + assertNotEq(bungeeApproveAndBridge.readUint256(result, 0), bungeeApproveAndBridge.readUint256(data, 0)); + // but this is returning the modified input value, so this should be the new value + assertEq(bungeeApproveAndBridge.readUint256(original, 0), newAmount); + } + + /*////////////////////////////////////////////////////////////// + _parseCalldata() + //////////////////////////////////////////////////////////////*/ + function test_parseCalldata_minimumLength() public { + // Test with minimum valid length (4 bytes routeId + 96 bytes BungeeApproveAndBridge.ModifyCalldataParams = 100 bytes) + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 32; + bool modifyOutput = true; + uint256 outputAmountIdx = 64; + + bytes memory data = abi.encodePacked(routeId, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + (bytes memory routeCalldata, BungeeApproveAndBridge.ModifyCalldataParams memory params) = + bungeeApproveAndBridge.parseCalldata(data); + + // Verify route calldata is correct + assertEq(routeCalldata.length, 4); + assertEq(routeCalldata, routeId); + + // Verify BungeeApproveAndBridge.ModifyCalldataParams are correct + assertEq(params.inputAmountIdx, inputAmountIdx); + assertEq(params.modifyOutput, modifyOutput); + assertEq(params.outputAmountIdx, outputAmountIdx); + } + + function test_parseCalldata_largerRouteCalldata() public { + // Test with larger route calldata + bytes memory routeId = hex"12345678"; + bytes memory additionalData = hex"deadbeefdeadbeefdeadbeefdeadbeef"; + uint256 inputAmountIdx = 32; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + + bytes memory routeCalldata = abi.encodePacked(routeId, additionalData); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + (bytes memory parsedRouteCalldata, BungeeApproveAndBridge.ModifyCalldataParams memory params) = + bungeeApproveAndBridge.parseCalldata(data); + + // Verify route calldata is correct + assertEq(parsedRouteCalldata.length, 20); // routeId(4) + additionalData(16) + assertEq(parsedRouteCalldata, routeCalldata); + + // Verify BungeeApproveAndBridge.ModifyCalldataParams are correct + assertEq(params.inputAmountIdx, inputAmountIdx); + assertEq(params.modifyOutput, modifyOutput); + assertEq(params.outputAmountIdx, outputAmountIdx); + } + + function test_parseCalldata_complexRouteCalldata() public { + // Test with complex route calldata containing multiple parameters + bytes memory routeId = hex"12345678"; + bytes memory param1 = hex"1111111111111111111111111111111111111111111111111111111111111111"; + bytes memory param2 = hex"2222222222222222222222222222222222222222222222222222222222222222"; + bytes memory param3 = hex"3333333333333333333333333333333333333333333333333333333333333333"; + + uint256 inputAmountIdx = 68; // After routeId (4) + param1 (32) + param2 (32) + bool modifyOutput = true; + uint256 outputAmountIdx = 100; // After routeId (4) + param1 (32) + param2 (32) + param3 (32) + + bytes memory routeCalldata = abi.encodePacked(routeId, param1, param2, param3); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + (bytes memory parsedRouteCalldata, BungeeApproveAndBridge.ModifyCalldataParams memory params) = + bungeeApproveAndBridge.parseCalldata(data); + + // Verify route calldata is correct + assertEq(parsedRouteCalldata.length, 100); // 4 + 32 + 32 + 32 + assertEq(parsedRouteCalldata, routeCalldata); + + // Verify BungeeApproveAndBridge.ModifyCalldataParams are correct + assertEq(params.inputAmountIdx, inputAmountIdx); + assertEq(params.modifyOutput, modifyOutput); + assertEq(params.outputAmountIdx, outputAmountIdx); + } + + function test_parseCalldata_edgeCaseValues() public { + // Test with edge case values for BungeeApproveAndBridge.ModifyCalldataParams + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 0; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + + bytes memory data = abi.encodePacked(routeId, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + (bytes memory routeCalldata, BungeeApproveAndBridge.ModifyCalldataParams memory params) = + bungeeApproveAndBridge.parseCalldata(data); + + assertEq(routeCalldata, routeId); + assertEq(params.inputAmountIdx, 0); + assertEq(params.modifyOutput, false); + assertEq(params.outputAmountIdx, 0); + } + + function test_parseCalldata_maxValues() public { + // Test with maximum uint256 values + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = type(uint256).max; + bool modifyOutput = true; + uint256 outputAmountIdx = type(uint256).max; + + bytes memory data = abi.encodePacked(routeId, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + (bytes memory routeCalldata, BungeeApproveAndBridge.ModifyCalldataParams memory params) = + bungeeApproveAndBridge.parseCalldata(data); + + assertEq(routeCalldata, routeId); + assertEq(params.inputAmountIdx, type(uint256).max); + assertEq(params.modifyOutput, true); + assertEq(params.outputAmountIdx, type(uint256).max); + } + + function test_parseCalldata_shouldRevert_InvalidInput_tooShort() public { + // Test with data shorter than minimum length (100 bytes) + bytes memory shortData = hex"12345678"; // Only 4 bytes, should revert + + vm.expectRevert(BungeeApproveAndBridge.InvalidInput.selector); + bungeeApproveAndBridge.parseCalldata(shortData); + } + + function test_parseCalldata_shouldRevert_InvalidInput_emptyData() public { + // Test with empty data + bytes memory emptyData = ""; + + vm.expectRevert(BungeeApproveAndBridge.InvalidInput.selector); + bungeeApproveAndBridge.parseCalldata(emptyData); + } + + function test_parseCalldata_shouldRevert_InvalidInput_exactlyOneByteShort() public { + // Test with data exactly one byte shorter than minimum + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 32; + bool modifyOutput = true; + uint256 outputAmountIdx = 64; + + // Create data that's 99 bytes (1 byte short of minimum 100) + bytes memory data = abi.encodePacked(routeId, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + bytes memory shortData = new bytes(data.length - 1); + for (uint256 i = 0; i < shortData.length; i++) { + shortData[i] = data[i]; + } + + vm.expectRevert(BungeeApproveAndBridge.InvalidInput.selector); + bungeeApproveAndBridge.parseCalldata(shortData); + } + + function test_parseCalldata_veryLargeRouteCalldata() public { + // Test with very large route calldata + bytes memory routeId = hex"12345678"; + bytes memory largeData = new bytes(1000); + for (uint256 i = 0; i < 1000; i++) { + largeData[i] = bytes1(uint8(i % 256)); + } + + uint256 inputAmountIdx = 32; + bool modifyOutput = true; + uint256 outputAmountIdx = 64; + + bytes memory routeCalldata = abi.encodePacked(routeId, largeData); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + (bytes memory parsedRouteCalldata, BungeeApproveAndBridge.ModifyCalldataParams memory params) = + bungeeApproveAndBridge.parseCalldata(data); + + // Verify route calldata is correct + assertEq(parsedRouteCalldata.length, 1004); // 4 + 1000 + assertEq(parsedRouteCalldata, routeCalldata); + + // Verify BungeeApproveAndBridge.ModifyCalldataParams are correct + assertEq(params.inputAmountIdx, inputAmountIdx); + assertEq(params.modifyOutput, modifyOutput); + assertEq(params.outputAmountIdx, outputAmountIdx); + } + + function test_parseCalldata_multipleTestCases() public { + // Test multiple different scenarios + bytes memory routeId = hex"12345678"; + + // Test case 1: Basic case + { + uint256 inputAmountIdx = 32; + bool modifyOutput = true; + uint256 outputAmountIdx = 64; + + bytes memory data = abi.encodePacked(routeId, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + (bytes memory routeCalldata, BungeeApproveAndBridge.ModifyCalldataParams memory params) = + bungeeApproveAndBridge.parseCalldata(data); + + assertEq(routeCalldata, routeId); + assertEq(params.inputAmountIdx, inputAmountIdx); + assertEq(params.modifyOutput, modifyOutput); + assertEq(params.outputAmountIdx, outputAmountIdx); + } + + // Test case 2: Different values + { + uint256 inputAmountIdx = 100; + bool modifyOutput = false; + uint256 outputAmountIdx = 200; + + bytes memory data = abi.encodePacked(routeId, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + (bytes memory routeCalldata, BungeeApproveAndBridge.ModifyCalldataParams memory params) = + bungeeApproveAndBridge.parseCalldata(data); + + assertEq(routeCalldata, routeId); + assertEq(params.inputAmountIdx, inputAmountIdx); + assertEq(params.modifyOutput, modifyOutput); + assertEq(params.outputAmountIdx, outputAmountIdx); + } + + // Test case 3: Large indices + { + uint256 inputAmountIdx = 1000000; + bool modifyOutput = true; + uint256 outputAmountIdx = 2000000; + + bytes memory data = abi.encodePacked(routeId, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + (bytes memory routeCalldata, BungeeApproveAndBridge.ModifyCalldataParams memory params) = + bungeeApproveAndBridge.parseCalldata(data); + + assertEq(routeCalldata, routeId); + assertEq(params.inputAmountIdx, inputAmountIdx); + assertEq(params.modifyOutput, modifyOutput); + assertEq(params.outputAmountIdx, outputAmountIdx); + } + } + + function test_parseCalldata_abiEncodingConsistency() public { + // Test that the ABI encoding/decoding is consistent + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 32; + bool modifyOutput = true; + uint256 outputAmountIdx = 64; + + // Create data using ABI encoding + bytes memory data = abi.encodePacked(routeId, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + // Parse the data + (bytes memory routeCalldata, BungeeApproveAndBridge.ModifyCalldataParams memory params) = + bungeeApproveAndBridge.parseCalldata(data); + + // Re-encode the params and verify they match + bytes memory reEncodedParams = abi.encode(params.inputAmountIdx, params.modifyOutput, params.outputAmountIdx); + bytes memory originalParams = abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx); + + assertEq(reEncodedParams, originalParams); + assertEq(routeCalldata, routeId); + } + + function test_parseCalldata_boundaryConditions() public { + // Test boundary conditions around the minimum length + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 32; + bool modifyOutput = true; + uint256 outputAmountIdx = 64; + + // Test exactly at minimum length (should work) + bytes memory minData = abi.encodePacked(routeId, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + (bytes memory routeCalldata, BungeeApproveAndBridge.ModifyCalldataParams memory params) = + bungeeApproveAndBridge.parseCalldata(minData); + + assertEq(routeCalldata, routeId); + assertEq(params.inputAmountIdx, inputAmountIdx); + assertEq(params.modifyOutput, modifyOutput); + assertEq(params.outputAmountIdx, outputAmountIdx); + + // Test one byte less than minimum (should revert) + bytes memory shortData = new bytes(minData.length - 1); + for (uint256 i = 0; i < shortData.length; i++) { + shortData[i] = minData[i]; + } + + vm.expectRevert(BungeeApproveAndBridge.InvalidInput.selector); + bungeeApproveAndBridge.parseCalldata(shortData); + } + + /*////////////////////////////////////////////////////////////// + _parseAndModifyCalldata() + //////////////////////////////////////////////////////////////*/ + function test_parseAndModifyCalldata_onlyInput() public { + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(100)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + bytes memory modified = bungeeApproveAndBridge.parseAndModifyCalldata(200, data); + uint256 newInput = bungeeApproveAndBridge.readUint256(modified, inputAmountIdx); + assertEq(newInput, 200); + for (uint256 i = 0; i < 4; i++) { + assertEq(modified[i], routeId[i]); + } + } + + function test_parseAndModifyCalldata_inputAndOutput() public { + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = true; + uint256 outputAmountIdx = 36; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(100), uint256(50)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + bytes memory modified = bungeeApproveAndBridge.parseAndModifyCalldata(200, data); + uint256 newInput = bungeeApproveAndBridge.readUint256(modified, inputAmountIdx); + assertEq(newInput, 200); + uint256 newOutput = bungeeApproveAndBridge.readUint256(modified, outputAmountIdx); + assertEq(newOutput, 100); + } + + function test_parseAndModifyCalldata_outOfBounds() public { + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 1000; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(100)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + vm.expectRevert(BungeeApproveAndBridge.PositionOutOfBounds.selector); + bungeeApproveAndBridge.parseAndModifyCalldata(200, data); + } + + function test_parseAndModifyCalldata_modifyOutputFalse() public { + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = false; + uint256 outputAmountIdx = 36; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(100), uint256(50)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + bytes memory modified = bungeeApproveAndBridge.parseAndModifyCalldata(200, data); + uint256 output = bungeeApproveAndBridge.readUint256(modified, outputAmountIdx); + assertEq(output, 50); + } + + function test_parseAndModifyCalldata_minimumLength() public { + bytes memory data = hex"12345678"; + vm.expectRevert(BungeeApproveAndBridge.InvalidInput.selector); + bungeeApproveAndBridge.parseAndModifyCalldata(100, data); + } + + /*////////////////////////////////////////////////////////////// + INSUFFICIENT BALANCE TESTS + //////////////////////////////////////////////////////////////*/ + + function test_insufficientBalance_ERC20() public { + // Give test contract some tokens but not enough + mockToken.mint(address(bungeeApproveAndBridge), 100e18); + + uint256 minAmount = 200e18; // More than available balance + + vm.expectRevert(ApproveAndBridge.MinAmountNotMet.selector); + bungeeApproveAndBridge.approveAndBridge(IERC20(mockToken), minAmount, 0, hex""); + } + + function test_insufficientBalance_nativeToken() public { + // Give test contract some ETH but not enough + vm.deal(address(bungeeApproveAndBridge), 1e18); + + uint256 minAmount = 2e18; // More than available balance + + vm.expectRevert(ApproveAndBridge.MinAmountNotMet.selector); + bungeeApproveAndBridge.approveAndBridge(IERC20(NATIVE_TOKEN_ADDRESS), minAmount, 0, hex""); + } + + function test_insufficientBalance_withExtraFee() public { + // Give test contract exactly the min amount but with extra fee + uint256 minAmount = 1e18; + uint256 nativeTokenExtraFee = 0.1e18; + vm.deal(address(bungeeApproveAndBridge), minAmount + nativeTokenExtraFee - 0.01e18); // Slightly less than needed + + vm.expectRevert(ApproveAndBridge.MinAmountNotMet.selector); + bungeeApproveAndBridge.approveAndBridge(IERC20(NATIVE_TOKEN_ADDRESS), minAmount, nativeTokenExtraFee, hex""); + } + + function test_zeroBalance_ERC20() public { + // Test contract has no tokens + uint256 minAmount = 1e18; + bytes memory routeId = hex"12345678"; + + vm.expectRevert(ApproveAndBridge.MinAmountNotMet.selector); + bungeeApproveAndBridge.approveAndBridge(IERC20(mockToken), minAmount, 0, hex""); + } + + function test_zeroBalance_nativeToken() public { + // Test contract has no ETH + uint256 minAmount = 1e18; + + vm.expectRevert(ApproveAndBridge.MinAmountNotMet.selector); + bungeeApproveAndBridge.approveAndBridge(IERC20(NATIVE_TOKEN_ADDRESS), minAmount, 0, hex""); + } + + /*////////////////////////////////////////////////////////////// + FAILED BRIDGE CALL TESTS + //////////////////////////////////////////////////////////////*/ + + function test_failedBridgeCall_ERC20() public { + // Create a new bridge contract with failing gateway + PublicBungeeApproveAndBridge failingBridgeApproveAndBridgeContract = + new PublicBungeeApproveAndBridge(address(failingBridge)); + + // Give test contract enough tokens + mockToken.mint(address(failingBridgeApproveAndBridgeContract), 100e18); + + uint256 minAmount = 50e18; + uint256 nativeTokenExtraFee = 0; + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(50e18)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + vm.expectRevert(BungeeApproveAndBridge.BridgeFailed.selector); + failingBridgeApproveAndBridgeContract.approveAndBridge(IERC20(mockToken), minAmount, nativeTokenExtraFee, data); + } + + function test_failedBridgeCall_nativeToken() public { + // Create a new bridge contract with failing gateway + PublicBungeeApproveAndBridge failingBridgeApproveAndBridgeContract = + new PublicBungeeApproveAndBridge(address(failingBridge)); + + // Give test contract enough ETH + vm.deal(address(failingBridgeApproveAndBridgeContract), 2e18); + + uint256 minAmount = 1e18; + uint256 nativeTokenExtraFee = 0.1e18; + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(1e18)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + vm.expectRevert(BungeeApproveAndBridge.BridgeFailed.selector); + failingBridgeApproveAndBridgeContract.approveAndBridge( + IERC20(NATIVE_TOKEN_ADDRESS), minAmount, nativeTokenExtraFee, data + ); + } + + /*////////////////////////////////////////////////////////////// + EDGE CASES - ZERO AMOUNTS + //////////////////////////////////////////////////////////////*/ + + function test_zeroMinAmount_ERC20() public { + // Give test contract some tokens + mockToken.mint(address(bungeeApproveAndBridge), 100e18); + + uint256 minAmount = 0; // Zero minimum amount + uint256 nativeTokenExtraFee = 0; + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(50e18)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + // Should succeed with zero min amount + bungeeApproveAndBridge.approveAndBridge(IERC20(mockToken), minAmount, nativeTokenExtraFee, data); + + // Should succeed with zero min amount + bungeeApproveAndBridge.approveAndBridge(IERC20(NATIVE_TOKEN_ADDRESS), minAmount, nativeTokenExtraFee, data); + } + + function test_zeroBalance_zeroMinAmount() public { + // Test contract has no tokens but zero min amount + uint256 minAmount = 0; + uint256 nativeTokenExtraFee = 0; + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(0)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + // Will succeed with zero min amount and zero balance + bungeeApproveAndBridge.approveAndBridge(IERC20(mockToken), minAmount, nativeTokenExtraFee, data); + } + + /*////////////////////////////////////////////////////////////// + EDGE CASES - EXACT MINIMUM AMOUNTS + //////////////////////////////////////////////////////////////*/ + + function test_exactMinimumAmount_ERC20() public { + // Give test contract exactly the minimum amount + uint256 minAmount = 50e18; + mockToken.mint(address(bungeeApproveAndBridge), minAmount); + + uint256 nativeTokenExtraFee = 0; + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(minAmount)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + // Should succeed with exact minimum amount + bungeeApproveAndBridge.approveAndBridge(IERC20(mockToken), minAmount, nativeTokenExtraFee, data); + } + + function test_exactMinimumAmount_nativeToken() public { + // Give test contract exactly the minimum amount plus extra fee + uint256 minAmount = 1e18; + uint256 nativeTokenExtraFee = 0.1e18; + vm.deal(address(bungeeApproveAndBridge), minAmount + nativeTokenExtraFee); + + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(minAmount)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + // Should succeed with exact minimum amount + bungeeApproveAndBridge.approveAndBridge(IERC20(NATIVE_TOKEN_ADDRESS), minAmount, nativeTokenExtraFee, data); + } + + function test_exactMinimumAmount_withExtraFee() public { + // Give test contract exactly the minimum amount plus extra fee + uint256 minAmount = 1e18; + uint256 nativeTokenExtraFee = 0.1e18; + vm.deal(address(bungeeApproveAndBridge), minAmount + nativeTokenExtraFee); + + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(minAmount)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + // Should succeed with exact minimum amount plus extra fee + bungeeApproveAndBridge.approveAndBridge(IERC20(NATIVE_TOKEN_ADDRESS), minAmount, nativeTokenExtraFee, data); + } + + /*////////////////////////////////////////////////////////////// + EDGE CASES - LARGE AMOUNTS + //////////////////////////////////////////////////////////////*/ + + function test_largeAmount_ERC20() public { + // Test with very large amounts + uint256 largeAmount = type(uint256).max / 2; + mockToken.mint(address(bungeeApproveAndBridge), largeAmount); + + uint256 minAmount = largeAmount; + uint256 nativeTokenExtraFee = 0; + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(largeAmount)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + // Should succeed with large amount + bungeeApproveAndBridge.approveAndBridge(IERC20(mockToken), minAmount, nativeTokenExtraFee, data); + } + + function test_largeAmount_nativeToken() public { + // Test with very large amounts (but not max to avoid overflow) + uint256 largeAmount = 1e20; // 100 ETH + vm.deal(address(bungeeApproveAndBridge), largeAmount); + + uint256 minAmount = largeAmount; + uint256 nativeTokenExtraFee = 0; + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(largeAmount)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + // Should succeed with large amount + bungeeApproveAndBridge.approveAndBridge(IERC20(NATIVE_TOKEN_ADDRESS), minAmount, nativeTokenExtraFee, data); + } + + /*////////////////////////////////////////////////////////////// + EDGE CASES - MAXIMUM VALUES + //////////////////////////////////////////////////////////////*/ + + function test_maxUint256_minAmount() public { + // Test with maximum uint256 as min amount + MockERC20 newToken = new MockERC20(0); + uint256 maxAmount = type(uint256).max; + newToken.mint(address(bungeeApproveAndBridge), maxAmount); + + uint256 minAmount = maxAmount; + uint256 nativeTokenExtraFee = 0; + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(maxAmount)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + // Should succeed with maximum amount + bungeeApproveAndBridge.approveAndBridge(IERC20(newToken), minAmount, nativeTokenExtraFee, data); + } + + function test_maxUint256_extraFee() public { + // Test with maximum uint256 as extra fee + uint256 minAmount = 1e18; + uint256 nativeTokenExtraFee = type(uint256).max - 1e18; + vm.deal(address(bungeeApproveAndBridge), minAmount + nativeTokenExtraFee); + + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(minAmount)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + // Should succeed with maximum extra fee + bungeeApproveAndBridge.approveAndBridge(IERC20(NATIVE_TOKEN_ADDRESS), minAmount, nativeTokenExtraFee, data); + } + + /*////////////////////////////////////////////////////////////// + EDGE CASES - APPROVAL BEHAVIOR + //////////////////////////////////////////////////////////////*/ + + function test_approval_calledForERC20() public { + // Give test contract tokens + mockToken.mint(address(bungeeApproveAndBridge), 100e18); + + uint256 minAmount = 50e18; + uint256 nativeTokenExtraFee = 0; + bytes memory routeId = hex"12345678"; + uint256 inputAmountIdx = 4; + bool modifyOutput = false; + uint256 outputAmountIdx = 0; + bytes memory routeCalldata = abi.encodePacked(routeId, uint256(50e18)); + bytes memory data = abi.encodePacked(routeCalldata, abi.encode(inputAmountIdx, modifyOutput, outputAmountIdx)); + + // Should succeed and call approval for ERC20 token + vm.expectCall(address(mockToken), abi.encodeCall(IERC20.approve, (address(SOCKET_GATEWAY), 100e18))); + bungeeApproveAndBridge.approveAndBridge(IERC20(mockToken), minAmount, nativeTokenExtraFee, data); + } +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 54b724f..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/test/e2e/BungeeApproveAndBridge.t.sol b/test/e2e/BungeeApproveAndBridge.t.sol new file mode 100644 index 0000000..914a365 --- /dev/null +++ b/test/e2e/BungeeApproveAndBridge.t.sol @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8; + +import {Test, Vm} from "forge-std/Test.sol"; + +import {ForkedRpc} from "./lib/ForkedRpc.sol"; +import {BungeeApproveAndBridge, IERC20} from "src/BungeeApproveAndBridge.sol"; + +import {IApproveAndBridge} from "src/interface/IApproveAndBridge.sol"; + +interface COWShedFactory { + function initializeProxy(address user, bool withEns) external; + function proxyOf(address who) external view returns (address); +} + +interface COWShed { + struct Call { + address target; + uint256 value; + bytes callData; + bool allowFailure; + bool isDelegateCall; + } + + function trustedExecuteHooks(Call[] calldata calls) external; +} + +// Across on Base needs Shanghai for PUSH0 +/// forge-config: default.evm_version = "shanghai" +contract E2EBungeeApproveAndBridgeTest is Test { + using ForkedRpc for Vm; + + uint256 private constant BASE_FORK_BLOCK = 32853375; + address constant SOCKET_GATEWAY = 0x3a23F943181408EAC424116Af7b7790c94Cb97a5; + address constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + IERC20 constant USDC = IERC20(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913); + // https://github.com/cowdao-grants/cow-shed/blob/96cbe1ef68f5fd16a3d2899a13cd3dca52444c17/networks.json + COWShedFactory constant factory = COWShedFactory(0x00E989b87700514118Fa55326CD1cCE82faebEF6); + address constant user = 0x8D43954F116A4BF1dd9b75712631402F37dE5eAc; // Some USDC holder + + BungeeApproveAndBridge public approveAndBridge; + address public receiver; + + function setUp() public { + vm.label(user, "user"); + vm.label(SOCKET_GATEWAY, "socket gateway"); + vm.label(address(USDC), "USDC"); + + vm.forkBaseAtBlock(BASE_FORK_BLOCK); + approveAndBridge = new BungeeApproveAndBridge(SOCKET_GATEWAY); + receiver = makeAddr("E2EBungeeApproveAndBridgeTest: receiver"); + } + + function test_across_USDC_to_USDC() external { + // Note: deployment and initialization is handled in `executeHooks` and + // doesn't need to be done in the actual trade setting. + // However, it's easier to build the test without handling the + // authentication part needed for that and use `trustedExecuteHooks` + // through the factory instead. + factory.initializeProxy(user, false); + COWShed shed = COWShed(factory.proxyOf(user)); + vm.label(address(shed), "shed"); + assertGt(address(shed).code.length, 0); + + uint256 orderProceeds = 5e6; + uint256 minProceeds = 4.9e6; + assertGt(orderProceeds, minProceeds); + + // For simplicity we take the funds from the user, but they should come + // from an order. + vm.prank(user); + USDC.transfer(address(shed), orderProceeds); + assertEq(USDC.balanceOf(address(shed)), orderProceeds); + + /* Across */ + /* bridgeERC20To */ + bytes memory BungeeApiCalldata = + hex"0000019b792ebcb900000000000000000000000000000000000000000000000000000000004bea47000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000001f9a0000000000000000000000000000000000000000000000000000000000000a2d00000000000000000000000000000000000000000000000000000000000000020000000000000000000000007851b96b5798774258437195183d7c8094583c40000000000000000000000000daee4d2156de6fe6f7d50ca047136d758f96a6f00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000004bcaad000000000000000000000000000000000000000000000000000000000000a4b10000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000006874f43f0000000000000000000000000000000000000000000000000000000068754845d00dfeeddeadbeef765753be7f7a64d5509974b0d678e1e3149b02f4"; + uint256 inputAmountStartIndex = 8; + bool modifyOutputAmount = true; + uint256 outputAmountStartIndex = 488; + uint256 additionalValue = 0; + + bytes memory extraData = abi.encode(inputAmountStartIndex, modifyOutputAmount, outputAmountStartIndex); + bytes memory _calldata = abi.encodePacked(BungeeApiCalldata, extraData); + + emit log_named_bytes("BungeeApiCalldata", BungeeApiCalldata); + emit log_named_bytes("extraData", extraData); + emit log_named_bytes("_calldata", _calldata); + + COWShed.Call[] memory calls = new COWShed.Call[](1); + calls[0] = COWShed.Call({ + target: address(approveAndBridge), + value: 0, + callData: abi.encodeCall(IApproveAndBridge.approveAndBridge, (USDC, minProceeds, additionalValue, _calldata)), + allowFailure: false, + isDelegateCall: true + }); + + vm.prank(address(factory)); + shed.trustedExecuteHooks(calls); + assertEq(USDC.balanceOf(address(shed)), 0); + } + + function test_across_ETH_to_ETH() external { + // Note: deployment and initialization is handled in `executeHooks` and + // doesn't need to be done in the actual trade setting. + // However, it's easier to build the test without handling the + // authentication part needed for that and use `trustedExecuteHooks` + // through the factory instead. + factory.initializeProxy(user, false); + COWShed shed = COWShed(factory.proxyOf(user)); + vm.label(address(shed), "shed"); + assertGt(address(shed).code.length, 0); + + uint256 orderProceeds = 5e18; + uint256 minProceeds = 4.9e18; + assertGt(orderProceeds, minProceeds); + + // For simplicity we take the funds from the user, but they should come + // from an order. + vm.deal(address(shed), orderProceeds); + assertEq(address(shed).balance, orderProceeds); + + /* Across */ + /* bridgeNativeTo */ + bytes memory BungeeApiCalldata = + hex"0000019be421f35200000000000000000000000000000000000000000000000000022e61132e3919000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab100000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000002c28f6978c000000000000000000000000000000000000000000000000000000000000000cd0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000daee4d2156de6fe6f7d50ca047136d758f96a6f0000000000000000000000000daee4d2156de6fe6f7d50ca047136d758f96a6f0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000022b9e83c4c059000000000000000000000000000000000000000000000000000000000000a4b10000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000006874f43f0000000000000000000000000000000000000000000000000000000068754845d00dfeeddeadbeef765753be7f7a64d5509974b0d678e1e3149b02f4"; + uint256 inputAmountStartIndex = 8; + bool modifyOutputAmount = true; + uint256 outputAmountStartIndex = 392; + uint256 additionalValue = 0; + + bytes memory extraData = abi.encode(inputAmountStartIndex, modifyOutputAmount, outputAmountStartIndex); + bytes memory _calldata = abi.encodePacked(BungeeApiCalldata, extraData); + + emit log_named_bytes("BungeeApiCalldata", BungeeApiCalldata); + emit log_named_bytes("extraData", extraData); + emit log_named_bytes("_calldata", _calldata); + + COWShed.Call[] memory calls = new COWShed.Call[](1); + calls[0] = COWShed.Call({ + target: address(approveAndBridge), + value: 0, + callData: abi.encodeCall( + IApproveAndBridge.approveAndBridge, (IERC20(NATIVE_TOKEN_ADDRESS), minProceeds, additionalValue, _calldata) + ), + allowFailure: false, + isDelegateCall: true + }); + + vm.prank(address(factory)); + shed.trustedExecuteHooks(calls); + assertEq(address(shed).balance, 0); + } + + function test_cctp_USDC_to_USDC() external { + // Note: deployment and initialization is handled in `executeHooks` and + // doesn't need to be done in the actual trade setting. + // However, it's easier to build the test without handling the + // authentication part needed for that and use `trustedExecuteHooks` + // through the factory instead. + factory.initializeProxy(user, false); + COWShed shed = COWShed(factory.proxyOf(user)); + vm.label(address(shed), "shed"); + assertGt(address(shed).code.length, 0); + + uint256 orderProceeds = 5e6; + uint256 minProceeds = 4.9e6; + assertGt(orderProceeds, minProceeds); + + // For simplicity we take the funds from the user, but they should come + // from an order. + vm.prank(user); + USDC.transfer(address(shed), orderProceeds); + assertEq(USDC.balanceOf(address(shed)), orderProceeds); + + /* CCTP */ + /* bridgeERC20To */ + bytes memory BungeeApiCalldata = + hex"0000018db7dfe9d000000000000000000000000000000000000000000000000000000000000ef52f0000000000000000000000000000000000000000000000000000000000000a2d000000000000000000000000daee4d2156de6fe6f7d50ca047136d758f96a6f0000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000061a80000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + uint256 inputAmountStartIndex = 8; + bool modifyOutputAmount = false; + uint256 outputAmountStartIndex = 0; + uint256 additionalValue = 0; + + bytes memory extraData = abi.encode(inputAmountStartIndex, modifyOutputAmount, outputAmountStartIndex); + bytes memory _calldata = abi.encodePacked(BungeeApiCalldata, extraData); + + emit log_named_bytes("BungeeApiCalldata", BungeeApiCalldata); + emit log_named_bytes("extraData", extraData); + emit log_named_bytes("_calldata", _calldata); + + COWShed.Call[] memory calls = new COWShed.Call[](1); + calls[0] = COWShed.Call({ + target: address(approveAndBridge), + value: 0, + callData: abi.encodeCall(IApproveAndBridge.approveAndBridge, (USDC, minProceeds, additionalValue, _calldata)), + allowFailure: false, + isDelegateCall: true + }); + + vm.prank(address(factory)); + shed.trustedExecuteHooks(calls); + assertEq(USDC.balanceOf(address(shed)), 0); + } +} diff --git a/test/e2e/lib/ForkedRpc.sol b/test/e2e/lib/ForkedRpc.sol new file mode 100644 index 0000000..4ccb1d0 --- /dev/null +++ b/test/e2e/lib/ForkedRpc.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8; + +import {Vm} from "forge-std/Test.sol"; + +library ForkedRpc { + function forkEthereumMainnetAtBlock(Vm vm, uint256 blockNumber) internal returns (uint256 forkId) { + string memory forkUrl; + try vm.envString("MAINNET_ARCHIVE_NODE_URL") returns (string memory url) { + forkUrl = url; + } catch { + forkUrl = "https://eth.merkle.io"; + } + forkId = vm.createSelectFork(forkUrl, blockNumber); + } + + function forkBaseAtBlock(Vm vm, uint256 blockNumber) internal returns (uint256 forkId) { + string memory forkUrl; + try vm.envString("BASE_ARCHIVE_NODE_URL") returns (string memory url) { + forkUrl = url; + } catch { + forkUrl = "https://base.drpc.org"; + } + forkId = vm.createSelectFork(forkUrl, blockNumber); + } +} diff --git a/test/mocks/MockBridge.sol b/test/mocks/MockBridge.sol new file mode 100644 index 0000000..0f7c237 --- /dev/null +++ b/test/mocks/MockBridge.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8; + +// Mock contract that always fails bridge calls +contract MockFailingBridge { + fallback() external payable { + revert("Bridge call failed"); + } +} diff --git a/test/mocks/MockERC20.sol b/test/mocks/MockERC20.sol new file mode 100644 index 0000000..ead0293 --- /dev/null +++ b/test/mocks/MockERC20.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8; + +import {IERC20} from "src/vendored/IERC20.sol"; + +contract MockERC20 is IERC20 { + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + uint256 public totalSupply; + string public name = "Mock Token"; + string public symbol = "MOCK"; + uint8 public decimals = 18; + + constructor(uint256 initialSupply) { + totalSupply = initialSupply; + balanceOf[msg.sender] = initialSupply; + } + + function transfer(address to, uint256 amount) external returns (bool) { + require(balanceOf[msg.sender] >= amount, "Insufficient balance"); + balanceOf[msg.sender] -= amount; + balanceOf[to] += amount; + emit Transfer(msg.sender, to, amount); + return true; + } + + function approve(address spender, uint256 amount) external returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function transferFrom(address from, address to, uint256 amount) external returns (bool) { + require(balanceOf[from] >= amount, "Insufficient balance"); + require(allowance[from][msg.sender] >= amount, "Insufficient allowance"); + balanceOf[from] -= amount; + balanceOf[to] += amount; + allowance[from][msg.sender] -= amount; + emit Transfer(from, to, amount); + return true; + } + + function forceApprove(address spender, uint256 amount) external returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + // Function to mint tokens for testing + function mint(address to, uint256 amount) external { + balanceOf[to] += amount; + totalSupply += amount; + emit Transfer(address(0), to, amount); + } + + // Function to burn tokens for testing + function burn(address from, uint256 amount) external { + require(balanceOf[from] >= amount, "Insufficient balance"); + balanceOf[from] -= amount; + totalSupply -= amount; + emit Transfer(from, address(0), amount); + } +}