Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/MyPerfectCalculatorLive.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity 0.8.26;\r
// import "hardhat/console.sol";\r
import { IERC20 } from "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol";\r
import { Address } from "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol";\r
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/utils/SafeERC20.sol";\r
\r
interface IWETH is IERC20 {\r
function deposit() external payable;\r
function withdraw(uint256 amount) external payable;\r
}\r
import { Token } from "contracts/token/Token.sol";\r
import { TokenLibrary } from "contracts/token/TokenLibrary.sol";\r
import { IVault as IBalancerVault } from "contracts/vault/IVault.sol";\r
import { IFlashLoanRecipient as IBalancerFlashLoanRecipient } from "contracts/vault/IFlashLoanRecipient.sol";\r
import { castTokens as castToBalancerTokens } from "contracts/vault/BalancerUtils.sol";\r
import { IBancorNetwork, IFlashLoanRecipient } from "contracts/exchanges/interfaces/IBancorNetwork.sol";\r
import { IBancorNetworkV2 } from "contracts/exchanges/interfaces/IBancorNetworkV2.sol";\r
import { ICarbonController } from "contracts/exchanges/interfaces/ICarbonController.sol";\r
import { ICarbonPOL } from "contracts/exchanges/interfaces/ICarbonPOL.sol";\r
\r
import { IUniversalRouter } from "./src/IUniversalRouter.sol";\r
import { IPermit2 } from "https://github.com/Uniswap/permit2/src/interfaces/IPermit2.sol";\r
\r
interface IHyperliquid {\r
function swapIn(\r
bool swap0To1,\r
uint256 amountIn,\r
uint256 amountOutMin,\r
address recipient\r
) external payable returns (uint256 amountOut);\r
}\r
\r
interface IHyperliquidPools {\r
function getPoolTokens(address pool_) external view returns (address token0_, address token1_);\r
}\r
\r
interface IMooniSwap {\r
function swap(IERC20 src, IERC20 dst, uint256 amount, uint256 minReturn, address referral) external payable returns(uint256 result);\r
}\r
\r
interface IUniswapV4Helper {\r
struct SwapData {\r
bytes commands;\r
bytes[] inputs;\r
address inputToken;\r
address outputToken;\r
uint256 value;\r
}\r
function buildSwapData(TradeRoute calldata route) external pure returns (SwapData memory);\r
}\r
\r
interface IGSP { \r
function sellQuote(address to) external payable returns (uint256);\r
function sellBase(address to) external payable returns (uint256);\r
function _BASE_TOKEN_() external view returns (IERC20);\r
function _QUOTE_TOKEN_() external view returns (IERC20);\r
}\r
\r
import "contracts/executors/IExecutor.sol";\r
\r
interface IBribeVault {\r
function sendBribe(uint256 amount) external;\r
}\r
\r
contract MyPerfectCalculatorLive { \r
\r
using TokenLibrary for Token;\r
mapping(uint16 => address) public executors;\r
mapping(uint256 => bool) private _delegatePlatforms;\r
mapping(uint256 => address) private _platformRouterOverride;\r
mapping(address => mapping(address => bool)) private _approved;\r
\r
function setIUniswapV4Helper(address router) external {\r
_IUniswapV4Helper = IUniswapV4Helper(router);\r
}\r
\r
function setPlatformRouter(uint256 platformId, address router) external {\r
_platformRouterOverride[platformId] = router;\r
}\r
\r
function getsetExecutor(uint16 platformId, address newExecutor) public returns (address) {\r
if (newExecutor != address(0)) {\r
executors[platformId] = newExecutor;\r
}\r
return executors[platformId];\r
} \r
\r
function getsetDelegatePlatform(uint256 platformId, bool update, bool enabled) public returns (bool) {\r
if (update) {\r
_delegatePlatforms[platformId] = enabled;\r
}\r
return _delegatePlatforms[platformId];\r
}\r
\r
using SafeERC20 for IERC20;\r
\r
error InvalidWethTrade();\r
error UnsupportedPlatform();\r
error EthTransferFailed();\r
\r
address private owner;\r
\r
// WETH9 contract\r
address internal constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;\r
address internal constant ETH_TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;\r
IERC20 internal constant BNT_TOKEN = IERC20(0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C);\r
\r
IBalancerVault internal constant _balancerVault= IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);\r
IBancorNetwork internal constant _bancorNetworkV3 = IBancorNetwork(0xeEF417e1D5CC832e619ae18D2F140De2999dD4fB); \r
IBancorNetworkV2 internal constant _bancorNetworkV2 = IBancorNetworkV2(0x2F9EC37d6CcFFf1caB21733BdaDEdE11c823cCB0);\r
ICarbonController internal constant _iCarbonController = ICarbonController(0xC537e898CD774e2dCBa3B14Ea6f34C93d5eA45e1);\r
IHyperliquidPools internal constant hyperPools = IHyperliquidPools(0xC93876C0EEd99645DD53937b25433e311881A27C);\r
IUniswapV4Helper internal _IUniswapV4Helper;\r
\r
IPermit2 internal constant PERMIT2 = IPermit2(0x000000000022D473030F116dDEE9F6B43aC78BA3);\r
IUniversalRouter internal constant UNIVERSAL_ROUTER = IUniversalRouter(payable(0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af));\r
IBribeVault internal constant bribeVault = IBribeVault(0xA19451094F53ED12Ff0277879C19cf40e51b7df0);\r
\r
constructor( \r
address _ownerAddress \r
) {\r
owner = _ownerAddress;\r
_delegatePlatforms[3] = true;\r
_delegatePlatforms[4] = true;\r
_delegatePlatforms[15] = true; \r
_platformRouterOverride[15] = 0x208fF5Eb543814789321DaA1B5Eb551881D16b06;\r
_delegatePlatforms[18] = true;\r
_platformRouterOverride[18] = 0x62e31802c6145A2D5E842EeD8efe01fC224422fA;\r
\r
_delegatePlatforms[12] = true;//FluidDexLite\r
_delegatePlatforms[13] = true;//FluidDex\r
_delegatePlatforms[9] = true;//Curve\r
//LOKAAL _delegatePlatforms[11] = false;//GSP\r
\r
// 1 2 66 8 11 14 16 17 zijn lokaal gezet hier => alles waar msg.value wordt gebruikt\r
executors[15] = 0x6D7fa3d442A81E6adEFaAeED5C75CE6C190aA296;\r
executors[3] = 0x54A9C8265aB7F03161409b36C30e55f85eccD6C1; //dit mag deze zijn zonder try catch\r
executors[4] = 0xcCe3C009E029dd9CEc6D1C9A734091B907A8b79a; //dit mag deze zijn zonder try catch \r
executors[18] = 0x041b49bBbF5693EC2DB0da96ee638E30Baac32d8;\r
_IUniswapV4Helper = IUniswapV4Helper(0xdEcBDbd2af4cbc55F3401b6bc60Af60ce6a852cD); // note the interface type here \r
executors[12] = 0xBD918D8f7F4c42FE40A6bC71B6Dd950dE9Dac483;//FluidDexLite\r
executors[13] = 0x90b738ba024d61a01C4B7e43b6bfE1Df1F92c23F;//FluidDex\r
executors[9] = 0x6c87AF08bA3fB1A8C37848d635cEdB364BF2028B;//Curve\r
//LOKAAL executors[11] = 0x59926F11DD1D8c6798c07dCb1F01d19e4653d251;//GSP no delegate \r
\r
_delegatePlatforms[12] = true;//FluidDexLite\r
_delegatePlatforms[13] = true;//FluidDex\r
executors[12] = 0xE5c3Be8fbeBB1391C380EBC443a5Ce01cFF3D66d;//FluidDexLite\r
executors[13] = 0x90b738ba024d61a01C4B7e43b6bfE1Df1F92c23F;//FluidDex\r
_delegatePlatforms[9] = true;//Curve\r
executors[9] = 0x6443d7cd185432448EE8Ac081FE04d7a389f1977;//Curve\r
\r
}\r
\r
receive() external payable {}\r
fallback() external payable {}\r
\r
function flashloanAndArbBalancerMulti(\r
uint256 fl_amount,\r
address fl_token,\r
TradeRoute[] memory routes,\r
uint256 gas_amount,\r
uint256 bribe_amount,\r
uint256 bribe_amount_eth\r
) public { \r
bytes memory encodedData = abi.encode(routes, fl_amount, gas_amount, bribe_amount, bribe_amount_eth); \r
_takeFlashloan_balancer(fl_amount, fl_token, encodedData); \r
}\r
\r
function _takeFlashloan_balancer(uint256 fl_amount, address fl_token, bytes memory data) private {\r
\r
IERC20[] memory tokens = new IERC20[](1);\r
uint256[] memory amounts = new uint256[](1);\r
\r
tokens[0] = IERC20(fl_token);\r
amounts[0] = fl_amount; \r
\r
if(fl_token == address(BNT_TOKEN))\r
{\r
// take a flashloan on Bancor v3, execution continues in `onFlashloan` \r
_bancorNetworkV3.flashLoan(\r
Token(fl_token),\r
fl_amount,\r
IFlashLoanRecipient(address(this)),\r
data\r
);\r
}\r
else {\r
// take a flashloan on Balancer, execution continues in `receiveFlashLoan`\r
_balancerVault.flashLoan(\r
IBalancerFlashLoanRecipient(address(this)),\r
castToBalancerTokens(tokens),\r
amounts,\r
data\r
);\r
} \r
} \r
\r
function onFlashLoan(\r
address /*caller*/,\r
IERC20 token,\r
uint256 amount,\r
uint256 feeAmount,\r
bytes memory data\r
) external { \r
\r
_handleFlashLoan(token, amount, feeAmount, data);\r
} \r
\r
function receiveFlashLoan(\r
IERC20[] memory tokens,\r
uint256[] memory amounts,\r
uint256[] memory feeAmounts,\r
bytes memory userData\r
) external { \r
_handleFlashLoan(tokens[0], amounts[0], feeAmounts[0], userData);\r
}\r
\r
function _handleFlashLoan(\r
IERC20 token,\r
uint256 amount,\r
uint256 feeAmount,\r
bytes memory userData\r
) internal {\r
\r
(, , uint256 gas_amount, uint256 bribe_amount, uint256 bribe_amount_eth) = abi.decode(userData, (TradeRoute[], uint256, uint256, uint256, uint256));\r
\r
// uint256 balance_before = token.balanceOf(address(this)); \r
// console.log("balance_before", balance_before);\r
\r
_arbitrageV2Internal(userData);\r
\r
// uint256 balance_after = token.balanceOf(address(this)); \r
// console.log("balance_before", balance_after);\r
\r
SafeERC20.safeTransfer(IERC20(token), msg.sender, amount + feeAmount); \r
\r
uint256 balance = token.balanceOf(address(this)); \r
// console.log("balance", balance);\r
if(balance < gas_amount){\r
require(1 > 1 , "balance lt gas!"); \r
}\r
if ((bribe_amount > 0) && (bribe_amount < balance)){\r
// SafeERC20.safeTransfer(IERC20(token), block.coinbase, bribe_amount);\r
// balance = balance - bribe_amount;\r
// ✅ NEW LOGIC:\r
if (bribe_amount_eth > 0) {\r
bribeVault.sendBribe(bribe_amount_eth);\r
}\r
}\r
transferToken(address(token), balance);\r
\r
// require(1 > 1 , "einde flashloan");\r
} \r
\r
function transferToken(address tokenAddress, uint256 balance) public payable{\r
if (tokenAddress == NATIVE_TOKEN) {\r
payable(owner).transfer(address(this).balance);\r
} \r
\r
if (balance > 0) {\r
SafeERC20.safeTransfer(IERC20(tokenAddress), owner, balance);\r
}\r
\r
}\r
\r
function _arbitrageV2Internal(\r
bytes memory userData\r
) internal {\r
\r
(TradeRoute[] memory path, uint256 amount, , ) = abi.decode(userData, (TradeRoute[], uint256, uint256, uint256));\r
\r
uint256 ret = amount; \r
for (uint256 j = 0; j < path.length; j = _uncheckedInc(j)) {\r
TradeRoute memory r = path[j]; \r
r.sourceAmount = ret; \r
ret = trade(r);\r
\r
//sometimes we buy a token but the return is different because of some internal fee\r
// if((address(path[j].targetToken) != NATIVE_TOKEN) && (address(path[j].targetToken) != ETH_TOKEN)){\r
// uint256 balance = IERC20(address(path[j].targetToken)).balanceOf(address(this)); \r
// if(balance < ret){ \r
// ret = balance;\r
// }\r
// }\r
}\r
} \r
\r
function tryMooniSwap(\r
TradeRoute memory route\r
) internal returns (uint256 quote) \r
{ \r
uint256 val = 0;\r
address sourceToken = address(route.sourceToken); \r
address targetToken = address(route.targetToken); \r
\r
if(address(route.sourceToken) == ETH_TOKEN){\r
sourceToken = address(0); \r
}\r
if(address(route.targetToken) == ETH_TOKEN){\r
targetToken = address(0); \r
} \r
\r
if (sourceToken != address(0)) {\r
_approveAndTransfer(route.sourceToken, address(route.customAddress), route.sourceAmount, false);\r
} else {\r
val = route.sourceAmount;\r
}\r
\r
quote = IMooniSwap(route.customAddress).swap{value: val}(\r
IERC20(sourceToken),\r
IERC20(targetToken),\r
route.sourceAmount,\r
0,\r
address(0)\r
);\r
}\r
\r
function tryGSPSwap(\r
TradeRoute memory route\r
) internal returns (uint256 quote) {\r
\r
IERC20(route.sourceToken).forceApprove(route.customAddress, type(uint256).max);\r
IERC20(route.sourceToken).safeTransfer(route.customAddress, route.sourceAmount);\r
\r
address source = address(IGSP(route.customAddress)._QUOTE_TOKEN_());\r
\r
if(route.sourceToken == source){\r
quote = IGSP(route.customAddress).sellQuote(address(this));\r
}\r
else{\r
quote = IGSP(route.customAddress).sellBase(address(this));\r
} \r
}\r
\r
function tryBancorPOLSwap(\r
TradeRoute memory route\r
) internal returns (uint256 quote) \r
{ \r
uint256 val = 0;\r
\r
if (route.sourceToken != NATIVE_TOKEN) {\r
_approveAndTransfer(route.sourceToken, address(route.customAddress), route.sourceAmount, false);\r
} else {\r
val = route.sourceAmount;\r
}\r
\r
uint128 targetAmount = ICarbonPOL(route.customAddress).expectedTradeReturn(Token(route.targetToken), uint128(route.sourceAmount));\r
\r
ICarbonPOL(route.customAddress).trade{value: val}(\r
Token(route.targetToken),\r
targetAmount,\r
uint128(route.sourceAmount));\r
quote = route.targetToken == NATIVE_TOKEN \r
? address(this).balance\r
: IERC20(route.targetToken).balanceOf(address(this));\r
return quote; \r
}\r
\r
function tryHyperSwap(\r
TradeRoute memory route\r
) internal returns (uint256 quote) \r
{ \r
uint256 val = 0;\r
if (route.sourceToken != NATIVE_TOKEN && route.sourceToken != ETH_TOKEN) {\r
_approveAndTransfer(route.sourceToken, address(route.customAddress), route.sourceAmount, false);\r
} else {\r
val = route.sourceAmount;\r
}\r
// Get swap direction\r
bool swapDirection;\r
(address token0,) = hyperPools.getPoolTokens(route.customAddress);\r
swapDirection = route.sourceToken == token0;\r
\r
// Perform swap\r
quote = IHyperliquid(route.customAddress).swapIn{value: val}(\r
swapDirection,\r
route.sourceAmount,\r
0,\r
address(this)\r
); \r
\r
}\r
function tryBancorV2Swap(\r
TradeRoute memory route\r
) internal returns (uint256 quote) \r
{ \r
\r
address[] memory path = new address[](3);\r
path[0] = address(route.sourceToken);\r
path[1] = route.customAddress; \r
path[2] = address(route.targetToken); \r
\r
uint256 val = _prepSwap(address(route.sourceToken), address(_bancorNetworkV2), route.sourceAmount);\r
quote = _bancorNetworkV2.convertByPath{ value: val }(\r
path,\r
route.sourceAmount,\r
route.minTargetAmount,\r
address(0x0),\r
address(0x0),\r
0\r
);\r
}\r
\r
function tryBancorV3Swap(\r
TradeRoute memory route\r
) internal returns (uint256 quote) \r
{ \r
uint256 val = _prepSwap(address(route.sourceToken), address(_bancorNetworkV3), route.sourceAmount);\r
quote = _bancorNetworkV3.tradeBySourceAmount{ value: val }(\r
Token(route.sourceToken),\r
Token(route.targetToken),\r
route.sourceAmount,\r
1,\r
block.timestamp + 60, \r
address(this)\r
); \r
}\r
\r
\r
\r
function trySwapBancorController(\r
TradeRoute memory route\r
) internal returns (uint256 quote) \r
{ \r
ICarbonController.TradeAction[] memory tradeActions;\r
tradeActions = new ICarbonController.TradeAction[](1);\r
tradeActions[0] = ICarbonController.TradeAction({\r
strategyId: route.customInt,\r
amount: uint128(route.sourceAmount)\r
}); \r
\r
uint256 val = _prepSwap(address(route.sourceToken), address(_iCarbonController), route.sourceAmount); \r
quote = _iCarbonController.tradeBySourceAmount{ value: val }(\r
Token(address(route.sourceToken)),\r
Token(address(route.targetToken)),\r
tradeActions,\r
route.deadline,\r
uint128(1)\r
); \r
}\r
\r
function tryUniswapV4(\r
TradeRoute memory route\r
) internal returns (uint256 quote) \r
{ \r
IUniswapV4Helper.SwapData memory data = _IUniswapV4Helper.buildSwapData(route);\r
\r
if (data.inputToken != address(0)) {\r
try this.approveAndPermit(data.inputToken, route.sourceAmount) {\r
} catch {\r
return 2; \r
}\r
}\r
uint256 deadline = block.timestamp + 60;\r
UNIVERSAL_ROUTER.execute{value: data.value}(data.commands, data.inputs, deadline);\r
\r
quote = data.outputToken == address(0) \r
? address(this).balance\r
: IERC20(data.outputToken).balanceOf(address(this));\r
return quote; \r
} \r
\r
\r
function trade(TradeRoute memory route) public payable returns (uint256 ret) {\r
if (route.platformId == 10) { \r
if (address(route.sourceToken) == NATIVE_TOKEN && address(route.targetToken) == ETH_TOKEN) {\r
IWETH(ETH_TOKEN).deposit{ value: route.sourceAmount }();\r
} else if (address(route.sourceToken) == ETH_TOKEN && address(route.targetToken) == NATIVE_TOKEN) {\r
IWETH(ETH_TOKEN).withdraw(route.sourceAmount);\r
} else {\r
revert InvalidWethTrade();\r
} \r
return route.sourceAmount;\r
} \r
else if (route.platformId == 1) { \r
ret = tryBancorV2Swap(route);\r
return ret;\r
} \r
else if (route.platformId == 2) { \r
ret = tryBancorV3Swap(route);\r
return ret;\r
} \r
else if (route.platformId == 66) { \r
ret = trySwapBancorController(route);\r
return ret;\r
} \r
else if (route.platformId == 8) { \r
ret = tryBancorPOLSwap(route);\r
return ret;\r
} \r
else if (route.platformId == 11) { \r
ret = tryGSPSwap(route);\r
return ret;\r
}\r
else if (route.platformId == 14) { \r
ret = tryUniswapV4(route);\r
return ret;\r
} \r
else if (route.platformId == 16) { \r
ret = tryHyperSwap(route);\r
return ret;\r
} \r
else if (route.platformId == 17) { \r
ret = tryMooniSwap(route);\r
return ret;\r
} \r
else\r
{\r
address executorAddress = getsetExecutor(route.platformId, address(0)); \r
if (executorAddress == address(0)) revert UnsupportedPlatform();\r
\r
if(getsetDelegatePlatform (route.platformId, false, false)) {\r
\r
address customAddress = _platformRouterOverride[route.platformId];\r
if (customAddress == address(0)) {\r
customAddress = route.customAddress;\r
}\r
_approveAndTransfer(\r
address(route.sourceToken),\r
customAddress, \r
route.sourceAmount,\r
false \r
); \r
ret = _delegateTrySwap(executorAddress, route);\r
}\r
else \r
{\r
ret = 25; // ret = IExecutor(executorAddress).trySwap(route); // direct call, no try/catch \r
} \r
\r
return ret;\r
}\r
}\r
\r
function _delegateTrySwap(address impl, TradeRoute memory route) internal returns (uint256 ret) {\r
bytes memory data = abi.encodeWithSelector(IExecutor.trySwap.selector, route);\r
(, bytes memory r) = impl.delegatecall(data);\r
ret = abi.decode(r, (uint256));\r
} \r
\r
function _approveAndTransfer(\r
address token,\r
address to_approve_address,\r
uint256 amount,\r
bool transfer\r
) internal {\r
if(token == NATIVE_TOKEN){\r
return;\r
} \r
if (!_approved[token][to_approve_address]) {\r
IERC20(token).forceApprove(to_approve_address, type(uint256).max);\r
_approved[token][to_approve_address] = true;\r
}\r
if(transfer){\r
IERC20(token).safeTransfer(to_approve_address, amount);\r
} \r
} \r
\r
function approveAndPermit(address inputTokenAddr, uint256 amountIn) external {\r
_approveAndTransfer(inputTokenAddr, address(PERMIT2), amountIn, false); \r
PERMIT2.approve(\r
inputTokenAddr,\r
address(UNIVERSAL_ROUTER),\r
uint160(amountIn),\r
uint48(block.timestamp + 60)\r
); \r
}\r
\r
function _uncheckedInc(uint256 i) private pure returns (uint256 j) {\r
unchecked {\r
j = i + 1;\r
}\r
} \r
\r
function _prepSwap(\r
address token,\r
address spender,\r
uint256 amount\r
) internal returns (uint256 value) {\r
if (token == NATIVE_TOKEN) {\r
value = amount;\r
} else {\r
_approveAndTransfer(token, spender, amount, false);\r
}\r
}\r
}"
},
"contracts/executors/IExecutor.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.0;\r
import "./TokenStructs.sol";\r
\r
interface IExecutor {\r
/**\r
* @notice Attempts a swap using platform-specific logic.\r
* @param route The trade route containing all necessary parameters.\r
* @return result A uint256 result code or value, depending on implementation.\r
*/\r
function trySwap(TradeRoute calldata route) external payable returns (uint256 result);\r
}\r
"
},
"https://github.com/Uniswap/permit2/src/interfaces/IPermit2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {ISignatureTransfer} from "./ISignatureTransfer.sol";
import {IAllowanceTransfer} from "./IAllowanceTransfer.sol";
/// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer.
/// @dev Users must approve Permit2 before calling any of the transfer functions.
interface IPermit2 is ISignatureTransfer, IAllowanceTransfer {
// IPermit2 unifies the two interfaces so users have maximal flexibility with their approval.
}
"
},
"contracts/src/IUniversalRouter.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later\r
pragma solidity ^0.8.24;\r
\r
interface IUniversalRouter {\r
/// @notice Thrown when a required command has failed\r
error ExecutionFailed(uint256 commandIndex, bytes message);\r
\r
/// @notice Thrown when attempting to send ETH directly to the contract\r
error ETHNotAccepted();\r
\r
/// @notice Thrown when executing commands with an expired deadline\r
error TransactionDeadlinePassed();\r
\r
/// @notice Thrown when attempting to execute commands and an incorrect number of inputs are provided\r
error LengthMismatch();\r
\r
// @notice Thrown when an address that isn't WETH tries to send ETH to the router without calldata\r
error InvalidEthSender();\r
\r
/// @notice Executes encoded commands along with provided inputs. Reverts if deadline has expired.\r
/// @param commands A set of concatenated commands, each 1 byte in length\r
/// @param inputs An array of byte strings containing abi encoded inputs for each command\r
/// @param deadline The deadline by which the transaction must be executed\r
function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable;\r
}"
},
"contracts/exchanges/interfaces/ICarbonPOL.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import { Token } from "../../token/Token.sol";
/**
* @notice CarbonPOL interface
*/
interface ICarbonPOL {
/**
* @notice returns the expected trade output (tokens received) given an token amount sent
*/
function expectedTradeReturn(Token token, uint128 ethAmount) external view returns (uint128 tokenAmount);
/**
* @notice returns the expected trade input (how many tokens to send) given a token amount received
*/
function expectedTradeInput(Token token, uint128 tokenAmount) external view returns (uint128 ethAmount);
/**
* @notice trades ETH for *amount* of token based on the current token price (trade by target amount)
* @notice if token == ETH, trades BNT for amount of ETH
*/
function trade(Token token, uint128 targetAmount, uint128 maxInput) external payable;
function minTokenSaleAmount(Token token) external view returns (uint128);
function minTokenSaleAmountMultiplier() external view returns (uint32);
function amountAvailableForTrading(Token token) external view returns (uint128);
}"
},
"contracts/exchanges/interfaces/ICarbonController.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity 0.8.26;\r
\r
import { IERC20 } from "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol";\r
import { Token } from "contracts/token/Token.sol";\r
/**\r
* Carbon controller interface\r
*/\r
interface ICarbonController {\r
struct Strategy {\r
uint256 id;\r
address owner;\r
Token[2] tokens;\r
Order[2] orders;\r
}\r
struct Order {\r
uint128 y;\r
uint128 z;\r
uint64 A;\r
uint64 B;\r
}\r
struct TradeAction {\r
uint256 strategyId;\r
uint128 amount;\r
}\r
\r
struct Pair {\r
uint128 id;\r
Token[2] tokens;\r
}\r
\r
function pair(Token token0, Token token1) external view returns (Pair memory);\r
\r
function strategiesByPair(\r
Token token0,\r
Token token1,\r
uint256 startIndex,\r
uint256 endIndex\r
) external view returns (Strategy[] memory);\r
\r
function calculateTradeSourceAmount(\r
Token sourceToken,\r
Token targetToken,\r
TradeAction[] calldata tradeActions\r
) external view returns (uint128);\r
\r
function calculateTradeTargetAmount(\r
Token sourceToken,\r
Token targetToken,\r
TradeAction[] calldata tradeActions\r
) external view returns (uint128);\r
\r
function tradeBySourceAmount(\r
Token sourceToken,\r
Token targetToken,\r
TradeAction[] calldata tradeActions,\r
uint256 deadline,\r
uint128 minReturn\r
) external payable returns (uint128);\r
\r
function tradeByTargetAmount(\r
Token sourceToken,\r
Token targetToken,\r
TradeAction[] calldata tradeActions,\r
uint256 deadline,\r
uint128 maxInput\r
) external payable returns (uint128);\r
}"
},
"contracts/exchanges/interfaces/IBancorNetworkV2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import { IERC20 } from "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol";
import { Token } from "../../token/Token.sol";
/**
* Bancor Network V2 interface
*/
interface IBancorNetworkV2 {
function convertByPath(
address[] memory _path,
uint256 _amount,
uint256 _minReturn,
address _beneficiary,
address _affiliateAccount,
uint256 _affiliateFee
) external payable returns (uint256);
function conversionPath(Token _sourceToken, Token _targetToken) external view returns (address[] memory);
}"
},
"contracts/exchanges/interfaces/IBancorNetwork.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import { IERC20 } from "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol";
import { Token } from "../../token/Token.sol";
/**
* @dev Flash-loan recipient interface
*/
interface IFlashLoanRecipient {
/**
* @dev a flash-loan recipient callback after each the caller must return the borrowed amount and an additional fee
*/
function onFlashLoan(
address caller,
IERC20 erc20Token,
uint256 amount,
uint256 feeAmount,
bytes memory data
) external;
}
/**
* @dev Bancor Network interface
*/
interface IBancorNetwork {
/**
* @dev returns the respective pool collection for the provided pool
*/
function collectionByPool(Token pool) external view returns (address);
function tradeBySourceAmount(
Token sourceToken,
Token targetToken,
uint256 sourceAmount,
uint256 minReturnAmount,
uint256 deadline,
address beneficiary
) external payable returns (uint256);
function tradeByTargetAmount(
Token sourceToken,
Token targetToken,
uint256 targetAmount,
uint256 maxSourceAmount,
uint256 deadline,
address beneficiary
) external payable returns (uint256);
/**
* @dev provides a flash-loan
*
* requirements:
*
* - the recipient's callback must return *at least* the borrowed amount and fee back to the specified return address
*/
function flashLoan(Token token, uint256 amount, IFlashLoanRecipient recipient, bytes calldata data) external;
}
"
},
"contracts/vault/BalancerUtils.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import { IERC20 as IStandardERC20 } from "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol";
import { IERC20 as IBalancerERC20 } from "./IFlashLoanRecipient.sol";
function castTokens(IStandardERC20[] memory inputTokens) pure returns (IBalancerERC20[] memory outputTokens) {
// solhint-disable no-inline-assembly
assembly {
outputTokens := inputTokens
}
// solhint-enable no-inline-assembly
}"
},
"contracts/vault/IFlashLoanRecipient.sol": {
"content": "// 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 <0.9.0;
// Inspired by Aave Protocol's IFlashLoanReceiver.
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IFlashLoanRecipient {
/**
* @dev When `flashLoan` is called on the Vault, it invokes the `receiveFlashLoan` hook on the recipient.
*
* At the time of the call, the Vault will have transferred `amounts` for `tokens` to the recipient. Before this
* call returns, the recipient must have transferred `amounts` plus `feeAmounts` for each token back to the
* Vault, or else the entire flash loan will revert.
*
* `userData` is the same value passed in the `IVault.flashLoan` call.
*/
function receiveFlashLoan(
IERC20[] memory tokens,
uint256[] memory amounts,
uint256[] memory feeAmounts,
bytes memory userData
) external;
}"
},
"contracts/vault/IVault.sol": {
"content": "// 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 experimental ABIEncoderV2;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "contracts/vault/IAuthentication.sol";
import "contracts/vault/ISignaturesValidator.sol";
import "contracts/vault/ITemporarilyPausable.sol";
//interface IWETH {
// function deposit() external payable;
// function transfer(address to, uint value) external returns (bool);
// function withdraw(uint) external;
//}
import "contracts/vault/IAsset.sol";
import "contracts/vault/IAuthorizer.sol";
import "contracts/vault/IFlashLoanRecipient.sol";
import "contracts/vault/IProtocolFeesCollector.sol";
pragma solidity >=0.7.0 <0.9.0;
/**
* @dev Full external interface for the Vault core contract - no external or public methods exist in the contract that
* don't override one of these declarations.
*/
interface IVault is ISignaturesValidator, ITemporarilyPausable, IAuthentication {
// Generalities about the Vault:
//
// - Whenever documentation refers to 'tokens', it strictly refers to ERC20-compliant token contracts. Tokens are
// transferred out of the Vault by calling the `IERC20.transfer` function, and transferred in by calling
// `IERC20.transferFrom`. In these cases, the sender must have previously allowed the Vault to use their tokens by
// calling `IERC20.approve`. The only deviation from the ERC20 standard that is supported is functions not returning
// a boolean value: in these scenarios, a non-reverting call is assumed to be successful.
//
// - All non-view functions in the Vault are non-reentrant: calling them while another one is mid-execution (e.g.
// while execution control is transferred to a token contract during a swap) will result in a revert. View
// functions can be called in a re-reentrant way, but doing so might cause them to return inconsistent results.
// Contracts calling view functions in the Vault must make sure the Vault has not already been entered.
//
// - View functions revert if referring to either unregistered Pools, or unregistered tokens for registered Pools.
// Authorizer
//
// Some system actions are permissioned, like setting and collecting protocol fees. This permissioning system exists
// outside of the Vault in the Authorizer contract: the Vault simply calls the Authorizer to check if the caller
// can perform a given action.
/**
* @dev Returns the Vault's Authorizer.
*/
function getAuthorizer() external view returns (IAuthorizer);
/**
* @dev Sets a new Authorizer for the Vault. The caller must be allowed by the current Authorizer to do this.
*
* Emits an `AuthorizerChanged` event.
*/
function setAuthorizer(IAuthorizer newAuthorizer) external;
/**
* @dev Emitted when a new authorizer is set by `setAuthorizer`.
*/
event AuthorizerChanged(IAuthorizer indexed newAuthorizer);
// Relayers
//
// Additionally, it is possible for an account to perform certain actions on behalf of another one, using their
// Vault ERC20 allowance and Internal Balance. These accounts are said to be 'relayers' for these Vault functions,
// and are expected to be smart contracts with sound authentication mechanisms. For an account to be able to wield
// this power, two things must occur:
// - The Authorizer must grant the account the permission to be a relayer for the relevant Vault function. This
// means that Balancer governance must approve each individual contract to act as a relayer for the intended
// functions.
// - Each user must approve the relayer to act on their behalf.
// This double protection means users cannot be tricked into approving malicious relayers (because they will not
// have been allowed by the Authorizer via governance), nor can malicious relayers approved by a compromised
// Authorizer or governance drain user funds, since they would also need to be approved by each individual user.
/**
* @dev Returns true if `user` has approved `relayer` to act as a relayer for them.
*/
function hasApprovedRelayer(address user, address relayer) external view returns (bool);
/**
* @dev Allows `relayer` to act as a relayer for `sender` if `approved` is true, and disallows it otherwise.
*
* Emits a `RelayerApprovalChanged` event.
*/
function setRelayerApproval(
address sender,
address relayer,
bool approved
) external;
/**
* @dev Emitted every time a relayer is approved or disapproved by `setRelayerApproval`.
*/
event RelayerApprovalChanged(address indexed relayer, address indexed sender, bool approved);
// Internal Balance
//
// Users can deposit tokens into the Vault, where they are allocated to their Internal Balance, and later
// transferred or withdrawn. It can also be used as a source of tokens when joining Pools, as a destination
// when exiting them, and as either when performing swaps. This usage of Internal Balance results in greatly reduced
// gas costs when compared to relying on plain ERC20 transfers, leading to large savings for frequent users.
//
// Internal Balance management features batching, which means a single contract call can be used to perform multiple
// operations of different kinds, with different senders and recipients, at once.
/**
* @dev Returns `user`'s Internal Balance for a set of tokens.
*/
function getInternalBalance(address user, IERC20[] memory tokens) external view returns (uint256[] memory);
/**
* @dev Performs a set of user balance operations, which involve Internal Balance (deposit, withdraw or transfer)
* and plain ERC20 transfers using the Vault's allowance. This last feature is particularly useful for relayers, as
* it lets integrators reuse a user's Vault allowance.
*
* For each operation, if the caller is not `sender`, it must be an authorized relayer for them.
*/
function manageUserBalance(UserBalanceOp[] memory ops) external payable;
/**
* @dev Data for `manageUserBalance` operations, which include the possibility for ETH to be sent and received
without manual WETH wrapping or unwrapping.
*/
struct UserBalanceOp {
UserBalanceOpKind kind;
IAsset asset;
uint256 amount;
address sender;
address payable recipient;
}
// There are four possible operations in `manageUserBalance`:
//
// - DEPOSIT_INTERNAL
// Increases the Internal Balance of the `recipient` account by transferring tokens from the corresponding
// `sender`. The sender must have allowed the Vault to use their tokens via `IERC20.approve()`.
//
// ETH can be used by passing the ETH sentinel value as the asset and forwarding ETH in the call: it will be wrapped
// and deposited as WETH. Any ETH amount remaining will be sent back to the caller (not the sender, which is
// relevant for relayers).
//
// Emits an `InternalBalanceChanged` event.
//
//
// - WITHDRAW_INTERNAL
// Decreases the Internal Balance of the `sender` account by transferring tokens to the `recipient`.
//
// ETH can be used by passing the ETH sentinel value as the asset. This will deduct WETH instead, unwrap it and send
// it to the recipient as ETH.
//
// Emits an `InternalBalanceChanged` event.
//
//
// - TRANSFER_INTERNAL
// Transfers tokens from the Internal Balance of the `sender` account to the Internal Balance of `recipient`.
//
// Reverts if the ETH sentinel value is passed.
//
// Emits an `InternalBalanceChanged` event.
//
//
// - TRANSFER_EXTERNAL
// Transfers tokens from `sender` to `recipient`, using the Vault's ERC20 allowance. This is typically used by
// relayers, as it lets them reuse a user's Vault allowance.
//
// Reverts if the ETH sentinel value is passed.
//
// Emits an `ExternalBalanceTransfer` event.
enum UserBalanceOpKind { DEPOSIT_INTERNAL, WITHDRAW_INTERNAL, TRANSFER_INTERNAL, TRANSFER_EXTERNAL }
/**
* @dev Emitted when a user's Internal Balance changes, either from calls to `manageUserBalance`, or through
* interacting with Pools using Internal Balance.
*
* Because Internal Balance works exclusively with ERC20 tokens, ETH deposits and withdrawals will use the WETH
* address.
*/
event InternalBalanceChanged(address indexed user, IERC20 indexed token, int256 delta);
/**
* @dev Emitted when a user's Vault ERC20 allowance is used by the Vault to transfer tokens to an external account.
*/
event ExternalBalanceTransfer(IERC20 indexed token, address indexed sender, address recipient, uint256 amount);
// Pools
//
// There are three specialization settings for Pools, which allow for cheaper swaps at the cost of reduced
// functionality:
//
// - General: no specialization, suited for all Pools. IGeneralPool is used for swap request callbacks, passing the
// balance of all tokens in the Pool. These Pools have the largest swap costs (because of the extra storage reads),
// which increase with the number of registered tokens.
//
// - Minimal Swap Info: IMinimalSwapInfoPool is used instead of IGeneralPool, which saves gas by only passing the
// balance of the two tokens involved in the swap. This is suitable for some pricing algorithms, like the weighted
// constant product one popularized by Balancer V1. Swap costs are smaller compared to general Pools, and are
// independent of the number of registered tokens.
//
// - Two Token: only allows two tokens to be registered. This achieves the lowest possible swap gas cost. Like
// minimal swap info Pools, these are called via IMinimalSwapInfoPool.
enum PoolSpecialization { GENERAL, MINIMAL_SWAP_INFO, TWO_TOKEN }
/**
* @dev Registers the caller account as a Pool with a given specialization setting. Returns the Pool's ID, which
* is used in all Pool-related functions. Pools cannot be deregistered, nor can the Pool's specialization be
* changed.
*
* The caller is expected to be a smart contract that implements either `IGeneralPool` or `IMinimalSwapInfoPool`,
* depending on the chosen specialization setting. This contract is known as the Pool's contract.
*
* Note that the same contract may register itself as multiple Pools with unique Pool IDs, or in other words,
* multiple Pools may share the same contract.
*
* Emits a `PoolRegistered` event.
*/
function registerPool(PoolSpecialization specialization) external returns (bytes32);
/**
* @dev Emitted when a Pool is registered by calling `registerPool`.
*/
event PoolRegistered(bytes32 indexed poolId, address indexed poolAddress, PoolSpecialization specialization);
/**
* @dev Returns a Pool's contract address and specialization setting.
*/
function getPool(bytes32 poolId) external view returns (address, PoolSpecialization);
/**
* @dev Registers `tokens` for the `poolId` Pool. Must be called by the Pool's contract.
*
* Pools can only interact with tokens they have registered. Users join a Pool by transferring registered tokens,
* exit by receiving registered tokens, and can only swap registered tokens.
*
* Each token can only be registered once. For Pools with the Two Token specialization, `tokens` must have a length
* of two, that is, both tokens must be registered in the same `registerTokens` call, and they must be sorted in
* ascending order.
*
* The `tokens` and `assetManagers` arrays must have the same length, and each entry in these indicates the Asset
* Manager for the corresponding token. Asset Managers can manage a Pool's tokens via `managePoolBalance`,
* depositing and withdrawing them directly, and can even set their balance to arbitrary amounts. They are therefore
* expected to be highly secured smart contracts with sound design principles, and the decision to register an
* Asset Manager should not be made lightly.
*
* Pools can choose not to assign an Asset Manager to a given token by passing in the zero address. Once an Asset
* Manager is set, it cannot be changed except by deregistering the associated token and registering again with a
* different Asset Manager.
*
* Emits a `TokensRegistered` event.
*/
function registerTokens(
bytes32 poolId,
IERC20[] memory tokens,
address[] memory assetManagers
) external;
/**
* @dev Emitted when a Pool registers tokens by calling `registerTokens`.
*/
event TokensRegistered(bytes32 indexed poolId, IERC20[] tokens, address[] assetManagers);
/**
* @dev Deregisters `tokens` for the `poolId` Pool. Must be called by the Pool's contract.
*
* Only registered tokens (via `registerTokens`) can be deregistered. Additionally, they must have zero total
* balance. For Pools with the Two Token specialization, `tokens` must have a length of two, that is, both tokens
* must be deregistered in the same `deregisterTokens` call.
*
* A deregistered token can be re-registered later on, possibly with a different Asset Manager.
*
* Emits a `TokensDeregistered` event.
*/
function deregisterTokens(bytes32 poolId, IERC20[] memory tokens) external;
/**
* @dev Emitted when a Pool deregisters tokens by calling `deregisterTokens`.
*/
event TokensDeregistered(bytes32 indexed poolId, IERC20[] tokens);
/**
* @dev Returns detailed information for a Pool's registered token.
*
* `cash` is the number of tokens the Vault currently holds for the Pool. `managed` is the number of tokens
* withdrawn and held outside the Vault by the Pool's token Asset Manager. The Pool's total balance for `token`
* equals the sum of `cash` and `managed`.
*
* Internally, `cash` and `managed` are stored using 112 bits. No action can ever cause a Pool's token `cash`,
* `managed` or `total` balance to be greater than 2^112 - 1.
*
* `lastChangeBlock` is the number of the block in which `token`'s total balance was last modified (via either a
* join, exit, swap, or Asset Manager update). This value is useful to avoid so-called 'sandwich attacks', for
* example when developing price oracles. A change of zero (e.g. caused by a swap with amount zero) is considered a
* change for this purpose, and will update `lastChangeBlock`.
*
* `assetManager` is the Pool's token Asset Manager.
*/
function getPoolTokenInfo(bytes32 poolId, IERC20 token)
external
view
returns (
uint256 cash,
uint256 managed,
uint256 lastChangeBlock,
address assetManager
);
/**
* @dev Returns a Pool's registered tokens, the total balance for each, and the latest block when *any* of
* the tokens' `balances` changed.
*
* The order of the `tokens` array is the same order that will be used in `joinPool`, `exitPool`, as well as in all
* Pool hooks (where applicable). Calls to `registerTokens` and `deregisterTokens` may change this order.
*
* If a Pool only registers tokens once, and these are sorted in ascending order, they will be stored in the same
* order as passed to `registerTokens`.
*
* Total balances include both tokens held by the Vault and those withdrawn by the Pool's Asset Managers. These are
* the amounts used by joins, exits and swaps. For a detailed breakdown of token balances, use `getPoolTokenInfo`
* instead.
*/
function getPoolTokens(bytes32 poolId)
external
view
returns (
IERC20[] memory tokens,
uint256[] memory balances,
uint256 lastChangeBlock
);
/**
* @dev Called by users to join a Pool, which transfers tokens from `sender` into the Pool's balance. This will
* trigger custom Pool behavior, which will typically grant something in return to `recipient` - often tokenized
* Pool shares.
*
* If the caller is not `sender`, it must be an authorized relayer for them.
*
* The `assets` and `maxAmountsIn` arrays must have the same length, and each entry indicates the maximum amount
* to send for each asset. The amounts to send are decided by the Pool and not the Vault: it just enforces
* these maximums.
*
* If joining a Pool that holds WETH, it is possible to send ETH directly: the Vault will do the wrapping. To enable
* this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead of the
* WETH address. Note that it is not possible to combine ETH and WETH in the same join. Any excess ETH will be sent
* back to the caller (not the sender, which is important for relayers).
*
* `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when
* interacting with Pools that register and deregister tokens frequently. If sending ETH however, the array must be
* sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the final
* `assets` array might not be sorted. Pools with no registered tokens cannot be joined.
*
* If `fromInternalBalance` is true, the caller's Internal Balance will be preferred: ERC20 transfers will only
* be made for the difference between the requested amount and Internal Balance (if any). Note that ETH cannot be
* withdrawn from Internal Balance: attempting to do so will trigger a revert.
*
* This causes the Vault to call the `IBasePool.onJoinPool` hook on the Pool's contract, where Pools implement
* their own custom logic. This typically requires additional information from the user (such as the expected number
* of Pool shares). This can be encoded in the `userData` argument, which is ignored by the Vault and passed
* directly to the Pool's contract, as is `recipient`.
*
* Emits a `PoolBalanceChanged` event.
*/
function joinPool(
bytes32 poolId,
address sender,
address recipient,
JoinPoolRequest memory request
) external payable;
struct JoinPoolRequest {
IAsset[] assets;
uint256[] maxAmountsIn;
bytes userData;
bool fromInternalBalance;
}
/**
* @dev Called by users to exit a Pool, which transfers tokens from the Pool's balance to `recipient`. This will
* trigger custom Pool behavior, which will typically ask for something in return from `sender` - often tokenized
* Pool shares. The amount of tokens that can be withdrawn is limited by the Pool's `cash` balance (see
* `getPoolTokenInfo`).
*
* If the caller is not `sender`, it must be an authorized relayer for them.
*
* The `tokens` and `minAmountsOut` arrays must have the same length, and each entry in these indicates the minimum
* token amount to receive for each token contract. The amounts to send are decided by the Pool and not the Vault:
* it just enforces these minimums.
*
* If exiting a Pool that holds WETH, it is possible to receive ETH directly: the Vault will do the unwrapping. To
* enable this mechanism, the IAsset sentinel value (the zero address) must be passed in the `assets` array instead
* of the WETH address. Note that it is not possible to combine ETH and WETH in the same exit.
*
* `assets` must have the same length and order as the array returned by `getPoolTokens`. This prevents issues when
* interacting with Pools that register and deregister tokens frequently. If receiving ETH however, the array must
* be sorted *before* replacing the WETH address with the ETH sentinel value (the zero address), which means the
* final `assets` array might not be sorted. Pools with no registered tokens cannot be exited.
*
* If `toInternalBalance` is true, the tokens will be deposited to `recipient`'s Internal Balance. Otherwise,
* an ERC20 transfer will be performed. Note that ETH cannot be deposited to Internal Balance: attempting to
* do so will trigger a revert.
*
* `minAmountsOut` is the minimum amount of tokens the user expects to get out of the Pool, for each token in the
* `tokens` array. This array must match the Pool's registered tokens.
*
* This causes the Vault to call the `IBasePool.onExitPool` hook on the Pool's contract, where Pools implement
* their own custom logic. This typically requires additional information from the user (such as the expected number
* of Pool shares to return). This can be encoded in the `userData` argument, which is ignored by the Vault and
* passed directly to the Pool's contract.
*
* Emits a `PoolBalanceChanged` event.
*/
function exitPool(
bytes32 poolId,
address sender,
address payable recipient,
ExitPoolRequest memory request
) external;
struct ExitPoolRequest {
IAsset[] assets;
uint256[] minAmountsOut;
bytes userData;
bool toInternalBalance;
}
/**
* @dev Emitted when a user joins or exits a Pool by calling `joinPool` or `exitPool`, respectively.
*/
event PoolBalanceChanged(
bytes32 indexed poolId,
address indexed liquidityProvider,
IERC20[] tokens,
int256[] deltas,
uint256[] protocolFeeAmounts
);
enum PoolBalanceChangeKind { JOIN, EXIT }
// Swaps
//
// Users can swap tokens with Pools by calling the `swap` and `batchSwap` functions. To do this,
// they need not trust Pool contracts in any way: all security checks are made by the Vault. They must however be
// aware of the Pools' pricing algorithms in order to estimate the prices Pools will quote.
//
// The `swap` function executes a single swap, while `batchSwap` can perform multiple swaps in sequence.
// In each individual swap, tokens of one kind are sent from the sender to the Pool (this is the 'token in'),
// and tokens of another kind are sent from the Pool to the recipient in exchange (this is the 'token out').
// More complex swaps, such as one token in to multiple tokens out can be achieved by batching together
// individual swaps.
//
// There are two swap kinds:
// - 'given in' swaps, where the amount of tokens in (sent to the Pool) is known, and the Pool determines (via the
// `onSwap` hook) the amount of tokens out (to send to the recipient).
// - 'given out' swaps, where the amount of tokens out (received from the Pool) is known, and the Pool determines
// (via the `onSwap` hook) the amount of tokens in (to receive from the sender).
//
// Additionally, it is possible to chain swaps using a placeholder input amount, which the Vault replaces with
// the calculated output of the previous swap. If the previous swap was 'given in', this will be the calculated
// tokenOut amount. If the previous swap was 'given out', it will use the calculated tokenIn amount. These extended
// swaps are known as 'multihop' swaps, since they 'hop' through a number of intermediate tokens before arriving at
// the final intended token.
//
// In all cases, tokens are only transferred in and out of the Vault (or withdrawn from and deposited into Internal
// Balance) after all individual swaps have been completed, and the net token balance change computed. This makes
// certain swap patterns, such as multihops, or swaps that interact with the same token pair in multiple Pools, cost
// much less gas than they would otherwise.
//
// It also means that under certain conditions it is possible to perform arbitrage by swapping with multiple
// Pools in a way that results in net token movement out of the Vault (profit), with no tokens being sent in (only
// updating the Pool's internal accounting).
//
// To protect users from front-running or the market changing rapidly, they supply a list of 'limits' for each token
// involved in the swap, where either the maximum number of tokens to send (by passing a positive value) or the
// minimum amount of tokens to receive (by passing a negative value) is specified.
//
// Additionally, a 'deadline' timestamp can also be provided, forcing the swap to fail if it occurs after
// this point in time (e.g. if the transaction failed to be included in a block promptly).
//
// If interacting with Pools that hold WETH, it is possible to both send and receive ETH directly: the Vault will do
// the wrapping and unwrapping. To enable this mechanism, the IAsset sentinel value (the zero address) must be
// passed in the `assets` array instead of the WETH address. Note that it is possible to combine ETH and WETH in the
// same swap. Any excess ETH will be sent back to the caller (not the sender, which is relevant for relayers).
//
// Finally, Internal Balance can be used when either sending or receiving tokens.
enum SwapKind { GIVEN_IN, GIVEN_OUT }
/**
* @dev Performs a swap with a single Pool.
*
* If the swap is 'given in' (the number of tokens to send to the Pool is known), it returns the amount of tokens
* taken from the Pool, which must be greater than or equal to `limit`.
*
* If the swap is 'given out' (the number of tokens to take from the Pool is known), it returns the amount of tokens
* sent to the Pool, which must be less than or equal to `limit`.
*
* Internal Balance usage and the recipient are determined by the `funds` struct.
*
* Emits a `Swap` event.
*/
function swap(
SingleSwap memory singleSwap,
FundManagement memory funds,
uint256 limit,
uint256 deadline
) external payable returns (uint256);
/**
* @dev Data for a single swap executed by `swap`. `amount` is either `amountIn` or `amountOut` depending on
* the `kind` value.
*
* `assetIn` and `assetOut` are either token addresses, or the IAsset sentinel value for ETH (the zero address).
* Note that Pools never interact with ETH directly: it will be wrapped to or unwrapped from WETH by the Vault.
*
* The `userData` field is ignored by the Vault, but forwarded to the Pool in the `onSwap` hook, and may be
* used to extend swap behavior.
*/
struct SingleSwap {
bytes32 poolId;
SwapKind kind;
IAsset assetIn;
IAsset assetOut;
uint256 amount;
bytes userData;
}
/**
* @dev Performs a series of swaps with one or multiple Pools. In each individual swap, the caller determines either
* the amount of tokens sent to or received from the Pool, depending on the `kind` value.
*
* Returns an array with the net Vault asset balance deltas. Positive amounts represent tokens (or ETH) sent to the
* Vault, and negative amounts represent tokens (or ETH) sent by the Vault. Each delta corresponds to the asset at
* the same index in the `assets` array.
*
* Swaps are executed sequentially, in the order specified by the `swaps` array. Each array element describes a
* Pool, the token to be sent to this Pool, the token to receive from it, and an amount that is either `amountIn` or
* `amountOut` depending on the swap kind.
*
* Multihop swaps can be executed by passing an `amount` value of zero for a swap. This will cause the amount in/out
* of the previous swap to be used as the amount in for the current one. In a 'given in' swap, 'tokenIn' must equal
* the previous swap's `tokenOut`. For a 'given out' swap, `tokenOut` must equal the previous swap's `tokenIn`.
*
* The `assets` array contains the addresses of all assets involved in the swaps. These are either token addresses,
* or the IAsset sentinel value for ETH (the zero address). Each entry in the `swaps` array specifies tokens in and
* out by referencing an index in `assets`. Note that Pools never interact with ETH directly: it will be wrapped to
* or unwrapped from WETH by the Vault.
*
* Internal Balance usage, sender, and recipient are determined by the `funds` struct. The `limits` array specifies
* the minimum or maximum amount of each token the vault is allowed to transfer.
*
* `batchSwap` can be used to make a single swap, like `swap` does, but doing so requires more gas than the
* equivalent `swap` call.
*
* Emits `Swap` events.
*/
function batchSwap(
SwapKind kind,
BatchSwapStep[] memory swaps,
IAsset[] memory assets,
FundManagement memory funds,
int256[] memory limits,
uint256 deadline
) external payable returns (int256[] memory);
/**
* @dev Data for each individual swap executed by `batchSwap`. The asset in and out fields are indexes into the
* `assets` array passed to that function, and ETH assets are converted to WETH.
*
* If `amount` is zero, the multihop mechanism is used to determine the ac
Submitted on: 2025-10-29 10:45:46
Comments
Log in to comment.
No comments yet.