Skip to content

Commit

Permalink
Balancer pool adaptor (#216)
Browse files Browse the repository at this point in the history
* Balancer pool adaptor

* review items 1

* Rework pool ID parsing

* Fmt

* Clippy
  • Loading branch information
cbrit authored Aug 16, 2023
1 parent 1098e96 commit 48de065
Show file tree
Hide file tree
Showing 18 changed files with 26,357 additions and 689 deletions.
1 change: 1 addition & 0 deletions docs/api/cellar_v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ Represents a call to adaptor an. The cellar must be authorized to call the targe
| morpho_aave_v3_a_token_collateral_v1_calls | [MorphoAaveV3ATokenCollateralAdaptorV1Calls](#steward-v3-MorphoAaveV3ATokenCollateralAdaptorV1Calls) | | Represents function calls to the MorphoAaveV3ATokenCollateral V1 |
| morpho_aave_v3_a_token_p2p_v1_calls | [MorphoAaveV3ATokenP2PAdaptorV1Calls](#steward-v3-MorphoAaveV3ATokenP2PAdaptorV1Calls) | | Represents function calls to the MorphoAaveV3ATokenP2P V1 |
| morpho_aave_v3_debt_token_v1_calls | [MorphoAaveV3DebtTokenAdaptorV1Calls](#steward-v3-MorphoAaveV3DebtTokenAdaptorV1Calls) | | Represents function calls to the MorphoAaveV3DebtToken V1 |
| balancer_pool_v1_calls | [BalancerPoolAdaptorV1Calls](#steward-v3-BalancerPoolAdaptorV1Calls) | | Represents function calls to the BalancerPoolAdaptor V1 |



Expand Down
3 changes: 3 additions & 0 deletions proto/adaptors/aave/aave_v3_debt_token.proto
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ option go_package = "/steward_proto";
import "adaptors/aave/a_token.proto";
import "adaptors/aave/debt_token.proto";
import "adaptors/aave/aave_v3_a_token.proto";
import "adaptors/balancer/balancer_pool.proto";
import "adaptors/compound/c_token.proto";
import "adaptors/frax/f_token.proto";
import "adaptors/morpho/morpho_aave_v2_a_token.proto";
Expand Down Expand Up @@ -149,6 +150,8 @@ message AaveV3DebtTokenAdaptorV1 {
MorphoAaveV3ATokenP2PAdaptorV1Calls morpho_aave_v3_a_token_p2p_v1_calls = 22;
// Represents function calls to the MorphoAaveV3DebtToken V1
MorphoAaveV3DebtTokenAdaptorV1Calls morpho_aave_v3_debt_token_v1_calls = 23;
// Represents function calls to the BalancerPoolAdaptor V1
BalancerPoolAdaptorV1Calls balancer_pool_v1_calls = 24;
}
}
}
Expand Down
165 changes: 165 additions & 0 deletions proto/adaptors/balancer/balancer_pool.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
* Protos for function calls to the Balancer Pool adaptor.
*/

syntax = "proto3";
package steward.v3;

option go_package = "/steward_proto";

import "adaptors/base.proto";

// Represents call data for the Balancer Pool adaptor V1, for managing pool positions on Balancer.
message BalancerPoolAdaptorV1 {
oneof function {
/***** BASE ADAPTOR FUNCTIONS *****/

// Represents function `revokeApproval(ERC20 asset, address spender)`
RevokeApproval revoke_approval = 1;

/***** ADAPTOR-SPECIFIC FUNCTIONS *****/

// Represents function `relayerJoinPool(ERC20[] tokensIn, uint256[] amountsIn, ERC20 btpOut, bytes[] memory callData)`
JoinPool join_pool = 2;
// Represents function `relayerExitPool(ERC20 bptIn, uint256 amountIn, ERC20[] memory tokensOut, bytes[] memory callData)`
ExitPool exit_pool = 3;
// Represents function `stakeBPT(ERC20 _bpt, address _liquidityGauge, uint256 _amountIn)`
StakeBPT stake_bpt = 4;
// Represents function `unstakeBPT(ERC20 _bpt, address _liquidityGauge, uint256 _amountOut)`
UnstakeBPT unstake_bpt = 5;
// Represents function `claimRewards(address gauge)`
ClaimRewards claim_rewards = 6;

}

// Represents the SwapKind enum defined here:
// https://github.com/PeggyJV/cellar-contracts/blob/main/src/interfaces/external/Balancer/IVault.sol
enum SwapKind {
SWAP_KIND_UNSPECIFIED = 0;
SWAP_KIND_GIVEN_IN = 1;
SWAP_KIND_GIVEN_OUT = 2;
}

// Data for a single swap executed by `swap`. `amount` is either `amountIn` or `amountOut` depending on the `kind` value.
// Represents the SingleSwap struct defined here:
// https://github.com/PeggyJV/cellar-contracts/blob/main/src/interfaces/external/Balancer/IVault.sol
message SingleSwap {
// The pool ID (bytes32)
string pool_id = 1;

// The swap kind (enum)
SwapKind kind = 2;

// The asset in (address)
string asset_in = 3;

// The asset out (address)
string asset_out = 4;

// The amount (uint256)
string amount = 5;

// The user data (bytes)
bytes user_data = 6;
}

// Stores each swaps min amount, and deadline
message SwapData {
// The minimum amounts for swaps
repeated string min_amounts_for_swaps = 1;

// The swap deadlines
repeated string swap_deadlines = 2;
}

/*
* Allows strategists to join Balancer pools using EXACT_TOKENS_IN_FOR_BPT_OUT joins
*
* Represents function `joinPool(ERC20 targetBpt, IVault.SingleSwap[] memory swapsBeforeJoin, SwapData memory swapData, uint256 minimumBpt)`
*/
message JoinPool {
// The target pool
string target_bpt = 1;

// Swap to execute before joining pool
repeated SingleSwap swaps_before_join = 2;

// Data for swaps
SwapData swap_data = 3;

// The minimum BPT to mint
string minimum_bpt = 4;
}


message ExitPoolRequest {
repeated string assets = 1;
repeated string min_amounts_out = 2;
bytes user_data = 3;
bool to_internal_balance = 4;
}

/*
* Call `BalancerRelayer` on mainnet to carry out exit txs
*
* Represents function `exitPool(ERC20 targetBpt, IVault.SingleSwap[] memory swapsBeforeJoin, SwapData memory swapData, IVault.ExitPoolRequest request)`
*/
message ExitPool {
// The target pool
string target_bpt = 1;

// Swaps to execute after exiting pool
repeated SingleSwap swaps_after_exit = 2;

// Data for swaps
SwapData swap_data = 3;

ExitPoolRequest request = 4;
}

/*
* Stake (deposit) BPTs into respective pool gauge
*
* Represents `function stakeBPT(ERC20 _bpt, address _liquidityGauge, uint256 _amountIn)``
*/
message StakeBPT {
// The BPT to stake
string bpt = 1;

// The liquidity gauge to stake into
string liquidity_gauge = 2;

// The amount to stake
string amount_in = 3;
}

/*
* Unstake (withdraw) BPT from respective pool gauge
*
* Represents `function unstakeBPT(ERC20 _bpt, address _liquidityGauge, uint256 _amountOut)``
*/
message UnstakeBPT {
// The BPT to unstake
string bpt = 1;

// The liquidity gauge to unstake from
string liquidity_gauge = 2;

// The amount to unstake
string amount_out = 3;
}

/*
* Claim rewards ($BAL) from LP position
*
* Represents `function claimRewards(address gauge)`
*/
message ClaimRewards {
// The gauge to claim rewards from
string gauge = 1;
}
}

message BalancerPoolAdaptorV1Calls {
repeated BalancerPoolAdaptorV1 calls = 1;
}
3 changes: 3 additions & 0 deletions proto/cellar_v2.proto
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import "adaptors/aave/a_token.proto";
import "adaptors/aave/debt_token.proto";
import "adaptors/aave/aave_v3_a_token.proto";
import "adaptors/aave/aave_v3_debt_token.proto";
import "adaptors/balancer/balancer_pool.proto";
import "adaptors/compound/c_token.proto";
import "adaptors/frax/f_token.proto";
import "adaptors/morpho/morpho_aave_v2_a_token.proto";
Expand Down Expand Up @@ -438,5 +439,7 @@ message AdaptorCall {
MorphoAaveV3ATokenP2PAdaptorV1Calls morpho_aave_v3_a_token_p2p_v1_calls = 22;
// Represents function calls to the MorphoAaveV3DebtToken V1
MorphoAaveV3DebtTokenAdaptorV1Calls morpho_aave_v3_debt_token_v1_calls = 23;
// Represents function calls to the BalancerPoolAdaptor V1
BalancerPoolAdaptorV1Calls balancer_pool_v1_calls = 24;
}
}
1 change: 1 addition & 0 deletions steward/src/cellars/adaptors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod aave_v2;
pub mod aave_v2_collateral;
pub mod aave_v3;
pub mod balancer_pool;
pub mod compound;
pub mod f_token;
pub mod fees_and_reserves;
Expand Down
3 changes: 3 additions & 0 deletions steward/src/cellars/adaptors/aave_v3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@ fn get_encoded_adaptor_calls(
MorphoAaveV3DebtTokenV1Calls(params) => {
calls.extend(adaptors::morpho::morpho_aave_v3_debt_token_adaptor_v1_calls(params)?)
}
BalancerPoolV1Calls(params) => calls.extend(
adaptors::balancer_pool::balancer_pool_adaptor_v1_calls(params)?,
),
};

result.push(AbiAdaptorCall {
Expand Down
171 changes: 171 additions & 0 deletions steward/src/cellars/adaptors/balancer_pool.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
use std::convert::TryInto;

use ethers::{abi::AbiEncode, types::Bytes};
use steward_abi::balancer_pool_adaptor_v1::{
BalancerPoolAdaptorV1Calls, ExitPoolRequest, SingleSwap as AbiSingleSwap,
SwapData as AbiSwapData,
};
use steward_proto::steward::balancer_pool_adaptor_v1::{self, SingleSwap, SwapData};

use crate::{
error::{Error, ErrorKind},
utils::{sp_call_error, sp_call_parse_address, string_to_u256},
};

pub(crate) fn balancer_pool_adaptor_v1_calls(
params: steward_proto::steward::BalancerPoolAdaptorV1Calls,
) -> Result<Vec<Bytes>, Error> {
let mut calls = Vec::new();
for c in params.calls {
let function = c
.function
.ok_or_else(|| sp_call_error("function cannot be empty".to_string()))?;

match function {
balancer_pool_adaptor_v1::Function::RevokeApproval(p) => {
let call = steward_abi::balancer_pool_adaptor_v1::RevokeApprovalCall {
asset: sp_call_parse_address(p.asset)?,
spender: sp_call_parse_address(p.spender)?,
};
calls.push(
BalancerPoolAdaptorV1Calls::RevokeApproval(call)
.encode()
.into(),
)
}
balancer_pool_adaptor_v1::Function::JoinPool(p) => {
if !p.swaps_before_join.is_empty()
&& p.swaps_before_join.iter().any(|s| s.kind == 0)
{
return Err(sp_call_error("invalid swap kind".to_string()));
}

let swaps_before_join = convert_single_swap(p.swaps_before_join)?;

if p.swap_data.is_none() {
return Err(sp_call_error("swap data must be set".to_string()));
}

let swap_data = convert_swap_data(p.swap_data.unwrap())?;

let call = steward_abi::balancer_pool_adaptor_v1::JoinPoolCall {
target_bpt: sp_call_parse_address(p.target_bpt)?,
swaps_before_join,
swap_data,
minimum_bpt: string_to_u256(p.minimum_bpt)?,
};

calls.push(BalancerPoolAdaptorV1Calls::JoinPool(call).encode().into())
}
balancer_pool_adaptor_v1::Function::ExitPool(p) => {
if !p.swaps_after_exit.is_empty() && p.swaps_after_exit.iter().any(|s| s.kind == 0)
{
return Err(sp_call_error("invalid swap kind".to_string()));
}

let swaps_after_exit = convert_single_swap(p.swaps_after_exit)?;

if p.swap_data.is_none() {
return Err(sp_call_error("swap data must be set".to_string()));
}

let swap_data = convert_swap_data(p.swap_data.unwrap())?;

let request = match p.request {
Some(r) => ExitPoolRequest {
assets: r
.assets
.into_iter()
.map(sp_call_parse_address)
.collect::<Result<Vec<_>, Error>>()?,
min_amounts_out: r
.min_amounts_out
.into_iter()
.map(string_to_u256)
.collect::<Result<Vec<_>, Error>>()?,
user_data: r.user_data.into(),
to_internal_balance: r.to_internal_balance,
},
None => return Err(sp_call_error("exit pool request must be set".to_string())),
};

let call = steward_abi::balancer_pool_adaptor_v1::ExitPoolCall {
target_bpt: sp_call_parse_address(p.target_bpt)?,
swaps_after_exit,
swap_data,
request,
};

calls.push(BalancerPoolAdaptorV1Calls::ExitPool(call).encode().into())
}
balancer_pool_adaptor_v1::Function::StakeBpt(p) => {
let call = steward_abi::balancer_pool_adaptor_v1::StakeBPTCall {
bpt: sp_call_parse_address(p.bpt)?,
liquidity_gauge: sp_call_parse_address(p.liquidity_gauge)?,
amount_in: string_to_u256(p.amount_in)?,
};
calls.push(BalancerPoolAdaptorV1Calls::StakeBPT(call).encode().into())
}
balancer_pool_adaptor_v1::Function::UnstakeBpt(p) => {
let call = steward_abi::balancer_pool_adaptor_v1::UnstakeBPTCall {
bpt: sp_call_parse_address(p.bpt)?,
liquidity_gauge: sp_call_parse_address(p.liquidity_gauge)?,
amount_out: string_to_u256(p.amount_out)?,
};
calls.push(BalancerPoolAdaptorV1Calls::UnstakeBPT(call).encode().into())
}
balancer_pool_adaptor_v1::Function::ClaimRewards(p) => {
let call = steward_abi::balancer_pool_adaptor_v1::ClaimRewardsCall {
gauge: sp_call_parse_address(p.gauge)?,
};
calls.push(
BalancerPoolAdaptorV1Calls::ClaimRewards(call)
.encode()
.into(),
)
}
}
}

Ok(calls)
}

fn convert_single_swap(swaps: Vec<SingleSwap>) -> Result<Vec<AbiSingleSwap>, Error> {
swaps
.into_iter()
.map(|s| {
let pool_id = hex::decode(s.pool_id)
.map_err(|e| {
ErrorKind::SPCallError.context(format!("failed to decode pool_id: {e}"))
})?
.try_into()
.map_err(|_| {
ErrorKind::SPCallError.context("pool ID must be 32 bytes".to_string())
})?;

Ok(AbiSingleSwap {
pool_id,
kind: (s.kind - 1) as u8,
asset_in: sp_call_parse_address(s.asset_in)?,
asset_out: sp_call_parse_address(s.asset_out)?,
amount: string_to_u256(s.amount)?,
user_data: s.user_data.into(),
})
})
.collect::<Result<Vec<_>, Error>>()
}

fn convert_swap_data(data: SwapData) -> Result<AbiSwapData, Error> {
Ok(AbiSwapData {
min_amounts_for_swaps: data
.min_amounts_for_swaps
.into_iter()
.map(string_to_u256)
.collect::<Result<Vec<_>, Error>>()?,
swap_deadlines: data
.swap_deadlines
.into_iter()
.map(string_to_u256)
.collect::<Result<Vec<_>, Error>>()?,
})
}
Loading

0 comments on commit 48de065

Please sign in to comment.