From 2c9704a9984b062d727044177c4093ce8137d569 Mon Sep 17 00:00:00 2001 From: Kaze Date: Tue, 30 Sep 2025 16:34:47 +0900 Subject: [PATCH] feat: pull in required vendor dependencies --- .gitmodules | 9 + foundry.lock | 6 + lib/cow | 1 + lib/euler-vault-kit | 1 + lib/evc | 1 + lib/forge-std | 2 +- remappings.txt | 4 + src/vendor/interfaces/IERC20.sol | 112 +++++ .../interfaces/IEthereumVaultConnector.sol | 439 ++++++++++++++++++ src/vendor/interfaces/IGPv2Authentication.sol | 16 + src/vendor/interfaces/IGPv2Settlement.sol | 71 +++ test/helpers/GPv2OrderHelper.sol | 37 ++ test/helpers/SignerECDSA.sol | 106 +++++ 13 files changed, 804 insertions(+), 1 deletion(-) create mode 160000 lib/cow create mode 160000 lib/euler-vault-kit create mode 160000 lib/evc create mode 100644 remappings.txt create mode 100644 src/vendor/interfaces/IERC20.sol create mode 100644 src/vendor/interfaces/IEthereumVaultConnector.sol create mode 100644 src/vendor/interfaces/IGPv2Authentication.sol create mode 100644 src/vendor/interfaces/IGPv2Settlement.sol create mode 100644 test/helpers/GPv2OrderHelper.sol create mode 100644 test/helpers/SignerECDSA.sol diff --git a/.gitmodules b/.gitmodules index 888d42d..837af89 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,12 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/cow"] + path = lib/cow + url = https://github.com/cowprotocol/contracts +[submodule "lib/euler-vault-kit"] + path = lib/euler-vault-kit + url = https://github.com/euler-xyz/euler-vault-kit +[submodule "lib/evc"] + path = lib/evc + url = https://github.com/euler-xyz/ethereum-vault-connector diff --git a/foundry.lock b/foundry.lock index 5643642..775a9f7 100644 --- a/foundry.lock +++ b/foundry.lock @@ -1,4 +1,10 @@ { + "lib/cow": { + "rev": "39d7f4d68e37d14adeaf3c0caca30ea5c1a2fad9" + }, + "lib/euler-vault-kit": { + "rev": "5b98b42048ba11ae82fb62dfec06d1010c8e41e6" + }, "lib/forge-std": { "tag": { "name": "v1.10.0", diff --git a/lib/cow b/lib/cow new file mode 160000 index 0000000..1e8127f --- /dev/null +++ b/lib/cow @@ -0,0 +1 @@ +Subproject commit 1e8127f476f8fef7758cf25033a0010d325dba8d diff --git a/lib/euler-vault-kit b/lib/euler-vault-kit new file mode 160000 index 0000000..5b98b42 --- /dev/null +++ b/lib/euler-vault-kit @@ -0,0 +1 @@ +Subproject commit 5b98b42048ba11ae82fb62dfec06d1010c8e41e6 diff --git a/lib/evc b/lib/evc new file mode 160000 index 0000000..34bb788 --- /dev/null +++ b/lib/evc @@ -0,0 +1 @@ +Subproject commit 34bb788288a0eb0fbba06bc370cb8ca3dd42614e diff --git a/lib/forge-std b/lib/forge-std index 8bbcf6e..0768d9c 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 8bbcf6e3f8f62f419e5429a0bd89331c85c37824 +Subproject commit 0768d9c08c085c79bb31d88683a78770764fec49 diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..acecbea --- /dev/null +++ b/remappings.txt @@ -0,0 +1,4 @@ +cow/=lib/cow/src/contracts +evc/=lib/euler-vault-kit/lib/ethereum-vault-connector/src/ +ethereum-vault-connector/=lib/euler-vault-kit/lib/ethereum-vault-connector/src/ +openzeppelin/=lib/euler-vault-kit/lib/ethereum-vault-connector/lib/openzeppelin-contracts/contracts/ diff --git a/src/vendor/interfaces/IERC20.sol b/src/vendor/interfaces/IERC20.sol new file mode 100644 index 0000000..9b77d2f --- /dev/null +++ b/src/vendor/interfaces/IERC20.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT + +// Vendored from OpenZeppelin contracts with minor modifications: +// - Modified Solidity version +// - Formatted code +// - Added `name`, `symbol` and `decimals` function declarations +// + +pragma solidity ^0.8; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the number of decimals the token uses. + */ + function decimals() external view returns (uint8); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer( + address recipient, + uint256 amount + ) 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 `amount` 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 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @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 + ); +} diff --git a/src/vendor/interfaces/IEthereumVaultConnector.sol b/src/vendor/interfaces/IEthereumVaultConnector.sol new file mode 100644 index 0000000..a77c4e9 --- /dev/null +++ b/src/vendor/interfaces/IEthereumVaultConnector.sol @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +// Vendored from Euler's Ethereum Vault Connector repo with minor modifications: +// - Modified Solidity version +// - Formatted code +// + +pragma solidity ^0.8; + +/// @title IEVC +/// @custom:security-contact security@euler.xyz +/// @author Euler Labs (https://www.eulerlabs.com/) +/// @notice This interface defines the methods for the Ethereum Vault Connector. +interface IEVC { + /// @notice A struct representing a batch item. + /// @dev Each batch item represents a single operation to be performed within a checks deferred context. + struct BatchItem { + /// @notice The target contract to be called. + address targetContract; + /// @notice The account on behalf of which the operation is to be performed. msg.sender must be authorized to + /// act on behalf of this account. Must be address(0) if the target contract is the EVC itself. + address onBehalfOfAccount; + /// @notice The amount of value to be forwarded with the call. If the value is type(uint256).max, the whole + /// balance of the EVC contract will be forwarded. Must be 0 if the target contract is the EVC itself. + uint256 value; + /// @notice The encoded data which is called on the target contract. + bytes data; + } + + /// @notice A struct representing the result of a batch item operation. + /// @dev Used only for simulation purposes. + struct BatchItemResult { + /// @notice A boolean indicating whether the operation was successful. + bool success; + /// @notice The result of the operation. + bytes result; + } + + /// @notice A struct representing the result of the account or vault status check. + /// @dev Used only for simulation purposes. + struct StatusCheckResult { + /// @notice The address of the account or vault for which the check was performed. + address checkedAddress; + /// @notice A boolean indicating whether the status of the account or vault is valid. + bool isValid; + /// @notice The result of the check. + bytes result; + } + + /// @notice Returns current raw execution context. + /// @dev When checks in progress, on behalf of account is always address(0). + /// @return context Current raw execution context. + function getRawExecutionContext() external view returns (uint256 context); + + /// @notice Returns an account on behalf of which the operation is being executed at the moment and whether the + /// controllerToCheck is an enabled controller for that account. + /// @dev This function should only be used by external smart contracts if msg.sender is the EVC. Otherwise, the + /// account address returned must not be trusted. + /// @dev When checks in progress, on behalf of account is always address(0). When address is zero, the function + /// reverts to protect the consumer from ever relying on the on behalf of account address which is in its default + /// state. + /// @param controllerToCheck The address of the controller for which it is checked whether it is an enabled + /// controller for the account on behalf of which the operation is being executed at the moment. + /// @return onBehalfOfAccount An account that has been authenticated and on behalf of which the operation is being + /// executed at the moment. + /// @return controllerEnabled A boolean value that indicates whether controllerToCheck is an enabled controller for + /// the account on behalf of which the operation is being executed at the moment. Always false if controllerToCheck + /// is address(0). + function getCurrentOnBehalfOfAccount(address controllerToCheck) + external + view + returns (address onBehalfOfAccount, bool controllerEnabled); + + /// @notice Checks if checks are deferred. + /// @return A boolean indicating whether checks are deferred. + function areChecksDeferred() external view returns (bool); + + /// @notice Checks if checks are in progress. + /// @return A boolean indicating whether checks are in progress. + function areChecksInProgress() external view returns (bool); + + /// @notice Checks if control collateral is in progress. + /// @return A boolean indicating whether control collateral is in progress. + function isControlCollateralInProgress() external view returns (bool); + + /// @notice Checks if an operator is authenticated. + /// @return A boolean indicating whether an operator is authenticated. + function isOperatorAuthenticated() external view returns (bool); + + /// @notice Checks if a simulation is in progress. + /// @return A boolean indicating whether a simulation is in progress. + function isSimulationInProgress() external view returns (bool); + + /// @notice Checks whether the specified account and the other account have the same owner. + /// @dev The function is used to check whether one account is authorized to perform operations on behalf of the + /// other. Accounts are considered to have a common owner if they share the first 19 bytes of their address. + /// @param account The address of the account that is being checked. + /// @param otherAccount The address of the other account that is being checked. + /// @return A boolean flag that indicates whether the accounts have the same owner. + function haveCommonOwner(address account, address otherAccount) external pure returns (bool); + + /// @notice Returns the address prefix of the specified account. + /// @dev The address prefix is the first 19 bytes of the account address. + /// @param account The address of the account whose address prefix is being retrieved. + /// @return A bytes19 value that represents the address prefix of the account. + function getAddressPrefix(address account) external pure returns (bytes19); + + /// @notice Returns the owner for the specified account. + /// @dev The function returns address(0) if the owner is not registered. Registration of the owner happens on the + /// initial + /// interaction with the EVC that requires authentication of an owner. + /// @param account The address of the account whose owner is being retrieved. + /// @return owner The address of the account owner. An account owner is an EOA/smart contract which address matches + /// the first 19 bytes of the account address. + function getAccountOwner(address account) external view returns (address); + + /// @notice Checks if lockdown mode is enabled for a given address prefix. + /// @param addressPrefix The address prefix to check for lockdown mode status. + /// @return A boolean indicating whether lockdown mode is enabled. + function isLockdownMode(bytes19 addressPrefix) external view returns (bool); + + /// @notice Checks if permit functionality is disabled for a given address prefix. + /// @param addressPrefix The address prefix to check for permit functionality status. + /// @return A boolean indicating whether permit functionality is disabled. + function isPermitDisabledMode(bytes19 addressPrefix) external view returns (bool); + + /// @notice Returns the current nonce for a given address prefix and nonce namespace. + /// @dev Each nonce namespace provides 256 bit nonce that has to be used sequentially. There's no requirement to use + /// all the nonces for a given nonce namespace before moving to the next one which allows to use permit messages in + /// a non-sequential manner. + /// @param addressPrefix The address prefix for which the nonce is being retrieved. + /// @param nonceNamespace The nonce namespace for which the nonce is being retrieved. + /// @return nonce The current nonce for the given address prefix and nonce namespace. + function getNonce(bytes19 addressPrefix, uint256 nonceNamespace) external view returns (uint256 nonce); + + /// @notice Returns the bit field for a given address prefix and operator. + /// @dev The bit field is used to store information about authorized operators for a given address prefix. Each bit + /// in the bit field corresponds to one account belonging to the same owner. If the bit is set, the operator is + /// authorized for the account. + /// @param addressPrefix The address prefix for which the bit field is being retrieved. + /// @param operator The address of the operator for which the bit field is being retrieved. + /// @return operatorBitField The bit field for the given address prefix and operator. The bit field defines which + /// accounts the operator is authorized for. It is a 256-position binary array like 0...010...0, marking the account + /// positionally in a uint256. The position in the bit field corresponds to the account ID (0-255), where 0 is the + /// owner account's ID. + function getOperator(bytes19 addressPrefix, address operator) external view returns (uint256 operatorBitField); + + /// @notice Returns whether a given operator has been authorized for a given account. + /// @param account The address of the account whose operator is being checked. + /// @param operator The address of the operator that is being checked. + /// @return authorized A boolean value that indicates whether the operator is authorized for the account. + function isAccountOperatorAuthorized(address account, address operator) external view returns (bool authorized); + + /// @notice Enables or disables lockdown mode for a given address prefix. + /// @dev This function can only be called by the owner of the address prefix. To disable this mode, the EVC + /// must be called directly. It is not possible to disable this mode by using checks-deferrable call or + /// permit message. + /// @param addressPrefix The address prefix for which the lockdown mode is being set. + /// @param enabled A boolean indicating whether to enable or disable lockdown mode. + function setLockdownMode(bytes19 addressPrefix, bool enabled) external payable; + + /// @notice Enables or disables permit functionality for a given address prefix. + /// @dev This function can only be called by the owner of the address prefix. To disable this mode, the EVC + /// must be called directly. It is not possible to disable this mode by using checks-deferrable call or (by + /// definition) permit message. To support permit functionality by default, note that the logic was inverted here. To + /// disable the permit functionality, one must pass true as the second argument. To enable the permit + /// functionality, one must pass false as the second argument. + /// @param addressPrefix The address prefix for which the permit functionality is being set. + /// @param enabled A boolean indicating whether to enable or disable the disable-permit mode. + function setPermitDisabledMode(bytes19 addressPrefix, bool enabled) external payable; + + /// @notice Sets the nonce for a given address prefix and nonce namespace. + /// @dev This function can only be called by the owner of the address prefix. Each nonce namespace provides a 256 + /// bit nonce that has to be used sequentially. There's no requirement to use all the nonces for a given nonce + /// namespace before moving to the next one which allows the use of permit messages in a non-sequential manner. To + /// invalidate signed permit messages, set the nonce for a given nonce namespace accordingly. To invalidate all the + /// permit messages for a given nonce namespace, set the nonce to type(uint).max. + /// @param addressPrefix The address prefix for which the nonce is being set. + /// @param nonceNamespace The nonce namespace for which the nonce is being set. + /// @param nonce The new nonce for the given address prefix and nonce namespace. + function setNonce(bytes19 addressPrefix, uint256 nonceNamespace, uint256 nonce) external payable; + + /// @notice Sets the bit field for a given address prefix and operator. + /// @dev This function can only be called by the owner of the address prefix. Each bit in the bit field corresponds + /// to one account belonging to the same owner. If the bit is set, the operator is authorized for the account. + /// @param addressPrefix The address prefix for which the bit field is being set. + /// @param operator The address of the operator for which the bit field is being set. Can neither be the EVC address + /// nor an address belonging to the same address prefix. + /// @param operatorBitField The new bit field for the given address prefix and operator. Reverts if the provided + /// value is equal to the currently stored value. + function setOperator(bytes19 addressPrefix, address operator, uint256 operatorBitField) external payable; + + /// @notice Authorizes or deauthorizes an operator for the account. + /// @dev Only the owner or authorized operator of the account can call this function. An operator is an address that + /// can perform actions for an account on behalf of the owner. If it's an operator calling this function, it can + /// only deauthorize itself. + /// @param account The address of the account whose operator is being set or unset. + /// @param operator The address of the operator that is being installed or uninstalled. Can neither be the EVC + /// address nor an address belonging to the same owner as the account. + /// @param authorized A boolean value that indicates whether the operator is being authorized or deauthorized. + /// Reverts if the provided value is equal to the currently stored value. + function setAccountOperator(address account, address operator, bool authorized) external payable; + + /// @notice Returns an array of collaterals enabled for an account. + /// @dev A collateral is a vault for which an account's balances are under the control of the currently enabled + /// controller vault. + /// @param account The address of the account whose collaterals are being queried. + /// @return An array of addresses that are enabled collaterals for the account. + function getCollaterals(address account) external view returns (address[] memory); + + /// @notice Returns whether a collateral is enabled for an account. + /// @dev A collateral is a vault for which account's balances are under the control of the currently enabled + /// controller vault. + /// @param account The address of the account that is being checked. + /// @param vault The address of the collateral that is being checked. + /// @return A boolean value that indicates whether the vault is an enabled collateral for the account or not. + function isCollateralEnabled(address account, address vault) external view returns (bool); + + /// @notice Enables a collateral for an account. + /// @dev A collaterals is a vault for which account's balances are under the control of the currently enabled + /// controller vault. Only the owner or an operator of the account can call this function. Unless it's a duplicate, + /// the collateral is added to the end of the array. There can be at most 10 unique collaterals enabled at a time. + /// Account status checks are performed. + /// @param account The account address for which the collateral is being enabled. + /// @param vault The address being enabled as a collateral. + function enableCollateral(address account, address vault) external payable; + + /// @notice Disables a collateral for an account. + /// @dev This function does not preserve the order of collaterals in the array obtained using the getCollaterals + /// function; the order may change. A collateral is a vault for which account’s balances are under the control of + /// the currently enabled controller vault. Only the owner or an operator of the account can call this function. + /// Disabling a collateral might change the order of collaterals in the array obtained using getCollaterals + /// function. Account status checks are performed. + /// @param account The account address for which the collateral is being disabled. + /// @param vault The address of a collateral being disabled. + function disableCollateral(address account, address vault) external payable; + + /// @notice Swaps the position of two collaterals so that they appear switched in the array of collaterals for a + /// given account obtained by calling getCollaterals function. + /// @dev A collateral is a vault for which account’s balances are under the control of the currently enabled + /// controller vault. Only the owner or an operator of the account can call this function. The order of collaterals + /// can be changed by specifying the indices of the two collaterals to be swapped. Indices are zero-based and must + /// be in the range of 0 to the number of collaterals minus 1. index1 must be lower than index2. Account status + /// checks are performed. + /// @param account The address of the account for which the collaterals are being reordered. + /// @param index1 The index of the first collateral to be swapped. + /// @param index2 The index of the second collateral to be swapped. + function reorderCollaterals(address account, uint8 index1, uint8 index2) external payable; + + /// @notice Returns an array of enabled controllers for an account. + /// @dev A controller is a vault that has been chosen for an account to have special control over the account's + /// balances in enabled collaterals vaults. A user can have multiple controllers during a call execution, but at + /// most one can be selected when the account status check is performed. + /// @param account The address of the account whose controllers are being queried. + /// @return An array of addresses that are the enabled controllers for the account. + function getControllers(address account) external view returns (address[] memory); + + /// @notice Returns whether a controller is enabled for an account. + /// @dev A controller is a vault that has been chosen for an account to have special control over account’s + /// balances in the enabled collaterals vaults. + /// @param account The address of the account that is being checked. + /// @param vault The address of the controller that is being checked. + /// @return A boolean value that indicates whether the vault is enabled controller for the account or not. + function isControllerEnabled(address account, address vault) external view returns (bool); + + /// @notice Enables a controller for an account. + /// @dev A controller is a vault that has been chosen for an account to have special control over account’s + /// balances in the enabled collaterals vaults. Only the owner or an operator of the account can call this function. + /// Unless it's a duplicate, the controller is added to the end of the array. Transiently, there can be at most 10 + /// unique controllers enabled at a time, but at most one can be enabled after the outermost checks-deferrable + /// call concludes. Account status checks are performed. + /// @param account The address for which the controller is being enabled. + /// @param vault The address of the controller being enabled. + function enableController(address account, address vault) external payable; + + /// @notice Disables a controller for an account. + /// @dev A controller is a vault that has been chosen for an account to have special control over account’s + /// balances in the enabled collaterals vaults. Only the vault itself can call this function. Disabling a controller + /// might change the order of controllers in the array obtained using getControllers function. Account status checks + /// are performed. + /// @param account The address for which the calling controller is being disabled. + function disableController(address account) external payable; + + /// @notice Executes signed arbitrary data by self-calling into the EVC. + /// @dev Low-level call function is used to execute the arbitrary data signed by the owner or the operator on the + /// EVC contract. During that call, EVC becomes msg.sender. + /// @param signer The address signing the permit message (ECDSA) or verifying the permit message signature + /// (ERC-1271). It's also the owner or the operator of all the accounts for which authentication will be needed + /// during the execution of the arbitrary data call. + /// @param sender The address of the msg.sender which is expected to execute the data signed by the signer. If + /// address(0) is passed, the msg.sender is ignored. + /// @param nonceNamespace The nonce namespace for which the nonce is being used. + /// @param nonce The nonce for the given account and nonce namespace. A valid nonce value is considered to be the + /// value currently stored and can take any value between 0 and type(uint256).max - 1. + /// @param deadline The timestamp after which the permit is considered expired. + /// @param value The amount of value to be forwarded with the call. If the value is type(uint256).max, the whole + /// balance of the EVC contract will be forwarded. + /// @param data The encoded data which is self-called on the EVC contract. + /// @param signature The signature of the data signed by the signer. + function permit( + address signer, + address sender, + uint256 nonceNamespace, + uint256 nonce, + uint256 deadline, + uint256 value, + bytes calldata data, + bytes calldata signature + ) external payable; + + /// @notice Calls into a target contract as per data encoded. + /// @dev This function defers the account and vault status checks (it's a checks-deferrable call). If the outermost + /// call ends, the account and vault status checks are performed. + /// @dev This function can be used to interact with any contract while checks are deferred. If the target contract + /// is msg.sender, msg.sender is called back with the calldata provided and the context set up according to the + /// account provided. If the target contract is not msg.sender, only the owner or the operator of the account + /// provided can call this function. + /// @dev This function can be used to recover the remaining value from the EVC contract. + /// @param targetContract The address of the contract to be called. + /// @param onBehalfOfAccount If the target contract is msg.sender, the address of the account which will be set + /// in the context. It assumes msg.sender has authenticated the account themselves. If the target contract is + /// not msg.sender, the address of the account for which it is checked whether msg.sender is authorized to act + /// on behalf of. + /// @param value The amount of value to be forwarded with the call. If the value is type(uint256).max, the whole + /// balance of the EVC contract will be forwarded. + /// @param data The encoded data which is called on the target contract. + /// @return result The result of the call. + function call( + address targetContract, + address onBehalfOfAccount, + uint256 value, + bytes calldata data + ) external payable returns (bytes memory result); + + /// @notice For a given account, calls into one of the enabled collateral vaults from the currently enabled + /// controller vault as per data encoded. + /// @dev This function defers the account and vault status checks (it's a checks-deferrable call). If the outermost + /// call ends, the account and vault status checks are performed. + /// @dev This function can be used to interact with any contract while checks are deferred as long as the contract + /// is enabled as a collateral of the account and the msg.sender is the only enabled controller of the account. + /// @param targetCollateral The collateral address to be called. + /// @param onBehalfOfAccount The address of the account for which it is checked whether msg.sender is authorized to + /// act on behalf. + /// @param value The amount of value to be forwarded with the call. If the value is type(uint256).max, the whole + /// balance of the EVC contract will be forwarded. + /// @param data The encoded data which is called on the target collateral. + /// @return result The result of the call. + function controlCollateral( + address targetCollateral, + address onBehalfOfAccount, + uint256 value, + bytes calldata data + ) external payable returns (bytes memory result); + + /// @notice Executes multiple calls into the target contracts while checks deferred as per batch items provided. + /// @dev This function defers the account and vault status checks (it's a checks-deferrable call). If the outermost + /// call ends, the account and vault status checks are performed. + /// @dev The authentication rules for each batch item are the same as for the call function. + /// @param items An array of batch items to be executed. + function batch(BatchItem[] calldata items) external payable; + + /// @notice Executes multiple calls into the target contracts while checks deferred as per batch items provided. + /// @dev This function always reverts as it's only used for simulation purposes. This function cannot be called + /// within a checks-deferrable call. + /// @param items An array of batch items to be executed. + function batchRevert(BatchItem[] calldata items) external payable; + + /// @notice Executes multiple calls into the target contracts while checks deferred as per batch items provided. + /// @dev This function does not modify state and should only be used for simulation purposes. This function cannot + /// be called within a checks-deferrable call. + /// @param items An array of batch items to be executed. + /// @return batchItemsResult An array of batch item results for each item. + /// @return accountsStatusCheckResult An array of account status check results for each account. + /// @return vaultsStatusCheckResult An array of vault status check results for each vault. + function batchSimulation(BatchItem[] calldata items) + external + payable + returns ( + BatchItemResult[] memory batchItemsResult, + StatusCheckResult[] memory accountsStatusCheckResult, + StatusCheckResult[] memory vaultsStatusCheckResult + ); + + /// @notice Retrieves the timestamp of the last successful account status check performed for a specific account. + /// @dev This function reverts if the checks are in progress. + /// @dev The account status check is considered to be successful if it calls into the selected controller vault and + /// obtains expected magic value. This timestamp does not change if the account status is considered valid when no + /// controller enabled. When consuming, one might need to ensure that the account status check is not deferred at + /// the moment. + /// @param account The address of the account for which the last status check timestamp is being queried. + /// @return The timestamp of the last status check as a uint256. + function getLastAccountStatusCheckTimestamp(address account) external view returns (uint256); + + /// @notice Checks whether the status check is deferred for a given account. + /// @dev This function reverts if the checks are in progress. + /// @param account The address of the account for which it is checked whether the status check is deferred. + /// @return A boolean flag that indicates whether the status check is deferred or not. + function isAccountStatusCheckDeferred(address account) external view returns (bool); + + /// @notice Checks the status of an account and reverts if it is not valid. + /// @dev If checks deferred, the account is added to the set of accounts to be checked at the end of the outermost + /// checks-deferrable call. There can be at most 10 unique accounts added to the set at a time. Account status + /// check is performed by calling into the selected controller vault and passing the array of currently enabled + /// collaterals. If controller is not selected, the account is always considered valid. + /// @param account The address of the account to be checked. + function requireAccountStatusCheck(address account) external payable; + + /// @notice Forgives previously deferred account status check. + /// @dev Account address is removed from the set of addresses for which status checks are deferred. This function + /// can only be called by the currently enabled controller of a given account. Depending on the vault + /// implementation, may be needed in the liquidation flow. + /// @param account The address of the account for which the status check is forgiven. + function forgiveAccountStatusCheck(address account) external payable; + + /// @notice Checks whether the status check is deferred for a given vault. + /// @dev This function reverts if the checks are in progress. + /// @param vault The address of the vault for which it is checked whether the status check is deferred. + /// @return A boolean flag that indicates whether the status check is deferred or not. + function isVaultStatusCheckDeferred(address vault) external view returns (bool); + + /// @notice Checks the status of a vault and reverts if it is not valid. + /// @dev If checks deferred, the vault is added to the set of vaults to be checked at the end of the outermost + /// checks-deferrable call. There can be at most 10 unique vaults added to the set at a time. This function can + /// only be called by the vault itself. + function requireVaultStatusCheck() external payable; + + /// @notice Forgives previously deferred vault status check. + /// @dev Vault address is removed from the set of addresses for which status checks are deferred. This function can + /// only be called by the vault itself. + function forgiveVaultStatusCheck() external payable; + + /// @notice Checks the status of an account and a vault and reverts if it is not valid. + /// @dev If checks deferred, the account and the vault are added to the respective sets of accounts and vaults to be + /// checked at the end of the outermost checks-deferrable call. Account status check is performed by calling into + /// selected controller vault and passing the array of currently enabled collaterals. If controller is not selected, + /// the account is always considered valid. This function can only be called by the vault itself. + /// @param account The address of the account to be checked. + function requireAccountAndVaultStatusCheck(address account) external payable; +} diff --git a/src/vendor/interfaces/IGPv2Authentication.sol b/src/vendor/interfaces/IGPv2Authentication.sol new file mode 100644 index 0000000..92a7e38 --- /dev/null +++ b/src/vendor/interfaces/IGPv2Authentication.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity ^0.8; + +// Vendored from CoW Protocol settlement contract repo with minor modifications: +// - Modified Solidity version +// - Formatted code +// + +/// @title Gnosis Protocol v2 Authentication Interface +/// @author Gnosis Developers +interface IGPv2Authentication { + /// @dev determines whether the provided address is an authenticated solver. + /// @param prospectiveSolver the address of prospective solver. + /// @return true when prospectiveSolver is an authenticated solver, otherwise false. + function isSolver(address prospectiveSolver) external view returns (bool); +} diff --git a/src/vendor/interfaces/IGPv2Settlement.sol b/src/vendor/interfaces/IGPv2Settlement.sol new file mode 100644 index 0000000..c1db9e5 --- /dev/null +++ b/src/vendor/interfaces/IGPv2Settlement.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: UNLICENSED +// !! THIS FILE WAS AUTOGENERATED BY abi-to-sol v0.8.0. SEE SOURCE BELOW. !! +pragma solidity ^0.8; +pragma experimental ABIEncoderV2; + +interface IGPv2Settlement { + + + +receive () external payable; +function authenticator( ) external view returns (address ) ; +function domainSeparator( ) external view returns (bytes32 ) ; +function filledAmount( bytes memory ) external view returns (uint256 ) ; +function freeFilledAmountStorage( bytes[] memory orderUids ) external ; +function freePreSignatureStorage( bytes[] memory orderUids ) external ; +function getStorageAt( uint256 offset,uint256 length ) external view returns (bytes memory ) ; +function invalidateOrder( bytes memory orderUid ) external ; +function preSignature( bytes memory ) external view returns (uint256 ) ; +function setPreSignature( bytes memory orderUid,bool signed ) external ; +function settle( address[] memory tokens,uint256[] memory clearingPrices,GPv2Trade.Data[] memory trades,GPv2Interaction.Data[][3] memory interactions ) external ; +function simulateDelegatecall( address targetContract,bytes memory calldataPayload ) external returns (bytes memory response) ; +function simulateDelegatecallInternal( address targetContract,bytes memory calldataPayload ) external returns (bytes memory response) ; +function swap( IVault.BatchSwapStep[] memory swaps,address[] memory tokens,GPv2Trade.Data memory trade ) external ; +function vault( ) external view returns (address ) ; +function vaultRelayer( ) external view returns (address ) ; +event Interaction( address indexed target,uint256 value,bytes4 selector ) ; +event OrderInvalidated( address indexed owner,bytes orderUid ) ; +event PreSignature( address indexed owner,bytes orderUid,bool signed ) ; +event Settlement( address indexed solver ) ; +event Trade( address indexed owner,address sellToken,address buyToken,uint256 sellAmount,uint256 buyAmount,uint256 feeAmount,bytes orderUid ) ; +} + +interface GPv2Trade { +struct Data { +uint256 sellTokenIndex; +uint256 buyTokenIndex; +address receiver; +uint256 sellAmount; +uint256 buyAmount; +uint32 validTo; +bytes32 appData; +uint256 feeAmount; +uint256 flags; +uint256 executedAmount; +bytes signature; +} +} + +interface GPv2Interaction { +struct Data { +address target; +uint256 value; +bytes callData; +} +} + +interface IVault { +struct BatchSwapStep { +bytes32 poolId; +uint256 assetInIndex; +uint256 assetOutIndex; +uint256 amount; +bytes userData; +} +} + + +// THIS FILE WAS AUTOGENERATED FROM THE FOLLOWING ABI JSON: +/* +[{"type":"constructor","inputs":[{"name":"authenticator_","type":"address","internalType":"contract GPv2Authentication"},{"name":"vault_","type":"address","internalType":"contract IVault"}],"stateMutability":"nonpayable"},{"type":"receive","stateMutability":"payable"},{"type":"function","name":"authenticator","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract GPv2Authentication"}],"stateMutability":"view"},{"type":"function","name":"domainSeparator","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"filledAmount","inputs":[{"name":"","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"freeFilledAmountStorage","inputs":[{"name":"orderUids","type":"bytes[]","internalType":"bytes[]"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"freePreSignatureStorage","inputs":[{"name":"orderUids","type":"bytes[]","internalType":"bytes[]"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"getStorageAt","inputs":[{"name":"offset","type":"uint256","internalType":"uint256"},{"name":"length","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bytes","internalType":"bytes"}],"stateMutability":"view"},{"type":"function","name":"invalidateOrder","inputs":[{"name":"orderUid","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"preSignature","inputs":[{"name":"","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"setPreSignature","inputs":[{"name":"orderUid","type":"bytes","internalType":"bytes"},{"name":"signed","type":"bool","internalType":"bool"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"settle","inputs":[{"name":"tokens","type":"address[]","internalType":"contract IERC20[]"},{"name":"clearingPrices","type":"uint256[]","internalType":"uint256[]"},{"name":"trades","type":"tuple[]","internalType":"struct GPv2Trade.Data[]","components":[{"name":"sellTokenIndex","type":"uint256","internalType":"uint256"},{"name":"buyTokenIndex","type":"uint256","internalType":"uint256"},{"name":"receiver","type":"address","internalType":"address"},{"name":"sellAmount","type":"uint256","internalType":"uint256"},{"name":"buyAmount","type":"uint256","internalType":"uint256"},{"name":"validTo","type":"uint32","internalType":"uint32"},{"name":"appData","type":"bytes32","internalType":"bytes32"},{"name":"feeAmount","type":"uint256","internalType":"uint256"},{"name":"flags","type":"uint256","internalType":"uint256"},{"name":"executedAmount","type":"uint256","internalType":"uint256"},{"name":"signature","type":"bytes","internalType":"bytes"}]},{"name":"interactions","type":"tuple[][3]","internalType":"struct GPv2Interaction.Data[][3]","components":[{"name":"target","type":"address","internalType":"address"},{"name":"value","type":"uint256","internalType":"uint256"},{"name":"callData","type":"bytes","internalType":"bytes"}]}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"simulateDelegatecall","inputs":[{"name":"targetContract","type":"address","internalType":"address"},{"name":"calldataPayload","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"response","type":"bytes","internalType":"bytes"}],"stateMutability":"nonpayable"},{"type":"function","name":"simulateDelegatecallInternal","inputs":[{"name":"targetContract","type":"address","internalType":"address"},{"name":"calldataPayload","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"response","type":"bytes","internalType":"bytes"}],"stateMutability":"nonpayable"},{"type":"function","name":"swap","inputs":[{"name":"swaps","type":"tuple[]","internalType":"struct IVault.BatchSwapStep[]","components":[{"name":"poolId","type":"bytes32","internalType":"bytes32"},{"name":"assetInIndex","type":"uint256","internalType":"uint256"},{"name":"assetOutIndex","type":"uint256","internalType":"uint256"},{"name":"amount","type":"uint256","internalType":"uint256"},{"name":"userData","type":"bytes","internalType":"bytes"}]},{"name":"tokens","type":"address[]","internalType":"contract IERC20[]"},{"name":"trade","type":"tuple","internalType":"struct GPv2Trade.Data","components":[{"name":"sellTokenIndex","type":"uint256","internalType":"uint256"},{"name":"buyTokenIndex","type":"uint256","internalType":"uint256"},{"name":"receiver","type":"address","internalType":"address"},{"name":"sellAmount","type":"uint256","internalType":"uint256"},{"name":"buyAmount","type":"uint256","internalType":"uint256"},{"name":"validTo","type":"uint32","internalType":"uint32"},{"name":"appData","type":"bytes32","internalType":"bytes32"},{"name":"feeAmount","type":"uint256","internalType":"uint256"},{"name":"flags","type":"uint256","internalType":"uint256"},{"name":"executedAmount","type":"uint256","internalType":"uint256"},{"name":"signature","type":"bytes","internalType":"bytes"}]}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"vault","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract IVault"}],"stateMutability":"view"},{"type":"function","name":"vaultRelayer","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract GPv2VaultRelayer"}],"stateMutability":"view"},{"type":"event","name":"Interaction","inputs":[{"name":"target","type":"address","indexed":true,"internalType":"address"},{"name":"value","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"selector","type":"bytes4","indexed":false,"internalType":"bytes4"}],"anonymous":false},{"type":"event","name":"OrderInvalidated","inputs":[{"name":"owner","type":"address","indexed":true,"internalType":"address"},{"name":"orderUid","type":"bytes","indexed":false,"internalType":"bytes"}],"anonymous":false},{"type":"event","name":"PreSignature","inputs":[{"name":"owner","type":"address","indexed":true,"internalType":"address"},{"name":"orderUid","type":"bytes","indexed":false,"internalType":"bytes"},{"name":"signed","type":"bool","indexed":false,"internalType":"bool"}],"anonymous":false},{"type":"event","name":"Settlement","inputs":[{"name":"solver","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Trade","inputs":[{"name":"owner","type":"address","indexed":true,"internalType":"address"},{"name":"sellToken","type":"address","indexed":false,"internalType":"contract IERC20"},{"name":"buyToken","type":"address","indexed":false,"internalType":"contract IERC20"},{"name":"sellAmount","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"buyAmount","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"feeAmount","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"orderUid","type":"bytes","indexed":false,"internalType":"bytes"}],"anonymous":false}] +*/ diff --git a/test/helpers/GPv2OrderHelper.sol b/test/helpers/GPv2OrderHelper.sol new file mode 100644 index 0000000..34c8f2d --- /dev/null +++ b/test/helpers/GPv2OrderHelper.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8; + +import {GPv2Signing} from "cow/mixins/GPv2Signing.sol"; +import {GPv2Order} from "cow/libraries/GPv2Order.sol"; +import {GPv2Trade, IERC20} from "cow/libraries/GPv2Trade.sol"; + +import {console} from "forge-std/Test.sol"; + +// Vendored and adapted from CoW Protocol contrats repo with minor modifications: +// - Use only `extractOrderUidParams` +// - Add `extractOrder` which basically does parameter shuffling +// - Modified Solidity version +// - Formatted code +// + +contract GPv2OrderHelper { + using GPv2Order for bytes; + + function extractOrderUidParams(bytes calldata orderUid) + external + pure + returns (bytes32 orderDigest, address owner, uint32 validTo) + { + return orderUid.extractOrderUidParams(); + } + + function extractOrder(GPv2Trade.Data calldata trade, IERC20[] calldata tokens) + external + pure + returns (GPv2Order.Data memory extractedOrder, GPv2Signing.Scheme scheme) + { + GPv2Order.Data memory order; + scheme = GPv2Trade.extractOrder(trade, tokens, order); + return (order, scheme); + } +} diff --git a/test/helpers/SignerECDSA.sol b/test/helpers/SignerECDSA.sol new file mode 100644 index 0000000..bbec68a --- /dev/null +++ b/test/helpers/SignerECDSA.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8; + +import "forge-std/Test.sol"; +import "ethereum-vault-connector/EthereumVaultConnector.sol"; + +import "openzeppelin/utils/cryptography/ECDSA.sol"; + +// Vendored and minimized from Euler's Ethereum Vault Connector repo with minor modifications: +// - Only used SignerECDSA (don't need any other other permit logic) +// - Modified Solidity version +// - Formatted code +// + +abstract contract EIP712 { + + bytes32 internal constant _TYPE_HASH = + keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); + + bytes32 internal immutable _hashedName; + string private _name; + string private _nameFallback; + + /** + * @dev Initializes the domain separator. + * + * The meaning of `name` is specified in + * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: + * + * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. + * + * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart + * contract upgrade]. + */ + constructor(string memory name) { + _name = name; + _hashedName = keccak256(bytes(name)); + } + + /** + * @dev Returns the domain separator for the current chain. + */ + function _domainSeparatorV4() internal view returns (bytes32) { + return _buildDomainSeparator(); + } + + function _buildDomainSeparator() internal view virtual returns (bytes32) { + return bytes32(0); + } + + /** + * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this + * function returns the hash of the fully encoded EIP712 message for this domain. + * + * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: + * + * ```solidity + * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( + * keccak256("Mail(address to,string contents)"), + * mailTo, + * keccak256(bytes(mailContents)) + * ))); + * address signer = ECDSA.recover(digest, signature); + * ``` + */ + function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { + return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash); + } +} + +contract SignerECDSA is EIP712, Test { + EthereumVaultConnector private immutable evc; + uint256 private privateKey; + + bytes32 internal constant PERMIT_TYPEHASH = keccak256( + "Permit(address signer,address sender,uint256 nonceNamespace,uint256 nonce,uint256 deadline,uint256 value,bytes data)" + ); + + constructor(EthereumVaultConnector _evc) EIP712(_evc.name()) { + evc = _evc; + } + + function setPrivateKey(uint256 _privateKey) external { + privateKey = _privateKey; + } + + function _buildDomainSeparator() internal view override returns (bytes32) { + return keccak256(abi.encode(_TYPE_HASH, _hashedName, block.chainid, address(evc))); + } + + function signPermit( + address signer, + address sender, + uint256 nonceNamespace, + uint256 nonce, + uint256 deadline, + uint256 value, + bytes calldata data + ) external view returns (bytes memory signature) { + bytes32 structHash = keccak256( + abi.encode(PERMIT_TYPEHASH, signer, sender, nonceNamespace, nonce, deadline, value, keccak256(data)) + ); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, _hashTypedDataV4(structHash)); + signature = abi.encodePacked(r, s, v); + } +}