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 }