[
  {
    "path": ".github/workflows/test.yml",
    "content": "on: [push]\n\nname: test\n\njobs:\n  check:\n    name: Foundry project\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          submodules: recursive\n\n      - name: Install Foundry\n        uses: foundry-rs/foundry-toolchain@v1\n        with:\n          version: nightly\n\n      - name: Run tests @ 0.8.13\n        run: forge test -vvv --use 0.8.13\n\n      - name: Run tests @ 0.7.6\n        run: forge test -vvv --use 0.7.6\n\n      - name: Run snapshot\n        run: forge snapshot\n"
  },
  {
    "path": ".gitignore",
    "content": "cache/\nout/\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"lib/ds-test\"]\n\tpath = lib/ds-test\n\turl = https://github.com/dapphub/ds-test\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n### Unreleased\n\n### 0.0.1-rc.1\n\n- first release\n"
  },
  {
    "path": "README.md",
    "content": "# ExcessivelySafeCall\n\nThis solidity library helps you call untrusted contracts safely. Specifically,\nit seeks to prevent _all possible_ ways that the callee can maliciously cause\nthe caller to revert. Most of these revert cases are covered by the use of a\n[low-level call](https://solidity-by-example.org/call/). The main difference\nwith between `address.call()`call and `address.excessivelySafeCall()` is that\na regular solidity call will **automatically** copy bytes to memory without\nconsideration of gas.\n\nThis is to say, a low-level solidity call will copy _any amount of bytes_ to\nlocal memory. When bytes are copied from returndata to memory, the\n[memory expansion cost\n](https://ethereum.stackexchange.com/questions/92546/what-is-expansion-cost) is\npaid. This means that when using a standard solidity call, the callee can\n**\"returnbomb\"** the caller, imposing an arbitrary gas cost. Because this gas is\npaid _by the caller_ and _in the caller's context_, it can cause the caller to\nrun out of gas and halt execution.\n\nTo prevent returnbombing, we provide `excessivelySafeCall` and\n`excessivelySafeStaticCall`. These behave similarly to solidity's low-level\ncalls, however, they allow the user to specify a maximum number of bytes to be\ncopied to local memory. E.g. a user desiring a single return value should\nspecify a `_maxCopy` of 32 bytes. Refusing to copy large blobs to local memory\neffectively prevents the callee from triggering local OOG reversion. We _also_ recommend careful consideration of the gas amount passed to untrusted\ncallees.\n\nConsider the following contracts:\n\n```solidity\ncontract BadGuy {\n    function youveActivateMyTrapCard() external pure returns (bytes memory) {\n        assembly{\n            revert(0, 1_000_000)\n        }\n    }\n}\n\ncontract Mark {\n    function oops(address badGuy) {\n        bool success;\n        bytes memory ret;\n\n        // Mark pays a lot of gas for this copy 😬😬😬\n        (success, ret) == badGuy.call(\n            SOME_GAS,\n            abi.encodeWithSelector(\n                BadGuy.youveActivateMyTrapCard.selector\n            )\n        );\n\n        // Mark may OOG here, preventing local state changes\n        importantCleanup();\n    }\n}\n\ncontract ExcessivelySafeSam {\n    using ExcessivelySafeCall for address;\n\n    // Sam is cool and doesn't get returnbombed\n    function sunglassesEmoji(address badGuy) {\n        bool success;\n        bytes memory ret;\n\n        (success, ret) == badGuy.excessivelySafeCall(\n            SOME_GAS,\n            32,  // <-- the magic. Copy no more than 32 bytes to memory\n            abi.encodeWithSelector(\n                BadGuy.youveActivateMyTrapCard.selector\n            )\n        );\n\n        // Sam can afford to clean up after himself.\n        importantCleanup();\n    }\n}\n```\n\n## When would I use this\n\n`ExcessivelySafeCall` prevents malicious callees from affecting post-execution\ncleanup (e.g. state-based replay protection). Given that a dev is unlikely to\nhard-code a call to a malicious contract, we expect most danger to come from\ndynamic dispatch protocols, where neither the callee nor the code being called\nis known to the developer ahead of time.\n\nDynamic dispatch in solidity is probably _most_ useful for metatransaction\nprotocols. This includes gas-abstraction relayers, smart contract wallets,\nbridges, etc.\n\nNomad uses excessively safe calls for safe processing of cross-domain messages.\nThis guarantees that a message recipient cannot interfere with safe operation\nof the cross-domain communication channel and message processing layer.\n\n## Interacting with the repo\n\n**To install in your project**:\n\n- install [Foundry](https://github.com/gakonst/foundry)\n- `forge install nomad-xyz/ExcessivelySafeCall`\n\n**To run tests**:\n\n- install [Foundry](https://github.com/gakonst/foundry)\n- `forge test`\n\n## A note on licensing:\n\nTests are licensed GPLv3, as they extend the `DSTest` contract. Non-test work\nis avialable under user's choice of MIT and Apache2.0.\n"
  },
  {
    "path": "foundry.toml",
    "content": "[profile.default]\nsrc = 'src'\nout = 'out'\nlibs = ['lib']\nremappings = ['ds-test/=lib/ds-test/src/']\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@nomad-xyz/excessively-safe-call\",\n  \"version\": \"0.0.1-rc.1\",\n  \"description\": \"Helps you call untrusted contracts safely\",\n  \"keywords\": [\n    \"nomad\",\n    \"excessively safe call\"\n  ],\n  \"homepage\": \"https://github.com/nomad-xyz/ExcessivelySafeCall#readme\",\n  \"bugs\": {\n    \"url\": \"https://github.com/nomad-xyz/ExcessivelySafeCall/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git@github.com:nomad-xyz/ExcessivelySafeCall.git\"\n  },\n  \"license\": \"Apache-2.0 OR MIT\",\n  \"author\": \"Illusory Systems Inc.\",\n  \"main\": \"src/ExcessivelySafeCall.sol\",\n  \"files\": [\n    \"src/ExcessivelySafeCall.sol\"\n  ]\n}"
  },
  {
    "path": "src/ExcessivelySafeCall.sol",
    "content": "// SPDX-License-Identifier: MIT OR Apache-2.0\npragma solidity >=0.7.6;\n\nlibrary ExcessivelySafeCall {\n    uint256 constant LOW_28_MASK =\n        0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff;\n\n    /// @notice Use when you _really_ really _really_ don't trust the called\n    /// contract. This prevents the called contract from causing reversion of\n    /// the caller in as many ways as we can.\n    /// @dev The main difference between this and a solidity low-level call is\n    /// that we limit the number of bytes that the callee can cause to be\n    /// copied to caller memory. This prevents stupid things like malicious\n    /// contracts returning 10,000,000 bytes causing a local OOG when copying\n    /// to memory.\n    /// @param _target The address to call\n    /// @param _gas The amount of gas to forward to the remote contract\n    /// @param _value The value in wei to send to the remote contract\n    /// @param _maxCopy The maximum number of bytes of returndata to copy\n    /// to memory.\n    /// @param _calldata The data to send to the remote contract\n    /// @return success and returndata, as `.call()`. Returndata is capped to\n    /// `_maxCopy` bytes.\n    function excessivelySafeCall(\n        address _target,\n        uint256 _gas,\n        uint256 _value,\n        uint16 _maxCopy,\n        bytes memory _calldata\n    ) internal returns (bool, bytes memory) {\n        // set up for assembly call\n        uint256 _toCopy;\n        bool _success;\n        bytes memory _returnData = new bytes(_maxCopy);\n        // dispatch message to recipient\n        // by assembly calling \"handle\" function\n        // we call via assembly to avoid memcopying a very large returndata\n        // returned by a malicious contract\n        assembly {\n            _success := call(\n                _gas, // gas\n                _target, // recipient\n                _value, // ether value\n                add(_calldata, 0x20), // inloc\n                mload(_calldata), // inlen\n                0, // outloc\n                0 // outlen\n            )\n            // limit our copy to 256 bytes\n            _toCopy := returndatasize()\n            if gt(_toCopy, _maxCopy) {\n                _toCopy := _maxCopy\n            }\n            // Store the length of the copied bytes\n            mstore(_returnData, _toCopy)\n            // copy the bytes from returndata[0:_toCopy]\n            returndatacopy(add(_returnData, 0x20), 0, _toCopy)\n        }\n        return (_success, _returnData);\n    }\n\n    /// @notice Use when you _really_ really _really_ don't trust the called\n    /// contract. This prevents the called contract from causing reversion of\n    /// the caller in as many ways as we can.\n    /// @dev The main difference between this and a solidity low-level call is\n    /// that we limit the number of bytes that the callee can cause to be\n    /// copied to caller memory. This prevents stupid things like malicious\n    /// contracts returning 10,000,000 bytes causing a local OOG when copying\n    /// to memory.\n    /// @param _target The address to call\n    /// @param _gas The amount of gas to forward to the remote contract\n    /// @param _maxCopy The maximum number of bytes of returndata to copy\n    /// to memory.\n    /// @param _calldata The data to send to the remote contract\n    /// @return success and returndata, as `.call()`. Returndata is capped to\n    /// `_maxCopy` bytes.\n    function excessivelySafeStaticCall(\n        address _target,\n        uint256 _gas,\n        uint16 _maxCopy,\n        bytes memory _calldata\n    ) internal view returns (bool, bytes memory) {\n        // set up for assembly call\n        uint256 _toCopy;\n        bool _success;\n        bytes memory _returnData = new bytes(_maxCopy);\n        // dispatch message to recipient\n        // by assembly calling \"handle\" function\n        // we call via assembly to avoid memcopying a very large returndata\n        // returned by a malicious contract\n        assembly {\n            _success := staticcall(\n                _gas, // gas\n                _target, // recipient\n                add(_calldata, 0x20), // inloc\n                mload(_calldata), // inlen\n                0, // outloc\n                0 // outlen\n            )\n            // limit our copy to 256 bytes\n            _toCopy := returndatasize()\n            if gt(_toCopy, _maxCopy) {\n                _toCopy := _maxCopy\n            }\n            // Store the length of the copied bytes\n            mstore(_returnData, _toCopy)\n            // copy the bytes from returndata[0:_toCopy]\n            returndatacopy(add(_returnData, 0x20), 0, _toCopy)\n        }\n        return (_success, _returnData);\n    }\n\n    /**\n     * @notice Swaps function selectors in encoded contract calls\n     * @dev Allows reuse of encoded calldata for functions with identical\n     * argument types but different names. It simply swaps out the first 4 bytes\n     * for the new selector. This function modifies memory in place, and should\n     * only be used with caution.\n     * @param _newSelector The new 4-byte selector\n     * @param _buf The encoded contract args\n     */\n    function swapSelector(bytes4 _newSelector, bytes memory _buf)\n        internal\n        pure\n    {\n        require(_buf.length >= 4);\n        uint256 _mask = LOW_28_MASK;\n        assembly {\n            // load the first word of\n            let _word := mload(add(_buf, 0x20))\n            // mask out the top 4 bytes\n            // /x\n            _word := and(_word, _mask)\n            _word := or(_newSelector, _word)\n            mstore(add(_buf, 0x20), _word)\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/ExcessivelySafeCall.t.sol",
    "content": "// SPDX-License-Identifier: GPL-3.0-or-later\npragma solidity >=0.7.6;\n\nimport \"ds-test/test.sol\";\nimport \"src/ExcessivelySafeCall.sol\";\n\ncontract ContractTest is DSTest {\n    using ExcessivelySafeCall for address;\n\n    address target;\n    CallTarget t;\n\n    function returnSize() internal pure returns (uint256 _bytes) {\n        assembly {\n            _bytes := returndatasize()\n        }\n    }\n\n    function setUp() public {\n        t = new CallTarget();\n        target = address(t);\n    }\n\n    function testCall() public {\n        bool _success;\n        bytes memory _ret;\n\n        (_success, _ret) = target.excessivelySafeCall(\n            100_000,\n            0,\n            0,\n            abi.encodeWithSelector(CallTarget.one.selector)\n        );\n        assertTrue(_success);\n        assertEq(_ret.length, 0);\n        assertEq(t.called(), 1);\n\n        (_success, _ret) = target.excessivelySafeCall(\n            100_000,\n            0,\n            0,\n            abi.encodeWithSelector(CallTarget.two.selector)\n        );\n        assertTrue(_success);\n        assertEq(_ret.length, 0);\n        assertEq(t.called(), 2);\n\n        (_success, _ret) = target.excessivelySafeCall(\n            100_000,\n            0,\n            0,\n            abi.encodeWithSelector(CallTarget.any.selector, 5)\n        );\n        assertTrue(_success);\n        assertEq(_ret.length, 0);\n        assertEq(t.called(), 5);\n\n        (_success, _ret) = target.excessivelySafeCall(\n            100_000,\n            69,\n            0,\n            abi.encodeWithSelector(CallTarget.payme.selector)\n        );\n        assertTrue(_success);\n        assertEq(_ret.length, 0);\n        assertEq(t.called(), 69);\n    }\n\n    function testStaticCall() public {\n        bool _success;\n        bytes memory _ret;\n\n        (_success, _ret) = target.excessivelySafeStaticCall(\n            100_000,\n            0,\n            abi.encodeWithSelector(CallTarget.two.selector)\n        );\n        assertEq(t.called(), 0, \"t modified state\");\n        assertTrue(!_success, \"staticcall should error on state modification\");\n    }\n\n    function testCopy(uint16 _maxCopy, uint16 _requested) public {\n        uint16 _toCopy = _maxCopy < _requested ? _maxCopy : _requested;\n\n        bool _success;\n        bytes memory _ret;\n\n        (_success, _ret) = target.excessivelySafeCall(\n            100_000,\n            0,\n            _maxCopy,\n            abi.encodeWithSelector(CallTarget.retBytes.selector, uint256(_requested))\n        );\n        assertTrue(_success);\n        assertEq(_ret.length, _toCopy, \"return copied wrong amount\");\n\n        (_success, _ret) = target.excessivelySafeCall(\n            100_000,\n            0,\n            _maxCopy,\n            abi.encodeWithSelector(CallTarget.revBytes.selector, uint256(_requested))\n        );\n        assertTrue(!_success);\n        assertEq(_ret.length, _toCopy, \"revert copied wrong amount\");\n    }\n\n\n    function testStaticCopy(uint16 _maxCopy, uint16 _requested) public {\n        uint16 _toCopy = _maxCopy < _requested ? _maxCopy : _requested;\n\n        bool _success;\n        bytes memory _ret;\n\n        (_success, _ret) = target.excessivelySafeStaticCall(\n            100_000,\n            _maxCopy,\n            abi.encodeWithSelector(CallTarget.retBytes.selector, uint256(_requested))\n        );\n        assertTrue(_success);\n        assertEq(_ret.length, _toCopy, \"return copied wrong amount\");\n\n        (_success, _ret) = target.excessivelySafeStaticCall(\n            100_000,\n            _maxCopy,\n            abi.encodeWithSelector(CallTarget.revBytes.selector, uint256(_requested))\n        );\n        assertTrue(!_success);\n        assertEq(_ret.length, _toCopy, \"revert copied wrong amount\");\n    }\n\n    function testBadBehavior() public {\n        bool _success;\n        bytes memory _ret;\n\n        (_success, _ret) = target.excessivelySafeCall(\n            3_000_000,\n            0,\n            32,\n            abi.encodeWithSelector(CallTarget.badRet.selector)\n        );\n        assertTrue(_success);\n        assertEq(returnSize(), 1_000_000, \"didn't return all\");\n        assertEq(_ret.length, 32, \"revert didn't truncate\");\n\n\n        (_success, _ret) = target.excessivelySafeCall(\n            3_000_000,\n            0,\n            32,\n            abi.encodeWithSelector(CallTarget.badRev.selector)\n        );\n        assertTrue(!_success);\n        assertEq(returnSize(), 1_000_000, \"didn't return all\");\n        assertEq(_ret.length, 32, \"revert didn't truncate\");\n    }\n\n    function testStaticBadBehavior() public {\n        bool _success;\n        bytes memory _ret;\n\n        (_success, _ret) = target.excessivelySafeStaticCall(\n            2_002_000,\n            32,\n            abi.encodeWithSelector(CallTarget.badRet.selector)\n        );\n        assertTrue(_success);\n        assertEq(returnSize(), 1_000_000, \"didn't return all\");\n        assertEq(_ret.length, 32, \"revert didn't truncate\");\n\n                (_success, _ret) = target.excessivelySafeStaticCall(\n            2_002_000,\n            32,\n            abi.encodeWithSelector(CallTarget.badRev.selector)\n        );\n        assertTrue(!_success);\n        assertEq(returnSize(), 1_000_000, \"didn't return all\");\n        assertEq(_ret.length, 32, \"revert didn't truncate\");\n    }\n\n}\n\n\ncontract CallTarget {\n    uint256 public called;\n    constructor () {}\n\n    function one() external {\n        called = 1;\n    }\n\n    function two() external {\n        called = 2;\n    }\n\n    function any(uint256 _num) external {\n        called = _num;\n    }\n\n    function payme() external payable {\n        called = msg.value;\n    }\n\n    function retBytes(uint256 _bytes) public pure {\n        assembly {\n            return(0, _bytes)\n        }\n    }\n\n    function revBytes(uint256 _bytes) public pure {\n        assembly {\n            revert(0, _bytes)\n        }\n    }\n\n    function badRet() external pure returns (bytes memory) {\n        retBytes(1_000_000);\n    }\n\n    function badRev() external pure {\n        revBytes(1_000_000);\n    }\n}\n"
  }
]