Overview
This audit reviews the LayerswapDepository, a smart contract that forwards native and ERC20 tokens to whitelisted Layerswap receiver addresses. It accepts deposits from users and forwards funds immediately to pre-approved (whitelisted) Layerswap solver addresses. The contract never holds funds, as all assets are forwarded atomically within the same transaction. Our review was conducted over a one-day period and involved a comprehensive analysis of the contract. During the assessment, we did not identif y any high- or medium-severity vulnerabilities. We reported three informational findings. All identified issues were remediated by the development team and subsequently verified by our auditors. Following the remediation phase, we conclude that the protocol's overall security posture and code quality have improved as a result of this audit.
Scope
The analyzed resources are located on:
The issues described in this report were fixed in the following commit:
https://github.com/layerswap/layerswap-depository/commit/a7a4ccd89f0fb5046f8d0053283da6e36c6b638c
Summary
Weaknesses
This section contains the list of discovered weaknesses.
LYSWP3-1 | UPDATEWHITELISTEDADDRESS SHOULD REVERT IF OLDADDR AND NEWADDR ARE IDENTICAL
Severity:
Status:
Fixed
Path:
src/LayerswapDepository.sol#L103
Description:
In the updateWhitelistedAddress() function, when oldAddr == newAddr, the function still succeeds: the address is removed from the set and then re-added, and AddressUpdatedInWhitelist(addr, addr) is emitted. Although the whitelist remains unchanged, the emitted event is misleading.
function updateWhitelistedAddress(address oldAddr, address newAddr) external onlyOwner {
if (!_whitelist.remove(oldAddr)) revert NotWhitelisted();
if (newAddr == address(0)) revert ZeroAddress();
if (newAddr == address(this)) revert InvalidReceiver();
if (!_whitelist.add(newAddr)) revert AlreadyWhitelisted();
emit AddressUpdatedInWhitelist(oldAddr, newAddr);
}
Remediation:
Consider adding a check to revert when both addresses are the same:
function updateWhitelistedAddress(address oldAddr, address newAddr) external onlyOwner {
+ if (oldAddr == newAddr) revert InvalidReceiver();
if (!_whitelist.remove(oldAddr)) revert NotWhitelisted();
if (newAddr == address(0)) revert ZeroAddress();
if (newAddr == address(this)) revert InvalidReceiver();
if (!_whitelist.add(newAddr)) revert AlreadyWhitelisted();
emit AddressUpdatedInWhitelist(oldAddr, newAddr);
}
LYSWP3-2 | USE OWNABLE2STEP INSTEAD OF OWNABLE FOR BETTER SECURITY
Severity:
Status:
Fixed
Path:
src/LayerswapDepository.sol#L15
Description:
The LayerswapDepository contract inherits from OpenZeppelin's Ownable contract, which allows for immediate ownership transfers. This creates a risk where ownership could be accidentally transferred to an incorrect or inaccessible address, resulting in the contract becoming permanently locked without an owner.
contract LayerswapDepository is Ownable, Pausable, ReentrancyGuard {
Remediation:
Consider replacing the Ownable inheritance with Ownable2Step:
- import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
+ import { Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
- contract LayerswapDepository is Ownable, Pausable, ReentrancyGuard {
+ contract LayerswapDepository is Ownable2Step, Pausable, ReentrancyGuard {
This change maintains the same functionality while adding an additional security layer to ownership transfers. The new owner will need to call acceptOwnership() to complete the transfer process, preventing accidental transfers to incorrect addresses.
LYSWP3-3 | DEPOSITERC20 WITH FEE-ON-TRANSFER TOKENS WILL EMIT AN INCORRECT AMOUNT
Severity:
Status:
Fixed
Path:
src/LayerswapDepository.sol#L80-L82
Description:
In the depositERC20() function, if the token is a fee-on-transfer token, the receiver will receive less than amount, but the event logs the full amount. The off-chain system would have an incorrect amount of the actual transfer.
/// @notice Forwards ERC20 tokens from caller to a whitelisted receiver.
/// Caller must approve this contract before calling.
/// @param id Unique identifier for this deposit (correlates with off-chain order)
/// @param token ERC20 token address
/// @param receiver Whitelisted address to receive the funds
/// @param amount Amount of tokens to forward
function depositERC20(bytes32 id, address token, address receiver, uint256 amount)
external
nonReentrant
whenNotPaused
{
if (token == address(0)) revert ZeroAddress();
if (!_whitelist.contains(receiver)) revert NotWhitelisted();
if (amount == 0) revert ZeroAmount();
// Emit before external call (CEI pattern)
emit Deposited(id, token, receiver, amount);
IERC20(token).safeTransferFrom(msg.sender, receiver, amount);
}
Remediation:
Consider measuring the balance delta and emit the actual received amount.