UNPKG

@etherspot/contracts

Version:

Etherspot Solidity contracts

29 lines 256 kB
{ "language": "Solidity", "sources": { "src/bridges/Diamond.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport {LibDiamond} from \"./libs/LibDiamond.sol\";\nimport {IDiamondCut} from \"./interfaces/IDiamondCut.sol\";\n\ncontract Diamond {\n constructor(address _contractOwner, address _diamondCutFacet) payable {\n LibDiamond.setContractOwner(_contractOwner);\n\n // Add the diamondCut external function from the diamondCutFacet\n IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1);\n bytes4[] memory functionSelectors = new bytes4[](1);\n functionSelectors[0] = IDiamondCut.diamondCut.selector;\n cut[0] = IDiamondCut.FacetCut({\n facetAddress: _diamondCutFacet,\n action: IDiamondCut.FacetCutAction.Add,\n functionSelectors: functionSelectors\n });\n LibDiamond.diamondCut(cut, address(0), \"\");\n }\n\n // Find facet for function that is called and execute the\n // function if a facet is found and return any value.\n // solhint-disable-next-line no-complex-fallback\n fallback() external payable {\n LibDiamond.DiamondStorage storage ds;\n bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION;\n\n // get diamond storage\n // solhint-disable-next-line no-inline-assembly\n assembly {\n ds.slot := position\n }\n\n // get facet from function selector\n address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress;\n require(facet != address(0), \"Diamond: Function does not exist\");\n\n // Execute external function from facet using delegatecall and return any value.\n // solhint-disable-next-line no-inline-assembly\n assembly {\n // copy function selector and any arguments\n calldatacopy(0, 0, calldatasize())\n // execute function call using the facet\n let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)\n // get any return value\n returndatacopy(0, 0, returndatasize())\n // return any return value or error back to the caller\n switch result\n case 0 {\n revert(0, returndatasize())\n }\n default {\n return(0, returndatasize())\n }\n }\n }\n\n // Able to receive ether\n // solhint-disable-next-line no-empty-blocks\n receive() external payable {}\n}\n" }, "src/bridges/libs/LibDiamond.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport {IDiamondCut} from \"../interfaces/IDiamondCut.sol\";\n\nlibrary LibDiamond {\n bytes32 internal constant DIAMOND_STORAGE_POSITION =\n keccak256(\"diamond.standard.diamond.storage\");\n\n struct FacetAddressAndPosition {\n address facetAddress;\n uint96 functionSelectorPosition; // position in facetFunctionSelectors.functionSelectors array\n }\n\n struct FacetFunctionSelectors {\n bytes4[] functionSelectors;\n uint256 facetAddressPosition; // position of facetAddress in facetAddresses array\n }\n\n struct DiamondStorage {\n // maps function selector to the facet address and\n // the position of the selector in the facetFunctionSelectors.selectors array\n mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition;\n // maps facet addresses to function selectors\n mapping(address => FacetFunctionSelectors) facetFunctionSelectors;\n // facet addresses\n address[] facetAddresses;\n // Used to query if a contract implements an interface.\n // Used to implement ERC-165.\n mapping(bytes4 => bool) supportedInterfaces;\n // owner of the contract\n address contractOwner;\n }\n\n function diamondStorage()\n internal\n pure\n returns (DiamondStorage storage ds)\n {\n bytes32 position = DIAMOND_STORAGE_POSITION;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n ds.slot := position\n }\n }\n\n event OwnershipTransferred(\n address indexed previousOwner,\n address indexed newOwner\n );\n\n function setContractOwner(address _newOwner) internal {\n DiamondStorage storage ds = diamondStorage();\n address previousOwner = ds.contractOwner;\n ds.contractOwner = _newOwner;\n emit OwnershipTransferred(previousOwner, _newOwner);\n }\n\n function contractOwner() internal view returns (address contractOwner_) {\n contractOwner_ = diamondStorage().contractOwner;\n }\n\n function enforceIsContractOwner() internal view {\n require(\n msg.sender == diamondStorage().contractOwner,\n \"LibDiamond: Must be contract owner\"\n );\n }\n\n event DiamondCut(\n IDiamondCut.FacetCut[] _diamondCut,\n address _init,\n bytes _calldata\n );\n\n // Internal function version of diamondCut\n function diamondCut(\n IDiamondCut.FacetCut[] memory _diamondCut,\n address _init,\n bytes memory _calldata\n ) internal {\n for (\n uint256 facetIndex;\n facetIndex < _diamondCut.length;\n facetIndex++\n ) {\n IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action;\n if (action == IDiamondCut.FacetCutAction.Add) {\n addFunctions(\n _diamondCut[facetIndex].facetAddress,\n _diamondCut[facetIndex].functionSelectors\n );\n } else if (action == IDiamondCut.FacetCutAction.Replace) {\n replaceFunctions(\n _diamondCut[facetIndex].facetAddress,\n _diamondCut[facetIndex].functionSelectors\n );\n } else if (action == IDiamondCut.FacetCutAction.Remove) {\n removeFunctions(\n _diamondCut[facetIndex].facetAddress,\n _diamondCut[facetIndex].functionSelectors\n );\n } else {\n revert(\"LibDiamondCut: Incorrect FacetCutAction\");\n }\n }\n emit DiamondCut(_diamondCut, _init, _calldata);\n initializeDiamondCut(_init, _calldata);\n }\n\n function addFunctions(\n address _facetAddress,\n bytes4[] memory _functionSelectors\n ) internal {\n require(\n _functionSelectors.length > 0,\n \"LibDiamondCut: No selectors in facet to cut\"\n );\n DiamondStorage storage ds = diamondStorage();\n require(\n _facetAddress != address(0),\n \"LibDiamondCut: Add facet can't be address(0)\"\n );\n uint96 selectorPosition = uint96(\n ds.facetFunctionSelectors[_facetAddress].functionSelectors.length\n );\n // add new facet address if it does not exist\n if (selectorPosition == 0) {\n addFacet(ds, _facetAddress);\n }\n for (\n uint256 selectorIndex;\n selectorIndex < _functionSelectors.length;\n selectorIndex++\n ) {\n bytes4 selector = _functionSelectors[selectorIndex];\n address oldFacetAddress = ds\n .selectorToFacetAndPosition[selector]\n .facetAddress;\n require(\n oldFacetAddress == address(0),\n \"LibDiamondCut: Can't add function that already exists\"\n );\n addFunction(ds, selector, selectorPosition, _facetAddress);\n selectorPosition++;\n }\n }\n\n function replaceFunctions(\n address _facetAddress,\n bytes4[] memory _functionSelectors\n ) internal {\n require(\n _functionSelectors.length > 0,\n \"LibDiamondCut: No selectors in facet to cut\"\n );\n DiamondStorage storage ds = diamondStorage();\n require(\n _facetAddress != address(0),\n \"LibDiamondCut: Add facet can't be address(0)\"\n );\n uint96 selectorPosition = uint96(\n ds.facetFunctionSelectors[_facetAddress].functionSelectors.length\n );\n // add new facet address if it does not exist\n if (selectorPosition == 0) {\n addFacet(ds, _facetAddress);\n }\n for (\n uint256 selectorIndex;\n selectorIndex < _functionSelectors.length;\n selectorIndex++\n ) {\n bytes4 selector = _functionSelectors[selectorIndex];\n address oldFacetAddress = ds\n .selectorToFacetAndPosition[selector]\n .facetAddress;\n require(\n oldFacetAddress != _facetAddress,\n \"LibDiamondCut: Can't replace function with same function\"\n );\n removeFunction(ds, oldFacetAddress, selector);\n addFunction(ds, selector, selectorPosition, _facetAddress);\n selectorPosition++;\n }\n }\n\n function removeFunctions(\n address _facetAddress,\n bytes4[] memory _functionSelectors\n ) internal {\n require(\n _functionSelectors.length > 0,\n \"LibDiamondCut: No selectors in facet to cut\"\n );\n DiamondStorage storage ds = diamondStorage();\n // if function does not exist then do nothing and return\n require(\n _facetAddress == address(0),\n \"LibDiamondCut: Remove facet address must be address(0)\"\n );\n for (\n uint256 selectorIndex;\n selectorIndex < _functionSelectors.length;\n selectorIndex++\n ) {\n bytes4 selector = _functionSelectors[selectorIndex];\n address oldFacetAddress = ds\n .selectorToFacetAndPosition[selector]\n .facetAddress;\n removeFunction(ds, oldFacetAddress, selector);\n }\n }\n\n function addFacet(DiamondStorage storage ds, address _facetAddress)\n internal\n {\n enforceHasContractCode(\n _facetAddress,\n \"LibDiamondCut: New facet has no code\"\n );\n ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = ds\n .facetAddresses\n .length;\n ds.facetAddresses.push(_facetAddress);\n }\n\n function addFunction(\n DiamondStorage storage ds,\n bytes4 _selector,\n uint96 _selectorPosition,\n address _facetAddress\n ) internal {\n ds\n .selectorToFacetAndPosition[_selector]\n .functionSelectorPosition = _selectorPosition;\n ds.facetFunctionSelectors[_facetAddress].functionSelectors.push(\n _selector\n );\n ds.selectorToFacetAndPosition[_selector].facetAddress = _facetAddress;\n }\n\n function removeFunction(\n DiamondStorage storage ds,\n address _facetAddress,\n bytes4 _selector\n ) internal {\n require(\n _facetAddress != address(0),\n \"LibDiamondCut: Can't remove function that doesn't exist\"\n );\n // an immutable function is a function defined directly in a diamond\n require(\n _facetAddress != address(this),\n \"LibDiamondCut: Can't remove immutable function\"\n );\n // replace selector with last selector, then delete last selector\n uint256 selectorPosition = ds\n .selectorToFacetAndPosition[_selector]\n .functionSelectorPosition;\n uint256 lastSelectorPosition = ds\n .facetFunctionSelectors[_facetAddress]\n .functionSelectors\n .length - 1;\n // if not the same then replace _selector with lastSelector\n if (selectorPosition != lastSelectorPosition) {\n bytes4 lastSelector = ds\n .facetFunctionSelectors[_facetAddress]\n .functionSelectors[lastSelectorPosition];\n ds.facetFunctionSelectors[_facetAddress].functionSelectors[\n selectorPosition\n ] = lastSelector;\n ds\n .selectorToFacetAndPosition[lastSelector]\n .functionSelectorPosition = uint96(selectorPosition);\n }\n // delete the last selector\n ds.facetFunctionSelectors[_facetAddress].functionSelectors.pop();\n delete ds.selectorToFacetAndPosition[_selector];\n\n // if no more selectors for facet address then delete the facet address\n if (lastSelectorPosition == 0) {\n // replace facet address with last facet address and delete last facet address\n uint256 lastFacetAddressPosition = ds.facetAddresses.length - 1;\n uint256 facetAddressPosition = ds\n .facetFunctionSelectors[_facetAddress]\n .facetAddressPosition;\n if (facetAddressPosition != lastFacetAddressPosition) {\n address lastFacetAddress = ds.facetAddresses[\n lastFacetAddressPosition\n ];\n ds.facetAddresses[facetAddressPosition] = lastFacetAddress;\n ds\n .facetFunctionSelectors[lastFacetAddress]\n .facetAddressPosition = facetAddressPosition;\n }\n ds.facetAddresses.pop();\n delete ds\n .facetFunctionSelectors[_facetAddress]\n .facetAddressPosition;\n }\n }\n\n function initializeDiamondCut(address _init, bytes memory _calldata)\n internal\n {\n if (_init == address(0)) {\n require(\n _calldata.length == 0,\n \"LibDiamondCut: _init is address(0) but_calldata is not empty\"\n );\n } else {\n require(\n _calldata.length > 0,\n \"LibDiamondCut: _calldata is empty but _init is not address(0)\"\n );\n if (_init != address(this)) {\n enforceHasContractCode(\n _init,\n \"LibDiamondCut: _init address has no code\"\n );\n }\n // solhint-disable-next-line avoid-low-level-calls\n (bool success, bytes memory error) = _init.delegatecall(_calldata);\n if (!success) {\n if (error.length > 0) {\n // bubble up the error\n revert(string(error));\n } else {\n revert(\"LibDiamondCut: _init function reverted\");\n }\n }\n }\n }\n\n function enforceHasContractCode(\n address _contract,\n string memory _errorMessage\n ) internal view {\n uint256 contractSize;\n // solhint-disable-next-line no-inline-assembly\n assembly {\n contractSize := extcodesize(_contract)\n }\n require(contractSize > 0, _errorMessage);\n }\n}\n" }, "src/bridges/interfaces/IDiamondCut.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\ninterface IDiamondCut {\n enum FacetCutAction {\n Add,\n Replace,\n Remove\n }\n // Add=0, Replace=1, Remove=2\n\n struct FacetCut {\n address facetAddress;\n FacetCutAction action;\n bytes4[] functionSelectors;\n }\n\n /// @notice Add/replace/remove any number of functions and optionally execute\n /// a function with delegatecall\n /// @param _diamondCut Contains the facet addresses and function selectors\n /// @param _init The address of the contract or facet to execute _calldata\n /// @param _calldata A function call, including function selector and arguments\n /// _calldata is executed with delegatecall on _init\n function diamondCut(\n FacetCut[] calldata _diamondCut,\n address _init,\n bytes calldata _calldata\n ) external;\n\n event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);\n}\n" }, "src/bridges/facets/DiamondCutFacet.sol": { "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.4 <0.9.0;\n\nimport { IDiamondCut } from \"../interfaces/IDiamondCut.sol\";\nimport { LibDiamond } from \"../libs/LibDiamond.sol\";\n\ncontract DiamondCutFacet is IDiamondCut {\n /// @notice Add/replace/remove any number of functions and optionally execute\n /// a function with delegatecall\n /// @param _diamondCut Contains the facet addresses and function selectors\n /// @param _init The address of the contract or facet to execute _calldata\n /// @param _calldata A function call, including function selector and arguments\n /// _calldata is executed with delegatecall on _init\n function diamondCut(\n FacetCut[] calldata _diamondCut,\n address _init,\n bytes calldata _calldata\n ) external override {\n LibDiamond.enforceIsContractOwner();\n LibDiamond.diamondCut(_diamondCut, _init, _calldata);\n }\n}\n" }, "@openzeppelin/contracts/token/ERC20/IERC20.sol": { "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) external returns (bool);\n}\n" }, "@openzeppelin/contracts/interfaces/IERC20.sol": { "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (interfaces/IERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../token/ERC20/IERC20.sol\";\n" }, "@openzeppelin/contracts/utils/Address.sol": { "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n" }, "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": { "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\nimport \"../../../utils/Address.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using Address for address;\n\n function safeTransfer(\n IERC20 token,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n uint256 newAllowance = token.allowance(address(this), spender) + value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n uint256 newAllowance = oldAllowance - value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) {\n // Return data is optional\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n" }, "@connext/nxtp-contracts/contracts/core/connext/libraries/AssetLogic.sol": { "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity 0.8.17;\n\nimport {SafeERC20} from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport {IERC20Metadata} from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\n\nimport {TypeCasts} from \"../../../shared/libraries/TypeCasts.sol\";\n\nimport {IStableSwap} from \"../interfaces/IStableSwap.sol\";\n\nimport {LibConnextStorage, AppStorage, TokenConfig} from \"./LibConnextStorage.sol\";\nimport {SwapUtils} from \"./SwapUtils.sol\";\nimport {Constants} from \"./Constants.sol\";\nimport {TokenId} from \"./TokenId.sol\";\n\nlibrary AssetLogic {\n // ============ Libraries ============\n\n using SwapUtils for SwapUtils.Swap;\n using SafeERC20 for IERC20Metadata;\n\n // ============ Errors ============\n\n error AssetLogic__handleIncomingAsset_nativeAssetNotSupported();\n error AssetLogic__handleIncomingAsset_feeOnTransferNotSupported();\n error AssetLogic__handleOutgoingAsset_notNative();\n error AssetLogic__getTokenIndexFromStableSwapPool_notExist();\n error AssetLogic__getConfig_notRegistered();\n error AssetLogic__swapAsset_externalStableSwapPoolDoesNotExist();\n\n // ============ Internal: Handle Transfer ============\n\n function getConfig(bytes32 _key) internal view returns (TokenConfig storage) {\n AppStorage storage s = LibConnextStorage.connextStorage();\n TokenConfig storage config = s.tokenConfigs[_key];\n\n // Sanity check: not empty\n // NOTE: adopted decimals will *always* be nonzero (or reflect what is onchain\n // for the asset). The same is not true for the representation assets, which\n // will always have 0 decimals on the canonical domain\n if (config.adoptedDecimals < 1) {\n revert AssetLogic__getConfig_notRegistered();\n }\n\n return config;\n }\n\n /**\n * @notice Handles transferring funds from msg.sender to the Connext contract.\n * @dev Does NOT work with fee-on-transfer tokens: will revert.\n *\n * @param _asset - The address of the ERC20 token to transfer.\n * @param _amount - The specified amount to transfer.\n */\n function handleIncomingAsset(address _asset, uint256 _amount) internal {\n // Sanity check: if amount is 0, do nothing.\n if (_amount == 0) {\n return;\n }\n // Sanity check: asset address is not zero.\n if (_asset == address(0)) {\n revert AssetLogic__handleIncomingAsset_nativeAssetNotSupported();\n }\n\n IERC20Metadata asset = IERC20Metadata(_asset);\n\n // Record starting amount to validate correct amount is transferred.\n uint256 starting = asset.balanceOf(address(this));\n\n // Transfer asset to contract.\n asset.safeTransferFrom(msg.sender, address(this), _amount);\n\n // Ensure correct amount was transferred (i.e. this was not a fee-on-transfer token).\n if (asset.balanceOf(address(this)) - starting != _amount) {\n revert AssetLogic__handleIncomingAsset_feeOnTransferNotSupported();\n }\n }\n\n /**\n * @notice Handles transferring funds from the Connext contract to a specified address\n * @param _asset - The address of the ERC20 token to transfer.\n * @param _to - The recipient address that will receive the funds.\n * @param _amount - The amount to withdraw from contract.\n */\n function handleOutgoingAsset(\n address _asset,\n address _to,\n uint256 _amount\n ) internal {\n // Sanity check: if amount is 0, do nothing.\n if (_amount == 0) {\n return;\n }\n // Sanity check: asset address is not zero.\n if (_asset == address(0)) revert AssetLogic__handleOutgoingAsset_notNative();\n\n // Transfer ERC20 asset to target recipient.\n SafeERC20.safeTransfer(IERC20Metadata(_asset), _to, _amount);\n }\n\n // ============ Internal: StableSwap Pools ============\n\n /**\n * @notice Return the index of the given token address. Reverts if no matching\n * token is found.\n * @param key the hash of the canonical id and domain\n * @param tokenAddress address of the token\n * @return the index of the given token address\n */\n function getTokenIndexFromStableSwapPool(bytes32 key, address tokenAddress) internal view returns (uint8) {\n AppStorage storage s = LibConnextStorage.connextStorage();\n uint8 index = s.tokenIndexes[key][tokenAddress];\n if (address(s.swapStorages[key].pooledTokens[index]) != tokenAddress)\n revert AssetLogic__getTokenIndexFromStableSwapPool_notExist();\n return index;\n }\n\n // ============ Internal: Handle Swap ============\n\n /**\n * @notice Swaps an adopted asset to the local (representation or canonical) asset.\n * @dev Will not swap if the asset passed in is the local asset.\n * @param _key - The hash of canonical id and domain.\n * @param _asset - The address of the adopted asset to swap into the local asset.\n * @param _amount - The amount of the adopted asset to swap.\n * @param _slippage - The maximum amount of slippage user will take on from _amount in BPS.\n * @return uint256 The amount of local asset received from swap.\n */\n function swapToLocalAssetIfNeeded(\n bytes32 _key,\n address _asset,\n address _local,\n uint256 _amount,\n uint256 _slippage\n ) internal returns (uint256) {\n // If there's no amount, no need to swap.\n if (_amount == 0) {\n return 0;\n }\n\n // Check the case where the adopted asset *is* the local asset. If so, no need to swap.\n if (_local == _asset) {\n return _amount;\n }\n\n // Get the configs.\n TokenConfig storage config = getConfig(_key);\n\n // Swap the asset to the proper local asset.\n (uint256 out, ) = _swapAsset(\n _key,\n _asset,\n _local,\n _amount,\n calculateSlippageBoundary(config.adoptedDecimals, config.representationDecimals, _amount, _slippage)\n );\n return out;\n }\n\n /**\n * @notice Swaps a local bridge asset for the adopted asset using the stored stable swap\n * @dev Will not swap if the asset passed in is the adopted asset\n * @param _key the hash of the canonical id and domain\n * @param _asset - The address of the local asset to swap into the adopted asset\n * @param _amount - The amount of the local asset to swap\n * @param _slippage - The minimum amount of slippage user will take on from _amount in BPS\n * @param _normalizedIn - The amount sent in on xcall to take the slippage from, in 18 decimals\n * by convention\n * @return The amount of adopted asset received from swap\n * @return The address of asset received post-swap\n */\n function swapFromLocalAssetIfNeeded(\n bytes32 _key,\n address _asset,\n uint256 _amount,\n uint256 _slippage,\n uint256 _normalizedIn\n ) internal returns (uint256, address) {\n // Get the token config.\n TokenConfig storage config = getConfig(_key);\n address adopted = config.adopted;\n\n // If the adopted asset is the local asset, no need to swap.\n if (adopted == _asset) {\n return (_amount, adopted);\n }\n\n // If there's no amount, no need to swap.\n if (_amount == 0) {\n return (_amount, adopted);\n }\n\n // Swap the asset to the proper local asset\n return\n _swapAsset(\n _key,\n _asset,\n adopted,\n _amount,\n // NOTE: To get the slippage boundary here, you must take the slippage % off of the\n // normalized amount in (at 18 decimals by convention), then convert that amount\n // to the proper decimals of adopted.\n calculateSlippageBoundary(\n Constants.DEFAULT_NORMALIZED_DECIMALS,\n config.adoptedDecimals,\n _normalizedIn,\n _slippage\n )\n );\n }\n\n /**\n * @notice Swaps a local bridge asset for the adopted asset using the stored stable swap\n * @dev Will not swap if the asset passed in is the adopted asset\n * @param _key the hash of the canonical id and domain\n * @param _asset - The address of the local asset to swap into the adopted asset\n * @param _amount - The exact amount to receive out of the swap\n * @param _maxIn - The most you will supply to the swap\n * @return The amount of local asset put into swap\n * @return The address of asset received post-swap\n */\n function swapFromLocalAssetIfNeededForExactOut(\n bytes32 _key,\n address _asset,\n uint256 _amount,\n uint256 _maxIn\n ) internal returns (uint256, address) {\n TokenConfig storage config = getConfig(_key);\n\n // If the adopted asset is the local asset, no need to swap.\n address adopted = config.adopted;\n if (adopted == _asset) {\n return (_amount, adopted);\n }\n\n return _swapAssetOut(_key, _asset, adopted, _amount, _maxIn);\n }\n\n /**\n * @notice Swaps assetIn to assetOut using the stored stable swap or internal swap pool.\n * @dev Will not swap if the asset passed in is the adopted asset\n * @param _key - The hash of canonical id and domain.\n * @param _assetIn - The address of the from asset\n * @param _assetOut - The address of the to asset\n * @param _amount - The amount of the local asset to swap\n * @param _minOut - The minimum amount of `_assetOut` the user will accept\n * @return The amount of asset received\n * @return The address of asset received\n */\n function _swapAsset(\n bytes32 _key,\n address _assetIn,\n address _assetOut,\n uint256 _amount,\n uint256 _minOut\n ) internal returns (uint256, address) {\n AppStorage storage s = LibConnextStorage.connextStorage();\n\n // Retrieve internal swap pool reference.\n SwapUtils.Swap storage ipool = s.swapStorages[_key];\n\n if (ipool.exists()) {\n // Swap via the internal pool.\n return (\n ipool.swapInternal(\n getTokenIndexFromStableSwapPool(_key, _assetIn),\n getTokenIndexFromStableSwapPool(_key, _assetOut),\n _amount,\n _minOut\n ),\n _assetOut\n );\n } else {\n // Otherwise, swap via external stableswap pool.\n IStableSwap pool = IStableSwap(getConfig(_key).adoptedToLocalExternalPools);\n\n IERC20Metadata assetIn = IERC20Metadata(_assetIn);\n\n assetIn.safeApprove(address(pool), 0);\n assetIn.safeIncreaseAllowance(address(pool), _amount);\n\n // NOTE: If pool is not registered here, then this call will revert.\n return (\n pool.swapExact(_amount, _assetIn, _assetOut, _minOut, block.timestamp + Constants.DEFAULT_DEADLINE_EXTENSION),\n _assetOut\n );\n }\n }\n\n /**\n * @notice Swaps assetIn to assetOut using the stored stable swap or internal swap pool.\n * @param _key - The hash of the canonical id and domain.\n * @param _assetIn - The address of the from asset.\n * @param _assetOut - The address of the to asset.\n * @param _amountOut - The amount of the _assetOut to swap.\n * @param _maxIn - The most you will supply to the swap.\n * @return amountIn The amount of assetIn. Will be 0 if the swap was unsuccessful (slippage\n * too high).\n * @return assetOut The address of asset received.\n */\n function _swapAssetOut(\n bytes32 _key,\n address _assetIn,\n address _assetOut,\n uint256 _amountOut,\n uint256 _maxIn\n ) internal returns (uint256, address) {\n AppStorage storage s = LibConnextStorage.connextStorage();\n\n // Retrieve internal swap pool reference. If it doesn't exist, we'll resort to using an\n // external stableswap below.\n SwapUtils.Swap storage ipool = s.swapStorages[_key];\n\n // Swap the asset to the proper local asset.\n // NOTE: IFF slippage was too high to perform swap in either case: success = false, amountIn = 0\n if (ipool.exists()) {\n // Swap via the internal pool.\n return (\n ipool.swapInternalOut(\n getTokenIndexFromStableSwapPool(_key, _assetIn),\n getTokenIndexFromStableSwapPool(_key, _assetOut),\n _amountOut,\n _maxIn\n ),\n _assetOut\n );\n } else {\n // Otherwise, swap via external stableswap pool.\n // NOTE: This call will revert if the external stableswap pool doesn't exist.\n IStableSwap pool = IStableSwap(getConfig(_key).adoptedToLocalExternalPools);\n address poolAddress = address(pool);\n\n // Perform the swap.\n // Edge case with some tokens: Example USDT in ETH Mainnet, after the backUnbacked call\n // there could be a remaining allowance if not the whole amount is pulled by aave.\n // Later, if we try to increase the allowance it will fail. USDT demands if allowance\n // is not 0, it has to be set to 0 first.\n // Example: https://github.com/aave/aave-v3-periphery/blob/ca184e5278bcbc10d28c3dbbc604041d7cfac50b/contracts/adapters/paraswap/ParaSwapRepayAdapter.sol#L138-L140\n IERC20Metadata assetIn = IERC20Metadata(_assetIn);\n\n assetIn.safeApprove(poolAddress, 0);\n assetIn.safeIncreaseAllowance(poolAddress, _maxIn);\n\n uint256 out = pool.swapExactOut(\n _amountOut,\n _assetIn,\n _assetOut,\n _maxIn,\n block.timestamp + Constants.DEFAULT_DEADLINE_EXTENSION\n );\n\n // Reset allowance\n assetIn.safeApprove(poolAddress, 0);\n return (out, _assetOut);\n }\n }\n\n /**\n * @notice Calculate amount of tokens you receive on a local bridge asset for the adopted asset\n * using the stored stable swap\n * @dev Will not use the stored stable swap if the asset passed in is the local asset\n * @param _key - The hash of the canonical id and domain\n * @param _asset - The address of the local asset to swap into the local asset\n * @param _amount - The amount of the local asset to swap\n * @return The amount of local asset received from swap\n * @return The address of asset received post-swap\n */\n function calculateSwapFromLocalAssetIfNeeded(\n bytes32 _key,\n address _asset,\n uint256 _amount\n ) internal view returns (uint256, address) {\n AppStorage storage s = LibConnextStorage.connextStorage();\n\n // If the adopted asset is the local asset, no need to swap.\n TokenConfig storage config = getConfig(_key);\n address adopted = config.adopted;\n if (adopted == _asset) {\n return (_amount, adopted);\n }\n\n SwapUtils.Swap storage ipool = s.swapStorages[_key];\n\n // Calculate the swap using the appropriate pool.\n if (ipool.exists()) {\n // Calculate with internal swap pool.\n uint8 tokenIndexIn = getTokenIndexFromStableSwapPool(_key, _asset);\n uint8 tokenIndexOut = getTokenIndexFromStableSwapPool(_key, adopted);\n return (ipool.calculateSwap(tokenIndexIn, tokenIndexOut, _amount), adopted);\n } else {\n // Otherwise, try to calculate with external pool.\n IStableSwap pool = IStableSwap(config.adoptedToLocalExternalPools);\n // NOTE: This call will revert if no external pool exists.\n return (pool.calculateSwapFromAddress(_asset, adopted, _amount), adopted);\n }\n }\n\n /**\n * @notice Calculate amount of tokens you receive of a local bridge asset for the adopted asset\n * using the stored stable swap\n * @dev Will not use the stored stable swap if the asset passed in is the local asset\n * @param _asset - The address of the asset to swap into the local asset\n * @param _amount - The amount of the asset to swap\n * @return The amount of local asset received from swap\n * @return The address of asset received post-swap\n */\n function calculateSwapToLocalAssetIfNeeded(\n bytes32 _key,\n address _asset,\n address _local,\n uint256 _amount\n ) internal view returns (uint256, address) {\n AppStorage storage s = LibConnextStorage.connextStorage();\n\n // If the asset is the local asset, no swap needed\n if (_asset == _local) {\n return (_amount, _local);\n }\n\n SwapUtils.Swap storage ipool = s.swapStorages[_key];\n\n // Calculate the swap using the appropriate pool.\n if (ipool.exists()) {\n // if internal swap pool exists\n uint8 tokenIndexIn = getTokenIndexFromStableSwapPool(_key, _asset);\n uint8 tokenIndexOut = getTokenIndexFromStableSwapPool(_key, _local);\n return (ipool.calculateSwap(tokenIndexIn, tokenIndexOut, _amount), _local);\n } else {\n IStableSwap pool = IStableSwap(getConfig(_key).adoptedToLocalExternalPools);\n\n return (pool.calculateSwapFromAddress(_asset, _local, _amount), _local);\n }\n }\n\n // ============ Internal: Token ID Helpers ============\n\n /**\n * @notice Gets the canonical information for a given candidate.\n * @dev First checks the `address(0)` convention, then checks if the asset given is the\n * adopted asset, then calculates the local address.\n * @return TokenId The canonical token ID information for the given