This repo contains smart contract implementations of all known Linear Pool factories on Balancer. Contributions are welcome!
IMPORTANT: Before developing your own factory, ensure that your yield-bearing token is truly incompatible with existing solutions. In most cases, unique factories are not required to support forks of existing integrations (e.g., Aave v2). Furthermore, yield vaults implementing the ERC-4626 standard can leverage that factory and will not require bespoke solutions.
For more information about Boosted Pools and Linear Pools, please consult the official documentation.
Each implementation should include the components below.
The Linear Pool contract defines how the exchange rate is calculated. This is the most critical component because it determines the price of each swap within the Pool.
NOTE: All external calls within the
_getWrappedTokenRate
function should include try/catch blocks and utilize theExternalCallLib
to prevent manipulation by nefarious tokens.
At a minimum, the unit tests for each Linear Pool should verify the following assumptions:
- There is strict relationship between a
mainToken
andwrappedToken
is respected (e.g., cDAI is not paired with USDC). - Asset Managers are set correctly.
- The token rate is calculated correctly and is normalized to 1e18 regardless of
mainToken
orwrappedToken
decimals. - Malicious queries (that manipulate the token rate) are reverted.
The Linear Pool Factory contract is responsible for creating new Linear Pools.
At a minimum, the unit tests for each Linear Pool Factory should verify the following assumptions:
- A Pool can be created and its constituent tokens are registered in the Vault.
- The Factory version and Pool version are set correctly.
- An Asset Manager is created and configured for each Pool.
The Linear Pool Rebalancer contract is a helper to enable efficient, capital-free arbitrage and maintain the target balance of mainToken
within the Pool.
It doesn't have unit tests. The logic of the Rebalancer is instead verified using fork tests, since it is very sensitive to the wrappedToken
implementation.
In order to implement a Linear Pool for a given yield protocol, we need to understand the following features of that protocol:
- Can the exchange rate between the
mainToken
andwrappedToken
be queried directly, and is the return value up to date?- If not, how can we calculate the exchange rate?
- How is
mainToken
deposited to the protocol? - How is
wrappedToken
redeemed, ormainToken
withdrawn, from the protocol? - How many decimals does the
wrappedToken
have? Is it at all related to themainToken
decimals, or is it fixed?
These questions will define which interfaces need to be declared. Some protocols expose all of this via the token contract itself, whereas others perform some or all operations via a central protocol vault.
In order to properly unit test Linear Pool contracts, mocked token contracts need to be implemented. These can be found inside the __mocks__
directory.
Fork tests are also essential because they act on real token contracts rather than mocks. The Rebalancer, especially, requires precision in order to function correctly, so it is safest to verify it on a real protocol token.
Fork tests can be found inside pkg/fork-tests
, and their implementation is described in the next section.
-
Clone this repository to your machine. Ensure that you have
nodejs
installed and that your version is at least 14.x (preferably 16.x). Runyarn && yarn build
to begin, and troubleshoot any errors you encounter before moving forward. -
Create a copy of
pkg/linear-pools/contracts/erc4626-linear-pool
, and change the name to match your protocol's name (e.g.,pkg/linear-pools/contracts/<YOUR_PROTOCOL>-linear-pool
) -
Change the names of all files accordingly, e.g.,
ERC4626LinearPool.sol
,ERC4626LinearPoolFactory.sol
,ERC4626LinearPoolRebalancer.sol
, and all corresponding test files. -
Within each file, change the names of variables and classes to suit your protocol's name.
-
Inside
<YOUR_PROTOCOL>LinearPool.sol
, adapt the_getWrappedTokenRate
function to your protocol. Make sure to wrap any external calls in try/catch blocks and utilize theExternalCallLib
.- NOTE: During this step, you'll probably need to define an interface for the token/vault of the protocol, especially the function pertaining to the exchange rate.
-
Inside
<YOUR_PROTOCOL>LinearPoolRebalancer.sol
, define the_wrapTokens
(deposit),_unwrapTokens
(redeem), and_getRequiredTokensToWrap
(given an amount ofwrappedToken
, how manymainToken
do I need?) functions.- IMPORTANT:
_getRequiredTokensToWrap
also uses the token rate, so make sure that_getWrappedTokenRate
and_getRequiredTokensToWrap
use the same source to fetch the token rate. - IMPORTANT: During this step, the interface created in Step 4 will need to be expanded to include withdraw/deposit functions.
- IMPORTANT:
-
Edit the
setup
section within your Linear Pool test file to make sure you're deploying and testing the correct Linear Pool. Do not delete any tests from the copied file, since many tests apply to all kinds of Linear Pools and protocols.- NOTE:
setup
deploys a mocked version of the token, so you'll also need to implement a mock. If your protocol uses a central vault contract as well, check theAaveLinearPool
tests for examples.
- NOTE:
-
Run
yarn test
and make sure the Linear Pool tests pass. -
Edit the Linear Pool Factory test file (especially
beforeEach('deploy factory & tokens')
) to adapt to your protocol. You don't need to change the Protocol ID right now. -
Run
yarn test
and make sure the Linear Pool Rebalancer tests pass. -
To begin fork testing, navigate to
pkg/fork-tests/tests
, duplicate the ERC-4626 test folder, and change the name to your protocol. Make sure the number in the folder matches the YYYYMMDD pattern. -
Delete the
output
directory and the contents ofbuild-info
. -
Adapt
index.ts
,input.ts
, andreadme.md
to your protocol name. -
Open
pkg/linear-pools
and runyarn hardhat compile
. Once the contracts are compiled, open theartifacts/build-info
directory and copy the json inside this file topkg/fork-tests/tests/YYYYMMDD-<YOUR-PROTOCOL>-linear-pool/build-info
. -
Inside your test folder, open
test.fork.ts
and adapt it to your protocol. Make sure that you're using a recent block number, token addresses are correctly defined, and your chosen token holder has a large balance at that block number. -
Go to
pkg/fork-tests
and runyarn test
. Make sure your tests are passing.