Skip to content

Commit

Permalink
Casweeney/token gateway swap test (#153)
Browse files Browse the repository at this point in the history
Co-authored-by: Seun Lanlege <[email protected]>
  • Loading branch information
casweeney and seunlanlege authored Apr 6, 2024
1 parent 639e4fd commit 5d053bc
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 11 deletions.
16 changes: 8 additions & 8 deletions evm/src/modules/TokenGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ struct TeleportParams {
uint64 timeout;
// destination contract call data
bytes data;
// calculated amountInMax:
// used if selected fee token is not expected fee token
uint256 amountInMax;
}

struct Body {
Expand Down Expand Up @@ -208,7 +211,7 @@ contract TokenGateway is BaseIsmpModule {

// only swap if the feeToken is not the token intended for fee
if (feeToken != params.feeToken) {
require(handleSwap(from, params.feeToken, feeToken, _fee), "Token swap failed");
require(handleSwap(from, params.feeToken, feeToken, _fee, params.amountInMax), "Token swap failed");
}
} else if (erc6160 != address(0)) {
IERC6160Ext20(erc6160).burn(from, params.amount, "");
Expand Down Expand Up @@ -341,24 +344,21 @@ contract TokenGateway is BaseIsmpModule {
}
}

function handleSwap(address _sender, address _fromToken, address _toToken, uint256 _toTokenAmountOut)
function handleSwap(address _sender, address _fromToken, address _toToken, uint256 _toTokenAmountOut, uint256 _amountInMax)
private
returns (bool)
{
address[] memory path = new address[](2);
path[0] = _fromToken;
path[1] = _toToken;

uint256 _fromTokenAmountIn = _uniswapV2Router.getAmountsIn(_toTokenAmountOut, path)[0];

// How do we handle cases of slippage - Todo: Handle Slippage
require(
IERC20(_fromToken).transferFrom(_sender, address(this), _fromTokenAmountIn),
IERC20(_fromToken).transferFrom(_sender, address(this), _amountInMax),
"insufficient intended fee token"
);
require(IERC20(_fromToken).approve(address(_uniswapV2Router), _fromTokenAmountIn), "approve failed.");
require(IERC20(_fromToken).approve(address(_uniswapV2Router), _amountInMax), "approve failed.");

_uniswapV2Router.swapTokensForExactTokens(_toTokenAmountOut, _fromTokenAmountIn, path, _sender, block.timestamp);
_uniswapV2Router.swapTokensForExactTokens(_toTokenAmountOut, _amountInMax, path, _sender, block.timestamp + 300);

return true;
}
Expand Down
106 changes: 106 additions & 0 deletions evm/test/MainnetForkBaseTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;

import "forge-std/Test.sol";
import {TestConsensusClient} from "./TestConsensusClient.sol";
import {TestHost} from "./TestHost.sol";
import {PingModule} from "../examples/PingModule.sol";
import {HandlerV1} from "../src/modules/HandlerV1.sol";
import {CallDispatcher} from "../src/modules/CallDispatcher.sol";
import {FeeToken} from "./FeeToken.sol";
import {HostParams} from "../src/hosts/EvmHost.sol";
import {HostManagerParams, HostManager} from "../src/modules/HostManager.sol";
import {TokenGateway, Asset, InitParams} from "../src/modules/TokenGateway.sol";
import {ERC6160Ext20} from "ERC6160/tokens/ERC6160Ext20.sol";
import {StateMachine} from "ismp/StateMachine.sol";
import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol";
import {IUniswapV2Router} from "../src/interfaces/IUniswapV2Router.sol";

contract MainnetForkBaseTest is Test {
/// @notice The Id of Role required to mint token
bytes32 public constant MINTER_ROLE = keccak256("MINTER ROLE");

/// @notice The Id of Role required to burn token
bytes32 public constant BURNER_ROLE = keccak256("BURNER ROLE");

// needs a test method so that integration-tests can detect it
function testPostTimeout() public {}

TestConsensusClient internal consensusClient;
TestHost internal host;
HandlerV1 internal handler;
PingModule internal testModule;
TokenGateway internal gateway;
IERC20 internal usdc;
IERC20 internal dai;
IERC20 internal feeToken;
IUniswapV2Router internal _uniswapV2Router;

uint256 internal mainnetFork;

function setUp() public virtual {
usdc = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
feeToken = dai;
_uniswapV2Router = IUniswapV2Router(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);


string memory fork_url = vm.envString("MAINNET_FORK_URL");

// mainnet fork creation
mainnetFork = vm.createFork(fork_url);

// mainnet fork selection
vm.selectFork(mainnetFork);

consensusClient = new TestConsensusClient();
handler = new HandlerV1();
CallDispatcher dispatcher = new CallDispatcher();

HostManagerParams memory gParams = HostManagerParams({admin: address(this), host: address(0)});
HostManager manager = new HostManager(gParams);

HostParams memory params = HostParams({
admin: address(0),
hostManager: address(manager),
handler: address(handler),
defaultTimeout: 0,
unStakingPeriod: 5000,
// for this test
challengePeriod: 0,
consensusClient: address(consensusClient),
lastUpdated: 0,
consensusState: new bytes(0),
baseGetRequestFee: 10000000000000000000,
perByteFee: 1000000000000000000, // 1FTK
feeTokenAddress: address(feeToken),
latestStateMachineHeight: 0,
hyperbridge: StateMachine.kusama(2000)
});

host = new TestHost(params);

testModule = new PingModule(address(this));
testModule.setIsmpHost(address(host));
manager.setIsmpHost(address(host));
gateway = new TokenGateway(address(this));
Asset[] memory assets = new Asset[](1);
assets[0] = Asset({identifier: keccak256("USD.h"), erc20: address(feeToken), erc6160: address(0)});

gateway.init(
InitParams({
hyperbridge: StateMachine.kusama(2000),
host: address(host),
uniswapV2Router: 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D,
protocolFeePercentage: 100, // 0.1
relayerFeePercentage: 300, // 0.3
assets: assets,
callDispatcher: address(dispatcher)
})
);
}

function module() public view returns (address) {
return address(testModule);
}
}
57 changes: 57 additions & 0 deletions evm/test/TeleportSwapTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;

import "forge-std/Test.sol";
import "forge-std/console.sol";

import {MainnetForkBaseTest} from "./MainnetForkBaseTest.sol";
import {GetResponseMessage, GetTimeoutMessage, GetRequest, PostRequest, Message} from "ismp/Message.sol";
import {TeleportParams, Body, BODY_BYTES_SIZE} from "../src/modules/TokenGateway.sol";
import {StateMachine} from "ismp/StateMachine.sol";

contract TeleportSwapTest is MainnetForkBaseTest {
// Maximum slippage of 0.5%
uint256 maxSlippagePercentage = 50; // 0.5 * 100

function testCanTeleportAssetsUsingUsdcForFee() public {
address mainnetUsdcHolder = address(0xf584F8728B874a6a5c7A8d4d387C9aae9172D621);

// relayer fee + per-byte fee
uint256 messagingFee = (9 * 1e17) + (BODY_BYTES_SIZE * host.perByteFee());

address[] memory path = new address[](2);
path[0] = address(usdc);
path[1] = address(feeToken);

uint256 _fromTokenAmountIn = _uniswapV2Router.getAmountsIn(messagingFee, path)[0];

// Handling Slippage Implementation
uint _slippageAmount = (_fromTokenAmountIn * maxSlippagePercentage) / 10000; // Adjusted for percentage times 100
uint _amountInMax = _fromTokenAmountIn + _slippageAmount;

// mainnet forking - impersonation
vm.startPrank(mainnetUsdcHolder);

dai.approve(address(gateway), 10000 * 1e18);
dai.approve(address(host), messagingFee);
usdc.approve(address(gateway), 10000 * 1e18);

gateway.teleport(
TeleportParams({
feeToken: address(usdc),
amount: 1000 * 1e18, // $1000
redeem: false,
dest: StateMachine.bsc(),
fee: 9 * 1e17, // $0.9
timeout: 0,
to: address(this),
assetId: keccak256("USD.h"),
data: new bytes(0),
amountInMax: _amountInMax
})
);

assert(feeToken.balanceOf(address(this)) == 0);
assert(feeToken.balanceOf(address(host)) == messagingFee);
}
}
9 changes: 6 additions & 3 deletions evm/test/TokenGatewayTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ contract TokenGatewayTest is BaseTest {
timeout: 0,
to: address(this),
assetId: keccak256("USD.h"),
data: new bytes(0)
data: new bytes(0),
amountInMax: 0
})
);

Expand Down Expand Up @@ -56,7 +57,8 @@ contract TokenGatewayTest is BaseTest {
timeout: 0,
to: address(miniStaking),
assetId: keccak256("USD.h"),
data: stakeCalldata
data: stakeCalldata,
amountInMax: 0
})
);

Expand All @@ -78,7 +80,8 @@ contract TokenGatewayTest is BaseTest {
timeout: 0,
to: address(this),
assetId: keccak256("USD.h"),
data: new bytes(0)
data: new bytes(0),
amountInMax: 0
})
);
}
Expand Down

0 comments on commit 5d053bc

Please sign in to comment.