Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Balancer pool adaptor #216

Merged
merged 5 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
EricBolten marked this conversation as resolved.
Show resolved Hide resolved
// 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(
EricBolten marked this conversation as resolved.
Show resolved Hide resolved
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
Loading