pragma solidity =0.6.6; import '../../core/interfaces/IAstraCallee.sol'; import '../libraries/AstraLibrary.sol'; import '../interfaces/V1/IAstraV1Factory.sol'; import '../interfaces/V1/IAstraV1Exchange.sol'; import '../interfaces/IAstraRouter01.sol'; import '../interfaces/IERC20.sol'; import '../interfaces/ISAMB.sol'; contract ExampleFlashSwap is IAstraCallee { IAstraV1Factory immutable factoryV1; address immutable factory; ISAMB immutable SAMB; constructor(address _factory, address _factoryV1, address router) public { factoryV1 = IAstraV1Factory(_factoryV1); factory = _factory; SAMB = ISAMB(IAstraRouter01(router).SAMB()); } // needs to accept AMB from any V1 exchange and SAMB. ideally this could be enforced, as in the router, // but it's not possible because it requires a call to the v1 factory, which takes too much gas receive() external payable {} // gets tokens/SAMB via a V2 flash swap, swaps for the AMB/tokens on V1, repays V2, and keeps the rest! function astraCall(address sender, uint amount0, uint amount1, bytes calldata data) external override { address[] memory path = new address[](2); uint amountToken; uint amountAMB; { // scope for token{0,1}, avoids stack too deep errors address token0 = IAstraPair(msg.sender).token0(); address token1 = IAstraPair(msg.sender).token1(); assert(msg.sender == AstraLibrary.pairFor(factory, token0, token1)); // ensure that msg.sender is actually a V2 pair assert(amount0 == 0 || amount1 == 0); // this strategy is unidirectional path[0] = amount0 == 0 ? token0 : token1; path[1] = amount0 == 0 ? token1 : token0; amountToken = token0 == address(SAMB) ? amount1 : amount0; amountAMB = token0 == address(SAMB) ? amount0 : amount1; } assert(path[0] == address(SAMB) || path[1] == address(SAMB)); // this strategy only works with a V2 SAMB pair IERC20 token = IERC20(path[0] == address(SAMB) ? path[1] : path[0]); IAstraV1Exchange exchangeV1 = IAstraV1Exchange(factoryV1.getExchange(address(token))); // get V1 exchange if (amountToken > 0) { (uint minAMB) = abi.decode(data, (uint)); // slippage parameter for V1, passed in by caller token.approve(address(exchangeV1), amountToken); uint amountReceived = exchangeV1.tokenToAMBSwapInput(amountToken, minAMB, uint(-1)); uint amountRequired = AstraLibrary.getAmountsIn(factory, amountToken, path)[0]; assert(amountReceived > amountRequired); // fail if we didn't get enough AMB back to repay our flash loan SAMB.deposit{value: amountRequired}(); assert(SAMB.transfer(msg.sender, amountRequired)); // return SAMB to V2 pair (bool success,) = sender.call{value: amountReceived - amountRequired}(new bytes(0)); // keep the rest! (AMB) assert(success); } else { (uint minTokens) = abi.decode(data, (uint)); // slippage parameter for V1, passed in by caller SAMB.withdraw(amountAMB); uint amountReceived = exchangeV1.AMBToTokenSwapInput{value: amountAMB}(minTokens, uint(-1)); uint amountRequired = AstraLibrary.getAmountsIn(factory, amountAMB, path)[0]; assert(amountReceived > amountRequired); // fail if we didn't get enough tokens back to repay our flash loan assert(token.transfer(msg.sender, amountRequired)); // return tokens to V2 pair assert(token.transfer(sender, amountReceived - amountRequired)); // keep the rest! (tokens) } } }