CRP Tutorial

This page has been deprecated. V1 documentation is partially maintained here

CRP Tutorial

Similar to Core Pools, Configurable Rights Pools are created from a public factory. Refer to Addresses to find the contract on your network of choice.

To deploy a new Configurable Rights Pool, call newCRP on the CRPFactory. This function takes three parameters (cheating a bit, since two of them are big structs):

function newCrp(
    address factoryAddress,
    ConfigurableRightsPool.PoolParams calldata poolParams,
    RightsManager.Rights calldata rights
)
    external
    returns (ConfigurableRightsPool)

The factoryAddress is that of the BFactory (see Core Concepts). The Configurable Rights Pool is the Smart Pool "wrapper" around the underlying Core Pool (BPool). You (caller of newCRP) are the controller of the Smart Pool. The Smart Pool is the controller of the Core Pool. So the Smart Pool needs to deploy a Core Pool - and requires the BFactory to do that.

The next argument is PoolParams - this is where you define the structure and basic parameters of the pool, such as the tokens it will hold, their initial weights and balances, and the swap fee.

struct PoolParams {
    // Balancer Pool Token (representing shares of the pool)
    string poolTokenSymbol;
    string poolTokenName;
    // Tokens inside the Pool
    address[] constituentTokens;
    uint[] tokenBalances;
    uint[] tokenWeights;
    uint swapFee;
}

Since the Balancer Pool Tokens are themselves ERC20 tokens, they have symbols and names. You can set both when creating your pool.

The tokens must be addresses of conforming ERC20 tokens. Balances and weights are expressed in Wei - and the weights are denormalized, not percentages. Valid denormalized weights range from 1 to 49, since the maximum total denormalized weight is 50. (This corresponds to a percentage range from 2% to 98%: 1/(1+49) = 2%; 49/(1+49) = 98%)

Note that balances must be "normalized" for the number of decimals in the token. For instance, USDC has 6 decimals, so "10" is "10000000" - not "10000000000000000000"!

Also note that adding new tokens (to pools with the addRemoveTokens right), and direct calls to updateWeight have stricter decimal limits. Core pools support tokens from 6-18 decimals. Smart pools can hold the full range of tokens - but only support addToken/updateWeight operations on 12-18 decimal tokens.

The swap fee is also expressed in Wei, as a percentage. For instance, toWei("0.01") means a 1% fee.

Finally, the Rights struct defines the permissions.

struct Rights {
    bool canPauseSwapping;
    bool canChangeSwapFee;
    bool canChangeWeights;
    bool canAddRemoveTokens;
    bool canWhitelistLPs;
    bool canChangeCap;
}

At this point (after calling newCRP), we have a deployed Configurable Rights Object with all its permissions and parameters defined. But we can't do much with it - mainly because there is no Core Pool yet. We need to deploy a new Core Pool, with our Smart Pool as the controller, by calling createPool(initialSupply). (There is also an overloaded version of createPool; more on that later.)

We've already defined the tokens and balances we want the pool to hold. When we call createPool with a value for initialSupply, it will mint initialSupply Balancer Pool Tokens (BPTs) and transfer them to the caller, simultaneously pulling the correct amount of collateral tokens into the contract. (They end up in the Core Pool, passed through the CRP.)

To accomplish this, we need to allow the CRP to spend our collateral tokens, before calling createPool. For an example three-token pool, we might write:

const MAX = web3.utils.toTwosComplement(-1);

// crpPool was returned from CRPFactory.newCRP()

await weth.approve(crpPool.address, MAX);
await dai.approve(crpPool.address, MAX);
await xyz.approve(crpPool.address, MAX);

// consume the collateral; mint and xfer 100 BPTs to caller
await crpPool.createPool(toWei('100'));

Now we're in business! The pool will already be set up for public swapping, and depending on the permissions settings, possibly public liquidity provision as well. The Core Pool will show up on the Exchange GUI.

Refer to Exchange and Reward Listing for instructions on adding any new tokens you might be introducing to the Exchange GUI, and making them eligible for BAL governance tokens. Earnings are distributed weekly, and are not applied retroactively, so you'll need to have the token approved by 00:00 UTC the Monday before you launch your pool.

Note that there is also an overloaded version of createPool, where you can specify additional parameters related to updateWeightsGradually.

function createPool(
    uint initialSupply,
    uint minimumWeightChangeBlockPeriod,
    uint addTokenTimeLockInBlocks
)

If the pool you're creating doesn't have permission to change weights, or you accept the default values of the time parameters, you can just use the single-argument version.

initialSupply can be set to a value of your choice - within min/max limits (currently 100 - 1 billion, in ether units).

minimumWeightChangeBlockPeriod enforces a minimum time between the start and end blocks of a gradual update. addTokenTimeLockInBlocks is used when adding a token (if you have the AddRemoveTokens permission), as the minimum wait time between committing a new token, and applying it (i.e., actually adding it to the pool). There is one additional constraint: minimumWeightChangeBlockPeriod >= addTokenTimeLockInBlocks.

These parameters have default values, and can only be changed by overriding them in this version of createPool. The addTokenTimeLock defaults to 500 blocks (~ 2 hours), and the blockPeriod defaults to 90,000 (~ 2 weeks). If you want to use different minimum values, be sure to set them in createPool, since they are immutable thereafter!

Also note that these only apply to gradual updates. The controller can call updateWeight directly at any time, as long as no gradual update is in progress.

You can use this simulator to explore how weight changes and updates work, estimate slippage and impermanent loss, etc. There is also a demo video illustrating the simulator functionality.

Complex functionality = lots of simulators! This one helps you calculate single- and multi-asset entry and exit.

Last updated