Contract reference
ZenoLOP is Ownable and ReentrancyGuard, compiled with Solidity ^0.8.7 and abicoder v2. Every
order and payout function is nonReentrant; the owner-only setters are not.
Constructor and configuration
constructor(
RailgunSmartWallet _railgun,
IVerifier _noteRequirementsVerifier,
address _treasury,
uint256 _claimWindow
)railgun and noteRequirementsVerifier are immutable. The owner can update the rest:
| Function | Access | Constraint |
|---|---|---|
setTreasury(address) | owner | cannot be the zero address |
setClaimWindow(uint256) | owner | must be greater than zero |
Constants
| Name | Value | Meaning |
|---|---|---|
takerFee | 10 | taker fee in basis points (0.1%), skimmed from each payout to the treasury. public constant, readable on-chain |
BASIS_POINTS | 10000 | denominator for the fee calculation. private constant, not readable on-chain |
Structs
struct EntryOrder {
address maker; // who submitted the order
bytes32 mpk; // pool key that must be able to spend the delivered note
TokenData makingToken; // token wanted as a shielded note
uint120 makingAmount; // total note value wanted
TokenData takingToken; // token escrowed as payment
uint256 takingAmount; // total escrowed
uint256 expiry; // fillable before; cancellable at or after
bytes32 listProviderPPoIKey; // recorded and emitted; not read by the contract
uint256 remainingAmount; // unallocated escrow; starts at takingAmount
}
struct Fill {
uint256 orderId;
uint120 amount; // makingToken delivered in this note
bytes32 commitment; // delivered note commitment
bytes proof; // Noir note-requirements proof
}
struct PendingPayout {
uint256 orderId;
uint256 amount; // takingToken owed to the filler
address recipient;
uint256 deadline; // last timestamp the filler can claim
bytes32 commitment;
uint120 noteAmount;
}TokenData is RAILGUN's (uint8 tokenType, address tokenAddress, uint256 tokenSubID).
Functions
| Function | Caller | Notes |
|---|---|---|
submitOrder(mpk, makingToken, makingAmount, takingToken, takingAmount, expiry, listProviderPPoIKey) | maker | escrows takingAmount up front |
fillOrder(Transaction[], Fill[], address recipient) | filler or relayer | executes the RAILGUN transactions and escrows payouts |
claimPayout(payoutId, random, proof) | anyone | releases escrow within the claim window; proof is currently ignored. There is no msg.sender check, so a relayer can submit it: funds always go to the recorded recipient |
reclaimPayout(payoutId) | maker | recovers escrow after the deadline passes |
cancelOrder(orderId) | maker | only at or after expiry; refunds remainingAmount |
Public getters: orders(orderId), pendingPayouts(payoutId), usedCommitment(commitment),
nextOrderId, nextPendingPayoutId, claimWindow, treasury, takerFee, railgun,
noteRequirementsVerifier, and owner() from Ownable.
Payout math
payout = deliveredNoteAmount * order.takingAmount / order.makingAmountFloored, so rounding dust favors the maker. The fee is then payout * takerFee / BASIS_POINTS to the
treasury, with the remainder to the recipient.
Events
event OrderCreated(uint256 indexed orderId, address indexed maker, bytes32 mpk,
TokenData makingToken, uint120 makingAmount, TokenData takingToken,
uint256 takingAmount, uint256 expiry, bytes32 listProviderPPoIKey);
event OrderCancelled(uint256 indexed orderId, address indexed maker, uint256 refundedAmount);
event OrderFilled(uint256 indexed payoutId, uint256 indexed orderId, address indexed recipient,
uint256 payoutAmount, uint120 noteAmount, bytes32 commitment, uint256 deadline);
event PayoutExecuted(uint256 indexed payoutId, uint256 indexed orderId, address indexed recipient,
uint256 payoutAmount, bytes32 commitment, bytes32 random);
event PayoutReclaimed(uint256 indexed payoutId, uint256 indexed orderId, address indexed maker,
uint256 amount);These drive the subgraph; regenerate subgraph/abis/ZenoLOP.json after changing any of them.
Reverts
| Message | Raised when |
|---|---|
Taking amount must be greater than zero | submitOrder with zero escrow |
Making amount must be greater than zero | submitOrder with zero note value |
Expiry must be in the future | submitOrder with a past expiry |
Custodied amount mismatch | the escrow token takes a transfer fee |
Unsupported token type | token id is not an ERC20 |
Invalid recipient | fillOrder with a zero recipient |
Invalid adapt contract / Invalid adapt params | a transaction is not bound to this bundle |
Commitment not created | the fill names a note the transactions did not create |
Order does not exist / Order expired | order lookup or expiry check failed |
Commitment already used | the note was already used for a fill |
Invalid note proof | the note-requirements proof failed |
Fill too small | the pro-rata payout rounds to zero |
Payout does not exist | unknown payout id |
Claim window closed / Claim window still open | claim or reclaim outside its window |
random not in field | claimPayout with random >= SNARK_SCALAR_FIELD |
random does not match delivered note | the revealed blinding does not reproduce the commitment |
Only maker can cancel / Only maker can reclaim | caller is not the maker |
Order not yet expired | cancelOrder before expiry |
Claim window must be positive / Treasury cannot be zero address | owner setters, invalid argument |
A fill whose payout exceeds the order's remainingAmount reverts on the subtraction with an
arithmetic panic (Panic(0x11)) rather than a message, so there is no string to match on.
Token support
Only ERC20 is supported. Token ids with bits above 160 are rejected, and fee-on-transfer tokens fail the custody balance-delta check. ERC721 and ERC1155 remain TODOs.
Related
- How ZenoLOP works - where these functions sit in the lifecycle.
- Integrating - calling them from a client.
- Subgraph reference - querying the events above.
Deployments
Current Sepolia deployment (chain id 11155111), matching the subgraph's indexed address:
| Contract | Address |
|---|---|
ZenoLOP | 0x960974EC73a2534DE907a610d623Aa61EA696804 |
HonkVerifier (note requirements) | 0x07408A1e804FC93fC588f28f550c82347f37E03c |
RailgunSmartWallet | 0xeCFCf3b4eC647c4Ca6D49108b311b7a7C9543fea |
Deployed at block 11238525 with claimWindow = 86400 (one day).