Rena V2 Advanced User Manual

In-depth overview of contract interactions for Rena protocol.

Explanation of terms:

rBond: This is a unique contract generated in the RenaV2 system that wraps tokens and locks them for a specified duration. Each rBond is tied to a pair of tokens and has a unique identifier. The tokens within the rBond can only be retrieved after a specific time period, determined at the creation of the rBond.

rToken: The rToken is a virtual representation of liquidity in a particular currency within the RenaV2 system. It's the second token in an rBond pair. It is not a real token but instead represents the liquidity of the currency being used in the rBond contract. Its value can be adjusted during the rebalancing process to affect the price of the paired token.

rDistributor: An rDistributor is an instance of a contract that is created and tied to an rBond upon its initialization. It manages the distribution and rebalancing of liquidity within an rBond. The rDistributor ensures the price of the token in the rBond corresponds to that on Uniswap (after applying any applicable discount), by adjusting the liquidity of the rToken.

rBalancer: The rBalancer is a contract instance in RenaV2 that can be assigned the BALANCER_ROLE, giving it the ability to call the "balance" function on the rDistributor. This function allows for scheduled rebalancing of the rBonds. Essentially, the rBalancer can automatically manage the rebalancing and reward distribution of multiple rBonds on a scheduled basis.

Overview

RenaV2 is a permissionless, one-way, single-sided liquidity DEX inspired by UniswapV2. Pairs on RenaV2 are called rBonds. Purchased tokens are wrapped and can only be retrieved after a specific time. Each rBond can have a different lock duration. The second token of the rBond is always an rToken, which is a virtual liquidity of a currency. When purchasing from rBonds with currency tokens (like WETH), the current token is sent to a destination and does not remain on the rBond contract. A rebalancing mechanism is embedded in RenaV2 rBonds, which are bound to a UniswapV2 or UniswapV3 pair with the same tokens. Upon rebalancing an rBond, the liquidity is adjusted in such a way that a discount is applied to the rBond tokens.

Creating rBonds

To create a new rBond one has to call the "addLiquidity" function on RenaV2PoolManager contract.

function addLiquidity(
    address tokenA, 
    address tokenB, 
    uint256 amountADesired, 
    uint256 amountBDesired, 
    uint256 amountAMin, 
    uint256 amountBMin, 
    address to, 
    uint256 deadline) external returns (uint256 amountA, uint256 amountB, uint256 liquidity)

RenaV2PoolManager will then call the RenaV2Factory to create a new Pair with the given tokens. Upon pair initialization, the rBond creator is set as the rBond admin and is the only one able to add or remove liquidity from the pair. RenaV2Factory will also create an rDistributor instance and bind it to the pair, thus creating an rBond. The rBond creator is set as the default admin for rDistributor. The final step to have a valid rBond is to set the uniswap version on the rDistributor instance.

function setUniswapVersion(bool isUniV2, uint24 uniV3Fee) external returns (address pair)

Before calling this function you must ensure that a UniswapV2 or UniswapV3 pair exists for your rBond. Setting "isUniV2" to true will automatically locate and bind the UniswapV2 pair to the rBond. "uniV3Fee" value is ignored in this case. Setting "isUniV2" to false will automatically locate and bind the UniswapV3 pair to the rBond. You need to provide a valid "uniV3Fee" value as multiple pairs of the same tokens could exist with a different fee on UniswapV3.

Only tokenA is taken from the caller, tokenB being the rToken the liquidity is entirely virtual. The liquidity token is credited to the "to" address.

Swapping

Like the Router in UniswapV2, RenaV2 has a helper contract to make swapping easier. RenaV2SwapHelper has similar functions to UniswapV2Router regarding swapping.

function swapExactETHForTokens(uint amountOutMin, address tokenOut, address to, uint deadline)
external payable ensure(deadline) returns (uint256 amountOut)

function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address tokenIn, address tokenOut, address to, uint256 deadline) external returns (uint256 amountOut)

function swapTokensForExactTokens(uint256 amountOut, uint256 amountInMax, address tokenIn, address tokenOut, address to, uint256 deadline) external returns (uint256 amountIn)

function quoteSwapExactTokensForTokens(uint256 amountIn, address tokenIn, address tokenOut) external view returns (uint256 amountOut)

function quoteSwapTokensForExactTokens(uint256 amountOut, address tokenIn, address tokenOut) external view returns (uint256 amountIn)

Since liquidity is single-sided, it is not possible to swap both ways and because of the rBonding system, it is impossible to perform multiple swaps at once, swapping is always limited to a single rBond at a time. Upon swapping with the helper contract, the sold token is taken from the user and sent to the RenaV2Pair contract. Each RenaV2Pair contract has an rBond contract instance storing the purchased tokens and locking them for a time. The locking duration is unique to each rBond. Each purchase is identified by a unique ID on the rBond instance.

Claiming Purchased Tokens

To claim purchased tokens, one has to call the "unlock" function on the rBond instance of the RenaV2Pair.

function unlock(uint256 id) external

Only the purchaser (the address who performed the swap) can claim the tokens. Tokens are claimable only after the lock duration has expired. The expiration corresponds to the locking duration specified by the rBond owner. Upon claiming, the tokens are transferred to the caller.

rBalancing

Only addresses with the BALANCER_ROLE can call the "balance" function on the rDistributor. By default, the rBond creator owns this role. The role can be shared by multiple addresses. RenaV2 has a rBalancer contract instance which can be credited with the role, allowing RenaV2 scheduled rebalancing to call the "balance" function on the rBond. the rBond owner can choose to call this function himself.

function balance() external

The call will fail if the rBond is not bound to a uniswap pair. What "balance" essentially does is to check the bound uniswap pair and adjust the rToken liquidity so that the price of the sold token is the same that on uniswap minus the discount to apply. The liquidity of the sold token is not changed, the price of the token is set only by changing the rToken liquidity.

Providing liquidity

Only the rBond owner can provide liquidity. It is done by calling the "addLiquidity" function from the RenaV2PoolManager instance, the same as for the rBond creation. Removing the liquidity is done by calling:

function removeLiquidity(address tokenA, address tokenB, uint256 liquidity, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline) public returns (uint256 amountA, uint256 amountB)

The liquidity token must be owned by the rBond owner for this to work. The liquidity token is transferred to the RenaV2Pair contract and burned. The caller receives back the token of the RenaV2Pair in proportion to, the percentage of removed liquidity. The rToken liquidity is also removed accordingly.

Rewarding

Rewarding is an optional feature of RenaV2 rBonds. It requires the deployment of an rStaking (or a custom staking contract) and rRewarder contracts. The idea behind rewarding is purchasing uniswapV2 lp tokens using the accumulating token and distributing those to rStakers. For example for official RenaV2 pairs, the destination to send the real token behind the rToken is set to be a rewarder. WETH is sent to RenaV2 rewarders when users swap ETH into rBonds on RenaV2 rBonds. The rewarder, in the same way as the rebalancing function of the rBond can then be called using:

function reward(uint256 minimalExpectedLiquidity, uint256 deadline) external

"minimalExpectedLiquidity" is the slippage argument and is very important to compute properly, this is the minimal amount of liquidity tokens purchased by the rewarder with the accumulating token (usually WETH). The "reward" function has a cooldown period, which is ignored by the REBALANCER_ROLE. One might chose to give the RenaV2 rBalancer contract the REBALANCER_ROLE role, allowing RenaV2 to periodically call the project rewarder on the same schedule as all the others. The "reward" function can also be called by REWARDER_ROLE, which belongs to the rRewarderV2 deployer by default, in this case the cooldown period is applied.

Upon calling the function, the contract will buy the second token of the pair using 50% of the accumulated token and use both tokens to provide liquidity to the uniswap pair. The resulting LP tokens are then transferred to the rStaking contract for distribution.

A delay is implemented within the contract, so only tokens accumulated before the last "reward" call are used in the process. This means that any tokens accumulated after the last reward call would only be available for selling 2 calls after.

The contract is able to receive ETH and will wrap them into WETH automatically.

rStaking

RenaV2 has a default staking contract.

function stakeFor(address account, uint256 amount, bytes data) public

function unstake(uint256 amount, bytes data) external

function stake(uint256 amount, bytes calldata data) external

"stakeFor" allows to stake from an address but credit another. "stake" is a simpler function than "stakeFor" and will use the caller as the default account. "unstake" can only be called by the stake owner, there is no permit system implemented.

"data" is the abi-encoded locking duration of the stake.

The main principle of the rStaking contract is that a boost to stakes can be applied if the stake is locked. A linear boost function assigns the boost of the stake, based on the locking duration and maximum locking duration. The maximum locking duration is always 1 year, thus to get 100% boost, the rStaker must lock the stake for a year.

The boost is translated to an increased amount of shares within the rStaking contract. When unstaking, only the original amount of staked tokens is retrieved, and not based on the boosted share.

The contract has a distribute function:

function distribute(uint256 amount) external

When called, it will distribute the rewards based on owned shares.

In case rewards are sent directly to the contract, another function allows for a subsequent distribution:

function forward() external

When rStaking, tokens are taken from the caller.

Upon unstaking, the rewards are automatically transferred to the caller. Additionally, a function allows for withdrawing rewards without unstaking:

function withdraw(uint256 amount)

The "amount" argument is the count of shares and not the desired amount of reward tokens.

Automation

RenaV2 has an rBalancer contract instance deployed, which is able to call rDistributor and rRewarder instances of projects to apply discounts and distribute lp rewards on a scheduled basis. An rBond creator can operate his rBond himself or decide to allow the rBalancer to manage his rBond for him. The rBalancer is called through a script hosted by RenaV2.

function rebalance(contract IRDistributor[] distributors, contract IRRewarder[] rewarders, uint256[] minimalExpectedLiquidity, uint256 deadline) external

The rebalance function takes a list of distributors and rewarders to call. "minimalExpectedLiquidity" is an array of the same size as "rewarders" and specifies which slippage to accept for each rRewarderV2 reward call. "minimalExpectedLiquidity" is automatically computed by the script.

Calls to distributors and rewarders can fail without impacting the call to the next rBond, thus a broken rBond will not prevent the other rBonds from operating.

Administration

Existing admin roles: rRewarderV2: REWARDER_ROLE, REBALANCER_ROLE, CONFIG_ROLE, DEFAULT_ADMIN_ROLE rDistributor: DISCOUNTER_ROLE, BALANCER_ROLE RenaV2Pair: owner

The owner of RenaV2Pair can be changed. The rRewarderV2 deployer has by default the DEFAULT_ADMIN_ROLE, CONFIG_ROLE, REWARDER_ROLE. For rRewerderV2, REBALANCER role can call the reward distribution without respecting the cooldown period. The admin role for all roles is DEFAULT_ADMIN_ROLE. The rDistributor grants DISCOUNTER_ROLE and BALANCER_ROLE to the addresses specified upon deployment. Those roles are also the admin roles for themselves.

Last updated