feat: Add swap method (first attempt)

Will add tests and fullproof it in a future PR

--- don't change below this line ---
ENG-4041 Took 8 minutes

Took 42 seconds

Took 5 seconds
This commit is contained in:
Diana Carvalho
2025-01-24 17:13:30 +00:00
parent 3b2d9fcbdf
commit a8f6fc1eec
2 changed files with 112 additions and 10 deletions

40
foundry/src/Swap.sol Normal file
View File

@@ -0,0 +1,40 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;
library Swap {
/// Returns the InToken index into an array of tokens
function tokenInIndex(bytes calldata swap)
internal
pure
returns (uint8 res)
{
res = uint8(swap[0]);
}
/// The OutToken index into an array of tokens
function tokenOutIndex(bytes calldata swap)
internal
pure
returns (uint8 res)
{
res = uint8(swap[1]);
}
/// The relative amount of token quantity routed into this swap
function splitPercentage(bytes calldata swap)
internal
pure
returns (uint24 res)
{
res = uint24(bytes3(swap[2:5]));
}
/// Remaining bytes are interpreted as protocol data
function protocolData(bytes calldata swap)
internal
pure
returns (bytes calldata res)
{
res = swap[5:];
}
}

View File

@@ -4,7 +4,6 @@ pragma solidity ^0.8.28;
import "../lib/IWETH.sol"; import "../lib/IWETH.sol";
import "../lib/bytes/LibPrefixLengthEncodedByteArray.sol"; import "../lib/bytes/LibPrefixLengthEncodedByteArray.sol";
import "./CallbackVerificationDispatcher.sol"; import "./CallbackVerificationDispatcher.sol";
import "./SwapExecutionDispatcher.sol";
import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
@@ -88,22 +87,85 @@ contract TychoRouter is
} }
/** /**
* @dev Executes a swap graph supporting internal splits token amount * @dev Executes a swap graph supporting internal token amount
* splits, checking that the user gets more than minUserAmount of buyToken. * splits, checking that the user gets more than minUserAmount of buyToken.
*/ */
function swap( function swap(
uint256 amountIn, uint256 amountIn,
address tokenIn, address tokenOut,
uint256 minUserAmount, uint256 checkAmountOut,
bool wrapEth, bool wrapEth, // This means ETH is the sell token
bool unwrapEth, bool unwrapEth, // This means ETH is the buy token
uint256 nTokens, uint256 nTokens,
bytes calldata swaps, address receiver,
IAllowanceTransfer.PermitSingle calldata permitSingle, IAllowanceTransfer.PermitSingle calldata permitSingle,
bytes calldata signature bytes calldata signature,
bytes calldata swaps
) external whenNotPaused returns (uint256 amountOut) { ) external whenNotPaused returns (uint256 amountOut) {
amountOut = 0; // For native ETH, assume funds already in our router. Else, transfer and handle approval.
// TODO if (wrapEth) {
_wrapETH(amountIn);
} else {
permit2.permit(msg.sender, permitSingle, signature);
permit2.transferFrom(
msg.sender,
address(this),
uint160(amountIn),
permitSingle.details.token
);
}
amountOut = _splitSwap(amountIn, nTokens, swaps);
if (fee > 0) {
uint256 feeAmount = (amountOut * fee) / 10000;
amountOut -= feeAmount;
IERC20(tokenOut).safeTransfer(feeReceiver, feeAmount);
}
if (amountOut < checkAmountOut) {
revert TychoRouter__NegativeSlippage(amountOut, checkAmountOut);
}
if (unwrapEth) {
_unwrapETH(amountOut);
payable(receiver).transfer(amountOut);
}
}
function _splitSwap(
uint256 amountIn,
uint256 nTokens,
bytes calldata swaps_
) internal returns (uint256) {
uint256 currentAmountIn;
uint256 currentAmountOut;
uint8 tokenInIndex;
uint8 tokenOutIndex;
uint24 split;
bytes calldata swapData;
uint256[] memory remainingAmounts = new uint256[](nTokens);
uint256[] memory amounts = new uint256[](nTokens);
amounts[0] = amountIn;
remainingAmounts[0] = amountIn;
while (swaps_.length > 0) {
(swapData, swaps_) = swaps_.next();
split = swapData.splitPercentage();
tokenInIndex = swapData.tokenInIndex();
tokenOutIndex = swapData.tokenOutIndex();
currentAmountIn = split > 0
? (amounts[tokenInIndex] * split) / 0xffffff
: remainingAmounts[tokenInIndex];
currentAmountOut =
_callExecutor(currentAmountIn, swapData.protocolData());
amounts[tokenOutIndex] += currentAmountOut;
remainingAmounts[tokenOutIndex] += currentAmountOut;
remainingAmounts[tokenInIndex] -= currentAmountIn;
}
return amounts[tokenOutIndex];
} }
/** /**