Maple Protocol Adapter
The Maple adapter enables the Yieldinator Facet to integrate with Maple Finance's undercollateralized lending protocol, allowing vaults to earn fixed-income yields by providing liquidity to Maple's lending pools.
Overview
Maple Finance is a decentralized corporate credit market that connects institutional borrowers with lenders through a DAO-managed infrastructure. The Maple adapter facilitates deposits into Maple's lending pools, manages liquidity positions, and harvests yield from loan interest payments and MPL rewards.
Implementation Details
Contract: MapleAdapter.sol
MapleAdapter.solThe adapter implements the standard YieldinatorAdapter interface with Maple-specific functionality:
contract MapleAdapter is YieldinatorAdapter {
using SafeERC20 for IERC20;
// Maple contracts
address public mplToken; // Maple governance token
address public xMPL; // Staked MPL token
IMapleStaking public mapleStaking; // MPL staking contract
// Pool configuration
struct PoolConfig {
address pool; // Maple pool address
address poolToken; // Pool token (LP token)
bool stakeMPL; // Whether to stake MPL rewards
bool active; // Whether the pool is active
}
// Mapping from token to pool configuration
mapping(address => PoolConfig) public tokenToPool;
// Deposited amounts per token
mapping(address => uint256) public depositedAmount;
// Constructor
constructor(
address _admin,
address _mplToken,
address _xMPL,
address _mapleStaking
) YieldinatorAdapter("Maple Finance", _admin) {
require(_mplToken != address(0), "MapleAdapter: MPL token cannot be zero address");
require(_xMPL != address(0), "MapleAdapter: xMPL token cannot be zero address");
require(_mapleStaking != address(0), "MapleAdapter: staking contract cannot be zero address");
mplToken = _mplToken;
xMPL = _xMPL;
mapleStaking = IMapleStaking(_mapleStaking);
}
}Key Functions
Pool Registration
Before using the adapter for a specific token, the pool must be registered:
function registerPool(
address _token,
address _pool,
address _poolToken,
bool _stakeMPL
) external onlyRole(ADMIN_ROLE) {
require(_token != address(0), "MapleAdapter: token cannot be zero address");
require(_pool != address(0), "MapleAdapter: pool cannot be zero address");
require(_poolToken != address(0), "MapleAdapter: pool token cannot be zero address");
// Create and store pool configuration
PoolConfig memory config = PoolConfig({
pool: _pool,
poolToken: _poolToken,
stakeMPL: _stakeMPL,
active: true
});
tokenToPool[_token] = config;
emit PoolRegistered(_token, _pool);
}Deposit
The deposit function handles depositing assets into Maple's lending pools:
function deposit(address _token, uint256 _amount)
external
override
onlyRole(YIELDINATOR_ROLE)
nonReentrant
returns (bool)
{
require(_amount > 0, "MapleAdapter: amount must be greater than 0");
PoolConfig memory config = tokenToPool[_token];
require(config.active, "MapleAdapter: pool not active for token");
// Transfer token from caller
IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
// Approve pool to use tokens
IERC20(_token).safeApprove(config.pool, _amount);
// Deposit into Maple pool
uint256 balanceBefore = IERC20(config.poolToken).balanceOf(address(this));
IMaplePool(config.pool).deposit(_amount);
uint256 balanceAfter = IERC20(config.poolToken).balanceOf(address(this));
// Calculate pool tokens received
uint256 poolTokensReceived = balanceAfter - balanceBefore;
require(poolTokensReceived > 0, "MapleAdapter: no pool tokens received");
// Update deposited amount
depositedAmount[_token] += _amount;
emit Deposited(_token, _amount);
return true;
}Withdraw
The withdraw function handles withdrawing assets from Maple's lending pools:
function withdraw(address _token, uint256 _amount)
external
override
onlyRole(YIELDINATOR_ROLE)
nonReentrant
returns (uint256)
{
require(_amount > 0, "MapleAdapter: amount must be greater than 0");
require(_amount <= depositedAmount[_token], "MapleAdapter: insufficient balance");
PoolConfig memory config = tokenToPool[_token];
require(config.active, "MapleAdapter: pool not active for token");
// Calculate proportion of pool tokens to redeem
uint256 poolTokenBalance = IERC20(config.poolToken).balanceOf(address(this));
uint256 poolTokensToRedeem = poolTokenBalance * _amount / depositedAmount[_token];
// Approve pool to use pool tokens
IERC20(config.poolToken).safeApprove(config.pool, poolTokensToRedeem);
// Withdraw from Maple pool
uint256 balanceBefore = IERC20(_token).balanceOf(address(this));
IMaplePool(config.pool).withdraw(poolTokensToRedeem);
uint256 balanceAfter = IERC20(_token).balanceOf(address(this));
// Calculate amount received
uint256 amountReceived = balanceAfter - balanceBefore;
require(amountReceived > 0, "MapleAdapter: no tokens received");
// Transfer withdrawn tokens to caller
IERC20(_token).safeTransfer(msg.sender, amountReceived);
// Update deposited amount
depositedAmount[_token] -= _amount;
emit Withdrawn(_token, amountReceived);
return amountReceived;
}Harvest Yield
The harvestYield function collects MPL rewards and optionally stakes them:
function harvestYield(address _token)
external
override
onlyRole(YIELDINATOR_ROLE)
nonReentrant
returns (uint256)
{
PoolConfig memory config = tokenToPool[_token];
require(config.active, "MapleAdapter: pool not active for token");
// Claim MPL rewards from the pool
IMaplePool(config.pool).claim();
return _harvestMPLRewards(config);
}
function _harvestMPLRewards(PoolConfig memory config) internal returns (uint256) {
uint256 mplBalance = IERC20(mplToken).balanceOf(address(this));
// If staking MPL rewards is enabled, stake in Maple staking contract
if (config.stakeMPL && mplBalance > 0) {
IERC20(mplToken).safeApprove(address(mapleStaking), mplBalance);
mapleStaking.stake(mplBalance);
// Calculate xMPL received
uint256 xMPLBalance = IERC20(xMPL).balanceOf(address(this));
emit YieldHarvested(mplToken, mplBalance, xMPL, xMPLBalance);
return xMPLBalance;
} else if (mplBalance > 0) {
// Transfer MPL rewards to caller
IERC20(mplToken).safeTransfer(msg.sender, mplBalance);
emit YieldHarvested(mplToken, mplBalance, address(0), 0);
return mplBalance;
}
return 0;
}Emergency Withdraw
The emergencyWithdraw function provides a safety mechanism to withdraw all funds:
function emergencyWithdraw(address _token)
external
override
onlyRole(ADMIN_ROLE)
nonReentrant
returns (uint256)
{
PoolConfig memory config = tokenToPool[_token];
require(config.active, "MapleAdapter: pool not active for token");
// Withdraw all pool tokens from Maple pool
uint256 poolTokenBalance = IERC20(config.poolToken).balanceOf(address(this));
if (poolTokenBalance > 0) {
IERC20(config.poolToken).safeApprove(config.pool, poolTokenBalance);
IMaplePool(config.pool).withdraw(poolTokenBalance);
}
// Transfer all token balance to caller
uint256 tokenBalance = IERC20(_token).balanceOf(address(this));
if (tokenBalance > 0) {
IERC20(_token).safeTransfer(msg.sender, tokenBalance);
}
// Transfer all MPL and xMPL to caller
uint256 mplBalance = IERC20(mplToken).balanceOf(address(this));
if (mplBalance > 0) {
IERC20(mplToken).safeTransfer(msg.sender, mplBalance);
}
uint256 xMPLBalance = IERC20(xMPL).balanceOf(address(this));
if (xMPLBalance > 0) {
IERC20(xMPL).safeTransfer(msg.sender, xMPLBalance);
}
// Reset deposited amount
depositedAmount[_token] = 0;
emit EmergencyWithdrawn(_token, tokenBalance);
return tokenBalance;
}APY Calculation
The getCurrentAPY function calculates the current APY for a token in the Maple pool:
function getCurrentAPY(address _token)
external
view
override
returns (uint256)
{
PoolConfig memory config = tokenToPool[_token];
if (!config.active) return 0;
// Get base APY from Maple pool
uint256 baseAPY = IMaplePool(config.pool).getCurrentInterestRate();
// Calculate MPL rewards APY
uint256 mplRewardsAPY = _calculateMPLRewardsAPY(config.pool);
// Add additional APY from staking MPL if enabled
uint256 stakingAPY = config.stakeMPL ? _calculateStakingAPY() : 0;
// Combine APYs
return baseAPY + mplRewardsAPY + stakingAPY;
}
function _calculateMPLRewardsAPY(address _pool) internal view returns (uint256) {
// Calculate MPL rewards APY based on pool's reward rate and total value locked
// Implementation depends on Maple's specific reward distribution mechanism
return IMaplePool(_pool).getRewardsAPY();
}
function _calculateStakingAPY() internal view returns (uint256) {
// Calculate staking APY based on xMPL growth rate
return mapleStaking.getStakingAPY();
}Get Total Deposited
The getTotalDeposited function returns the total amount of tokens deposited:
function getTotalDeposited(address _token)
external
view
override
returns (uint256)
{
return depositedAmount[_token];
}Usage Example
Here's an example of how to use the Maple adapter with the Yieldinator Facet:
// Register the Maple adapter with the Yieldinator Facet
address mapleAdapter = address(new MapleAdapter(
admin,
mplToken,
xMPL,
mapleStaking
));
yieldinatorFacet.registerAdapter("Maple Finance", mapleAdapter);
// Register a pool for USDC
MapleAdapter(mapleAdapter).registerPool(
USDC,
USDC_POOL,
USDC_POOL_TOKEN,
true // Stake MPL rewards
);
// Add a yield strategy using the Maple adapter
yieldinatorFacet.addYieldStrategy(
"Maple USDC Pool",
mapleAdapter,
USDC,
3, // Medium-high risk level (undercollateralized lending)
1200 // 12.00% APY
);Security Considerations
Credit Risk: Maple Finance pools involve undercollateralized lending to institutional borrowers, which carries inherent credit risk.
Liquidity Risk: Withdrawals from Maple pools are subject to liquidity availability and may be delayed if the pool has insufficient funds.
Pool Delegate Risk: Maple pools are managed by Pool Delegates who make lending decisions, introducing potential governance risks.
Smart Contract Risk: The adapter interacts with multiple external contracts which may have vulnerabilities.
Risk Mitigation
The adapter implements strict access controls to prevent unauthorized access.
Emergency withdrawal functionality is available to recover funds in case of critical issues.
The adapter validates all inputs and handles edge cases to prevent unexpected behavior.
Integration with Maple's risk assessment framework to evaluate borrower creditworthiness.
Gas Optimization
The adapter minimizes gas usage by batching operations when possible.
For harvesting rewards, users can choose whether to stake MPL rewards based on gas costs and expected returns.
The adapter avoids unnecessary approvals by only approving tokens when needed.
Future Improvements
Support for Maple's credit default swaps (CDS) to hedge against default risk
Integration with Maple's governance system for participating in protocol decisions
Support for multiple pool delegates to diversify lending risk
Automated reinvestment of interest payments for compounding returns