function swapTokensGeneric(LiFiData memory _lifiData, LibSwap.SwapData[] calldata _swapData) public payable {
uint256 receivingAssetIdBalance = LibAsset.getOwnBalance(_lifiData.receivingAssetId);
// Swap
_executeSwaps(_lifiData, _swapData);
uint256 postSwapBalance = LibAsset.getOwnBalance(_lifiData.receivingAssetId) - receivingAssetIdBalance;
LibAsset.transferAsset(_lifiData.receivingAssetId, payable(msg.sender), postSwapBalance);
emit LiFiTransferStarted(
_lifiData.transactionId,
_lifiData.integrator,
_lifiData.referrer,
_lifiData.sendingAssetId,
_lifiData.receivingAssetId,
_lifiData.receiver,
_lifiData.amount,
_lifiData.destinationChainId,
block.timestamp
);
}
function _executeSwaps(LiFiData memory _lifiData, LibSwap.SwapData[] calldata _swapData) internal {
// Swap
for (uint8 i; i < _swapData.length; i++) {
require(
ls.dexWhitelist[_swapData[i].approveTo] == true && ls.dexWhitelist[_swapData[i].callTo] == true,
"Contract call not allowed!"
);
LibSwap.swap(_lifiData.transactionId, _swapData[i]);
}
}
function swap(bytes32 transactionId, SwapData calldata _swapData) internal {
uint256 fromAmount = _swapData.fromAmount;
uint256 toAmount = LibAsset.getOwnBalance(_swapData.receivingAssetId);
address fromAssetId = _swapData.sendingAssetId;
if (!LibAsset.isNativeAsset(fromAssetId) && LibAsset.getOwnBalance(fromAssetId) < fromAmount) {
LibAsset.transferFromERC20(fromAssetId, msg.sender, address(this), fromAmount);
}
if (!LibAsset.isNativeAsset(fromAssetId)) {
LibAsset.approveERC20(IERC20(fromAssetId), _swapData.approveTo, fromAmount);
}
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory res) = _swapData.callTo.call{ value: msg.value }(_swapData.callData);
if (!success) {
string memory reason = LibUtil.getRevertMsg(res);
revert(reason);
}
toAmount = LibAsset.getOwnBalance(_swapData.receivingAssetId) - toAmount;
emit AssetSwapped(
transactionId,
_swapData.callTo,
_swapData.sendingAssetId,
_swapData.receivingAssetId,
fromAmount,
toAmount,
block.timestamp
);
}
In the AnyswapFacet.sol, _anyswapData.router is from the callerβs calldata, which can really be any contract, including a fake Anyswap router contract, as long as it complies to the interfaces used.
And in _startBridge, it will grant infinite approval for the _anyswapData.token to the _anyswapData.router.
This makes it possible for a attacker to steal all the funds from the contract.
Which we explained in [WP-H6], the diamond contract may be holding some funds for various of reasons.
Given:
The attacker can submit a swapTokensGeneric() with 0.1 ETH as receivingAssetId with the following SwapData[]:
1. Swap 0.1 ETH to USDC;
2. Swap 0.1 ETH to USDC.
The attacker will receive ~2,000 USDC. 1,000 of which is the 0.1 ETH stolen from the contract.
function swap(bytes32 transactionId, SwapData calldata _swapData) internal {
uint256 fromAmount = _swapData.fromAmount;
uint256 toAmount = LibAsset.getOwnBalance(_swapData.receivingAssetId);
address fromAssetId = _swapData.sendingAssetId;
if (!LibAsset.isNativeAsset(fromAssetId) && LibAsset.getOwnBalance(fromAssetId) < fromAmount) {
LibAsset.transferFromERC20(fromAssetId, msg.sender, address(this), fromAmount);
}
uint256 callValue = msg.value;
if (!LibAsset.isNativeAsset(fromAssetId)) {
callValue = 0;
LibAsset.approveERC20(IERC20(fromAssetId), _swapData.approveTo, fromAmount);
}
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory res) = _swapData.callTo.call{ value: callValue }(_swapData.callData);
if (!success) {
string memory reason = LibUtil.getRevertMsg(res);
revert(reason);
}
The text was updated successfully, but these errors were encountered:
All reactions