On 2019/09/13, the AirSwap team announced a critical vulnerability in a new AirSwap smart contract, which could allow attackers to perform a swap without requiring a signature from a counter-party. In other words, users, if affected, might get their assets lost and thus are strongly suggested to take the remedy procedure outlined in the medium announcement. As of today, the vulnerable smart contract is no longer used in AirSwap Instant and Trader products, but users might have interacted with the vulnerable smart contract earlier and some of them could still find themselves vulnerable (and their assets could be at risk).

Figure 1: AirSwap Logo From Its Official Home-page

The details of the related vulnerability have not been released yet. After reviewing the original AirSwap contract and related wrappers, researchers at PeckShield have independently identified the bug. As claimed in the above medium post, the vulnerability (we named it ItchySwap) is critical and could be used to steal all assets of affected users.

Here we will disclose the details, and hope each affected user could take necessary remedy procedure ASAP.


AirSwap is a decentralized, peer-to-peer token trading network built on the Ethereum blockchain. It centralizes on the Swap Protocol, which acts as an “automated escrow” that allows maker and taker to safely trade any two tokenized assets on Ethereum.

Different from many decentralized exchanges that, although not holding custodial control over funds, still have a centralized order book for matching purposes, AirSwap instead implements a full peer-to-peer model for both trading and order matching.

Particularly, there is an entity named indexer, which provides an off-chain service to aggregate traders’ intentions from both maker and taker, and then provide matching services for them. In particular, once a taker has found a suitable maker, they proceed to negotiate for the trade price off-chain. Once an agreement is reached, the order is filled and settled on-chain through the Swap Protocol smart contract. The interaction of maker, taker, and indexer is illustrated in the following diagram.

Figure 2: The Interaction of AirSwap Components

If we take a closer look in the Swap smart contract, the main functionality lies in the swap function, i.e., swap(Types.Order calldata _order).

This swap function basically performs a number of validity checks of the provided order to:

  • Ensure the order is not expired;
  • Ensure the order is not already taken;
  • Ensure the order is not already canceled; and
  • Ensure the order nonce is above the minimum.

After that, it validates both trading sides, including the taker and the maker. For the taker, if its address has not been specified in the order, the transaction sender will be assumed as the taker.

// Validate the taker side of the trade.
address finalTakerWallet;

if (_order.taker.wallet == address(0)) {
    * Taker is not specified. The sender of the transaction becomes
    * the taker of the _order.
  finalTakerWallet = msg.sender;

} else {
    * Taker is specified. If the sender is not the specified taker,
    * determine whether the sender has been authorized by the taker.
  if (msg.sender != _order.taker.wallet) {
    require(isAuthorized(_order.taker.wallet, msg.sender),
  // The specified taker is all clear.
  finalTakerWallet = _order.taker.wallet;

For the maker, if its signature has not been specified in the order, swap ensures the transaction sender has the maker’s authorization.

// Validate the maker side of the trade.
if (_order.signature.v == 0) {
    * Signature is not provided. The maker may have authorized the sender
    * to swap on its behalf, which does not require a signature.
  require(isAuthorized(_order.maker.wallet, msg.sender),

The above sanity checks are sound and swap function per se is safe for the subsequent token-swap operations (implemented in the transferToken() function).

Unfortunately, when interacting with the Wrapper contract that was deployed on 2019/9/10 08:03:10 PM (+UTC), the above sanity checks could be bypassed.

Specifically, the Wrapper contract wraps the swap function and, for it to work, further requires the user to authorize the Wrapper as her delegate, i.e., satisfying the above taker’s requirement in require(isAuthorized(_order.taker.wallet, msg.sender).

* @notice Send an Order
* @dev Taker must authorize this contract on the swapContract
* @dev Taker must approve this contract on the wethContract
* @param _order Types.Order
function swap(Types.Order calldata _order) external payable {
    // Ensure message sender is taker wallet.
    require(_order.taker.wallet == msg.sender,
    // Perform the swap.

Once the Wrapper has been authorized as a user’s delegate, an attacker can abuse the authorization relationship by crafting a legitimate order that:

  • Specifies the authorizing user as the maker with all holding tokens of a particular asset as the maker param for swap;

  • Specifies the attacker herself as the taker with zero as the taker token amount – param – for swap.

  • Specifies the signature.v is null, and both order.nonce and order.expiry sufficiently large (order.nonce >= makerMinimumNonce[order.maker.wallet] and order.expiry>block.timestamp)

Thanks to the authorization introduced by the Wrapper contract, the validity checks in the Swap Protocol can be bypassed to execute the transferToken() operations, exposing the unsuspecting authorizing users to the risk of losing their assets.


Date Action
2019.09.25 Review and identified the bug independently.
2019.09.26 Discuss with AirSwap team to confirm the vulnerability.
2019.10.04 Disclose the details after AirSwap team discloses the details.

About Us

PeckShield Inc. is a leading blockchain security company with the goal of elevating the security, privacy, and usability of current blockchain ecosystem. For any business or media inquiries (including the need for smart contract auditing), please contact us at telegram, twitter, or email.


27 September 2019