Weiroll Executor Guide

This guide explains how to use Weiroll to compose complex operation sequences in Flux Protocol, enabling managers and liquidators to execute sophisticated multi-step transactions efficiently.

Table of Contents


What is Weiroll?

Weiroll is a scripting language and virtual machine (VM) for composing sequences of smart contract calls into a single atomic transaction. Think of it as a "programmable callback" that lets you encode complex operations off-chain and execute them on-chain in one transaction.

Key Concepts

  • Commands: Encoded function calls represented as bytes32 values

  • State: Array of variables (inputs/outputs) that commands can reference

  • Planner: Off-chain tool (JavaScript library) for building command sequences

  • VM: On-chain executor that interprets and executes command sequences

How Weiroll Works

Example Weiroll Script:


Why Flux Uses Weiroll

Flux's unified callback architecture creates a unique opportunity for Weiroll:

1. Unified Callback Pattern

Both managers (unlock) and liquidators share the same callback interface:

This means one Weiroll executor can handle both manager operations and liquidations.

2. Complex Operation Sequences

Manager workflows often require multiple steps:

  • Borrow → Swap → Deposit collateral

  • Withdraw → Repay → Rebalance

  • Move funds between positions → Adjust leverage

Weiroll lets you compose these atomically without writing custom callback contracts.

3. Capital-Free Liquidations

Liquidators can use locked_borrow() during callbacks to access vault capital:

No upfront capital needed—use the vault's liquidity during liquidation.

4. Flexibility Without Code Changes

Deploy one WeirollExecutor, then compose different operations by changing the script:

  • No new contract deployments

  • No contract upgrades

  • Change strategies on the fly


WeirollExecutor Architecture

Contract Structure

Execution Flow

Access Control

The executor has two access modes:

  1. Self-call during callback: msg.sender == address(this)

    • When vault calls fluxUnlockCallback(), executor uses this.execute()

    • This converts memory arrays to calldata for the VM

  2. Direct manager call: msg.sender == manager

    • Manager can test scripts directly

    • Useful for debugging without involving the vault


Getting Started

Step 1: Deploy Your WeirollExecutor

Use the factory's deployment helper:

Or deploy manually:

Step 2: Install Weiroll JavaScript Library

Note: Use ethers v5 (weiroll.js is not yet compatible with v6)

Step 3: Create Your First Script

Step 4: Execute the Script


Manager Use Cases

Use Case 1: Basic Borrow

Goal: Deposit bond and borrow funds

Pre-execution setup:

What happens:

  1. Executor approves vault to spend bond

  2. Vault locks the callback

  3. Executor deposits bond via locked_depositBond()

  4. Executor borrows funds via locked_borrow()

  5. Vault validates solvency (bond ≥ minBondRatio * debt)

  6. Vault releases lock

  7. Borrowed funds are in executor's working capital


Use Case 2: Borrow → Swap → Deposit Collateral

Goal: Leverage trading—borrow USDC, swap to WETH, deposit as collateral

What happens:

  1. Deposit bond and borrow USDC

  2. Withdraw USDC from vault to executor

  3. Swap USDC → WETH on Uniswap

  4. Deposit WETH back into vault as collateral

  5. Vault validates: collateral value ≥ debt / (1 - minBondRatio)

  6. Position is now leveraged long WETH


Use Case 3: Full Repayment

Goal: Move bond funds to cover interest, repay all debt, withdraw remaining bond

What happens:

  1. Move 5k from bond to working capital

  2. Vault uses working capital to repay debt

  3. Debt becomes 0

  4. Withdraw remaining 20k bond to executor

  5. Manager can withdraw from executor to their EOA


Use Case 4: Rebalancing Collateral

Goal: Withdraw some WETH, swap to USDC, deposit as working capital

What happens:

  1. Withdraw 10 WETH from collateral

  2. Swap to USDC

  3. Deposit USDC as working capital (reduces debt)

  4. Vault validates solvency after rebalancing


Liquidator Use Cases

Use Case 1: Capital-Free Liquidation

Goal: Liquidate a manager without providing upfront capital

Strategy: Use seized working capital and bond to pay minPayment

Using Weiroll for the same pattern:

Triggering liquidation:

What happens:

  1. Vault transfers manager's positions to liquidator

  2. Vault calls liquidator.fluxUnlockCallback(encodedMinPayment)

  3. Liquidator withdraws seized assets

  4. Liquidator deposits minPayment to vault

  5. Vault validates payment and releases lock

  6. Liquidator keeps remaining assets as profit


Use Case 2: Borrow-Based Liquidation

Goal: Use vault's own capital to liquidate (leveraged liquidation)

What happens:

  1. Liquidator deposits small bond (e.g., 5k)

  2. Liquidator borrows 50k from vault (uses vault's LP capital!)

  3. Liquidator withdraws and sells seized collateral

  4. Repays 50k borrowed + deposits minPayment

  5. Withdraws bond back

  6. Profit = seized value - minPayment (no capital lockup)


Use Case 3: Swap-Optimized Liquidation

Goal: Route through multiple DEXs for best price

Advantages:

  • Maximize profit by getting best swap prices

  • Route through Uniswap, Sushiswap, Curve simultaneously

  • Reduce slippage on large liquidations


JavaScript API

Core Objects

1. Planner

The Planner builds a sequence of commands:

Methods:

  • add(command): Add a command to the sequence

  • plan(): Generate {commands, state} for execution

  • replace(oldCommand, newCommand): Replace a command

  • precompile(address): Mark an address as precompiled

2. Contract

Wraps an ethers.js contract for Weiroll:

Methods:

  • createContract(ethersContract): Wrap ethers contract

  • createLibrary(ethersContract): Wrap as library (for return values)

3. State Variables

Reference dynamic values (return values, inputs):

Complete JavaScript Example


Solidity Integration

Deploying WeirollExecutor

Using Pre-Generated Scripts

If you generate scripts off-chain, you can hardcode them:

Testing Scripts in Foundry


Security Considerations

1. Vault Verification

WeirollExecutor MUST verify the caller is a legitimate vault:

Why: Without this check, an attacker could deploy a fake vault that calls your executor with malicious scripts.

2. Executor Access Control

Only the manager or executor itself can call execute():

Why: Prevents unauthorized parties from executing arbitrary commands.

3. Script Validation

Always validate scripts before execution:

4. Slippage Protection

Always include slippage protection in swap commands:

5. Gas Limits

Complex scripts can exceed block gas limits:

Best practice: Keep scripts under 20 commands for safety.

6. Reentrancy Protection

The vault's lock pattern prevents reentrancy:

What this means:

  • During callback, vault is "locked" to msg.sender

  • No other operations can execute on that manager

  • Script cannot call unlock() again (nested callbacks prevented)

Exception: Liquidation can trigger nested callbacks (e.g., recursive liquidation). This is safe because positions are transferred first.

7. Token Approval Management

Be careful with infinite approvals:

Recommendation: Use exact approvals in scripts, set up infinite approvals separately if needed.


Debugging & Testing

1. Generating Scripts

Create a script generation tool:

Run:

2. Testing in Foundry

3. Decoding Failed Scripts

When a script fails, decode the revert reason:

4. Gas Profiling

Expected overhead: ~5-10k gas per command.

5. Event Monitoring


Best Practices

1. Keep Scripts Focused

Good: One script per operation type

Bad: One giant script for everything

2. Separate State from Commands

Commands should be static (reusable), state should be dynamic:

3. Version Your Scripts

4. Document Script Behavior

5. Handle Errors Gracefully

6. Use Constants for Position IDs

7. Simulate Before Executing

8. Monitor Execution

Last updated