Skip to content

Commit

Permalink
Relayer: query swap/join/exit (V2) (#2521)
Browse files Browse the repository at this point in the history
  • Loading branch information
EndymionJkb authored Oct 26, 2023
1 parent 3f2a99e commit df4ec3a
Show file tree
Hide file tree
Showing 11 changed files with 1,027 additions and 219 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ interface IBalancerRelayer {
function getVault() external view returns (IVault);

function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);

function vaultActionsQueryMulticall(bytes[] calldata data) external returns (bytes[] memory results);
}
31 changes: 31 additions & 0 deletions pkg/standalone-utils/contracts/BatchRelayerQueryLibrary.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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 <http://www.gnu.org/licenses/>.

pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;

import "./relayer/BaseRelayerLibraryCommon.sol";

import "./relayer/VaultQueryActions.sol";

/**
* @title Batch Relayer Library
* @notice This contract is not a relayer by itself and calls into it directly will fail.
* The associated relayer can be found by calling `getEntrypoint` on this contract.
*/
contract BatchRelayerQueryLibrary is BaseRelayerLibraryCommon, VaultQueryActions {
constructor(IVault vault) BaseRelayerLibraryCommon(vault) {
//solhint-disable-previous-line no-empty-blocks
}
}
23 changes: 21 additions & 2 deletions pkg/standalone-utils/contracts/relayer/BalancerRelayer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ contract BalancerRelayer is IBalancerRelayer, Version, ReentrancyGuard {

IVault private immutable _vault;
address private immutable _library;
address private immutable _queryLibrary;

/**
* @dev This contract is not meant to be deployed directly by an EOA, but rather during construction of a contract
Expand All @@ -57,10 +58,12 @@ contract BalancerRelayer is IBalancerRelayer, Version, ReentrancyGuard {
constructor(
IVault vault,
address libraryAddress,
address queryLibrary,
string memory version
) Version(version) {
_vault = vault;
_library = libraryAddress;
_queryLibrary = queryLibrary;
}

receive() external payable {
Expand All @@ -80,14 +83,30 @@ contract BalancerRelayer is IBalancerRelayer, Version, ReentrancyGuard {
}

function multicall(bytes[] calldata data) external payable override nonReentrant returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
uint256 numData = data.length;

results = new bytes[](numData);
for (uint256 i = 0; i < numData; i++) {
results[i] = _library.functionDelegateCall(data[i]);
}

_refundETH();
}

function vaultActionsQueryMulticall(bytes[] calldata data)
external
override
nonReentrant
returns (bytes[] memory results)
{
uint256 numData = data.length;

results = new bytes[](numData);
for (uint256 i = 0; i < numData; i++) {
results[i] = _queryLibrary.functionDelegateCall(data[i]);
}
}

function _refundETH() private {
uint256 remainingEth = address(this).balance;
if (remainingEth > 0) {
Expand Down
159 changes: 6 additions & 153 deletions pkg/standalone-utils/contracts/relayer/BaseRelayerLibrary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol";
import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/SafeERC20.sol";

import "./IBaseRelayerLibrary.sol";
import "../BatchRelayerQueryLibrary.sol";
import "./BalancerRelayer.sol";

/**
Expand All @@ -41,20 +42,19 @@ import "./BalancerRelayer.sol";
* do not revert in a call involving ETH. This also applies to functions that do not alter the state and would be
* usually labeled as `view`.
*/
contract BaseRelayerLibrary is IBaseRelayerLibrary {
contract BaseRelayerLibrary is BaseRelayerLibraryCommon {
using Address for address;
using SafeERC20 for IERC20;

IVault private immutable _vault;
IBalancerRelayer private immutable _entrypoint;

constructor(IVault vault, string memory version) IBaseRelayerLibrary(vault.WETH()) {
constructor(IVault vault, string memory version) BaseRelayerLibraryCommon(vault) {
_vault = vault;
_entrypoint = new BalancerRelayer(vault, address(this), version);
}

function getVault() public view override returns (IVault) {
return _vault;
IBaseRelayerLibrary queryLibrary = new BatchRelayerQueryLibrary(vault);

_entrypoint = new BalancerRelayer(vault, address(this), address(queryLibrary), version);
}

function getEntrypoint() external view returns (IBalancerRelayer) {
Expand All @@ -77,151 +77,4 @@ contract BaseRelayerLibrary is IBaseRelayerLibrary {

address(_vault).functionCall(data);
}

/**
* @notice Approves the Vault to use tokens held in the relayer
* @dev This is needed to avoid having to send intermediate tokens back to the user
*/
function approveVault(IERC20 token, uint256 amount) external payable override {
if (_isChainedReference(amount)) {
amount = _getChainedReferenceValue(amount);
}
// TODO: gas golf this a bit
token.safeApprove(address(getVault()), amount);
}

/**
* @notice Returns the amount referenced by chained reference `ref`.
* @dev It does not alter the reference (even if it's marked as temporary).
*
* This function does not alter the state in any way. It is not marked as view because it has to be `payable`
* in order to be used in a batch transaction.
*
* Use a static call to read the state off-chain.
*/
function peekChainedReferenceValue(uint256 ref) external payable override returns (uint256 value) {
(, value) = _peekChainedReferenceValue(ref);
}

function _pullToken(
address sender,
IERC20 token,
uint256 amount
) internal override {
if (amount == 0) return;
IERC20[] memory tokens = new IERC20[](1);
tokens[0] = token;
uint256[] memory amounts = new uint256[](1);
amounts[0] = amount;

_pullTokens(sender, tokens, amounts);
}

function _pullTokens(
address sender,
IERC20[] memory tokens,
uint256[] memory amounts
) internal override {
IVault.UserBalanceOp[] memory ops = new IVault.UserBalanceOp[](tokens.length);
for (uint256 i; i < tokens.length; i++) {
ops[i] = IVault.UserBalanceOp({
asset: IAsset(address(tokens[i])),
amount: amounts[i],
sender: sender,
recipient: payable(address(this)),
kind: IVault.UserBalanceOpKind.TRANSFER_EXTERNAL
});
}

getVault().manageUserBalance(ops);
}

/**
* @dev Returns true if `amount` is not actually an amount, but rather a chained reference.
*/
function _isChainedReference(uint256 amount) internal pure override returns (bool) {
// First 3 nibbles are enough to determine if it's a chained reference.
return
(amount & 0xfff0000000000000000000000000000000000000000000000000000000000000) ==
0xba10000000000000000000000000000000000000000000000000000000000000;
}

/**
* @dev Returns true if `ref` is temporary reference, i.e. to be deleted after reading it.
*/
function _isTemporaryChainedReference(uint256 amount) internal pure returns (bool) {
// First 3 nibbles determine if it's a chained reference.
// If the 4th nibble is 0 it is temporary; otherwise it is considered read-only.
// In practice, we shall use '0xba11' for read-only references.
return
(amount & 0xffff000000000000000000000000000000000000000000000000000000000000) ==
0xba10000000000000000000000000000000000000000000000000000000000000;
}

/**
* @dev Stores `value` as the amount referenced by chained reference `ref`.
*/
function _setChainedReferenceValue(uint256 ref, uint256 value) internal override {
bytes32 slot = _getStorageSlot(ref);

// Since we do manual calculation of storage slots, it is easier (and cheaper) to rely on internal assembly to
// access it.
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(slot, value)
}
}

/**
* @dev Returns the amount referenced by chained reference `ref`.
* If the reference is temporary, it will be cleared after reading it, so they can each only be read once.
* If the reference is not temporary (i.e. read-only), it will not be cleared after reading it
* (see `_isTemporaryChainedReference` function).
*/
function _getChainedReferenceValue(uint256 ref) internal override returns (uint256) {
(bytes32 slot, uint256 value) = _peekChainedReferenceValue(ref);

if (_isTemporaryChainedReference(ref)) {
// solhint-disable-next-line no-inline-assembly
assembly {
sstore(slot, 0)
}
}
return value;
}

/**
* @dev Returns the storage slot for reference `ref` as well as the amount referenced by it.
* It does not alter the reference (even if it's marked as temporary).
*/
function _peekChainedReferenceValue(uint256 ref) private view returns (bytes32 slot, uint256 value) {
slot = _getStorageSlot(ref);

// Since we do manual calculation of storage slots, it is easier (and cheaper) to rely on internal assembly to
// access it.
// solhint-disable-next-line no-inline-assembly
assembly {
value := sload(slot)
}
}

// solhint-disable-next-line var-name-mixedcase
bytes32 private immutable _TEMP_STORAGE_SUFFIX = keccak256("balancer.base-relayer-library");

function _getStorageSlot(uint256 ref) private view returns (bytes32) {
// This replicates the mechanism Solidity uses to allocate storage slots for mappings, but using a hash as the
// mapping's storage slot, and subtracting 1 at the end. This should be more than enough to prevent collisions
// with other state variables this or derived contracts might use.
// See https://docs.soliditylang.org/en/v0.8.9/internals/layout_in_storage.html

return bytes32(uint256(keccak256(abi.encodePacked(_removeReferencePrefix(ref), _TEMP_STORAGE_SUFFIX))) - 1);
}

/**
* @dev Returns a reference without its prefix.
* Use this function to calculate the storage slot so that it's the same for temporary and read-only references.
*/
function _removeReferencePrefix(uint256 ref) private pure returns (uint256) {
return (ref & 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
}
}
Loading

0 comments on commit df4ec3a

Please sign in to comment.