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
bytes32valuesState: 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:
Self-call during callback:
msg.sender == address(this)When vault calls
fluxUnlockCallback(), executor usesthis.execute()This converts memory arrays to calldata for the VM
Direct manager call:
msg.sender == managerManager 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:
Executor approves vault to spend bond
Vault locks the callback
Executor deposits bond via
locked_depositBond()Executor borrows funds via
locked_borrow()Vault validates solvency (bond ≥ minBondRatio * debt)
Vault releases lock
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:
Deposit bond and borrow USDC
Withdraw USDC from vault to executor
Swap USDC → WETH on Uniswap
Deposit WETH back into vault as collateral
Vault validates: collateral value ≥ debt / (1 - minBondRatio)
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:
Move 5k from bond to working capital
Vault uses working capital to repay debt
Debt becomes 0
Withdraw remaining 20k bond to executor
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:
Withdraw 10 WETH from collateral
Swap to USDC
Deposit USDC as working capital (reduces debt)
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:
Vault transfers manager's positions to liquidator
Vault calls
liquidator.fluxUnlockCallback(encodedMinPayment)Liquidator withdraws seized assets
Liquidator deposits
minPaymentto vaultVault validates payment and releases lock
Liquidator keeps remaining assets as profit
Use Case 2: Borrow-Based Liquidation
Goal: Use vault's own capital to liquidate (leveraged liquidation)
What happens:
Liquidator deposits small bond (e.g., 5k)
Liquidator borrows 50k from vault (uses vault's LP capital!)
Liquidator withdraws and sells seized collateral
Repays 50k borrowed + deposits minPayment
Withdraws bond back
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 sequenceplan(): Generate{commands, state}for executionreplace(oldCommand, newCommand): Replace a commandprecompile(address): Mark an address as precompiled
2. Contract
Wraps an ethers.js contract for Weiroll:
Methods:
createContract(ethersContract): Wrap ethers contractcreateLibrary(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