Skip to content
12 changes: 7 additions & 5 deletions script/10_Swap.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,30 @@ contract Swap is ScriptUtils {
string memory json = getScriptFile(inputScriptFileName);
address uniswapRouterV2 = vm.parseJsonAddress(json, ".uniswapRouterV2");
address uniswapRouterV3 = vm.parseJsonAddress(json, ".uniswapRouterV3");
address evc = vm.parseJsonAddress(json, ".evc");
address permit2 = vm.parseJsonAddress(json, ".permit2");

(swapper, swapVerifier) = execute(uniswapRouterV2, uniswapRouterV3);
(swapper, swapVerifier) = execute(evc, permit2, uniswapRouterV2, uniswapRouterV3);

string memory object;
object = vm.serializeAddress("swap", "swapper", swapper);
object = vm.serializeAddress("swap", "swapVerifier", swapVerifier);
vm.writeJson(object, string.concat(vm.projectRoot(), "/script/", outputScriptFileName));
}

function deploy(address uniswapRouterV2, address uniswapRouterV3)
function deploy(address evc, address permit2, address uniswapRouterV2, address uniswapRouterV3)
public
broadcast
returns (address swapper, address swapVerifier)
{
(swapper, swapVerifier) = execute(uniswapRouterV2, uniswapRouterV3);
(swapper, swapVerifier) = execute(evc, permit2, uniswapRouterV2, uniswapRouterV3);
}

function execute(address uniswapRouterV2, address uniswapRouterV3)
function execute(address evc, address permit2, address uniswapRouterV2, address uniswapRouterV3)
public
returns (address swapper, address swapVerifier)
{
swapper = address(new Swapper(uniswapRouterV2, uniswapRouterV3));
swapVerifier = address(new SwapVerifier());
swapVerifier = address(new SwapVerifier(evc, permit2));
}
}
2 changes: 1 addition & 1 deletion script/50_CoreAndPeriphery.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ contract CoreAndPeriphery is BatchBuilder, SafeMultisendBuilder {
console.log("+ Deploying Swapper...");
Swap deployer = new Swap();
(peripheryAddresses.swapper, peripheryAddresses.swapVerifier) =
deployer.deploy(input.uniswapV2Router, input.uniswapV3Router);
deployer.deploy(coreAddresses.evc, input.permit2, input.uniswapV2Router, input.uniswapV3Router);
} else {
console.log("- At least one of the Swapper contracts already deployed. Skipping...");
}
Expand Down
8 changes: 7 additions & 1 deletion src/Swaps/SwapVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@
pragma solidity ^0.8.0;

import {IEVault, IERC20} from "evk/EVault/IEVault.sol";
import {TransferFromSender} from "./TransferFromSender.sol";

/// @title SwapVerifier
/// @custom:security-contact [email protected]
/// @author Euler Labs (https://www.eulerlabs.com/)
/// @notice Simple contract used to verify post swap conditions
/// @dev This contract is the only trusted code in the EVK swap periphery
contract SwapVerifier {
contract SwapVerifier is TransferFromSender {
error SwapVerifier_skimMin();
error SwapVerifier_debtMax();
error SwapVerifier_pastDeadline();

/// @notice Contract constructor
/// @param evc Address of the EthereumVaultConnector contract
/// @param permit2 Address of the Permit2 contract
constructor(address evc, address permit2) TransferFromSender(evc, permit2) {}

/// @notice Verify results of a regular swap, when bought tokens are sent to the vault and skim for the buyer
/// @param vault The EVault to query
/// @param receiver Account to skim to
Expand Down
36 changes: 36 additions & 0 deletions src/Swaps/TransferFromSender.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.8.0;

import {IERC20} from "evk/EVault/IEVault.sol";
import {SafeERC20Lib} from "evk/EVault/shared/lib/SafeERC20Lib.sol";
import {EVCUtil} from "ethereum-vault-connector/utils/EVCUtil.sol";

/// @title TransferFromSender
/// @custom:security-contact [email protected]
/// @author Euler Labs (https://www.eulerlabs.com/)
/// @notice Simple contract used to pull tokens from the sender.
/// @dev This contract is trusted and can safely receive allowance on token transfers from users.
contract TransferFromSender is EVCUtil {
using SafeERC20Lib for IERC20;

error TransferFromSender_InvalidAddress();

/// @notice Address of Permit2 contract
address public immutable permit2;

/// @notice Contract constructor
/// @param _permit2 Address of the Permit2 contract
constructor(address _evc, address _permit2) EVCUtil(_evc) {
if (_permit2 == address(0)) revert TransferFromSender_InvalidAddress();
permit2 = _permit2;
}

/// @notice Pull tokens from sender to the designated receiver
/// @param token ERC20 token address
/// @param amount Amount of the token to transfer
/// @param to Receiver of the token
function transferFromSender(address token, uint256 amount, address to) public {
IERC20(token).safeTransferFrom(_msgSender(), to, amount, permit2);
}
}
77 changes: 76 additions & 1 deletion test/Swaps/Swaps1Inch.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {ISwapper} from "../../src/Swaps/ISwapper.sol";
import {Swapper} from "../../src/Swaps/Swapper.sol";
import {SwapVerifier} from "../../src/Swaps/SwapVerifier.sol";

import {Permit2ECDSASigner} from "evk-test/mocks/Permit2ECDSASigner.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";

import "./Payloads.sol";

import "forge-std/Test.sol";
Expand All @@ -25,6 +28,7 @@ contract Swaps1Inch is EVaultTestBase {
address constant uniswapRouterV2 = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
address constant uniswapRouterV3 = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
address constant uniswapRouter02 = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45;
address constant permit2Address = 0x000000000022D473030F116dDEE9F6B43aC78BA3;

address constant GRT = 0xc944E90C64B2c07662A292be6244BDf05Cda44a7;
address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
Expand Down Expand Up @@ -53,7 +57,7 @@ contract Swaps1Inch is EVaultTestBase {
user2 = makeAddr("user2");

swapper = new Swapper(uniswapRouterV2, uniswapRouterV3);
swapVerifier = new SwapVerifier();
swapVerifier = new SwapVerifier(address(evc), permit2Address);

if (bytes(FORK_RPC_URL).length != 0) {
mainnetFork = vm.createSelectFork(FORK_RPC_URL);
Expand Down Expand Up @@ -1453,4 +1457,75 @@ contract Swaps1Inch is EVaultTestBase {
assertEq(eTST.debtOf(user2), 0);
assertEq(eTST.balanceOf(user2), 2e18);
}

function test_transferFromSender_allowance() external {
assetTST.mint(user, 1e18);
assertEq(assetTST.balanceOf(address(swapper)), 0);

startHoax(user);
vm.expectRevert(); // no approvals
swapVerifier.transferFromSender(address(assetTST), 1e18, address(swapper));

assetTST.approve(address(swapVerifier), 2e18);

startHoax(user2);
vm.expectRevert(); // other users can't pull
swapVerifier.transferFromSender(address(assetTST), 1e18, address(swapper));

startHoax(user);
swapVerifier.transferFromSender(address(assetTST), 1e18, address(swapper));

assertEq(assetTST.balanceOf(address(swapper)), 1e18);
}

function test_transferFromSender_permit2() external {
assertEq(assetTST.balanceOf(address(swapper)), 0);

Permit2ECDSASigner permit2Signer = new Permit2ECDSASigner(address(permit2));

uint256 userPK = 0x123400;
address signer = vm.addr(userPK);

assetTST.mint(signer, 1e18);

startHoax(signer);
vm.expectRevert(); // no approvals
swapVerifier.transferFromSender(address(assetTST), 1e18, address(swapper));


// approve permit2 contract to spend the tokens
assetTST.approve(permit2, type(uint160).max);

// build permit2 object
IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer.PermitSingle({
details: IAllowanceTransfer.PermitDetails({
token: address(assetTST),
amount: type(uint160).max,
expiration: type(uint48).max,
nonce: 0
}),
spender: address(swapVerifier),
sigDeadline: type(uint256).max
});

IEVC.BatchItem[] memory items = new IEVC.BatchItem[](2);
items[0].onBehalfOfAccount = signer;
items[0].targetContract = permit2;
items[0].value = 0;
items[0].data = abi.encodeWithSignature(
"permit(address,((address,uint160,uint48,uint48),address,uint256),bytes)",
signer,
permitSingle,
permit2Signer.signPermitSingle(userPK, permitSingle)
);

items[1].onBehalfOfAccount = signer;
items[1].targetContract = address(swapVerifier);
items[1].value = 0;
items[1].data = abi.encodeCall(swapVerifier.transferFromSender, (address(assetTST), 1e18, address(swapper)));

evc.batch(items);

assertEq(assetTST.balanceOf(address(swapper)), 1e18);
}
}