Introducing Rewards Farmer 🧑‍🌾 🏊

Facilitating rewards token distributions from pooled liquidity

Spencer Graham
saveDAI
5 min readSep 22, 2020

--

saveDAI and COMP

Here at saveDAI, we’re building a way for people to hold an insured, interest-bearing tokenized dollar. We do that by wrapping together cDAI — interest-bearing DAI from Compound — with ocDAI — insurance on the value of cDAI from the Opyn protocol.

So far, so good. saveDAI holders get some interest and protection against smart contract, governance, and other financial risks. And now, that interest rate is even higher thanks to Compound distributing COMP to their users.

There’s only one problem: how do saveDAI users actually get their COMP? This is an issue because saveDAI users only hold saveDAI, which is a tokenized claim on a portion of the cDAI and ocDAI in the saveDAI contract. It’s the saveDAI contract which actually receives the COMP distribution!

saveDAI users want their COMP!

So now, in addition to cDAI and ocDAI, the the saveDAI contracts holds COMP as well — a total of three tokens. By design, 1 saveDAI token is a claim on 1 cDAI and 1 ocDAI token. How much COMP should 1 saveDAI token get? The answer to this question is surprisingly difficult to find. Since the distribution rate of COMP per cDAI token changes with every deposit and withdrawal into Compound, there’s no way of figuring that out without some gas-intensive accounting or sacrificing accuracy.

This is a a general problem

It turns out that this problem is not unique to saveDAI. Any protocol that’s pooling assets that generate rewards tokens will run into the same issue of distributing the rewards tokens to the pool participants. For our purposes, any token distributed as an incentive for liquidity mining is a rewards token — e.g., COMP, BAL, UMA, and many more.

Assets that earn rewards tokens are tokenized claims on underlying assets locked or staked in the protocols distributing the rewards token— e.g., cDAI, Balancer pool tokens, Uniswap pool tokens, CRV, etc.

Critically, the distribution rate of rewards tokens a user can expect to receive is not fully dependent on how much of rewards-earning assets that user holds—rather, the distribution rate depends on other factors like how many other users are earning rewards and how many rewards tokens protocol governance has assigned to that particular pool.

As a result, the amount of rewards tokens (e.g., COMP) a single rewards-earning token (e.g., cDAI) can expect to earn fluctuates significantly over time. Any contract that accepts cDAI or other rewards-earning assets as deposits will therefore be challenged to figure out how to pass on the rewards tokens it accrues to its users.

Rewards Farmer is here to help.

The solution

The main problem is that it is difficult to track how many rewards tokens each individual member of a pool deserves. Our solution separates the pool into vaults and gives each member of the pool their own vault (called “Farmers”). Since rewards tokens accrue to each member’s farmer instead of the pool, there’s no need to keep track of individual balances!

Our solution to this problem takes advantage of the Proxy Factory architecture. We’ve built a factory, FarmerFactory, which deploys a Proxy contract, Farmer, on behalf of a user. The Farmer proxy contract holds the rewards-earning asset and is where those rewards accrue. This way, we don’t need to worry about keeping track of user balances and the rewards token they have accrued.

Rewards Farmer architecture

Under the hood, the Farmer contract is very simple. Every time a Farmer proxy is deployed for a given user, the FarmerFactory maps the user’s address to the address of their deployed Farmer proxy. That’s it.

contract FarmerFactory is ProxyFactory {  /// Maps user address to the address of deployed farmer proxy.
mapping (address => address) public farmerProxy;
/// Logic contract's address
address public logicContract;
/// @dev Constructor that stores the address of the logicContract.
/// @param _logicContract The address of the logic contract.
constructor(address _logicContract)
public
{
logicContract = _logicContract;
}
/// @dev Creates new farmer proxy contract on behalf of the user.
function deployProxy(
address owner,
address assetToken,
address underlyingToken,
address rewardsToken)
public
virtual
returns (address proxy)
{
bytes memory data = _encodeData(
owner,
assetToken,
underlyingToken,
rewardsToken);
proxy = deployMinimal(logicContract, data);
farmerProxy[owner] = proxy;
return proxy;
}
}

FarmerFactory code snippet on Github .

The Farmer proxy contract can be customized to do whatever is needed for your specific use case. For example, if you need to farm COMP, the proxy contract would deposit into Compound and receive cTokens. If farming BAL, the proxy contract would deposit tokens into Balancer pools and receive pool tokens. All you need to do is inherit from the Farmer contract and add your logic.

Let us explain further with an example.

Example: saveDAI

Whenever a user mints saveDAI tokens, the original saveDAI contract deposits mints cDAI and buys ocDAI. Now, with Rewards Farmer integrated into the saveDAI contract, whenever a new user mints saveDAI, a Farmer proxy contract is deployed for that user and the cDAI is minted and stored in the proxy contract.

function mint(uint256 _amount)
external
override
whenNotPaused
returns (bool)
{
address proxy;
// if msg.sender does not have a proxy, deploy proxy
if (farmerProxy[msg.sender] == address(0)) {
proxy = deployProxy(
msg.sender,
address(cDai),
address(dai),
compToken);
} else {
proxy = farmerProxy[msg.sender];
}
// calculate DAI needed to mint _amount of cDAI & tokens
uint256 assetCost = _getCostofAsset(_amount);
// calculate DAI needed to buy _amount of ocDAI tokens
uint256 oTokenCost = getCostOfOToken(_amount);
// transfer total DAI needed
require(
dai.transferFrom(
msg.sender,
address(this),
(assetCost.add(oTokenCost))
)
);
// mint the insurance token
uint256 oTokenAmount = _uniswapBuyOCDai(oTokenCost);
// transfer DAI to the user's SaveTokenFarmer to mint cDAI
require(dai.transfer(proxy, assetCost));
// mint the interest bearing token
uint256 assetAmount = ISaveTokenFarmer(proxy).mint();
require(assetAmount == _amount, "cDAI must equal _amount"); require(oTokenAmount == _amount,"oTokens must equal _amount"); super._mint(msg.sender, _amount); emit Mint(_amount, msg.sender); return true;
}

saveDAI’s mint function using Rewards Farmer on Github

The user’s address is mapped to the Farmer proxy, and the Farmer proxy owns the cDAI and accrues all the user’s COMP. We don’t have to track individual user COMP balances or worry about anything fancy to distribute the right amount of COMP to users.

Rewards Farmer in saveDAI

There are a couple other important scenarios:

  • Minting additional saveDAI: the user deposits additional DAI into the saveDAI contract, which forwards (most of) the DAI to the users’ Farmer proxy, where it gets deposited into Compound to mint additional cDAI. contract mints additional cDAI and accrues more COMP.
  • Transferring saveDAI to another address: a Farmer proxy would be deployed for the second user (if they do not already have one), and the portion of cDAI being sent would be sent from the first user’s proxy contract to the second user’s proxy contract.

Interested in learning more about Rewards Farmer and saveDAI? Join our community on Discord!

Thanks to Kseniya Lifanova and Doug Crescenzi for their input and collaboration on this article.

--

--