Skip to main content

Generalized Wrappers

Generalized wrappers are smart contracts that enable custom logic to execute before and after CoW Protocol settlement operations. This reference documents the contract interfaces, implementation patterns, and on-chain behavior of the wrapper system.

Architectureโ€‹

Execution Flowโ€‹

Key Design Principlesโ€‹

  1. Abstract: There are very few limitations on what a wrapper can/can't do around a settlement transaction
  2. Efficient Encoding: Wrapper-specific data appended to minimize gas overhead
  3. Nested Support: Multiple wrappers chain by encoding addresses sequentially, allowing CoW orders
  4. Authentication: Only allowlisted wrappers can call settlement contract

Implementationโ€‹

Developers looking to integrate using wrappers should copy this all-in-one solidity file into their project as vendored dependency.

It provides a few base contracts which can serve as the foundation for integration:

  • ICowWrapper: Core interface all wrappers must implement
  • CowWrapper: Abstract base contract providing security and utilities that all wrappers should use

See the code for CowWrapper.sol on GitHub.

Quick Start Exampleโ€‹

The EmptyWrapper serves as a good starting point for building a wrapper. Following this, the implementation functions should be filled in as described below.

Virtual Functions for integratorsโ€‹

_wrapโ€‹

Contains custom surrounding settlement logic. Must call _next() to continue the chain to the settlement contract.

function _wrap(bytes calldata settleData, bytes calldata wrapperData, bytes calldata remainingWrapperData) internal virtual;

Implementation Requirements:

  • Parse wrapper-specific data from wrapperData as required
  • Execute pre-settlement logic
  • Call _next(remainingWrapperData) to continue chain
  • Execute post-settlement logic

Example:

function _wrap(bytes calldata settleData, bytes calldata wrapperData, bytes calldata remainingWrapperData) internal override {
// 1. Parse data
(address token, uint256 amount) = abi.decode(wrapperData, (address, uint256));

// 2. Pre-settlement logic. Example, receive tokens from user
IERC20(token).transferFrom(msg.sender, address(this), amount);

// 3. Continue chain (REQUIRED)
_next(settleData, remainingWrapperData);

// 4. Post-settlement logic. Example: stake tokens to a contract (for swap and stake)
stakeTokens(token, amount);
}

validateWrapperDataโ€‹

Validates wrapper-specific data. Must be deterministic and revert on invalid input.

function validateWrapperData(
bytes calldata wrapperData
) external view override;

Requirements:

  • Deterministic: Same input must always produce same output. This means you cannot check the timestamp and revert if the order is valid, for example.
  • View function: Cannot modify state
  • Revert on invalid: Revert if data is malformed

Internal Functions (Provided for integrator use)โ€‹

_nextโ€‹

Continues the wrapper chain or calls settlement. Handles all parsing and routing automatically.

function _next(bytes calldata settleData, bytes calldata remainingWrapperData) internal;

Behavior:

  • Extracts the next target address (first 20 bytes of remainingWrapperData)
  • Calls the next wrapper via wrappedSettle(), or the settlement contract via settle() if no wrappers remain

Calldata Encoding Specificationโ€‹

The chainedWrapperData parameter of wrappedSettle(settleData, chainedWrapperData) is specially encoded to minimize bit shuffling overhead:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Lenโ‚ โ”‚ Dataโ‚ โ”‚ Addrโ‚‚ โ”‚ Lenโ‚‚ โ”‚ Dataโ‚‚ โ”‚
โ”‚(2 B) โ”‚ (wrap1) โ”‚ (20 B) โ”‚(2 B) โ”‚ (wrap2) โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚<โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ chainedWrapperData โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€>โ”‚

Components:

  • Lenโ‚: 2-byte (uint16) length of wrapper 1's data
  • Dataโ‚: Wrapper 1's custom data
  • Addrโ‚‚: 20-byte address of wrapper 2
  • Lenโ‚‚: 2-byte (uint16) length of wrapper 2's data
  • Dataโ‚‚: Wrapper 2's custom data ... (repeat Addrโ‚‚, Lenโ‚‚, Dataโ‚‚ for every subsequent wrapper)

Wrapper Public Functionsโ€‹

wrappedSettleโ€‹

Entry point for wrapper execution. Validates caller authentication and delegates to _wrap()--where integrators place their custom logic. See the full implementation in CowWrapper.sol.

Parameters:

CowWrapperHelperโ€‹

A view-only periphery contract for validating and encoding wrapper chains. See the source code for the complete implementation.

Interfaceโ€‹

contract CowWrapperHelpers {
/// @notice A definition for a single call to a wrapper
struct WrapperCall {
/// @notice The smart contract that will be receiving the call
address target;

/// @notice Any additional data which will be required to execute the wrapper call
bytes data;
}

/// @notice Validates a wrapper chain configuration and builds the properly formatted wrapper data
/// @param wrapperCalls Array of calls in execution order
/// @return chainedWrapperData The encoded wrapper data ready to be passed to the first wrapper's wrappedSettle
function verifyAndBuildWrapperData(
WrapperCall[] memory wrapperCalls
) external view returns (bytes memory chainedWrapperData);
}

Implementation Requirements for Integratorsโ€‹

Security Requirementsโ€‹

Critical Requirements

1. Audit by a Reputable Firmโ€‹

The wrapper contract must be audited by a reputable security firm before submission for allowlist approval. The audit report should be made available to the CoW DAO as part of the approval process.

2. Use CowWrapper Abstract Contractโ€‹

It is strongly recommended to NOT implement ICowWrapper directly. The CowWrapper abstract contract provides:

  • Solver authentication checks
  • Correct calldata parsing and decoding
  • Safe wrapper chain continuation
  • Sanity checks for the settlement call

3. Intermediate Contracts for User Callsโ€‹

If allowing user-defined calls, route through intermediate contracts:

// โŒ DANGEROUS
function _wrap(bytes calldata settleData, bytes calldata wrapperData, bytes calldata remainingWrapperData) internal override {
(address target, bytes memory data) = abi.decode(wrapperData, (address, bytes));
target.call(data); // User could call anything they want, including the settlement contract, using the wrapper's authenticated context
}

// โœ… SAFE
function _wrap(bytes calldata settleData, bytes calldata wrapperData, bytes calldata remainingWrapperData) internal override {
(address target, bytes memory data) = abi.decode(wrapperData, (address, bytes));
HooksTrampoline(TRAMPOLINE).execute(target, data); // Isolated execution
}

4. Assume All Parameters Are Untrustedโ€‹

Settlement data can be modified by nested wrappers, and solvers can supply arbitrary calldata. If it is important for your wrapper to be able to validate the wrapper it is receiving, only trust signature-protected or on-chain validated parameters.

As a concrete implication: hooks cannot be enforced by inspecting settlement interaction data. Neither checking that a desired hook is present in settleData, nor injecting a hook into the forwarded settleData, provides any guarantee โ€” an intermediate wrapper can freely modify settleData before passing it along.

5. Deterministic Parsing Requiredโ€‹

validateWrapperData() must always produce the same result (revert or not) for same input:

// โŒ NOT DETERMINISTIC
function validateWrapperData(bytes calldata wrapperData)
external view override
{
uint256 deadline = abi.decode(wrapperData, (uint256));
require(block.timestamp < deadline, "Expired"); // Changes over time!
}

// โœ… DETERMINISTIC
function validateWrapperData(bytes calldata wrapperData)
external view override
{
uint256 deadline = abi.decode(wrapperData, (uint256));
require(deadline > 0, "Invalid deadline"); // Always same result
}

In the example above, your _wrap code can always reject deadline past expired instead.

6. Defensive Designโ€‹

Though a solver would be slashed for doing so, there is no hard guarantee wrapper executes even if user specifies it. Additionally, even when a wrapper does execute, there is no guarantee that settle will eventually be called โ€” an earlier wrapper in the chain could skip calling _next() entirely. Wrappers should not assume that settlement will complete.

Gas Overheadโ€‹

Wrapper execution adds gas overhead to settlements.

Benchmark (EmptyWrapper on Ethereum mainnet):

MetricValue
With EmptyWrapper217,033 gas
Without wrapper194,761 gas
Overhead22,272 gas (11.4%)

Scaling Factors:

  • Settlement data size (calldata copying)
  • Wrapper logic complexity
  • Number of nested wrappers

Methodology: Single Uniswap V3 WETHโ†’USDC trade. See services PR #3700.

Example Implementationsโ€‹

EmptyWrapperโ€‹

Minimal wrapper demonstrating the pattern:

contract EmptyWrapper is CowWrapper {
constructor(address settlement) CowWrapper(settlement) {}

function _wrap(bytes calldata settleData, bytes calldata wrapperData, bytes calldata remainingWrapperData) internal override {
// No pre-settlement logic
_next(settleData, remainingWrapperData); // Pass through
// No post-settlement logic
}

function validateWrapperData(bytes calldata wrapperData)
external
view
override
{
// No validation needed
}
}

Resourcesโ€‹

Documentationโ€‹

Codeโ€‹