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

Add Pendle Finance adapter #164

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ All the deployed contracts' addresses are available [here](../../wiki/Addresses)
| [Melon](contracts/adapters/melon) | A protocol for decentralized on-chain asset management. | [Asset adapter](contracts/adapters/melon/MelonAssetAdapter.sol) | ["MelonToken"](./contracts/adapters/melon/MelonTokenAdapter.sol) |
| [mStable](./contracts/adapters/mstable) | mStable unifies stablecoins, lending and swapping into one standard. | [Asset adapter](./contracts/adapters/mstable/MstableAssetAdapter.sol) <br> [Staking adapter](./contracts/adapters/mstable/MstableStakingAdapter.sol) | ["Masset"](./contracts/adapters/mstable/MstableTokenAdapter.sol) |
| [Nexus Mutual](./contracts/adapters/nexus) | A people-powered alternative to insurance. | [Staking adapter](./contracts/adapters/nexus/NexusStakingAdapter.sol) | — |
| [Pendle Finance](./contracts/adapters/pendle) | Trading future yield. | [Asset adapter](./contracts/adapters/pendle/PendleMarketAdapter.sol) <br> [Staking adapter](./contracts/adapters/pendle/PendleStakingAdapter.sol) | ["Pendle Market"](./contracts/adapters/pendle/PendleMarketTokenAdapter.sol) |
| [Pickle Finance](./contracts/adapters/pickle) | Off peg bad, on peg good. | [Asset adapter](./contracts/adapters/pickle/PickleAssetAdapter.sol) <br> [Staking adapter V1](./contracts/adapters/pickle/PickleStakingV1Adapter.sol) <br> [Staking adapter V2](./contracts/adapters/pickle/PickleStakingV2Adapter.sol) | ["PickleJar"](./contracts/adapters/pickle/PickleTokenAdapter.sol) |
| [PieDAO](./contracts/adapters/pieDAO) | The Asset Allocation DAO. | [Asset adapter](./contracts/adapters/pieDAO/PieDAOPieAdapter.sol) <br> [Staking adapter](./contracts/adapters/pieDAO/PieDAOStakingAdapter.sol) | ["PieDAO Pie Token"](./contracts/adapters/pieDAO/PieDAOPieTokenAdapter.sol) |
| [PoolTogether](./contracts/adapters/poolTogether) | Decentralized no-loss lottery. Supports SAI, DAI, and USDC pools. | [Asset adapter](./contracts/adapters/poolTogether/PoolTogetherAdapter.sol) | ["PoolTogether pool"](./contracts/adapters/poolTogether/PoolTogetherTokenAdapter.sol) |
Expand Down
42 changes: 42 additions & 0 deletions contracts/adapters/pendle/PendleMarketAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (C) 2020 Zerion Inc. <https://zerion.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity 0.6.5;
pragma experimental ABIEncoderV2;

import { ERC20 } from "../../ERC20.sol";
import { ProtocolAdapter } from "../ProtocolAdapter.sol";

/**
* @title Asset adapter for Pendle Finance (markets).
* @dev Implementation of ProtocolAdapter abstract contract.
* @author Anton Buenavista <anton@pendle.finance>
*/
contract PendleMarketAdapter is ProtocolAdapter {

string public constant override adapterType = "Asset";

string public constant override tokenType = "PendleMarket LP token";

/**
* @return Amount of Pendle Market LP tokens held by the given account.
* @param token Address of the Pendle Market.
* @param account Address of the user.
* @dev Implementation of ProtocolAdapter interface function.
*/
function getBalance(address token, address account) public view override returns (uint256) {
return ERC20(token).balanceOf(account);
}
}
135 changes: 135 additions & 0 deletions contracts/adapters/pendle/PendleMarketTokenAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright (C) 2020 Zerion Inc. <https://zerion.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity 0.6.5;
pragma experimental ABIEncoderV2;

import { ERC20 } from "../../ERC20.sol";
import { TokenMetadata, Component } from "../../Structs.sol";
import { TokenAdapter } from "../TokenAdapter.sol";


/**
* @dev PendleMarket contract interface.
* Only the functions required for PendleTokenAdapter contract are added.
* The PendleMarket contract is available here
* github.com/pendle-finance/pendle-core/blob/master/contracts/interfaces/IPendleMarket.sol.
*/
interface PendleMarket {
function xyt() external view returns (address);
function token() external view returns (address);
function getReserves()
external
view
returns (
uint256 xytBalance,
uint256 xytWeight,
uint256 tokenBalance,
uint256 tokenWeight,
uint256 currentBlock
);
}

/**
* @title Token adapter for PendleMarket Tokens.
* @dev Implementation of TokenAdapter abstract contract.
* @author Anton Buenavista <anton@pendle.finance>
*/
contract PendleMarketTokenAdapter is TokenAdapter {

/**
* @return TokenMetadata struct with ERC20-style token info.
* @dev Implementation of TokenAdapter interface function.
*/
function getMetadata(address token) external view override returns (TokenMetadata memory) {
return TokenMetadata({
token: token,
name: getMarketName(token),
symbol: ERC20(token).symbol(),
decimals: ERC20(token).decimals()
});
}

/**
* @return Array of Component structs with underlying tokens rates for the given token.
* @dev Implementation of TokenAdapter abstract contract function.
*/
function getComponents(address market) external view override returns (Component[] memory) {
address token = PendleMarket(market).token();
address xyt = PendleMarket(market).xyt();
uint256 totalSupply = ERC20(market).totalSupply();
(uint256 xytBalance, ,uint256 tokenBalance, , ) = PendleMarket(market).getReserves();

Component[] memory components = new Component[](2);

components[0] = Component({
token: xyt,
tokenType: "ERC20",
rate: totalSupply == 0 ? 0 : xytBalance * 1e18 / totalSupply
});
components[1] = Component({
token: token,
tokenType: "ERC20",
rate: totalSupply == 0 ? 0 : tokenBalance * 1e18 / totalSupply
});

return components;
}

function getMarketName(address market) internal view returns (string memory) {
return string(
abi.encodePacked(
getSymbol(PendleMarket(market).xyt()),
"/",
getSymbol(PendleMarket(market).token()),
" Market"
)
);
}

function getSymbol(address token) internal view returns (string memory) {
(, bytes memory returnData) = token.staticcall(
abi.encodeWithSelector(ERC20(token).symbol.selector)
);

if (returnData.length == 32) {
return convertToString(abi.decode(returnData, (bytes32)));
} else {
return abi.decode(returnData, (string));
}
}

/**
* @dev Internal function to convert bytes32 to string and trim zeroes.
*/
function convertToString(bytes32 data) internal pure returns (string memory) {
uint256 length = 0;
bytes memory result;

for (uint256 i = 0; i < 32; i++) {
if (data[i] != byte(0)) {
length++;
}
}

result = new bytes(length);

for (uint256 i = 0; i < length; i++) {
result[i] = data[i];
}

return string(result);
}
}
53 changes: 53 additions & 0 deletions contracts/adapters/pendle/PendleStakingAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (C) 2020 Zerion Inc. <https://zerion.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity 0.6.5;
pragma experimental ABIEncoderV2;

import { ProtocolAdapter } from "../ProtocolAdapter.sol";


/**
* @dev Proxy contract interface for calculating liquidity mining rewards.
* Only the functions required for PendleStakingZerionProxy contract are added.
*/
interface PendleStakingZerionProxy {
function earned(address user) external view returns (uint256);
}

/**
* @title Adapter for Pendle Finance protocol.
* @dev Implementation of ProtocolAdapter interface.
* @author Anton Buenavista <anton@pendle.finance>
*/
contract PendleStakingAdapter is ProtocolAdapter {

string public constant override adapterType = "Asset";

string public constant override tokenType = "ERC20";

address internal constant PENDLE = 0x808507121B80c02388fAd14726482e061B8da827;
address internal constant STAKING_WRAPPER = 0x3cFfEd42e0BD6d45894283d90cF3F75e7CA55855;

/**
* @return Amount of PENDLE rewards after staking for a given account.
* @dev Implementation of ProtocolAdapter interface function.
*/
function getBalance(address token, address account) external view override returns (uint256) {
if (token == PENDLE) {
return PendleStakingZerionProxy(STAKING_WRAPPER).earned(account);
}
}
}
99 changes: 99 additions & 0 deletions test/PendleMarketAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import displayToken from './helpers/displayToken';

const AdapterRegistry = artifacts.require('AdapterRegistry');
const ProtocolAdapter = artifacts.require('PendleMarketAdapter');
const TokenAdapter = artifacts.require('PendleMarketTokenAdapter');
const ERC20TokenAdapter = artifacts.require('ERC20TokenAdapter');

contract('PendleMarketTokenAdapter', () => {
const ytUSDCMarket = '0x8315BcBC2c5C1Ef09B71731ab3827b0808A2D6bD';
const ytUSDCAddress = '0xcDb5b940E95C8632dEcDc806B90dD3fC44E699fE';
const usdcAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
// Random address with positive balances
const testAddress = '0x76A16d9325E9519Ef1819A4e7d16B168956f325F';

let accounts;
let adapterRegistry;
let protocolAdapterAddress;
let tokenAdapterAddress;
let erc20TokenAdapterAddress;
const PENDLE_LPT = [
ytUSDCMarket,
'YT-aUSDC-29DEC2022/USDC Market',
'PENDLE-LPT',
'18',
];
const YT = [
ytUSDCAddress,
'YT Aave interest bearing USDC 29DEC2022',
'YT-aUSDC-29DEC2022',
'6',
];
const USDC = [
usdcAddress,
'USD Coin',
'USDC',
'6',
];

beforeEach(async () => {
accounts = await web3.eth.getAccounts();
await ProtocolAdapter.new({ from: accounts[0] })
.then((result) => {
protocolAdapterAddress = result.address;
});
await TokenAdapter.new({ from: accounts[0] })
.then((result) => {
tokenAdapterAddress = result.address;
});
await ERC20TokenAdapter.new({ from: accounts[0] })
.then((result) => {
erc20TokenAdapterAddress = result.address;
});
await AdapterRegistry.new({ from: accounts[0] })
.then((result) => {
adapterRegistry = result.contract;
});
await adapterRegistry.methods.addProtocols(
['Pendle Market'],
[[
'Mock Protocol Name',
'Mock protocol description',
'Mock website',
'Mock icon',
'0',
]],
[[
protocolAdapterAddress,
]],
[[[
ytUSDCMarket,
]]],
)
.send({
from: accounts[0],
gas: '1000000',
});
await adapterRegistry.methods.addTokenAdapters(
['ERC20', 'PendleMarket LP token'],
[erc20TokenAdapterAddress, tokenAdapterAddress],
)
.send({
from: accounts[0],
gas: '1000000',
});
});

it('should return correct balances', async () => {
await adapterRegistry.methods['getBalances(address)'](testAddress)
.call()
.then((result) => {
displayToken(result[0].adapterBalances[0].balances[0].base);
displayToken(result[0].adapterBalances[0].balances[0].underlying[0]);
displayToken(result[0].adapterBalances[0].balances[0].underlying[1]);
assert.deepEqual(result[0].adapterBalances[0].balances[0].base.metadata, PENDLE_LPT);
assert.deepEqual(result[0].adapterBalances[0].balances[0].underlying[0].metadata, YT);
assert.deepEqual(result[0].adapterBalances[0].balances[0].underlying[1].metadata, USDC);
});
});
});
Loading