VSR and VAR

Overview

Verifiable Sequencing Rule (VSR) and Verifiable Aggregation Rule (VAR) can be deployed as smart contracts on SL-Chain to enable application-specific sequencing.

Verifiable Sequencing Rule (VSR)

Sequencing rules can be deployed by developers as smart contracts, exposing a read-only (view or pure) function that Semantic Layer node uses to compute the VSR score for an MTX.

VSR allows sequencing based on the solver bid amount as well as any other custom criteria defined by the dApp. The SolverMTX with the highest VSR score will be selected for a given UserMTX.

DApp developers can define sequencing rules as VSR to achieve application-specific sequencing. For example, dApp developers can define the score function to return the amount of solver MEV payment. This VSR incentivizes solvers to compete with each other to maximize the amount of MEV payment made to the protocol treasury, thus achieving MEV internalization for the protocol. VSR as a smart contract can provide maximal flexibility to dApp developers, and we expect developers to leverage that flexibility and create more sophisticated sequencing rule to build novel applications.

The VSR.score() function contains two fields: bid and data.

  • bid: The bid amount specified in SolverMTX. When calculating the VSR score, the SL node takes the bid amount number from the SolverMTX as the bid argument value.
  • data: Any other necessary data required, is packed into UserMTX when generated by the dApp frontend.
interface IVSR {
    /**
     *
     * @param bid solver bid amount
     * @param data other data
     */
    function score(uint256 bid, bytes calldata data) external view returns (uint256);
}

struct VSRStruct {
    IVSR vsrContract;
    bytes data; // for the `data` argument of the IVSR.score() function
}

Example: VSR considers solver bid amount only

If the data field is empty, the VSR score is based solely on the solver bid amount. When calculating the VSR score, the SL node takes the bid amount from the SolverMTX and inputs it into the VSR.score() function.

If the data field is not empty, the VSR score is then calculated with the logic defined in the VSR.score() function. See below for an example.

Example: VSR for a DEX

In this example, a VSR contract is defined for a Uniswap-like decentralized exchange (DEX). The VSR score calculation considers factors beyond just the solver bid amount. The user's swap information is packed into the VSR data field. The solver operation that yields the best swap outcome for the user receives the highest VSR score, and solvers must compete with each other to yield the best swap outcome. For example, a solver can add liquidity to the DEX to improve the user's swap result.

// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.6.6;

import "../../interfaces/IVSR.sol";
import "@uniswap/v2-periphery/contracts/libraries/UniswapV2Library.sol";
/**
 * @title VSR (Verifiable Sequencing Rule contract)
 * @dev This simple VSR example calculates goal score based the amount out when user swap.
 */

contract VSR is IVSR {
    address immutable univ2Factory;

    constructor(address factory_) public {
        univ2Factory = factory_;
    }

    function score(uint256 bid, bytes calldata data) external view override returns (uint256) {
        (uint256 amountIn, address[] memory path) = abi.decode(data, (uint256, address[]));
        uint256[] memory amounts = UniswapV2Library.getAmountsOut(univ2Factory, amountIn, path);
        uint256 len = amounts.length;
        return amounts[len - 1];
    }
}

In this example, the SL node simulates executing all user dApp and solver operations to evaluate their impact. The VSR.score() function is then called based on this simulation.

When the VSR.score() function is called, the solver bid amount from the SolverMTX is used for the bid argument, and the data field from the VSRStruct in the UserMTX is used for the data argument.

Verifiable Aggregation Rule (VAR)

DApp developers can create their aggregation rules using VAR. With VAR, MTXs can be bundled and executed together based on the customized rules defined by dApps.

The VAR contract must implement the IVAR interface. MTXs that can be aggregated are stored under the same queue ID.

  • The canAggregate() function indicates whether the current MTX queue is ready for aggregation. dApp developers writes evaluation logics in this function, which evaluates to true once the MTXs in the queue are ready for aggregation and execution.
  • The aggregateCurrentQueue() function is called by SL nodes to aggregate the MTXs in the queue. The batch size defines the maximum number of MTXs aggregated per operation, with any remaining MTXs processed in subsequent operations. The SL-Chain processes these MTX bundles and batch executes them in the destination execution environment.

Note: VAR support is still under active development, and its design may undergo significant changes. Developers and contributors can refer to the SL-Chain codebase for the latest updates.

interface IVAR {
    /**
     * @dev It returns the UserMTXs queueing for processing.
     * @param id the id of the queue. MTX needs to be bundled together can be added to queue with the same id.
     */
    function mtxQueue(uint256 id) external view returns (UserMTX[] memory);

    /**
     * @dev It signals whether a mtx queue is ready for aggregation.
     * @param id the id of the queue. MTX needs to be bundled together can be added to queue with the same id.
     */
    function canAggregate(uint256 id) external view returns (bool);

    /**
     * @dev Call this function to process the MTXs queueing for processing.
     * @param id the id of the queue. MTX needs to be bundled together can be added to queue with the same id.
     * @param batchSize the number of the mtx to aggregated
     */
    function aggregateCurrentQueue(uint256 id, uint256 batchSize) external;
}