Skip to main content

CurveLMCallbackFactory

The CurveLMCallbackFactory deploys LMCallback contracts for Llamalend markets from a single blueprint. An LM Callback acts as a liquidity gauge for the collateral deposited in a market's AMM: once it receives gauge weight in the GaugeController, it streams CRV emissions to the market's borrowers.

The factory only deploys callbacks and keeps a registry of them. Deploying a callback does not activate it: a callback starts earning only after it is set as the AMM's liquidity mining callback through the market's Configurator and added to the GaugeController by the DAO.

Deployment is permissionless. Anyone can call deploy_lm_callback for an AMM, while the owner of the factory controls the blueprint, can pause new deployments, and is the account allowed to pause CRV rewards on every callback deployed by this factory (see LMCallback.set_killed).

LMCallbackFactory.vy

The source code for the LMCallbackFactory.vy contract can be found on GitHub. The contract is written in Vyper version 0.4.3 and uses the Snekmate ownable and pausable modules.

The contract is deployed on Ethereum at 0x916E2B5E3Df268Ce2348265d2AAD4BB9f2B3dFA0.

{ }Contract ABI▼
[{"anonymous":false,"inputs":[{"indexed":true,"name":"previous_owner","type":"address"},{"indexed":true,"name":"new_owner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"old_blueprint","type":"address"},{"indexed":false,"name":"new_blueprint","type":"address"}],"name":"UpdateLMCallbackBlueprint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"amm","type":"address"},{"indexed":true,"name":"deployer","type":"address"},{"indexed":true,"name":"blueprint","type":"address"},{"indexed":false,"name":"lm_callback","type":"address"}],"name":"DeployedLMCallback","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"new_owner","type":"address"}],"name":"transfer_ownership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_amm","type":"address"}],"name":"deploy_lm_callback","outputs":[{"name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_i","type":"uint256"}],"name":"get_lm_callback","outputs":[{"name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"get_lm_callback_count","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_blueprint","type":"address"}],"name":"set_blueprint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lm_callback_blueprint","outputs":[{"name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"arg0","type":"address"}],"name":"is_valid_gauge","outputs":[{"name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"arg0","type":"address"}],"name":"get_lm_callback_by_amm","outputs":[{"name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"arg0","type":"address"}],"name":"get_blueprint_by_lm_callback","outputs":[{"name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_owner","type":"address"},{"name":"_blueprint","type":"address"}],"outputs":[],"stateMutability":"nonpayable","type":"constructor"}]

Deploying LM Callbacks​

deploy_lm_callback​

LMCallbackFactory.deploy_lm_callback(_amm: IAMM) -> address

Deploys a new LMCallback for the AMM _amm from the current lm_callback_blueprint. Anyone can call this function while the factory is not paused.

The new callback is registered as a valid gauge (is_valid_gauge), appended to the list of deployed callbacks, recorded as the newest callback for the AMM (get_lm_callback_by_amm), and linked to the blueprint it was created from (get_blueprint_by_lm_callback).

The call reverts with "already deployed" if the AMM's newest callback was created from the blueprint that is currently set, so a market cannot receive two identical callbacks back to back. Only the newest callback is compared: after the blueprint has been rotated, a new callback can be deployed for an AMM that already has one from an earlier blueprint.

The callback constructor reads the collateral token from _amm.coins(1) and requires it to have 18 decimals. The function is reentrancy-locked because the blueprint constructor calls into the caller-supplied AMM.

Deploying a callback does not attach it to the AMM. Attaching is done through the market's Configurator, and CRV only flows once the DAO has added the callback to the GaugeController.

InputTypeDescription
_ammaddressLlamalend AMM the LM Callback is deployed for.

Returns: address of the deployed LM Callback (address).

Emits: DeployedLMCallback event.

<>Source code▼
event DeployedLMCallback:
amm: indexed(address)
deployer: indexed(address)
blueprint: indexed(address)
lm_callback: address

MAX_LM_CALLBACKS: constant(uint256) = 10**18

lm_callback_blueprint: public(address)
is_valid_gauge: public(HashMap[address, bool])
get_lm_callback_by_amm: public(HashMap[address, address])
get_blueprint_by_lm_callback: public(HashMap[address, address])

_lm_callbacks: DynArray[address, MAX_LM_CALLBACKS]

@external
@nonreentrant
def deploy_lm_callback(_amm: IAMM) -> address:
"""
@notice Deploy an LM Callback
@dev Reentrancy-locked because the blueprint constructor hands control to
the caller-supplied `_amm`; the lock keeps the registry writes below
atomic with respect to the deployment they describe
@dev Reverts if the AMM's newest callback came from the blueprint currently
set, so a market cannot be handed two identical callbacks back to back.
Only that newest callback is compared, not every one ever deployed for the
AMM: rotating the blueprint away and back therefore does allow another
deployment from the earlier blueprint
@param _amm LlamaLend AMM the deployed LM Callback is going to be used for
@return Address of the deployed LM Callback
"""
pausable._require_not_paused()

lm_callback_blueprint: address = self.lm_callback_blueprint
existing_lm_callback: address = self.get_lm_callback_by_amm[_amm.address]
if existing_lm_callback != empty(address):
assert (
self.get_blueprint_by_lm_callback[existing_lm_callback]
!= lm_callback_blueprint
), "already deployed"

lm_callback: address = create_from_blueprint(
lm_callback_blueprint,
_amm,
code_offset=3,
)

self.is_valid_gauge[lm_callback] = True
self._lm_callbacks.append(lm_callback)
self.get_lm_callback_by_amm[_amm.address] = lm_callback
self.get_blueprint_by_lm_callback[lm_callback] = lm_callback_blueprint

log ILMCallbackFactory.DeployedLMCallback(
amm=_amm.address,
deployer=msg.sender,
blueprint=lm_callback_blueprint,
lm_callback=lm_callback,
)

return lm_callback
▶Example▼
>>> LMCallbackFactory.deploy_lm_callback("0xAMM...")
'0xLMCallback...'

Blueprint​

lm_callback_blueprint​

LMCallbackFactory.lm_callback_blueprint() -> address: view

Getter for the blueprint that new LM Callbacks are deployed from. It is set in the constructor and can be changed by the owner via set_blueprint. The blueprint can never be the zero address.

Returns: LM Callback blueprint (address).

<>Source code▼
lm_callback_blueprint: public(address)

@deploy
def __init__(
_owner: address,
_blueprint: address,
):
"""
@notice Factory which creates LM Callbacks for LlamaLend/crvUSD markets from a blueprint
@param _owner Owner of the factory, allowed to update the blueprint (ideally DAO)
@param _blueprint Address of the LM Callback blueprint
"""
ownable.__init__()
pausable.__init__()
assert _owner != empty(address) # dev: zero owner
ownable._transfer_ownership(_owner)

self._set_blueprint(_blueprint)

@internal
def _set_blueprint(_blueprint: address):
assert _blueprint != empty(address) # dev: zero blueprint
log ILMCallbackFactory.UpdateLMCallbackBlueprint(
old_blueprint=self.lm_callback_blueprint, new_blueprint=_blueprint
)
self.lm_callback_blueprint = _blueprint
▶Example▼

set_blueprint​

LMCallbackFactory.set_blueprint(_blueprint: address)
Guarded Method by Snekmate 🐍

This contract makes use of a Snekmate module to manage roles and permissions. This specific function can only be called by the current owner of the factory, which is intended to be the Curve DAO. Changing the code that future LM Callbacks are deployed from is therefore in the hands of the owner.

Sets a new blueprint for future LM Callback deployments. Callbacks that were already deployed are not affected. The blueprint can be rotated but never unset: passing the zero address reverts, so new deployments are halted with pause instead.

InputTypeDescription
_blueprintaddressAddress of the new LM Callback blueprint.

Emits: UpdateLMCallbackBlueprint event.

<>Source code▼
event UpdateLMCallbackBlueprint:
old_blueprint: address
new_blueprint: address

lm_callback_blueprint: public(address)

@external
def set_blueprint(_blueprint: address):
"""
@notice Set the blueprint
@dev Reverts on the empty address: the blueprint can be rotated but never
unset, so deployments are halted with pause() instead
@param _blueprint The address of the blueprint to use
"""
ownable._check_owner()
self._set_blueprint(_blueprint)

@internal
def _set_blueprint(_blueprint: address):
assert _blueprint != empty(address) # dev: zero blueprint
log ILMCallbackFactory.UpdateLMCallbackBlueprint(
old_blueprint=self.lm_callback_blueprint, new_blueprint=_blueprint
)
self.lm_callback_blueprint = _blueprint
▶Example▼
>>> LMCallbackFactory.set_blueprint("0xNewBlueprint...")

get_blueprint_by_lm_callback​

LMCallbackFactory.get_blueprint_by_lm_callback(arg0: address) -> address: view

Getter for the blueprint an LM Callback was deployed from. It is used for the duplicate check in deploy_lm_callback and lets integrations tell callback generations apart. Returns the zero address for addresses not deployed by this factory.

InputTypeDescription
arg0addressAddress of the LM Callback.

Returns: blueprint the callback was created from (address).

<>Source code▼
# Blueprint each callback was created from, kept for the duplicate check in
# `deploy_lm_callback` and so integrations can tell callback generations apart
get_blueprint_by_lm_callback: public(HashMap[address, address])
▶Example▼

Querying LM Callbacks​

get_lm_callback​

LMCallbackFactory.get_lm_callback(_i: uint256) -> address: view

Returns the LM Callback deployed at index _i, in deployment order. Reverts if _i is not lower than get_lm_callback_count.

InputTypeDescription
_iuint256Index of the LM Callback.

Returns: LM Callback (address).

<>Source code▼
_lm_callbacks: DynArray[address, MAX_LM_CALLBACKS]

@external
@view
def get_lm_callback(_i: uint256) -> address:
"""
@notice Get the LM Callback deployed at index `_i`
@param _i Index of the LM Callback
@return Address of the LM Callback
"""
return self._lm_callbacks[_i]
▶Example▼
>>> LMCallbackFactory.get_lm_callback(0)
'0xLMCallback...'

get_lm_callback_count​

LMCallbackFactory.get_lm_callback_count() -> uint256: view

Returns the number of LM Callbacks deployed by this factory.

Returns: number of deployed LM Callbacks (uint256).

<>Source code▼
_lm_callbacks: DynArray[address, MAX_LM_CALLBACKS]

@external
@view
def get_lm_callback_count() -> uint256:
"""
@notice Get the number of LM Callbacks deployed by this factory
@return Number of deployed LM Callbacks
"""
return len(self._lm_callbacks)
▶Example▼

get_lm_callback_by_amm​

LMCallbackFactory.get_lm_callback_by_amm(arg0: address) -> address: view

Getter for the newest LM Callback this factory deployed for an AMM. This records deploy-time intent, not live state: the Configurator can attach any address as the callback, and a detach is never reflected here. To find the callback currently attached to a market, query liquidity_mining_callback() on the AMM itself.

InputTypeDescription
arg0addressAddress of the AMM.

Returns: newest LM Callback deployed for the AMM (address).

<>Source code▼
# The newest callback this factory deployed for an AMM - deploy-time intent, not
# live state: the Configurator can attach any address it likes, and a detach is
# never reflected here. Ask the AMM itself for what is currently attached
get_lm_callback_by_amm: public(HashMap[address, address])
▶Example▼

is_valid_gauge​

LMCallbackFactory.is_valid_gauge(arg0: address) -> bool: view

Checks whether an address is an LM Callback deployed by this factory. The getter is named after the gauge factories' is_valid_gauge because integrations validate gauges by asking their factory, and LM Callbacks are gauges from their point of view.

A True result only means the callback was deployed by this factory. It says nothing about whether the callback is currently attached to its AMM or whether its rewards are paused; see LMCallback.is_killed for that.

InputTypeDescription
arg0addressAddress to check.

Returns: True if deployed by this factory (bool).

<>Source code▼
# Named after the gauge factories' getter rather than after the callback:
# integrations validate a gauge by asking its factory for `is_valid_gauge`,
# and LM Callbacks are gauges from their point of view
is_valid_gauge: public(HashMap[address, bool])
▶Example▼

Pausing Deployments​

Pausing only stops new deployments through deploy_lm_callback. It has no effect on callbacks that are already deployed; CRV rewards on an individual callback are paused with LMCallback.set_killed.

pause​

LMCallbackFactory.pause()
Guarded Method by Snekmate 🐍

This contract makes use of a Snekmate module to manage roles and permissions. This specific function can only be called by the current owner of the factory, which is intended to be the Curve DAO.

Pauses the factory, so deploy_lm_callback reverts until it is unpaused. Reverts if the factory is already paused.

Emits: Paused event.

<>Source code▼
@external
def pause():
"""
@notice Pause new LM Callback deployments
"""
ownable._check_owner()
pausable._pause()
▶Example▼
>>> LMCallbackFactory.pause()

unpause​

LMCallbackFactory.unpause()
Guarded Method by Snekmate 🐍

This contract makes use of a Snekmate module to manage roles and permissions. This specific function can only be called by the current owner of the factory, which is intended to be the Curve DAO.

Unpauses the factory to allow new LM Callback deployments again. Reverts if the factory is not paused.

Emits: Unpaused event.

<>Source code▼
@external
def unpause():
"""
@notice Unpause the factory to allow new LM Callback deployments
"""
ownable._check_owner()
pausable._unpause()
▶Example▼
>>> LMCallbackFactory.unpause()

paused​

LMCallbackFactory.paused() -> bool: view

Getter for whether the factory is paused. While paused, deploy_lm_callback reverts.

Returns: True if paused (bool).

<>Source code▼
from snekmate.utils import pausable

initializes: pausable

exports: (
...
pausable.paused,
)
▶Example▼

Contract Ownership​

The factory uses Snekmate's ownable module. renounce_ownership is intentionally not exported: with a zero owner the blueprint could never be updated again.

owner​

LMCallbackFactory.owner() -> address: view

Getter for the owner of the factory. The owner can change the blueprint, pause and unpause deployments, and pause or resume CRV rewards on any LM Callback deployed by this factory.

Returns: owner (address).

<>Source code▼
from snekmate.auth import ownable

initializes: ownable

exports: (
# `renounce_ownership` is intentionally not exported: with a zero owner the
# blueprint could never be updated again
ownable.owner,
ownable.transfer_ownership,
...
)
▶Example▼

transfer_ownership​

LMCallbackFactory.transfer_ownership(new_owner: address)
Guarded Method by Snekmate 🐍

This contract makes use of a Snekmate module to manage roles and permissions. This specific function can only be called by the current owner of the factory.

Transfers ownership of the factory to new_owner. The new owner cannot be the zero address. Because LM Callbacks read the factory owner when set_killed is called, this also hands over the right to pause CRV rewards on every callback deployed by this factory.

InputTypeDescription
new_owneraddressAddress of the new owner.

Emits: OwnershipTransferred event.

<>Source code▼
exports: (
ownable.owner,
ownable.transfer_ownership,
...
)
▶Example▼
>>> LMCallbackFactory.transfer_ownership("0xNewOwner...")

Other Methods​

version​

LMCallbackFactory.version() -> String[5]: view

Getter for the version of the contract.

Returns: contract version (String[5]).

<>Source code▼
version: public(constant(String[5])) = "1.0.0"
▶Example▼