-
Notifications
You must be signed in to change notification settings - Fork 261
Super Token Yield Backends: Aave & ERC4626 #2125
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
Merged
Merged
Changes from all commits
Commits
Show all changes
51 commits
Select commit
Hold shift + click to select a range
f2ced44
WIP: tokens routed through backend
d10r 7945dc7
add to host and governance contract
d10r 5c28327
added tests for measuring gas cost
d10r 1d3e9be
simplication
d10r d19348c
added method for withdrawing surplus
d10r 04a4e1c
natspec
d10r 19624e6
made it work for SETH
d10r 7db2940
use helper lib to reduce boilerplate code around delegatecall
d10r 0e4c436
remove duplicate submodule entry
d10r 8b9979c
added missing storage slot test, interface doc, updated solhint
d10r 806dba1
added missing storage slot test, interface doc, updated solhint
d10r a75947d
advance hardhat and truffle config to cancun
d10r 3621b96
immutable surplus receiver
d10r f217e28
added test for rounding error
d10r 82bc629
consider non-deposited underlying when calculating surplus
d10r 1987a65
added spark yield backend
d10r 2002d94
added missing newline
d10r 955a94f
removed unnecessary methods
d10r 47e8081
fix indentation
d10r 882ec21
add delegateCallChecked to CallUtils
d10r 5a615d8
updated CHANGELOG
d10r 8ab52e1
renamed SparkYieldBackend to ERC4626YieldBackend
d10r fd0b070
split out AaveETHYieldBackend
d10r 5f695a2
more comments
d10r 4bbbd2e
added test for grifting case
d10r 061a491
unit tests
d10r 3500962
remove console logs
d10r fd3aa4f
add re-done integration test for AaveYieldBackend
d10r e459e00
add randomized sequence
d10r 121df80
remove prev test code
d10r 17da953
Merge branch 'dev' into 2025-12-yield
d10r ce01fa7
appease linter
d10r 61af65b
adjust solhint rules for all packages
d10r 4272afd
change hot-fuzz evm target
d10r a9bc707
fixed solc requirement
d10r d77a933
use hardhat node for deployment test
d10r f2883eb
update dep solidity-coverage
d10r 886554a
update evm version in automation packages foundry config
d10r 1e49964
solve open TODOs
d10r d8ca70d
added integration test for AaveETHBackend and related logic fix
d10r 5fdb16e
Merge branch 'dev' into 2025-12-yield
d10r c044943
added tests for delegateCallChecked
d10r d69e4b0
Update README.md codecov link
hellwolf 1b04366
removed all uses of ganache
d10r fb61d8b
Merge branch 'dev' into 2025-12-yield
d10r 92112ab
remove TODOs
d10r ac0b856
corrected address of native token wrapper for Polygon
d10r 2a9428b
propagate error with delegateCallChecked
d10r 8f8ff4e
added integration test for ERC4626
d10r 4d24a83
added access control tests for new SuperToken methods
d10r 101b154
added negative test for withdrawSurplus
d10r File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
packages/ethereum-contracts/contracts/interfaces/superfluid/IYieldBackend.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity >= 0.8.11; | ||
|
|
||
| /** | ||
| * A yield backend acts as interface between an ERC20 wrapper SuperToken and a yield generating protocol. | ||
| * The underlying token can be deposited on upgrade and withdrawn on downgrade. | ||
| * | ||
| * It is possible to transition from no/one yield backend to another/no yield backend. | ||
| * one -> another could be seen as a composition of one -> no -> another | ||
| * | ||
| * one -> no means withdraw not in the context of a downgrade. | ||
| * | ||
| * Contracts implementing this act as a kind of hot-pluggable library, | ||
| * using delegatecall to execute its logic on the SuperToken contract. | ||
| * This means that underlying tokens are transferred directly between the SuperToken contract and the yield protocol, | ||
| * as are yield protocol tokens representing positions in that protocol. | ||
| * If an implementation requires to hold state, it shall do so using a namespaced storage layout (EIP-7201). | ||
| */ | ||
| interface IYieldBackend { | ||
| /// Invoked by `SuperToken` as delegatecall. | ||
| /// Sets up the SuperToken as needed, e.g. by giving required approvals. | ||
| function enable() external; | ||
|
|
||
| /// Invoked by `SuperToken` as delegatecall. | ||
| /// Restores the prior state, e.g. by revoking given approvals | ||
| function disable() external; | ||
|
|
||
| /// Invoked by `SuperToken` as delegatecall. | ||
| /// Deposits the given amount of the underlying asset into the yield backend. | ||
| function deposit(uint256 amount) external; | ||
|
|
||
| /// Invoked by `SuperToken` as delegatecall. | ||
| /// Withdraws the given amount of the underlying asset from the yield backend. | ||
| function withdraw(uint256 amount) external; | ||
|
|
||
| /// Invoked by `SuperToken` as delegatecall. | ||
| /// Withdraws the maximum withdrawable amount of the underlying asset from the yield backend. | ||
| function withdrawMax() external; | ||
|
|
||
| /// Invoked by `SuperToken` as delegatecall. | ||
| /// tranfers the deposited asset exceeding totalSupply of the SuperToken to the preset receiver account | ||
| function withdrawSurplus(uint256 totalSupply) external; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
packages/ethereum-contracts/contracts/superfluid/AaveETHYieldBackend.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.23; | ||
|
|
||
| import { AaveYieldBackend } from "./AaveYieldBackend.sol"; | ||
| import { IERC20 } from "../interfaces/superfluid/ISuperfluid.sol"; | ||
| import { IPool } from "aave-v3/src/contracts/interfaces/IPool.sol"; | ||
| import { IWETH } from "aave-v3/src/contracts/helpers/interfaces/IWETH.sol"; | ||
|
|
||
| /** | ||
| * @title a SuperToken yield backend for the Aave protocol for ETH/native tokens. | ||
| * This contract extends AaveYieldBackend to support native ETH by wrapping it to WETH. | ||
| * WETH addresses are hardcoded by chain id. | ||
| * | ||
| * NOTE: "WETH" is to be interpreted in a technical sense: the native token wrapper. | ||
| * On chains with ETH not being the native token, the ERC20 token with symbol "WETH" may be an ordinary ERC20 | ||
| * while the ERC20 wrapper of the native token may have a different symbol. We mean the latter! | ||
| * | ||
| * NOTE: Surplus WETH will NOT be unwrapped by `withdrawSurplus` (which is inherited from the Base contract) | ||
| * before transferring it to the configured SURPLUS_RECEIVER. | ||
| */ | ||
| contract AaveETHYieldBackend is AaveYieldBackend { | ||
| AaveETHYieldBackend internal immutable _SELF; | ||
|
|
||
| // THIS CONTRACT CANNOT HAVE STATE VARIABLES! | ||
| // IF STATE IS NEEDED, USE NAMESPACED STORAGE LAYOUT (EIP-7201) | ||
|
|
||
| /** | ||
| * @param aavePool the Aave pool | ||
| * @param surplusReceiver the address to receive the surplus asset when withdrawing the surplus | ||
| */ | ||
| constructor(IPool aavePool, address surplusReceiver) | ||
| AaveYieldBackend(IERC20(getWETHAddress()), aavePool, surplusReceiver) | ||
| { | ||
| _SELF = this; | ||
| } | ||
|
|
||
| /// get the canonical native token ERC20 wrapper contract address based on the chain id and Aave deployment. | ||
| /// Implemented for chains with official deployments of Aave and Superfluid. | ||
| function getWETHAddress() internal view returns (address) { | ||
| if (block.chainid == 1) { // Ethereum | ||
| return 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; | ||
| } | ||
| if (block.chainid == 10 || block.chainid == 8453) { | ||
| return 0x4200000000000000000000000000000000000006; | ||
| } | ||
| if (block.chainid == 137) { // Polygon | ||
| // Note this token has the symbol WPOL, wrapping the native token POL | ||
| return 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; | ||
| } | ||
| if (block.chainid == 42161) { // Arbitrum | ||
| return 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; | ||
| } | ||
| if (block.chainid == 100) { // Gnosis Chain | ||
| // Note this token has the symbol WXDAI, wrapping the native token xDAI | ||
| return 0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d; | ||
| } | ||
| if (block.chainid == 43114) { // Avalanche C-Chain | ||
| // Note this token has the symbol WAVAX, wrapping the native token AVAX | ||
| return 0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7; | ||
| } | ||
| if (block.chainid == 56) { // BNB | ||
| // Note this token has the symbol WBNB, wrapping the native token BNB | ||
| return 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; | ||
| } | ||
| if (block.chainid == 534352) { // Scroll | ||
| return 0x5300000000000000000000000000000000000004; | ||
| } | ||
| // Celo: WCELO does not implement IWETH | ||
|
|
||
| revert("chain not supported"); | ||
| } | ||
|
|
||
| function deposit(uint256 amount) public override { | ||
| // wrap ETH to WETH | ||
| IWETH(address(ASSET_TOKEN)).deposit{ value: amount }(); | ||
| // Deposit asset and get back aTokens | ||
| super.deposit(amount); | ||
| } | ||
|
|
||
| function withdraw(uint256 amount) public override { | ||
| // withdraw WETH by redeeming the corresponding aTokens amount. | ||
| // the receiver is set to the address of the implementation contract in order to not trigger the | ||
| // fallback function of the SuperToken contract. | ||
| uint256 withdrawnAmount = AAVE_POOL.withdraw(address(ASSET_TOKEN), amount, address(_SELF)); | ||
| // unwrap to ETH and transfer it to the calling SuperToken contract | ||
| _SELF.unwrapWETHAndForwardETH(withdrawnAmount, address(this)); | ||
| } | ||
|
|
||
| // ============ functions operating on this contract itself (NOT in delegatecall context) ============ | ||
|
|
||
| // allow unwrapping from WETH to this contract | ||
| receive() external payable { } | ||
|
|
||
| // To be invoked by `withdraw` which is executed via delegatecall in a SuperToken context. | ||
| // WETH deposited or withdrawn by the SuperToken never stays in this contract beyond the lifetime of the tx. | ||
| // Thus it is not necessary to restrict msg.sender. | ||
| // We accept that an alien caller may withdraw WETH deposited to this contract (for whatever reason). | ||
| function unwrapWETHAndForwardETH(uint256 amount, address recipient) external { | ||
| IWETH(address(ASSET_TOKEN)).withdraw(amount); | ||
| (bool success,) = recipient.call{ value: amount }(""); | ||
| require(success, "call failed"); | ||
| } | ||
| } | ||
|
|
78 changes: 78 additions & 0 deletions
78
packages/ethereum-contracts/contracts/superfluid/AaveYieldBackend.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.23; | ||
|
|
||
| import { IYieldBackend } from "../interfaces/superfluid/IYieldBackend.sol"; | ||
| import { IERC20, ISuperToken } from "../interfaces/superfluid/ISuperfluid.sol"; | ||
| import { IPool } from "aave-v3/src/contracts/interfaces/IPool.sol"; | ||
|
|
||
| /** | ||
| * @title a SuperToken yield backend for the Aave protocol. | ||
| * Aave supports a simple deposit/withdraw workflow nicely matching the IYieldBackend interface. | ||
| * Deposits are represented by transferrable aTokens. | ||
| * | ||
| * This contract is conceptually a hot-pluggable library. | ||
| * All methods are supposed to be invoked as delegatecall. | ||
| * | ||
| * In order to learn about the limitations and constraints of this implementation, see | ||
| * https://github.com/superfluid-org/protocol-monorepo/wiki/Yield-Backend | ||
| */ | ||
| contract AaveYieldBackend is IYieldBackend { | ||
| IERC20 public immutable ASSET_TOKEN; | ||
| IPool public immutable AAVE_POOL; | ||
| IERC20 public immutable A_TOKEN; | ||
| address public immutable SURPLUS_RECEIVER; | ||
|
|
||
| // THIS CONTRACT CANNOT HAVE STATE VARIABLES! | ||
| // IF STATE IS NEEDED, USE NAMESPACED STORAGE LAYOUT (EIP-7201) | ||
|
|
||
| /** | ||
| * @param assetToken the asset (Aave terminology) supplied to Aave for yield. Typically, this will be | ||
| * the underlyingToken of a SuperToken. Must be a valid ERC20 token address. | ||
| * @param aavePool the Aave pool | ||
| * @param surplusReceiver the address to receive the surplus asset when withdrawing the surplus | ||
| */ | ||
| constructor(IERC20 assetToken, IPool aavePool, address surplusReceiver) { | ||
| require(address(assetToken) != address(0), "assetToken cannot be address(0)"); | ||
| ASSET_TOKEN = assetToken; | ||
| AAVE_POOL = IPool(aavePool); | ||
| SURPLUS_RECEIVER = surplusReceiver; | ||
| A_TOKEN = IERC20(aavePool.getReserveAToken(address(ASSET_TOKEN))); | ||
| } | ||
|
|
||
| function enable() external { | ||
| // approve Aave pool to fetch asset | ||
| ASSET_TOKEN.approve(address(AAVE_POOL), type(uint256).max); | ||
| } | ||
|
|
||
| function disable() external { | ||
| // Revoke approval | ||
| ASSET_TOKEN.approve(address(AAVE_POOL), 0); | ||
| } | ||
|
|
||
| function deposit(uint256 amount) public virtual { | ||
| // TODO: can this constraint break anything? | ||
| require(amount > 0, "amount must be greater than 0"); | ||
| // Deposit asset and get back aTokens | ||
| AAVE_POOL.supply(address(ASSET_TOKEN), amount, address(this), 0); | ||
| } | ||
|
|
||
| function withdraw(uint256 amount) public virtual { | ||
| // withdraw amount asset by redeeming the corresponding aTokens amount | ||
| AAVE_POOL.withdraw(address(ASSET_TOKEN), amount, address(this)); | ||
| } | ||
|
|
||
| function withdrawMax() external virtual { | ||
| // We can delegate the max calculation to the Aave pool by setting amount to type(uint256).max | ||
| withdraw(type(uint256).max); | ||
| } | ||
|
|
||
| function withdrawSurplus(uint256 totalSupply) external { | ||
| // totalSupply is always 18 decimals while assetToken and aToken may not | ||
| (uint256 normalizedTotalSupply,) = ISuperToken(address(this)).toUnderlyingAmount(totalSupply); | ||
| // decrement by 100 in order to give ample of margin for offsetting Aave's potential rounding error | ||
| // If there's no surplus, this will simply revert due to arithmetic underflow. | ||
| uint256 surplusAmount = A_TOKEN.balanceOf(address(this)) + ASSET_TOKEN.balanceOf(address(this)) | ||
| - normalizedTotalSupply - 100; | ||
| AAVE_POOL.withdraw(address(ASSET_TOKEN), surplusAmount, SURPLUS_RECEIVER); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.