# gDAI Adapter Strategy https://github.com/murderteeth/gdai-tokenized-strategy-adapter ### !(YEARN'S_FINEST) murderteeth x 0xMirim ### problem You manage a dai metavault on v3 and want to get weird. The gDAI vault on Gains Network looks ineresting. It's a 4626, but requires redemption scheduling to make withdrawals. ### solution This prototype adapter strat wraps gDAI with a tokenized strategy compatible with v3 metavaults. It also provides manageOnly access to gDAI's redemption functions. ![](https://hackmd.io/_uploads/Syi6c072n.png) ### challenges - 0xMirim and Murderteeth, long time listeners, first time strategists. We probably needed more help than we asked for. - we had to work through a few pivots before landing on an mvp. - working with an unfamiliar defi product (gDAI) ### gDAI wtf? gDAI is a 4626 dai vault that provides dai rewards for depositors and trade settlement to the Gains decentralized trading platform, gTrade (a fork of GMX). Trades that close in profit are paid from the vault. Trades that close at a loss pay the vault. A portion of fees from all trades also go into the vault. This means that gDAI goes up vs DAI when this is true: `winning_trades < share_of_fees + loosing_trades` gDAI goes down when this is true: `winning_trades > share_of_fees + loosing_trades` current 7d APY, 3.5% inception APY, 9% (jan 2023) ### gDAI withdraw locks _For the security of the vault, and to prevent stakers from front-running PnL changes, DAI can't be withdrawn immediately._ ![](https://hackmd.io/_uploads/BkUJJJE32.png) gDAI locks up deposits for a minimum of one day. redemptions are scheduled in 3-day epochs. For a min lockup of 1 day and max lock up of 10 days. ### gDAI price per share share price is only updated during the the pnl reporting period when withdraws are disabled. (?) ## Strategy.sol ```solidity contract Strategy is BaseTokenizedStrategy { using SafeERC20 for ERC20; IGDai public constant GDAI = IGDai(0x91993f2101cc758D0dEB7279d41e880F7dEFe827); IGDaiOpenPnlFeed public constant GDAI_PNL_FEED = IGDaiOpenPnlFeed(0x8d687276543b92819F2f2B5C3faad4AD27F4440c); address public vault; uint256 public depositLimit; constructor( address _asset, string memory _name, address _vault, uint256 _depositLimit ) BaseTokenizedStrategy(_asset, _name) { vault = _vault; depositLimit = _depositLimit; ERC20(_asset).safeApprove(address(GDAI), type(uint256).max); } function _deployFunds(uint256 _amount) internal override { GDAI.deposit(_amount, address(this)); } function _freeFunds(uint256 _amount) internal override { GDAI.withdraw(_amount, address(this), address(this)); } function _harvestAndReport() internal view override returns (uint256 _totalAssets) { // All we need is a total assets report // nothing to harvest or redeploy uint256 gDaiShares = GDAI.balanceOf(address(this)); uint256 gDaiAssets = GDAI.convertToAssets(gDaiShares); _totalAssets = ERC20(asset).balanceOf(address(this)) + gDaiAssets; } function isRedemptionWindowOpen() external view returns (bool) { return GDAI_PNL_FEED.nextEpochValuesRequestCount() == 0; } /// @notice request a redemption of GDAI `shares` function requestGDAIRedemption(uint256 shares) external onlyManagement returns (uint256 unlockEpoch) { GDAI.makeWithdrawRequest(shares, address(this)); unlockEpoch = GDAI.currentEpoch() + GDAI.withdrawEpochsTimelock(); } /// @notice cancel request for GDAI `shares` at epoch `unlockEpoch` function cancelGDAIRedemption(uint256 shares, uint256 unlockEpoch) external onlyManagement { GDAI.cancelWithdrawRequest(shares, address(this), unlockEpoch); } /// @notice redeem GDAI `shares`. /// Reverts if current gdai epoch doesn't show enough requested shares to redeem. function redeemGDAI(uint256 shares) external onlyManagement { GDAI.redeem(shares, address(this), address(this)); } function availableDepositLimit( address _owner ) public view override returns (uint256) { if (_owner != vault) { return 0; } uint256 _depositLimit = depositLimit; if (_depositLimit == type(uint256).max) { return type(uint256).max; } uint256 _totalAssets = TokenizedStrategy.totalAssets(); return _totalAssets >= _depositLimit ? 0 : _depositLimit - _totalAssets; } function availableWithdrawLimit( address _owner ) public view override returns (uint256) { uint256 redeemableShares = GDAI.withdrawRequests(address(this), GDAI.currentEpoch()); return TokenizedStrategy.totalIdle() + GDAI.convertToAssets(redeemableShares); } ``` ## operations Allocating the gDAI adapter strat in your dai metavault is an excellent choice! But when reducing the strategy's debt ratio take care to also redeem enough of the strat's gDAI shares to cover the outflow. During an emergency shutdown you will also have to manually redeem the gDAI shares. pros - simple design cons - hard to operate - annoying saying "gDAI" a lot ### todos - tests, 0 passing, =( - review the role of GNS (gains gov token) in the gDAI vault - know GMX better since Gains is a fork - automate redemptions with keepers - accounting support for multiple vaults - permissionless deposits ### thank yous Mirim - die hard team mate, great ideas and code Schlag - ring mastering, office hours, always super helpful Milo - got us started in the right direction (twice) ### links https://gainsnetwork.io/ gDai docs - https://gains-network.gitbook.io/docs-home/liquidity-farming-pools/gdai-vault gDai UI - https://gains.trade/vault gDai logic - [0xc91336cd5c74D1d813b48C10E43f8b3c161191EA](https://polygonscan.com/address/0xc91336cd5c74d1d813b48c10e43f8b3c161191ea) gDai proxy - [0x91993f2101cc758D0dEB7279d41e880F7dEFe827](https://polygonscan.com/address/0x91993f2101cc758d0deb7279d41e880f7defe827) gDai pnl oracle - [0x8d687276543b92819f2f2b5c3faad4ad27f4440](https://polygonscan.com/address/0x8d687276543b92819f2f2b5c3faad4ad27f4440) all gains contracts - https://gains-network.gitbook.io/docs-home/what-is-gains-network/contract-addresses