diff --git a/src/contracts/GPv2Wrapper.sol b/src/contracts/GPv2Wrapper.sol new file mode 100644 index 00000000..d584a5c3 --- /dev/null +++ b/src/contracts/GPv2Wrapper.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity >=0.7.6 <0.9.0; +pragma abicoder v2; + +import "./interfaces/IGPv2Wrapper.sol"; + +/** + * @title A minimalist base that can be extended to safely implement a wrapper. It ensures the most important basic functions of a wrapper are fulfilled + */ +abstract contract GPv2Wrapper is IGPv2Wrapper { + IGPv2Settlement public immutable override UPSTREAM_SETTLEMENT; + GPv2Authentication public immutable override AUTHENTICATOR; + + constructor(address payable upstreamSettlement_) { + UPSTREAM_SETTLEMENT = IGPv2Settlement(upstreamSettlement_); + + // retrieve the authentication we are supposed to use from the settlement contract + AUTHENTICATOR = IGPv2Settlement(upstreamSettlement_).authenticator(); + } + + /** + * @inheritdoc IGPv2Wrapper + */ + function wrappedSettle( + IERC20[] calldata tokens, + uint256[] calldata clearingPrices, + GPv2Trade.Data[] calldata trades, + GPv2Interaction.Data[][3] calldata interactions, + bytes calldata wrapperData + ) external override { + // Revert if not a valid solver + if (!AUTHENTICATOR.isSolver(msg.sender)) { + revert("GPv2Wrapper: not a solver"); + } + + _wrap(tokens, clearingPrices, trades, interactions, wrapperData); + } + + /** + * @dev The logic for the wrapper. During this function, `_internalSettle` should be called + */ + function _wrap( + IERC20[] calldata tokens, + uint256[] calldata clearingPrices, + GPv2Trade.Data[] calldata trades, + GPv2Interaction.Data[][3] calldata interactions, + bytes calldata wrapperData + ) internal virtual; + + /** + * @dev Should be called from within (or otherwise as a result of) _wrap. Calls GPv2Settlement.settle(). + */ + function _internalSettle( + IERC20[] calldata tokens, + uint256[] calldata clearingPrices, + GPv2Trade.Data[] calldata trades, + GPv2Interaction.Data[][3] calldata interactions + ) internal { + UPSTREAM_SETTLEMENT.settle(tokens, clearingPrices, trades, interactions); + } +} diff --git a/src/contracts/interfaces/IGPv2Settlement.sol b/src/contracts/interfaces/IGPv2Settlement.sol new file mode 100644 index 00000000..a15976e1 --- /dev/null +++ b/src/contracts/interfaces/IGPv2Settlement.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +pragma solidity >=0.7.6 <0.9.0; +pragma abicoder v2; + +import "./IERC20.sol"; +import "./GPv2Authentication.sol"; +import "./IVault.sol"; +import "../libraries/GPv2Trade.sol"; +import "../libraries/GPv2Interaction.sol"; + +/// @title Gnosis Protocol v2 Settlement Interface +/// @author Gnosis Developers +interface IGPv2Settlement { + /// @dev Event emitted for each executed trade. + event Trade( + address indexed owner, + IERC20 sellToken, + IERC20 buyToken, + uint256 sellAmount, + uint256 buyAmount, + uint256 feeAmount, + bytes orderUid + ); + + /// @dev Event emitted for each executed interaction. + /// + /// For gas efficiency, only the interaction calldata selector (first 4 + /// bytes) is included in the event. For interactions without calldata or + /// whose calldata is shorter than 4 bytes, the selector will be `0`. + event Interaction(address indexed target, uint256 value, bytes4 selector); + + /// @dev Event emitted when a settlement completes + event Settlement(address indexed solver); + + /// @dev Event emitted when an order is invalidated. + event OrderInvalidated(address indexed owner, bytes orderUid); + + /// @dev The authenticator is used to determine who can call the settle function. + /// That is, only authorized solvers have the ability to invoke settlements. + /// Any valid authenticator implements an isSolver method called by the onlySolver + /// modifier below. + function authenticator() external view returns (GPv2Authentication); + + /// @dev The Balancer Vault the protocol used for managing user funds. + function vault() external view returns (IVault); + + /// @dev Map each user order by UID to the amount that has been filled so + /// far. If this amount is larger than or equal to the amount traded in the + /// order (amount sold for sell orders, amount bought for buy orders) then + /// the order cannot be traded anymore. If the order is fill or kill, then + /// this value is only used to determine whether the order has already been + /// executed. + function filledAmount(bytes calldata orderUid) external view returns (uint256); + + /// @dev Settle the specified orders at a clearing price. Note that it is + /// the responsibility of the caller to ensure that all GPv2 invariants are + /// upheld for the input settlement, otherwise this call will revert. + /// Namely: + /// - All orders are valid and signed + /// - Accounts have sufficient balance and approval. + /// - Settlement contract has sufficient balance to execute trades. Note + /// this implies that the accumulated fees held in the contract can also + /// be used for settlement. This is OK since: + /// - Solvers need to be authorized + /// - Misbehaving solvers will be slashed for abusing accumulated fees for + /// settlement + /// - Critically, user orders are entirely protected + /// + /// @param tokens An array of ERC20 tokens to be traded in the settlement. + /// Trades encode tokens as indices into this array. + /// @param clearingPrices An array of clearing prices where the `i`-th price + /// is for the `i`-th token in the [`tokens`] array. + /// @param trades Trades for signed orders. + /// @param interactions Smart contract interactions split into three + /// separate lists to be run before the settlement, during the settlement + /// and after the settlement respectively. + function settle( + IERC20[] calldata tokens, + uint256[] calldata clearingPrices, + GPv2Trade.Data[] calldata trades, + GPv2Interaction.Data[][3] calldata interactions + ) external; + + /// @dev Settle an order directly against Balancer V2 pools. + /// + /// @param swaps The Balancer V2 swap steps to use for trading. + /// @param tokens An array of ERC20 tokens to be traded in the settlement. + /// Swaps and the trade encode tokens as indices into this array. + /// @param trade The trade to match directly against Balancer liquidity. The + /// order will always be fully executed, so the trade's `executedAmount` + /// field is used to represent a swap limit amount. + function swap( + IVault.BatchSwapStep[] calldata swaps, + IERC20[] calldata tokens, + GPv2Trade.Data calldata trade + ) external; + + /// @dev Invalidate onchain an order that has been signed offline. + /// + /// @param orderUid The unique identifier of the order that is to be made + /// invalid after calling this function. The user that created the order + /// must be the sender of this message. See [`extractOrderUidParams`] + /// for details on orderUid. + function invalidateOrder(bytes calldata orderUid) external; + + /// @dev Free storage from the filled amounts of **expired** orders to claim + /// a gas refund. This method can only be called as an interaction. + /// + /// @param orderUids The unique identifiers of the expired order to free + /// storage for. + function freeFilledAmountStorage(bytes[] calldata orderUids) external; + + /// @dev Free storage from the pre signatures of **expired** orders to claim + /// a gas refund. This method can only be called as an interaction. + /// + /// @param orderUids The unique identifiers of the expired order to free + /// storage for. + function freePreSignatureStorage(bytes[] calldata orderUids) external; +} \ No newline at end of file diff --git a/src/contracts/interfaces/IGPv2Wrapper.sol b/src/contracts/interfaces/IGPv2Wrapper.sol new file mode 100644 index 00000000..44bf4bf1 --- /dev/null +++ b/src/contracts/interfaces/IGPv2Wrapper.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity >=0.7.6 <0.9.0; +pragma abicoder v2; + +import { IERC20 } from "./IERC20.sol"; +import { IGPv2Settlement } from "./IGPv2Settlement.sol"; +import { GPv2Authentication } from "./GPv2Authentication.sol"; +import { GPv2Trade } from "../libraries/GPv2Trade.sol"; +import { GPv2Interaction } from "../libraries/GPv2Interaction.sol"; + +/** + * @dev Interface for wrappers of the GPv2Settlement contract for CoW orders. It serves as a way for the functionality + * of the settlement contract to be expanded without needing to affect the underlying security. + * A wrapper should: + * * call the equivalent `settle` on the GPv2Settlement contract (0x9008D19f58AAbD9eD0D60971565AA8510560ab41) + * * verify that the caller is authorized via the GPv2Authentication contract registered on the settlement contract. + * A wrapper may also execute, or otherwise put the blockchain in a state that needs to be established prior or after settlement. + * Prior to usage, the wrapper itself needs to be approved by the GPv2Authentication contract. + */ +interface IGPv2Wrapper { + function UPSTREAM_SETTLEMENT() external view returns (IGPv2Settlement); + function AUTHENTICATOR() external view returns (GPv2Authentication); + + /** + * @dev Called to initiate a wrapped call against the settlement function. Most of the arguments are shared with GPv2Settlement.settle(). + * @param wrapperData any additional data which is needed for the wrapper to complete its task. + */ + function wrappedSettle( + IERC20[] calldata tokens, + uint256[] calldata clearingPrices, + GPv2Trade.Data[] calldata trades, + GPv2Interaction.Data[][3] calldata interactions, + bytes calldata wrapperData + ) external; +} diff --git a/test/src/EmptyWrapper.sol b/test/src/EmptyWrapper.sol new file mode 100644 index 00000000..3d52144d --- /dev/null +++ b/test/src/EmptyWrapper.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// solhint-disable-next-line compiler-version +pragma solidity >=0.7.6 <0.9.0; +pragma abicoder v2; + +import "src/contracts/GPv2Wrapper.sol"; + +contract EmptyWrapper is GPv2Wrapper { + constructor(address payable upstreamSettlement_) GPv2Wrapper(upstreamSettlement_) {} + + function _wrap( + IERC20[] calldata tokens, + uint256[] calldata clearingPrices, + GPv2Trade.Data[] calldata trades, + GPv2Interaction.Data[][3] calldata interactions, + bytes calldata /*wrappedData*/ + ) internal override { + _internalSettle(tokens, clearingPrices, trades, interactions); + } +}