/ test / Integration.t.sol
Integration.t.sol
  1  // SPDX-License-Identifier: MIT
  2  pragma solidity ^0.8.20;
  3  import "forge-std/Test.sol";
  4  
  5  import "@openzeppelin/contracts/utils/Address.sol";
  6  import "solmate/tokens/ERC20.sol";
  7  
  8  import './ChainlinkPriceFeed.sol';
  9  import './PermitSigUtils.sol';
 10  import './RewardSigUtils.sol';
 11  import "../src/GasBroker.sol";
 12  import "../src/GasProviderHelper.sol";
 13  
 14  contract IntegrationTest is Test {
 15    using Address for address payable;
 16  
 17    uint256 constant VALUE = 10e6;
 18    uint256 constant REWARD = 1e6;
 19    uint256 constant SIGNER_USDC_BALANCE = 15e6;
 20    uint256 constant SIGNER_PRIVATE_KEY = 0xA11CE;
 21    ERC20 constant usdc = ERC20(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359);//0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
 22    ERC20 constant weth = ERC20(0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270);
 23    address constant USDC_WHALE = address(0xC882b111A75C0c657fC507C04FbFcD2cC984F071);//0xDa9CE944a37d218c3302F6B82a094844C6ECEb17);
 24  
 25    address signer;
 26    uint256 deadline;
 27    IPriceOracle priceOracle;
 28    ChainlinkPriceFeed chainlinkPriceFeed;
 29    GasBroker gasBroker;
 30    PermitSigUtils permitSigUtils;
 31    RewardSigUtils rewardSigUtils;
 32  
 33  
 34    function setUp() public {
 35      chainlinkPriceFeed = new ChainlinkPriceFeed();
 36      bytes memory bytecode = abi.encodePacked(vm.getCode("PriceOracle.sol"));
 37      address deployed;
 38      assembly {
 39        deployed := create(0, add(bytecode, 0x20), mload(bytecode))
 40      }
 41      priceOracle = IPriceOracle(deployed);
 42  
 43      gasBroker = GasBroker(0x92f1C3d951018C90C364c234ff5fEE00f334072F);//new GasBroker(1, address(priceOracle));
 44  
 45      // deploy sigUtils
 46      permitSigUtils = new PermitSigUtils(usdc.DOMAIN_SEPARATOR());
 47      rewardSigUtils = new RewardSigUtils(gasBroker.DOMAIN_SEPARATOR());
 48  
 49      signer = vm.addr(SIGNER_PRIVATE_KEY);
 50      deadline = block.timestamp + 1 days;
 51  
 52      vm.prank(USDC_WHALE);
 53      usdc.transfer(signer, SIGNER_USDC_BALANCE);
 54    }
 55  
 56    function test_shouldSwapTokensToETH() public {
 57      // prepare signature for permit
 58      (uint8 permitV, bytes32 permitR, bytes32 permitS) = getPermitSignature(signer, VALUE);
 59  
 60      bytes32 permitHash = keccak256(abi.encodePacked(permitR,permitS,permitV));
 61      // prepare signature for reward
 62      (uint8 rewardV, bytes32 rewardR, bytes32 rewardS) = getRewardSignature(REWARD, permitHash);
 63      uint256 value = gasBroker.getEthAmount(address(usdc), VALUE - REWARD);
 64      gasBroker.swap{ value: value }(
 65        signer,
 66        address(usdc),
 67        VALUE,
 68        deadline,
 69        REWARD,
 70        permitV,
 71        permitR,
 72        permitS,
 73        rewardV,
 74        rewardR,
 75        rewardS
 76      );
 77  
 78      assertEq(usdc.balanceOf(address(this)), VALUE);
 79      assertEq(usdc.balanceOf(signer), SIGNER_USDC_BALANCE - VALUE);
 80      assertEq(signer.balance, value);
 81  
 82      uint256 usdWorth = chainlinkPriceFeed.getEthPriceInUsd() * signer.balance / 10**18;
 83      console2.log("10 USDC been exchanged with comission of 1 USDC to %s wei worth of %s cents", signer.balance, usdWorth);
 84  
 85    }
 86  
 87  
 88    function test_shouldSwapUsingFlashLoan() public {
 89      GasProviderHelper gasProviderHelper = new GasProviderHelper(0x92f1C3d951018C90C364c234ff5fEE00f334072F, address(usdc));
 90      (uint256 ethToSend, bytes memory swapCalldata) = getSwapCalldata();
 91      uint256 balanceBefore = address(this).balance;
 92  
 93      uint256 balanceAfter = gasProviderHelper.swapWithFlashloan(
 94        0xA374094527e1673A86dE625aa59517c5dE346d32,
 95        address(usdc),
 96        ethToSend,
 97        swapCalldata
 98      );
 99  
100      assertEq(usdc.balanceOf(address(gasProviderHelper)), 0);
101      assertEq(weth.balanceOf(address(gasProviderHelper)), 0);
102      assertEq(address(gasProviderHelper).balance, 0);
103  
104      uint256 usdWorth = chainlinkPriceFeed.getEthPriceInUsd() * signer.balance / 10**18;
105      console2.log("10K USDC been exchanged using flashloan with comission of 100 USDC to %s wei worth of %s cents", signer.balance, usdWorth);
106      uint256 profitInWei = address(this).balance - balanceBefore;
107      uint256 profitInUsd = chainlinkPriceFeed.getEthPriceInUsd() * profitInWei / 10**18;
108      console2.log("Gas provider made a profit of %s wei worth of %s cents", profitInWei, profitInUsd);
109      console2.log("Delta balance is: %s", balanceAfter - balanceBefore);
110      assertEq(profitInWei, balanceAfter - balanceBefore);
111    }
112  
113    function getSwapCalldata() private returns (uint256 ethToSend, bytes memory swapCalldata) {
114      uint256 value = 10_000 * 10**6;
115      uint256 reward = 100 * 10**6;
116      vm.prank(USDC_WHALE);
117      usdc.transfer(signer, value);
118  
119      // prepare signature for permit
120      (uint8 permitV, bytes32 permitR, bytes32 permitS) = getPermitSignature(signer, value);
121  
122      bytes32 permitHash = keccak256(abi.encodePacked(permitR,permitS,permitV));
123      // prepare signature for reward
124      (uint8 rewardV, bytes32 rewardR, bytes32 rewardS) = getRewardSignature(reward, permitHash);
125      ethToSend = gasBroker.getEthAmount(address(usdc), value - reward);
126      
127      swapCalldata = abi.encodeWithSignature(
128        "swap(address,address,uint256,uint256,uint256,uint8,bytes32,bytes32,uint8,bytes32,bytes32)",
129        signer,
130        address(usdc),
131        value,
132        deadline,
133        reward,
134        permitV,
135        permitR,
136        permitS,
137        rewardV,
138        rewardR,
139        rewardS
140      );
141    }
142  
143    function getPermitSignature(address _signer, uint256 _value) internal view returns (uint8 v, bytes32 r, bytes32 s) {
144      PermitSigUtils.Permit memory permit = PermitSigUtils.Permit({
145        owner: _signer,
146        spender: address(gasBroker),
147        value: _value,
148        nonce: 0,
149        deadline: deadline
150      });
151      bytes32 digest = permitSigUtils.getTypedDataHash(permit);
152      (v, r, s) = vm.sign(SIGNER_PRIVATE_KEY, digest);
153    }
154  
155    function getRewardSignature(uint256 reward, bytes32 permitHash) internal view returns (uint8 v, bytes32 r, bytes32 s) {
156      Reward memory reward = Reward({
157        value: reward,
158        permitHash: permitHash
159      });
160      bytes32 digest = rewardSigUtils.getTypedDataHash(reward);
161  
162      (v, r, s) = vm.sign(SIGNER_PRIVATE_KEY, digest);
163    }
164  
165    receive() external payable {}
166  
167  }