diff --git a/contracts/contracts/CrossChainSwapV2.sol b/contracts/contracts/CrossChainSwapV2.sol index 0edd059..3689efa 100644 --- a/contracts/contracts/CrossChainSwapV2.sol +++ b/contracts/contracts/CrossChainSwapV2.sol @@ -23,8 +23,9 @@ interface TokenBridge { bytes memory payload ) external payable returns (uint64); function completeTransferWithPayload( - bytes memory encodedVm - ) external returns (IWormhole.VM memory); + bytes memory encodedVm, + address feeRecipient + ) external returns (bytes memory); } @@ -77,21 +78,22 @@ contract CrossChainSwapV2 { function _getParsedPayload( bytes calldata encodedVaa, uint8 swapFunctionType, - uint8 swapCurrencyType + uint8 swapCurrencyType, + address feeRecipient ) private returns (SwapHelper.DecodedVaaParameters memory payload) { // complete the transfer on the token bridge - IWormhole.VM memory vm = TokenBridge( + bytes memory vmPayload = TokenBridge( tokenBridgeAddress - ).completeTransferWithPayload(encodedVaa); + ).completeTransferWithPayload(encodedVaa, feeRecipient); // make sure payload is the right size require( - vm.payload.length==expectedVaaLength, + vmPayload.length==expectedVaaLength, "VAA has the wrong number of bytes" ); // parse the payload - payload = SwapHelper.decodeVaaPayload(vm); + payload = SwapHelper.decodeVaaPayload(vmPayload); // sanity check payload parameters require( @@ -108,14 +110,32 @@ contract CrossChainSwapV2 { function recvAndSwapExactNativeIn( bytes calldata encodedVaa ) external payable returns (uint256[] memory amounts) { + // check token balance before redeeming the payload + (,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256)); + // redeem and fetch parsed payload SwapHelper.DecodedVaaParameters memory payload = _getParsedPayload( encodedVaa, typeExactIn, - typeNativeSwap + typeNativeSwap, + msg.sender // feeRecipient ); + // query token balance after redeeming the payload + (,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256)); + + // the balance change is the swap amount (less relayer fees) + uint256 swapAmountLessFees = balanceAfter - balanceBefore; + // create dynamic address array // uniswap won't take fixed size array address[] memory uniPath = new address[](2); @@ -132,12 +152,6 @@ contract CrossChainSwapV2 { "tokenOut must be wrapped native asset" ); - // pay relayer before attempting to do the swap - // reflect payment in second swap amount - IERC20 feeToken = IERC20(feeTokenAddress); - feeToken.safeTransfer(msg.sender, payload.relayerFee); - uint256 swapAmountLessFees = payload.swapAmount - payload.relayerFee; - // approve the router to spend tokens TransferHelper.safeApprove( uniPath[0], @@ -169,7 +183,7 @@ contract CrossChainSwapV2 { return amounts; } catch { // swap failed - return UST to recipient - feeToken.safeTransfer( + IERC20(feeTokenAddress).safeTransfer( payload.recipientAddress, swapAmountLessFees ); @@ -190,14 +204,32 @@ contract CrossChainSwapV2 { function recvAndSwapExactIn( bytes calldata encodedVaa ) external returns (uint256[] memory amounts) { + // check token balance before redeeming the payload + (,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256)); + // redeem and fetch the parsed payload SwapHelper.DecodedVaaParameters memory payload = _getParsedPayload( encodedVaa, typeExactIn, - typeTokenSwap + typeTokenSwap, + msg.sender // feeRecipient ); + // query token balance after redeeming the payload + (,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256)); + + // the balance change is the swap amount (less relayer fees) + uint256 swapAmountLessFees = balanceAfter - balanceBefore; + // create dynamic address array - uniswap won't take fixed size array address[] memory uniPath = new address[](2); uniPath[0] = payload.path[0]; @@ -206,14 +238,6 @@ contract CrossChainSwapV2 { // make sure first element in path is UST require(uniPath[0]==feeTokenAddress, "tokenIn must be UST"); - // pay relayer before attempting to do the swap - // reflect payment in second swap amount - IERC20 feeToken = IERC20(feeTokenAddress); - feeToken.safeTransfer(msg.sender, payload.relayerFee); - uint256 swapAmountLessFees = ( - payload.swapAmount - payload.relayerFee - ); - // approve the router to spend tokens TransferHelper.safeApprove( uniPath[0], @@ -241,7 +265,7 @@ contract CrossChainSwapV2 { return amounts; } catch { // swap failed - return UST to recipient - feeToken.safeTransfer( + IERC20(feeTokenAddress).safeTransfer( payload.recipientAddress, swapAmountLessFees ); @@ -430,13 +454,31 @@ contract CrossChainSwapV2 { function recvAndSwapExactNativeOut( bytes calldata encodedVaa ) external returns (uint256 amountInUsed) { + // check token balance before redeeming the payload + (,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256)); + // redeem and fetch parsed payload SwapHelper.DecodedVaaParameters memory payload = _getParsedPayload( encodedVaa, typeExactOut, - typeNativeSwap + typeNativeSwap, + msg.sender // feeRecipient ); + + // query token balance after redeeming the payload + (,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256)); + + // the balance change is the swap amount (less relayer fees) + uint256 maxAmountInLessFees = balanceAfter - balanceBefore; // amountOut is the estimated swap amount for exact out methods uint256 amountOut = payload.estimatedAmount; @@ -455,12 +497,6 @@ contract CrossChainSwapV2 { payload.path[1]==wrappedNative, "tokenOut must be wrapped native asset" ); - - // pay relayer before attempting to do the swap - // reflect payment in second swap amount - IERC20 feeToken = IERC20(feeTokenAddress); - feeToken.safeTransfer(msg.sender, payload.relayerFee); - uint256 maxAmountInLessFees = payload.swapAmount - payload.relayerFee; // approve the router to spend tokens TransferHelper.safeApprove( @@ -487,7 +523,7 @@ contract CrossChainSwapV2 { address(swapRouter), 0 ); - feeToken.safeTransfer( + IERC20(feeTokenAddress).safeTransfer( payload.recipientAddress, maxAmountInLessFees - amountInUsed ); @@ -509,7 +545,7 @@ contract CrossChainSwapV2 { return amountInUsed; } catch { // swap failed - return UST to recipient - feeToken.safeTransfer( + IERC20(feeTokenAddress).safeTransfer( payload.recipientAddress, maxAmountInLessFees ); @@ -530,13 +566,31 @@ contract CrossChainSwapV2 { function recvAndSwapExactOut( bytes calldata encodedVaa ) external returns (uint256 amountInUsed) { + // check token balance before redeeming the payload + (,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256)); + // redeem and fetch parsed payload SwapHelper.DecodedVaaParameters memory payload = _getParsedPayload( encodedVaa, typeExactOut, - typeTokenSwap + typeTokenSwap, + msg.sender // feeRecipient ); + + // query token balance after redeeming the payload + (,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256)); + + // the balance change is the swap amount (less relayer fees) + uint256 maxAmountInLessFees = balanceAfter - balanceBefore; // amountOut is the estimated swap amount for exact out methods uint256 amountOut = payload.estimatedAmount; @@ -546,12 +600,6 @@ contract CrossChainSwapV2 { uniPath[0] = payload.path[0]; uniPath[1] = payload.path[1]; require(uniPath[0]==feeTokenAddress, "tokenIn must be UST"); - - // pay relayer before attempting to do the swap - // reflect payment in second swap amount - IERC20 feeToken = IERC20(feeTokenAddress); - feeToken.safeTransfer(msg.sender, payload.relayerFee); - uint256 maxAmountInLessFees = payload.swapAmount - payload.relayerFee; // approve the router to spend tokens TransferHelper.safeApprove( @@ -578,7 +626,7 @@ contract CrossChainSwapV2 { address(swapRouter), 0 ); - feeToken.safeTransfer( + IERC20(feeTokenAddress).safeTransfer( payload.recipientAddress, maxAmountInLessFees - amountInUsed ); @@ -596,7 +644,7 @@ contract CrossChainSwapV2 { return amountInUsed; } catch { // swap failed - return UST to recipient - feeToken.safeTransfer( + IERC20(feeTokenAddress).safeTransfer( payload.recipientAddress, maxAmountInLessFees ); diff --git a/contracts/contracts/CrossChainSwapV3.sol b/contracts/contracts/CrossChainSwapV3.sol index 373e119..5ca0368 100644 --- a/contracts/contracts/CrossChainSwapV3.sol +++ b/contracts/contracts/CrossChainSwapV3.sol @@ -23,8 +23,9 @@ interface TokenBridge { bytes memory payload ) external payable returns (uint64); function completeTransferWithPayload( - bytes memory encodedVm - ) external returns (IWormhole.VM memory); + bytes memory encodedVm, + address feeRecipient + ) external returns (bytes memory); } @@ -81,21 +82,22 @@ contract CrossChainSwapV3 { function _getParsedPayload( bytes calldata encodedVaa, uint8 swapFunctionType, - uint8 swapCurrencyType + uint8 swapCurrencyType, + address feeRecipient ) private returns (SwapHelper.DecodedVaaParameters memory payload) { // complete the transfer on the token bridge - IWormhole.VM memory vm = TokenBridge( + bytes memory vmPayload = TokenBridge( tokenBridgeAddress - ).completeTransferWithPayload(encodedVaa); + ).completeTransferWithPayload(encodedVaa, feeRecipient); // make sure payload is the right size require( - vm.payload.length==expectedVaaLength, + vmPayload.length==expectedVaaLength, "VAA has the wrong number of bytes" ); // parse the payload - payload = SwapHelper.decodeVaaPayload(vm); + payload = SwapHelper.decodeVaaPayload(vmPayload); // sanity check payload parameters require( @@ -112,14 +114,31 @@ contract CrossChainSwapV3 { function recvAndSwapExactNativeIn( bytes calldata encodedVaa ) external returns (uint256 amountOut) { - // redeem and fetch parsed payload + // check token balance before redeeming the payload + (,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256)); + SwapHelper.DecodedVaaParameters memory payload = _getParsedPayload( encodedVaa, typeExactIn, - typeNativeSwap + typeNativeSwap, + msg.sender // feeRecipient ); + // query token balance after redeeming the payload + (,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256)); + + // the balance change is the swap amount (less relayer fees) + uint256 swapAmountLessFees = balanceAfter - balanceBefore; + // sanity check path require( payload.path[0]==feeTokenAddress, @@ -130,12 +149,6 @@ contract CrossChainSwapV3 { "tokenOut must be wrapped Native" ); - // pay relayer before attempting to do the swap - // reflect payment in second swap amount - IERC20 feeToken = IERC20(feeTokenAddress); - feeToken.safeTransfer(msg.sender, payload.relayerFee); - uint256 swapAmountLessFees = payload.swapAmount - payload.relayerFee; - // approve the router to spend tokens TransferHelper.safeApprove( payload.path[0], @@ -174,7 +187,7 @@ contract CrossChainSwapV3 { return amountOut; } catch { // swap failed - return UST to recipient - feeToken.safeTransfer( + IERC20(feeTokenAddress).safeTransfer( payload.recipientAddress, swapAmountLessFees ); @@ -195,23 +208,35 @@ contract CrossChainSwapV3 { function recvAndSwapExactIn( bytes calldata encodedVaa ) external returns (uint256 amountOut) { + // check token balance before redeeming the payload + (,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256)); + // redeem and fetch the parsed payload SwapHelper.DecodedVaaParameters memory payload = _getParsedPayload( encodedVaa, typeExactIn, - typeTokenSwap + typeTokenSwap, + msg.sender // feeRecipient ); + // query token balance after redeeming the payload + (,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256)); + + // the balance change is the swap amount (less relayer fees) + uint256 swapAmountLessFees = balanceAfter - balanceBefore; + // check path to see if first element is the feeToken require(payload.path[0]==feeTokenAddress, "tokenIn must be UST"); - // pay relayer before attempting to do the swap - // reflect payment in second swap amount - IERC20 feeToken = IERC20(feeTokenAddress); - feeToken.safeTransfer(msg.sender, payload.relayerFee); - uint256 swapAmountLessFees = payload.swapAmount - payload.relayerFee; - // approve the router to spend tokens TransferHelper.safeApprove( payload.path[0], @@ -246,7 +271,7 @@ contract CrossChainSwapV3 { return amountOut; } catch { // swap failed - return UST to recipient - feeToken.safeTransfer( + IERC20(feeTokenAddress).safeTransfer( payload.recipientAddress, swapAmountLessFees ); @@ -448,14 +473,32 @@ contract CrossChainSwapV3 { function recvAndSwapExactNativeOut( bytes calldata encodedVaa ) external returns (uint256 amountInUsed) { + // check token balance before redeeming the payload + (,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256)); + // redeem and fetch parsed payload SwapHelper.DecodedVaaParameters memory payload = _getParsedPayload( encodedVaa, typeExactOut, - typeNativeSwap + typeNativeSwap, + msg.sender // feeRecipient ); + // query token balance after redeeming the payload + (,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256)); + + // the balance change is the swap amount (less relayer fees) + uint256 maxAmountInLessFees = balanceAfter - balanceBefore; + // sanity check path require( payload.path[0]==feeTokenAddress, @@ -469,12 +512,6 @@ contract CrossChainSwapV3 { // amountOut is the estimated swap amount for exact out methods uint256 amountOut = payload.estimatedAmount; - // pay relayer before attempting to do the swap - // reflect payment in second swap amount - IERC20 feeToken = IERC20(feeTokenAddress); - feeToken.safeTransfer(msg.sender, payload.relayerFee); - uint256 maxAmountInLessFees = payload.swapAmount - payload.relayerFee; - // approve the router to spend tokens TransferHelper.safeApprove( payload.path[0], @@ -504,7 +541,7 @@ contract CrossChainSwapV3 { address(swapRouter), 0 ); - feeToken.safeTransfer( + IERC20(feeTokenAddress).safeTransfer( payload.recipientAddress, maxAmountInLessFees - amountInUsed ); @@ -526,7 +563,7 @@ contract CrossChainSwapV3 { return amountInUsed; } catch { // swap failed - return UST to recipient - feeToken.safeTransfer( + IERC20(feeTokenAddress).safeTransfer( payload.recipientAddress, maxAmountInLessFees ); @@ -547,13 +584,31 @@ contract CrossChainSwapV3 { function recvAndSwapExactOut( bytes calldata encodedVaa ) external returns (uint256 amountInUsed) { + // check token balance before redeeming the payload + (,bytes memory queriedBalanceBefore) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256)); + // redeem and fetch parsed payload SwapHelper.DecodedVaaParameters memory payload = _getParsedPayload( encodedVaa, typeExactOut, - typeTokenSwap + typeTokenSwap, + msg.sender // feeRecipient ); + + // query token balance after redeeming the payload + (,bytes memory queriedBalanceAfter) = feeTokenAddress.staticcall( + abi.encodeWithSelector(IERC20.balanceOf.selector, + address(this) + )); + uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256)); + + // the balance change is the swap amount (less relayer fees) + uint256 maxAmountInLessFees = balanceAfter - balanceBefore; // check path to see if first element is the feeToken require(payload.path[0]==feeTokenAddress, "tokenIn must be UST"); @@ -561,12 +616,6 @@ contract CrossChainSwapV3 { // amountOut is the estimated swap amount for exact out methods uint256 amountOut = payload.estimatedAmount; - // pay relayer before attempting to do the swap - // reflect payment in second swap amount - IERC20 feeToken = IERC20(feeTokenAddress); - feeToken.safeTransfer(msg.sender, payload.relayerFee); - uint256 maxAmountInLessFees = payload.swapAmount - payload.relayerFee; - // approve the router to spend tokens TransferHelper.safeApprove( payload.path[0], @@ -596,7 +645,7 @@ contract CrossChainSwapV3 { address(swapRouter), 0 ); - feeToken.safeTransfer( + IERC20(feeTokenAddress).safeTransfer( payload.recipientAddress, maxAmountInLessFees - amountInUsed ); @@ -614,7 +663,7 @@ contract CrossChainSwapV3 { return amountInUsed; } catch { // swap failed - return UST to recipient - feeToken.safeTransfer( + IERC20(feeTokenAddress).safeTransfer( payload.recipientAddress, maxAmountInLessFees ); diff --git a/contracts/contracts/SwapHelper.sol b/contracts/contracts/SwapHelper.sol index c9e925c..4b41f72 100644 --- a/contracts/contracts/SwapHelper.sol +++ b/contracts/contracts/SwapHelper.sol @@ -52,52 +52,52 @@ library SwapHelper { /// @dev Decodes parameters encoded in a VAA function decodeVaaPayload( - IWormhole.VM memory encodedVm + bytes memory vmPayload ) public view returns (DecodedVaaParameters memory decoded) { uint index = 0; - decoded.version = encodedVm.payload.toUint8(index); + decoded.version = vmPayload.toUint8(index); index += 1; - decoded.swapAmount = encodedVm.payload.toUint256(index); + decoded.swapAmount = vmPayload.toUint256(index); index += 32; // skip index += 46; - decoded.contractAddress = encodedVm.payload.toAddress(index); + decoded.contractAddress = vmPayload.toAddress(index); index += 20; // skip index += 2; - decoded.relayerFee = encodedVm.payload.toUint256(index); + decoded.relayerFee = vmPayload.toUint256(index); index += 32; - decoded.estimatedAmount = encodedVm.payload.toUint256(index); + decoded.estimatedAmount = vmPayload.toUint256(index); index += 44; - decoded.recipientAddress = encodedVm.payload.toAddress(index); + decoded.recipientAddress = vmPayload.toAddress(index); index += 20; - decoded.path[0] = encodedVm.payload.toAddress(index); + decoded.path[0] = vmPayload.toAddress(index); index += 20; - decoded.path[1] = encodedVm.payload.toAddress(index); + decoded.path[1] = vmPayload.toAddress(index); index += 20; - decoded.deadline = encodedVm.payload.toUint256(index); + decoded.deadline = vmPayload.toUint256(index); index += 32; // skip index += 1; - decoded.poolFee = encodedVm.payload.toUint16(index); + decoded.poolFee = vmPayload.toUint16(index); index += 2; - decoded.swapFunctionType = encodedVm.payload.toUint8(index); + decoded.swapFunctionType = vmPayload.toUint8(index); index += 1; - decoded.swapCurrencyType = encodedVm.payload.toUint8(index); + decoded.swapCurrencyType = vmPayload.toUint8(index); } } \ No newline at end of file diff --git a/contracts/deploy_v3.sh b/contracts/deploy_v3.sh index 53fc86a..9b22cd9 100755 --- a/contracts/deploy_v3.sh +++ b/contracts/deploy_v3.sh @@ -1,4 +1,4 @@ #!/bin/bash set -euo pipefail -npx truffle migrate --config truffle-config.ethereum.js --network goerli --reset \ No newline at end of file +npx truffle migrate --config cfg/truffle-config.ethereum.js --network goerli --reset