Forkchoice Ethereum Mainnet

Address Contract Verified

Address 0xA49fBb307A33F6aCD694509Ba09913BB97A069EB
Balance 0 ETH
Nonce 1
Code Size 10753 bytes
Indexed Transactions 0 (1 on-chain, 1.4% indexed)
External Etherscan · Sourcify

Contract Bytecode

10753 bytes
0x60806040526004361015610011575f80fd5b5f3560e01c806306fdde0314610fb857806307a79fc714610f745780632e1a7d4d14610f295780633737bcb414610ee557806338d52e0f1461090d5780633a2c677714610ea15780633f5bbcda14610e645780634641257d14610d9e5780635144107d14610d7d57806353e3cb5f14610d6057806353ee961e14610cd75780635504bd5f14610c9a5780635c975abb14610c785780635dc565c214610c5b5780635fcbd28514610b965780636a4874a114610c175780636f00020114610bda5780637158da7c14610b96578063722713f714610b7c578063853828b614610a665780639bec828814610a225780639ce110d7146109fa5780639f19915c146109b6578063a6f19c8414610972578063a879851014610951578063a919802d1461090d578063aaf5eb68146108eb578063ab0ebba0146108a7578063b6b55f251461064e578063bb42e5cd14610590578063ccb6a270146105b2578063d03153aa14610595578063d6ef236514610590578063d8c143f71461043b578063db2e21bc1461030d578063ec47df94146102c9578063eded3fda146102a7578063f0fa55a91461021d578063f9759518146101fc5763fbfa77cf146101d1575f80fd5b346101f8575f3660031901126101f8575f546040516001600160a01b039091168152602090f35b5f80fd5b346101f8575f3660031901126101f857602060405166b1a2bc2ec500008152f35b346101f85760203660031901126101f8575f54600435906001600160a01b031633036102995766b1a2bc2ec50000811161028a5760407f07af09e2b23ebab5dd29fa2271d6ca4795031f308caf26ca605f4a719ffeed6191600554908060055582519182526020820152a1005b638199f5f360e01b5f5260045ffd5b6282b42960e81b5f5260045ffd5b346101f8575f3660031901126101f85760206102c1611326565b604051908152f35b346101f8575f3660031901126101f8576040517f0000000000000000000000004684ac2e15cc25174134ed7586a72399b123da7d6001600160a01b03168152602090f35b346101f8575f3660031901126101f8575f546001600160a01b03163303610299576002600454146102995760026004556103456119aa565b5f6002556040516370a0823160e01b8152306004820152907f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec76020836024816001600160a01b0385165afa8015610430575f906103fd575b60209350806103df575b50507ff639e28400db464cf64a55c42636df1b181f180f2ad8519cbcec893760e6e6b782604051838152a16001600455604051908152f35b5f546103f6926001600160a01b0390911690611612565b82806103a7565b506020833d602011610428575b8161041760209383611061565b810103126101f8576020925161039d565b3d915061040a565b6040513d5f823e3d90fd5b346101f8575f3660031901126101f8575f546001600160a01b03163303610299576002600454146102995760026004555f60035460ff8082161516809160ff1916176003556104cb575b6020907f657d9755585644ef16264e8f7212a528a5f6ef63ab67fff29d9f533b5f8b448f604060ff60035416815190151581528385820152a16001600455604051908152f35b506104d461198c565b5f6002556040516370a0823160e01b8152306004820152907f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec76020836024816001600160a01b0385165afa8015610430575f9061055d575b602093508061053f575b50509050610485565b5f54610556926001600160a01b0390911690611612565b8280610536565b506020833d602011610588575b8161057760209383611061565b810103126101f8576020925161052c565b3d915061056a565b61103f565b346101f8575f3660031901126101f8576020600554604051908152f35b346101f85760203660031901126101f8576004356001600160a01b038116908190036101f8575f546001600160a01b03811661063f57811561063f57600154906001600160a01b0382163303610299576001600160a01b031990811683175f90815591166001557f733be0fdaf77621eb46ef87502d63fea13e94bc406be6ae17d75d783e0ceaa809080a2005b63e6c4247b60e01b5f5260045ffd5b346101f85760203660031901126101f8575f54600435906001600160a01b031633036102995760ff60035416610898576002600454146102995760026004558015610889576040516323b872dd60e01b60208201908152336024830152306044830152606480830184905282527f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec76001600160a01b031692915f9182916106f6608482611061565b519082865af16107046115bc565b9015908115610859575b5061084a57600254918183018093116108365760406108136107e17f06da3309189fa49284f335d2c2bcb4cb0b8ad2a59ad92a9bdebeeb8f1ceba51193602096600255610759611c30565b506005549086907f000000000000000000000000390f3595bca2df7d23783dfd126427cceb997bf4906107989083906001600160a01b03841690612633565b7f0000000000000000000000000000000000000000000000000000000000000001907f0000000000000000000000000000000000000000000000000000000000000000906127bc565b7fbe8e7aa863a4607c244d308cfc4b7a04afbf9846de82821448f25171c75ccb8c8380518781528389820152a1612390565b9261081d846125a0565b5081519081528385820152a16001600455604051908152f35b634e487b7160e01b5f52601160045260245ffd5b6312171d8360e31b5f5260045ffd5b805180151592508261086e575b50508361070e565b61088192506020809183010191016115fa565b158380610866565b631f2a200560e01b5f5260045ffd5b63e628b94960e01b5f5260045ffd5b346101f8575f3660031901126101f8576040517f000000000000000000000000eef0c605546958c1f899b6fb336c20671f9cd49f6001600160a01b03168152602090f35b346101f8575f3660031901126101f8576020604051670de0b6b3a76400008152f35b346101f8575f3660031901126101f8576040517f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec76001600160a01b03168152602090f35b346101f8575f3660031901126101f8576020604051662386f26fc100008152f35b346101f8575f3660031901126101f8576040517f000000000000000000000000f3c43e7d722963b9569d1e39873df9e2dfe8c0876001600160a01b03168152602090f35b346101f8575f3660031901126101f8576040517f000000000000000000000000390f3595bca2df7d23783dfd126427cceb997bf46001600160a01b03168152602090f35b346101f8575f3660031901126101f8576001546040516001600160a01b039091168152602090f35b346101f8575f3660031901126101f8576040517f000000000000000000000000f939e0a03fb07f59a73314e73794be0e57ac1b4e6001600160a01b03168152602090f35b346101f8575f3660031901126101f8575f546001600160a01b0316330361029957600260045414610299576002600455610a9e61198c565b5f6002556040516370a0823160e01b8152306004820152907f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec76020836024816001600160a01b0385165afa8015610430575f90610b49575b6020935080610b2b575b50505f51602061298c5f395f51905f52604080515f1981528385820152a16001600455604051908152f35b5f54610b42926001600160a01b0390911690611612565b8280610b00565b506020833d602011610b74575b81610b6360209383611061565b810103126101f85760209251610af6565b3d9150610b56565b346101f8575f3660031901126101f85760206102c16111f0565b346101f8575f3660031901126101f8576040517f000000000000000000000000ecb0f0d68c19bdaadaebe24f6752a4db34e2c2cb6001600160a01b03168152602090f35b346101f8575f3660031901126101f85760206040517f0000000000000000000000000000000000000000000000000000000000000001600f0b8152f35b346101f8575f3660031901126101f8576040517f000000000000000000000000d533a949740bb3306d119cc777fa900ba034cd526001600160a01b03168152602090f35b346101f8575f3660031901126101f857602060405162015f908152f35b346101f8575f3660031901126101f857602060ff600354166040519015158152f35b346101f8575f3660031901126101f85760206040517f0000000000000000000000000000000000000000000000000000000000000000600f0b8152f35b346101f8575f3660031901126101f85760405163722713f760e01b8152602081600481305afa908115610430575f91610d2e575b506002549081811115610d24576020916102c1916110c9565b505060205f6102c1565b90506020813d602011610d58575b81610d4960209383611061565b810103126101f8575181610d0b565b3d9150610d3c565b346101f8575f3660031901126101f8576020600254604051908152f35b346101f8575f3660031901126101f85760206040516611c37937e080008152f35b346101f8575f3660031901126101f8575f546001600160a01b031633036102995760ff60035416610898576002600454146102995760026004556020610e2e610de5611c30565b7f0000000000000000000000003e7d1eab13ad0104d2750b8863b489d65364e32d907f000000000000000000000000eef0c605546958c1f899b6fb336c20671f9cd49f906117d9565b7f8e55ccfc9778ff8eba1646d765cf1982537ce0f9257054a17b48aad74525018382604051838152a16001600455604051908152f35b346101f8575f3660031901126101f85760206040517f0000000000000000000000000000000000000000000000000000000000000001600f0b8152f35b346101f8575f3660031901126101f8576040517f0000000000000000000000007d3cde9ccf0109423e672c17bd36481cf8ce437d6001600160a01b03168152602090f35b346101f8575f3660031901126101f8576040517f000000000000000000000000ecb0f0d68c19bdaadaebe24f6752a4db34e2c2cb6001600160a01b03168152602090f35b346101f85760203660031901126101f8575f546001600160a01b03163303610299576002600454146102995760026004556020610f676004356110d6565b6001600455604051908152f35b346101f8575f3660031901126101f8576040517f0000000000000000000000003e7d1eab13ad0104d2750b8863b489d65364e32d6001600160a01b03168152602090f35b346101f8575f3660031901126101f857611017604051610fd9604082611061565b602081527f55534454202d3e20706d5553442f637276555344204c50205374726174656779602082015260405191829160208352602083019061101b565b0390f35b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b346101f8575f3660031901126101f857602060405167016345785d8a00008152f35b601f909101601f19168101906001600160401b0382119082101761108457604052565b634e487b7160e01b5f52604160045260245ffd5b8181029291811591840414171561083657565b81156110b5570490565b634e487b7160e01b5f52601260045260245ffd5b9190820391821161083657565b9081156108895760405163722713f760e01b8152602081600481305afa908115610430575f916111be575b5080156111b8578083116111b0575b60406111355f51602061298c5f395f51905f529261113086600254611098565b6110ab565b9361113f81611426565b6002549095808211156111a857611155916110c9565b6002558461116c575b8151908152846020820152a1565b5f546111a39086906001600160a01b03167f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7611612565b61115e565b50505f611155565b915081611110565b505f9150565b90506020813d6020116111e8575b816111d960209383611061565b810103126101f857515f611101565b3d91506111cc565b6111f86116a0565b600181106112e857604051630176f71760e71b8152906020826004817f000000000000000000000000ecb0f0d68c19bdaadaebe24f6752a4db34e2c2cb6001600160a01b03165afa918215610430575f926112b2575b506112656112af92670de0b6b3a764000092611098565b047f0000000000000000000000003e7d1eab13ad0104d2750b8863b489d65364e32d907f000000000000000000000000eef0c605546958c1f899b6fb336c20671f9cd49f906117d9565b90565b91506020823d6020116112e0575b816112cd60209383611061565b810103126101f85790519061126561124e565b3d91506112c0565b505f90565b908160209103126101f857516001600160a01b03811681036101f85790565b6001600160a01b0391821681529116602082015260400190565b60405163022e74a560e61b81527f0000000000000000000000007d3cde9ccf0109423e672c17bd36481cf8ce437d6001600160a01b031690602081600481855afa9182156104305761139a926020925f916113f9575b506040518080958194637a27db5760e01b835230906004840161130c565b03916001600160a01b03165afa5f91816113c5575b506113b957505f90565b6501d1a94a2000900490565b9091506020813d6020116113f1575b816113e160209383611061565b810103126101f85751905f6113af565b3d91506113d4565b6114199150833d851161141f575b6114118183611061565b8101906112ed565b5f61137c565b503d611407565b905f91611431611c30565b5061143a6111f0565b9060018210611555576040516370a0823160e01b81523060048201527f0000000000000000000000007d3cde9ccf0109423e672c17bd36481cf8ce437d6001600160a01b0316929091602083602481875afa928315610430575f9361156d575b506001831061156457906111306114b19284611098565b9080821161155c575b5060018110611555576114eb5f92602092604051948580948193635d043b2960e11b83523090309060048501612028565b03925af18015610430575f90611521575b61150a915060055490612048565b806115125750565b6112af919250600554906121ee565b506020813d60201161154d575b8161153b60209383611061565b810103126101f85761150a90516114fc565b3d915061152e565b505f925050565b90505f6114ba565b505f9450505050565b9092506020813d602011611599575b8161158960209383611061565b810103126101f85751915f61149a565b3d915061157c565b6001600160a01b039091168152602081019190915260400190565b3d156115f5573d906001600160401b03821161108457604051916115ea601f8201601f191660200184611061565b82523d5f602084013e565b606090565b908160209103126101f8575180151581036101f85790565b5f92918361163c61164a8295604051928391602083019663a9059cbb60e01b8852602484016115a1565b03601f198101835282611061565b51926001600160a01b03165af161165f6115bc565b9015908115611670575b5061084a57565b8051801515925082611685575b50505f611669565b61169892506020809183010191016115fa565b155f8061167d565b6040516370a0823160e01b81523060048201527f0000000000000000000000007d3cde9ccf0109423e672c17bd36481cf8ce437d6001600160a01b031690602081602481855afa908115610430575f91611761575b506001811061175b576020906024604051809481936303d1689d60e11b835260048301525afa908115610430575f9161172c575090565b90506020813d602011611753575b8161174760209383611061565b810103126101f8575190565b3d915061173a565b50505f90565b90506020813d60201161178b575b8161177c60209383611061565b810103126101f857515f6116f5565b3d915061176f565b51906001600160501b03821682036101f857565b908160a09103126101f8576117bb81611793565b916020820151916040810151916112af608060608401519301611793565b916001831061198557604051633fabe5a360e21b81529160a090839060049082906001600160a01b03165afa918215610430575f905f935f915f9161195a575b505f851392831593611943575b5050811561192c575b506118b657604051633fabe5a360e21b8152919060a090839060049082906001600160a01b03165afa918215610430575f905f935f915f916118f1575b505f8513928315936118da575b505081156118c3575b506118b6576118919192611098565b9064e8d4a5100081029080820464e8d4a510001490151715610836576112af916110ab565b505064e8d4a51000900490565b62015f9091506118d390426110c9565b115f611882565b6001600160501b0390811691161091505f80611879565b9250505061191891925060a03d60a011611925575b6119108183611061565b8101906117a7565b915093929193905f61186c565b503d611906565b62015f90915061193c90426110c9565b115f61182f565b6001600160501b0390811691161091505f80611826565b9250505061197891925060a03d60a011611925576119108183611061565b915093929193905f611819565b5050505f90565b5f9061199661226e565b600181106111b85760055461150a91612048565b5f906119b461226e565b600181106111b85760405163cc2b27d760e01b8152600481018290527f0000000000000000000000000000000000000000000000000000000000000001600f0b602482018190527f000000000000000000000000ecb0f0d68c19bdaadaebe24f6752a4db34e2c2cb6001600160a01b03169291602081604481875afa908115610430575f91611bfe575b50670c7d713b49da00009081810291818304149015171561083657670de0b6b3a7640000936020926064915f90611a9f87827f000000000000000000000000ecb0f0d68c19bdaadaebe24f6752a4db34e2c2cb6001600160a01b0316612633565b6040519788958694630d2680e960e11b865289600487015260248601520460448401525af1918215610430575f92611bc9575b5060405f51602061296c5f395f51905f52918151908152836020820152a180611af85750565b9091505f5160206129ac5f395f51905f526040611bb967016345785d8a0000847f000000000000000000000000390f3595bca2df7d23783dfd126427cceb997bf4611b70826001600160a01b03808416907f000000000000000000000000f939e0a03fb07f59a73314e73794be0e57ac1b4e16612633565b7f0000000000000000000000000000000000000000000000000000000000000000907f0000000000000000000000000000000000000000000000000000000000000001906127bc565b928151908152836020820152a190565b9091506020813d602011611bf6575b81611be560209383611061565b810103126101f85751906040611ad2565b3d9150611bd8565b90506020813d602011611c28575b81611c1960209383611061565b810103126101f857515f611a3e565b3d9150611c0c565b60405163022e74a560e61b81525f906020816004817f0000000000000000000000007d3cde9ccf0109423e672c17bd36481cf8ce437d6001600160a01b03165afa908115610430575f91612009575b5060408051611c8e8282611061565b6001815260208101601f19830180368337611ca88361233c565b7f000000000000000000000000f3c43e7d722963b9569d1e39873df9e2dfe8c0876001600160a01b03169052835190611ce18583611061565b600182525f5b818110611ff85750506020948451611cff8782611061565b5f8152611d0b8361233c565b52611d158261233c565b506001600160a01b031691823b156101f857845163f0a2351160e01b81526060600482015293516064850181905284929160848401915f5b89828210611fd8575050505060031983820301602484015281518082528782019188808360051b8301019401925f915b8a848410611fa85750505050505091815f81819530604483015203925af1611f93575b5080516370a0823160e01b81523060048201527f000000000000000000000000d533a949740bb3306d119cc777fa900ba034cd526001600160a01b03168382602481845afa918215611f89578592611f5a575b5067016345785d8a00008210611f5357825163a9059cbb60e01b81527f0000000000000000000000004684ac2e15cc25174134ed7586a72399b123da7d6001600160a01b03169185908290818981611e4f8989600484016115a1565b03925af18015611f49579185918793611f1e575b506024855180948193634a5c8c6f60e11b83528760048401525af1859181611eef575b50611e9a5763081ceff360e41b8552600485fd5b929360018410611ee75750827ffa5fc4e614ff1acb4e5e98dccf2a504cb9fb51a469dce5a5eca07ff5491a677b93948351928352820152a1611ee3611ede82612390565b6125a0565b5090565b935050505090565b9091508481813d8311611f17575b611f078183611061565b810103126101f85751905f611e86565b503d611efd565b611f3d90833d8511611f42575b611f358183611061565b8101906115fa565b611e63565b503d611f2b565b84513d88823e3d90fd5b5050505090565b9091508381813d8311611f82575b611f728183611061565b810103126101f85751905f611df3565b503d611f68565b83513d87823e3d90fd5b611fa09193505f90611061565b5f915f611da0565b809193959750611fc66001939597601f19868203018752895161101b565b97019301930190928795949293611d7d565b83516001600160a01b031685528896509384019390920191600101611d4d565b806060602080938601015201611ce7565b612022915060203d60201161141f576114118183611061565b5f611c7f565b9081526001600160a01b0391821660208201529116604082015260600190565b60405163cc2b27d760e01b8152600481018290527f0000000000000000000000000000000000000000000000000000000000000001600f0b602482018190527f000000000000000000000000ecb0f0d68c19bdaadaebe24f6752a4db34e2c2cb6001600160a01b03169493602083604481895afa928315610430575f936121ba575b50670de0b6b3a76400000391670de0b6b3a7640000831161083657670de0b6b3a76400006120fc602094606493611098565b049161213285887f000000000000000000000000ecb0f0d68c19bdaadaebe24f6752a4db34e2c2cb6001600160a01b0316612633565b5f6040519788948593630d2680e960e11b8552886004860152602485015260448401525af1928315610430575f93612185575b5060405f51602061296c5f395f51905f52918482519182526020820152a1565b9092506020813d6020116121b2575b816121a160209383611061565b810103126101f85751916040612165565b3d9150612194565b9092506020813d6020116121e6575b816121d660209383611061565b810103126101f85751915f6120ca565b3d91506121c9565b91905f5160206129ac5f395f51905f529060409061225f90857f000000000000000000000000390f3595bca2df7d23783dfd126427cceb997bf4611b70826001600160a01b03808416907f000000000000000000000000f939e0a03fb07f59a73314e73794be0e57ac1b4e16612633565b938151908152846020820152a1565b6040516370a0823160e01b81523060048201527f0000000000000000000000007d3cde9ccf0109423e672c17bd36481cf8ce437d6001600160a01b0316602082602481845afa918215610430575f92612308575b506001821061175b576122f36020915f93604051948580948193635d043b2960e11b83523090309060048501612028565b03925af1908115610430575f9161172c575090565b9091506020813d602011612334575b8161232460209383611061565b810103126101f85751905f6122c2565b3d9150612317565b8051156123495760200190565b634e487b7160e01b5f52603260045260245ffd5b90602080835192838152019201905f5b81811061237a5750505090565b825184526020938401939092019160010161236d565b6001600160a01b037f000000000000000000000000ecb0f0d68c19bdaadaebe24f6752a4db34e2c2cb81169291906123ed90829085907f000000000000000000000000f939e0a03fb07f59a73314e73794be0e57ac1b4e16612633565b604051926123fc606085611061565b60028452604036602086013783517f0000000000000000000000000000000000000000000000000000000000000001906001600160801b03821610156123495760051b6020600160851b031684016020908101839052604080516307b60dbb60e31b8152600481019190915294908580612479604482018561235d565b600160248301520381855afa948515610430575f9561256c575b50670dcef33a6f838000850294808604670dcef33a6f8380001490151715610836576020916124eb5f670de0b6b3a76400009360405198899586948593635b96faef60e11b855260406004860152604485019061235d565b9104602483015203925af1928315610430575f93612537575b5060407f38f8a0c92f4c5b0b6877f878cb4c0c8d348a47b76d716c8e78f425043df9515b918482519182526020820152a1565b9092506020813d602011612564575b8161255360209383611061565b810103126101f85751916040612504565b3d9150612546565b9094506020813d602011612598575b8161258860209383611061565b810103126101f85751935f612493565b3d915061257b565b5f906020906001600160a01b037f0000000000000000000000007d3cde9ccf0109423e672c17bd36481cf8ce437d81169160449161260390829085907f000000000000000000000000ecb0f0d68c19bdaadaebe24f6752a4db34e2c2cb16612633565b6040519485938492636e553f6560e01b845260048401523060248401525af1908115610430575f9161172c575090565b604051636eb1769f60e11b81526001600160a01b039190911692906020818061266086306004840161130c565b0381875afa908115610430575f9161278a575b501061267d575050565b604051636eb1769f60e11b81526020818061269c85306004840161130c565b0381865afa908115610430575f91612758575b506126fb575b5f91829182604051602081019263095ea7b360e01b845260018060a01b0316602482015281196044820152604481526126ef606482611061565b51925af161165f6115bc565b60405163095ea7b360e01b602082019081526001600160a01b03831660248301525f6044808401829052835291829190612736606482611061565b519082865af16127446115bc565b506126b5576312171d8360e31b5f5260045ffd5b90506020813d602011612782575b8161277360209383611061565b810103126101f857515f6126af565b3d9150612766565b90506020813d6020116127b4575b816127a560209383611061565b810103126101f857515f612673565b3d9150612798565b93929360018060a01b03169060405190635e0d443f60e01b8252600f0b92836004830152600f0b93846024830152856044830152602082606481865afa918215610430575f92612937575b50670de0b6b3a76400000390670de0b6b3a7640000821161083657670de0b6b3a76400009161283591611098565b0460405163ddc1f59d60e01b815283600482015284602482015285604482015281606482015230608482015260208160a4815f875af15f9181612903575b506128fa5750915f608492602095946040519788968795630f7c084960e21b875260048701526024860152604485015260648401525af15f91816128c6575b506112af57637cacd40760e01b5f5260045ffd5b9091506020813d6020116128f2575b816128e260209383611061565b810103126101f85751905f6128b2565b3d91506128d5565b94505050505090565b9091506020813d60201161292f575b8161291f60209383611061565b810103126101f85751905f612873565b3d9150612912565b9091506020813d602011612963575b8161295360209383611061565b810103126101f85751905f612807565b3d915061294656fe6f0f96292ae0038c04f9b6bab30f185d9ca02c471d0983f563f2a4f674aef1370c875c8d391179c5cf7ad8303d268efd50b8beb78b671f85cd54bfb91eb8ef40cf3ac02000d191e34429d258b04e042652e59ad03e9bac3f8c7989ccc7d52edda2646970667358221220f444e0242753b5f09a2b8e7f7ea3f1997e97f4e9aa1901c28882db568e8cb67364736f6c63430008210033

Verified Source Code Full Match

Compiler: v0.8.33+commit.64118f21 EVM: cancun Optimization: Yes (1 runs)
PmUsdCrvUsdStrategy.sol 316 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

import {BaseCurveRewardVaultStrategy} from "./BaseCurveRewardVaultStrategy.sol";
import {IYieldStrategy} from "../interfaces/IYieldStrategy.sol";
import {IChainlinkOracle} from "../interfaces/IChainlinkOracle.sol";
import {IAccountant} from "../interfaces/IAccountant.sol";
import {ICrvSwapper} from "../interfaces/ICrvSwapper.sol";
import {ICurveStableSwap} from "../interfaces/ICurveStableSwap.sol";
import {ICurveStableSwapNG} from "../interfaces/ICurveStableSwapNG.sol";
import {IERC20} from "../interfaces/IERC20.sol";
import {IStakeDaoRewardVault} from "../interfaces/IStakeDaoRewardVault.sol";
import {CurveUsdtSwapLib} from "../libraries/CurveUsdtSwapLib.sol";

/// @title PmUsdCrvUsdStrategy
/// @notice USDT -> crvUSD -> pmUSD/crvUSD LP -> Stake DAO RewardVault
/// @dev Accepts USDT debt, swaps to crvUSD, provides single-sided liquidity to pmUSD/crvUSD pool,
///      stakes LP in Stake DAO reward vault, and harvests CRV rewards back into the strategy
contract PmUsdCrvUsdStrategy is BaseCurveRewardVaultStrategy {
    error SwapFailed();
    // ============ Constants ============

    uint256 public constant DEFAULT_SLIPPAGE = 1e16; // 1% for stablecoin swaps
    uint256 public constant MAX_SLIPPAGE = 5e16; // 5% governance max
    uint256 public constant EMERGENCY_SLIPPAGE = 1e17; // 10% emergency
    uint256 public constant LP_SLIPPAGE = 5e15; // 0.5% for LP ops (stable pairs)
    uint256 public constant MIN_HARVEST_THRESHOLD = 1e17; // 0.1 CRV minimum
    uint256 public constant MAX_ORACLE_STALENESS = 90000; // 25 hours

    // ============ Immutables ============

    IERC20 public immutable crvUSD;
    IERC20 public immutable crv;
    ICurveStableSwap public immutable usdtCrvUsdPool;
    ICurveStableSwapNG public immutable lpPool;
    IChainlinkOracle public immutable crvUsdOracle;
    IChainlinkOracle public immutable usdtOracle;
    ICrvSwapper public immutable crvSwapper;
    address public immutable gauge;

    int128 public immutable usdtIndex;
    int128 public immutable crvUsdIndex;
    int128 public immutable lpCrvUsdIndex;

    // ============ State ============

    uint256 public slippageTolerance = DEFAULT_SLIPPAGE;

    // ============ Events ============

    event SlippageUpdated(uint256 oldSlippage, uint256 newSlippage);
    event SwappedUsdtToCrvUsd(uint256 usdtAmount, uint256 crvUsdReceived);
    event SwappedCrvUsdToUsdt(uint256 crvUsdAmount, uint256 usdtReceived);
    event LiquidityAdded(uint256 crvUsdAmount, uint256 lpReceived);
    event LiquidityRemoved(uint256 lpBurned, uint256 crvUsdReceived);
    event RewardsHarvested(uint256 crvAmount, uint256 crvUsdCompounded);

    // ============ Constructor ============

    /// @param _usdt USDT token address (debt asset)
    /// @param _crvUsd crvUSD token address
    /// @param _crv CRV token address
    /// @param _vault Zenji vault address
    /// @param _usdtCrvUsdPool Curve USDT/crvUSD StableSwap pool
    /// @param _lpPool Curve pmUSD/crvUSD StableSwapNG pool
    /// @param _rewardVault Stake DAO ERC4626 reward vault
    /// @param _crvSwapper CRV -> crvUSD swapper address
    /// @param _gauge Curve gauge for pmUSD/crvUSD LP (for accountant claims)
    /// @param _usdtIndex USDT coin index in USDT/crvUSD pool
    /// @param _crvUsdIndex crvUSD coin index in USDT/crvUSD pool
    /// @param _lpCrvUsdIndex crvUSD coin index in pmUSD/crvUSD pool
    /// @param _crvUsdOracle Chainlink crvUSD/USD oracle
    /// @param _usdtOracle Chainlink USDT/USD oracle
    constructor(
        address _usdt,
        address _crvUsd,
        address _crv,
        address _vault,
        address _usdtCrvUsdPool,
        address _lpPool,
        address _rewardVault,
        address _crvSwapper,
        address _gauge,
        int128 _usdtIndex,
        int128 _crvUsdIndex,
        int128 _lpCrvUsdIndex,
        address _crvUsdOracle,
        address _usdtOracle
    ) BaseCurveRewardVaultStrategy(_usdt, _vault, _rewardVault) {
        if (_crvUsd == address(0) || _crv == address(0)) revert InvalidAddress();
        if (_usdtCrvUsdPool == address(0) || _lpPool == address(0)) revert InvalidAddress();
        if (_crvSwapper == address(0)) revert InvalidAddress();
        if (_gauge == address(0)) revert InvalidAddress();
        if (_crvUsdOracle == address(0) || _usdtOracle == address(0)) revert InvalidAddress();

        crvUSD = IERC20(_crvUsd);
        crv = IERC20(_crv);
        usdtCrvUsdPool = ICurveStableSwap(_usdtCrvUsdPool);
        lpPool = ICurveStableSwapNG(_lpPool);
        crvSwapper = ICrvSwapper(_crvSwapper);
        gauge = _gauge;
        usdtIndex = _usdtIndex;
        crvUsdIndex = _crvUsdIndex;
        lpCrvUsdIndex = _lpCrvUsdIndex;
        crvUsdOracle = IChainlinkOracle(_crvUsdOracle);
        usdtOracle = IChainlinkOracle(_usdtOracle);
    }

    // ============ Admin ============

    function setSlippage(uint256 newSlippage) external onlyVault {
        if (newSlippage > MAX_SLIPPAGE) revert SlippageExceeded();
        uint256 oldSlippage = slippageTolerance;
        slippageTolerance = newSlippage;
        emit SlippageUpdated(oldSlippage, newSlippage);
    }

    // ============ View Functions ============

    /// @inheritdoc IYieldStrategy
    function underlyingAsset() external view override returns (address) {
        return address(lpToken);
    }

    /// @inheritdoc IYieldStrategy
    function name() external pure override returns (string memory) {
        return "USDT -> pmUSD/crvUSD LP Strategy";
    }

    /// @inheritdoc IYieldStrategy
    function balanceOf() public view override returns (uint256) {
        uint256 lpTokens = _rewardVaultBalance();
        if (lpTokens < 1) return 0;

        // LP -> crvUSD value via virtual_price (manipulation-resistant)
        uint256 virtualPrice = lpPool.get_virtual_price();
        uint256 crvUsdValue = (lpTokens * virtualPrice) / 1e18;

        return CurveUsdtSwapLib.convertCrvUsdToUsdt(
            crvUsdValue, crvUsdOracle, usdtOracle, MAX_ORACLE_STALENESS
        );
    }

    /// @inheritdoc IYieldStrategy
    function pendingRewards() external view override returns (uint256) {
        address accountant = rewardVault.ACCOUNTANT();
        try IAccountant(accountant).getPendingRewards(address(rewardVault), address(this)) returns (uint256 pendingCrv)
        {
            return pendingCrv / 2e12; // rough CRV->USDT view conversion
        } catch {
            return 0;
        }
    }

    // ============ Internal: Core Strategy ============

    function _deposit(uint256 usdtAmount) internal override returns (uint256 underlyingDeposited) {
        // Auto-compound any pending CRV rewards
        _claimAndCompound();

        // 1. Swap USDT -> crvUSD
        uint256 crvUsdReceived = _swapUsdtToCrvUsd(usdtAmount, slippageTolerance);

        // 2. Add single-sided liquidity to pmUSD/crvUSD pool
        uint256 lpReceived = _addLiquidity(crvUsdReceived);

        // 3. Stake LP tokens in reward vault
        _depositToRewardVault(lpReceived);

        // Return value in USDT terms
        underlyingDeposited = lpReceived;
    }

    function _withdraw(uint256 usdtAmount) internal override returns (uint256 usdtReceived) {
        // Auto-compound any pending CRV rewards
        _claimAndCompound();

        uint256 currentValue = balanceOf();
        if (currentValue < 1) return 0;

        uint256 shares = rewardVault.balanceOf(address(this));
        if (shares < 1) return 0;

        // Calculate proportional shares to redeem
        uint256 sharesToRedeem = (shares * usdtAmount) / currentValue;
        if (sharesToRedeem > shares) sharesToRedeem = shares;
        if (sharesToRedeem < 1) return 0;

        // 1. Unstake from reward vault
        uint256 lpReceived = _redeemFromRewardVault(sharesToRedeem);

        // 2. Remove liquidity (single-sided crvUSD)
        uint256 crvUsdReceived = _removeLiquidity(lpReceived, slippageTolerance);

        // 3. Swap crvUSD -> USDT
        if (crvUsdReceived > 0) {
            usdtReceived = _swapCrvUsdToUsdt(crvUsdReceived, slippageTolerance);
        }
    }

    function _withdrawAll() internal override returns (uint256 usdtReceived) {
        // 1. Unstake all LP tokens
        uint256 lpReceived = _redeemAllFromRewardVault();
        if (lpReceived < 1) return 0;

        // 2. Remove all liquidity for crvUSD
        uint256 crvUsdReceived = _removeLiquidity(lpReceived, slippageTolerance);

        // 3. Swap all crvUSD -> USDT
        if (crvUsdReceived > 0) {
            usdtReceived = _swapCrvUsdToUsdt(crvUsdReceived, slippageTolerance);
        }
    }

    function _harvest() internal override returns (uint256 rewardsValue) {
        uint256 crvUsdCompounded = _claimAndCompound();
        rewardsValue = CurveUsdtSwapLib.convertCrvUsdToUsdt(
            crvUsdCompounded, crvUsdOracle, usdtOracle, MAX_ORACLE_STALENESS
        );
    }

    /// @notice Claim CRV from accountant, swap to crvUSD, and compound back into LP
    /// @return crvUsdCompounded Amount of crvUSD compounded (0 if nothing to harvest)
    function _claimAndCompound() internal returns (uint256 crvUsdCompounded) {
        _accountantClaim();

        uint256 crvBalance = crv.balanceOf(address(this));
        if (crvBalance < MIN_HARVEST_THRESHOLD) return 0;

        crv.transfer(address(crvSwapper), crvBalance);
        uint256 crvUsdReceived = _swapCrv(crvBalance);
        if (crvUsdReceived < 1) return 0;

        emit RewardsHarvested(crvBalance, crvUsdReceived);

        uint256 lpReceived = _addLiquidity(crvUsdReceived);
        _depositToRewardVault(lpReceived);

        crvUsdCompounded = crvUsdReceived;
    }

    function _swapCrv(uint256 amount) internal returns (uint256 crvUsdReceived) {
        try crvSwapper.swap(amount) returns (uint256 received) {
            crvUsdReceived = received;
        } catch {
            revert SwapFailed();
        }
    }

    function _accountantClaim() internal {
        address accountant = rewardVault.ACCOUNTANT();
        address[] memory gauges = new address[](1);
        gauges[0] = gauge;
        bytes[] memory harvestData = new bytes[](1);
        harvestData[0] = bytes("");
        // NoPendingRewards is expected when nothing has accrued yet
        try IAccountant(accountant).claim(gauges, harvestData, address(this)) {} catch {}
    }

    function _emergencyWithdraw() internal override returns (uint256 usdtReceived) {
        // 1. Unstake all LP tokens
        uint256 lpReceived = _redeemAllFromRewardVault();
        if (lpReceived < 1) return 0;

        // 2. Remove all liquidity with emergency slippage
        uint256 crvUsdReceived = _removeLiquidity(lpReceived, EMERGENCY_SLIPPAGE);

        // 3. Swap all crvUSD -> USDT with emergency slippage
        if (crvUsdReceived > 0) {
            usdtReceived = _swapCrvUsdToUsdt(crvUsdReceived, EMERGENCY_SLIPPAGE);
        }
    }

    // ============ Internal: LP Operations ============

    /// @notice Add single-sided crvUSD liquidity to pmUSD/crvUSD pool
    function _addLiquidity(uint256 crvUsdAmount) internal returns (uint256 lpReceived) {
        _ensureApprove(address(crvUSD), address(lpPool), crvUsdAmount);

        // Build amounts array: only crvUSD side
        uint256[] memory amounts = new uint256[](2);
        amounts[uint256(uint128(lpCrvUsdIndex))] = crvUsdAmount;

        uint256 expectedLp = lpPool.calc_token_amount(amounts, true);
        uint256 minLp = (expectedLp * (PRECISION - LP_SLIPPAGE)) / PRECISION;

        lpReceived = lpPool.add_liquidity(amounts, minLp);
        emit LiquidityAdded(crvUsdAmount, lpReceived);
    }

    /// @notice Remove single-sided crvUSD liquidity from pmUSD/crvUSD pool
    function _removeLiquidity(uint256 lpAmount, uint256 slippage) internal returns (uint256 crvUsdReceived) {
        uint256 expectedOut = lpPool.calc_withdraw_one_coin(lpAmount, lpCrvUsdIndex);
        uint256 minOut = (expectedOut * (PRECISION - slippage)) / PRECISION;

        _ensureApprove(address(lpToken), address(lpPool), lpAmount);
        crvUsdReceived = lpPool.remove_liquidity_one_coin(lpAmount, lpCrvUsdIndex, minOut);
        emit LiquidityRemoved(lpAmount, crvUsdReceived);
    }

    // ============ Internal: USDT/crvUSD Swaps ============

    function _swapUsdtToCrvUsd(uint256 usdtAmount, uint256 slippage) internal returns (uint256 crvUsdReceived) {
        _ensureApprove(address(debtAsset), address(usdtCrvUsdPool), usdtAmount);
        crvUsdReceived =
            CurveUsdtSwapLib.swapUsdtToCrvUsd(usdtCrvUsdPool, usdtIndex, crvUsdIndex, usdtAmount, slippage);
        emit SwappedUsdtToCrvUsd(usdtAmount, crvUsdReceived);
    }

    function _swapCrvUsdToUsdt(uint256 crvUsdAmount, uint256 slippage) internal returns (uint256 usdtReceived) {
        _ensureApprove(address(crvUSD), address(usdtCrvUsdPool), crvUsdAmount);
        usdtReceived =
            CurveUsdtSwapLib.swapCrvUsdToUsdt(usdtCrvUsdPool, crvUsdIndex, usdtIndex, crvUsdAmount, slippage);
        emit SwappedCrvUsdToUsdt(crvUsdAmount, usdtReceived);
    }
}
BaseCurveRewardVaultStrategy.sol 68 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

import {BaseYieldStrategy} from "./BaseYieldStrategy.sol";
import {IStakeDaoRewardVault} from "../interfaces/IStakeDaoRewardVault.sol";
import {IERC20} from "../interfaces/IERC20.sol";

/// @title BaseCurveRewardVaultStrategy
/// @notice Abstract base for strategies that stake Curve LP tokens in ERC4626 reward vaults
/// @dev Provides shared deposit/redeem/balance helpers for Stake DAO-style reward vaults
abstract contract BaseCurveRewardVaultStrategy is BaseYieldStrategy {
    // ============ Immutables ============

    /// @notice Stake DAO ERC4626 reward vault
    IStakeDaoRewardVault public immutable rewardVault;

    /// @notice The Curve LP token staked in the reward vault
    IERC20 public immutable lpToken;

    // ============ Constructor ============

    constructor(address _debtAsset, address _vault, address _rewardVault)
        BaseYieldStrategy(_debtAsset, _vault)
    {
        if (_rewardVault == address(0)) revert InvalidAddress();
        rewardVault = IStakeDaoRewardVault(_rewardVault);
        lpToken = IERC20(rewardVault.asset());
    }

    // ============ Reward Vault Helpers ============

    /// @notice Deposit LP tokens into the reward vault
    /// @param lpAmount Amount of LP tokens to deposit
    /// @return shares Vault shares minted
    function _depositToRewardVault(uint256 lpAmount) internal returns (uint256 shares) {
        _ensureApprove(address(lpToken), address(rewardVault), lpAmount);
        shares = rewardVault.deposit(lpAmount, address(this));
    }

    /// @notice Redeem specific number of shares from reward vault
    /// @param shares Number of shares to redeem
    /// @return lpReceived Amount of LP tokens received
    function _redeemFromRewardVault(uint256 shares) internal returns (uint256 lpReceived) {
        lpReceived = rewardVault.redeem(shares, address(this), address(this));
    }

    /// @notice Redeem all shares from reward vault
    /// @return lpReceived Amount of LP tokens received
    function _redeemAllFromRewardVault() internal returns (uint256 lpReceived) {
        uint256 shares = rewardVault.balanceOf(address(this));
        if (shares < 1) return 0;
        lpReceived = rewardVault.redeem(shares, address(this), address(this));
    }

    /// @notice Claim all pending rewards from the reward vault
    /// @return amounts Array of reward amounts claimed
    function _claimRewards() internal returns (uint256[] memory amounts) {
        amounts = rewardVault.claim();
    }

    /// @notice Get total LP token balance in reward vault
    /// @return LP token amount (converted from shares)
    function _rewardVaultBalance() internal view returns (uint256) {
        uint256 shares = rewardVault.balanceOf(address(this));
        if (shares < 1) return 0;
        return rewardVault.convertToAssets(shares);
    }
}
IYieldStrategy.sol 82 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

/// @title IYieldStrategy
/// @notice Interface for yield strategies used by Zenji
/// @dev All strategies accept the debt asset from the vault and deploy to yield-generating protocols
interface IYieldStrategy {
    // ============ Events ============

    event Deposited(uint256 debtAssetAmount, uint256 underlyingDeposited);
    event Withdrawn(uint256 debtAssetAmount, uint256 debtAssetReceived);
    event Harvested(uint256 rewardsValue);
    event EmergencyWithdrawn(uint256 debtAssetReceived);
    event StrategyPauseToggled(bool paused, uint256 debtAssetReceived);

    // ============ Errors ============

    error Unauthorized();
    error ZeroAmount();
    error StrategyPaused();
    error SlippageExceeded();
    error TransferFailed();
    error InvalidAddress();

    // ============ Core Functions ============

    /// @notice Deposit debt asset into the yield strategy
    /// @param debtAssetAmount Amount of debt asset to deposit
    /// @return underlyingDeposited Amount deposited into the underlying protocol
    function deposit(uint256 debtAssetAmount) external returns (uint256 underlyingDeposited);

    /// @notice Withdraw debt asset from the yield strategy
    /// @param debtAssetAmount Amount of debt asset to withdraw
    /// @return debtAssetReceived Actual debt asset received (may differ due to slippage)
    function withdraw(uint256 debtAssetAmount) external returns (uint256 debtAssetReceived);

    /// @notice Withdraw all assets from the yield strategy
    /// @return debtAssetReceived Total debt asset received
    function withdrawAll() external returns (uint256 debtAssetReceived);

    /// @notice Harvest rewards and compound them back into the strategy
    /// @return rewardsValue Value of rewards harvested (in debt asset terms)
    function harvest() external returns (uint256 rewardsValue);

    /// @notice Emergency withdraw all assets, bypassing slippage checks
    /// @return debtAssetReceived Total debt asset received
    function emergencyWithdraw() external returns (uint256 debtAssetReceived);

    /// @notice Toggle pause state for the strategy
    /// @dev When pausing, should unwind all deployed assets back to the vault
    /// @return debtAssetReceived Total debt asset received if unwound (0 when unpausing)
    function pauseStrategy() external returns (uint256 debtAssetReceived);

    // ============ View Functions ============

    /// @notice Returns the asset accepted by the strategy (debt asset)
    function asset() external view returns (address);

    /// @notice Returns the underlying asset of the yield protocol (debt asset or swapped asset)
    function underlyingAsset() external view returns (address);

    /// @notice Returns the current value of the strategy holdings in debt asset terms
    function balanceOf() external view returns (uint256);

    /// @notice Returns the total debt asset deposited (cost basis)
    function costBasis() external view returns (uint256);

    /// @notice Returns unrealized profit (current value - cost basis)
    function unrealizedProfit() external view returns (uint256);

    /// @notice Returns pending rewards value in debt asset terms
    function pendingRewards() external view returns (uint256);

    /// @notice Returns whether the strategy is paused
    function paused() external view returns (bool);

    /// @notice Returns the strategy name
    function name() external view returns (string memory);

    /// @notice Returns the vault address
    function vault() external view returns (address);
}
IChainlinkOracle.sol 35 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

/// @title IChainlinkOracle
/// @notice Interface for Chainlink price feeds
interface IChainlinkOracle {
    /// @notice Get the latest round data
    /// @return roundId The round ID
    /// @return answer The price answer
    /// @return startedAt Timestamp when the round started
    /// @return updatedAt Timestamp when the round was updated
    /// @return answeredInRound The round in which the answer was computed
    function latestRoundData()
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );

    /// @notice Get decimals of the price feed
    /// @return Decimals
    function decimals() external view returns (uint8);

    /// @notice Get description of the price feed
    /// @return Description string
    function description() external view returns (string memory);

    /// @notice Get the latest answer
    /// @return Latest price
    function latestAnswer() external view returns (int256);
}
IAccountant.sol 49 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

import { IStrategy } from "src/interfaces/IStrategy.sol";

interface IAccountant {
    function checkpoint(
        address gauge,
        address from,
        address to,
        uint128 amount,
        IStrategy.PendingRewards calldata pendingRewards,
        IStrategy.HarvestPolicy policy
    ) external;

    function checkpoint(
        address gauge,
        address from,
        address to,
        uint128 amount,
        IStrategy.PendingRewards calldata pendingRewards,
        IStrategy.HarvestPolicy policy,
        address referrer
    ) external;

    function totalSupply(address asset) external view returns (uint128);
    function balanceOf(address asset, address account) external view returns (uint128);

    function claim(address[] calldata _gauges, bytes[] calldata harvestData) external;
    function claim(address[] calldata _gauges, bytes[] calldata harvestData, address receiver)
        external;
    function claim(
        address[] calldata _gauges,
        address account,
        bytes[] calldata harvestData,
        address receiver
    ) external;

    function claimProtocolFees() external;
    function harvest(address[] calldata _gauges, bytes[] calldata _harvestData, address _receiver)
        external;

    function REWARD_TOKEN() external view returns (address);

    function getPendingRewards(address vault) external view returns (uint128);
    function getPendingRewards(address vault, address account) external view returns (uint256);

    function SCALING_FACTOR() external view returns (uint128);
}
ICrvSwapper.sol 11 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

/// @title ICrvSwapper
/// @notice Minimal interface for CRV -> crvUSD swapper contracts
interface ICrvSwapper {
    /// @notice Swap CRV into crvUSD
    /// @param amount CRV amount to swap
    /// @return crvUsdReceived Amount of crvUSD received
    function swap(uint256 amount) external returns (uint256 crvUsdReceived);
}
ICurveStableSwap.sol 35 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

/// @title ICurveStableSwap
/// @notice Interface for Curve StableSwap pools (used for crvUSD/USDC swaps)
interface ICurveStableSwap {
    /// @notice Exchange tokens
    /// @param i Index of input token
    /// @param j Index of output token
    /// @param dx Amount of input token
    /// @param min_dy Minimum amount of output token
    /// @return Amount of output token received
    function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256);

    /// @notice Get expected output amount
    /// @param i Index of input token
    /// @param j Index of output token
    /// @param dx Amount of input token
    /// @return Expected output amount
    function get_dy(int128 i, int128 j, uint256 dx) external view returns (uint256);

    /// @notice Get coin address by index
    /// @param i Coin index
    /// @return Coin address
    function coins(uint256 i) external view returns (address);

    /// @notice Get pool balances
    /// @param i Coin index
    /// @return Balance of coin at index
    function balances(uint256 i) external view returns (uint256);

    /// @notice Get virtual price of LP token
    /// @return Virtual price scaled by 1e18
    function get_virtual_price() external view returns (uint256);
}
ICurveStableSwapNG.sol 63 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

/// @title ICurveStableSwapNG
/// @notice Interface for Curve StableSwapNG pools (e.g., pmUSD/crvUSD)
/// @dev Uses uint256 indices instead of int128 (NG pools use uint256)
interface ICurveStableSwapNG {
    /// @notice Add liquidity to the pool
    /// @param amounts Array of token amounts to deposit [coin0, coin1]
    /// @param min_mint_amount Minimum LP tokens to mint
    /// @return LP tokens minted
    function add_liquidity(uint256[] calldata amounts, uint256 min_mint_amount) external returns (uint256);

    /// @notice Remove liquidity in a single coin
    /// @param burn_amount Amount of LP tokens to burn
    /// @param i Index of coin to withdraw
    /// @param min_received Minimum amount of coin to receive
    /// @return Amount of coin received
    function remove_liquidity_one_coin(uint256 burn_amount, int128 i, uint256 min_received)
        external
        returns (uint256);

    /// @notice Calculate expected LP tokens from deposit
    /// @param amounts Array of token amounts [coin0, coin1]
    /// @param is_deposit True for deposit, false for withdrawal
    /// @return Expected LP token amount
    function calc_token_amount(uint256[] calldata amounts, bool is_deposit) external view returns (uint256);

    /// @notice Calculate expected output from removing liquidity in one coin
    /// @param burn_amount Amount of LP tokens to burn
    /// @param i Index of coin to withdraw
    /// @return Expected amount of coin received
    function calc_withdraw_one_coin(uint256 burn_amount, int128 i) external view returns (uint256);

    /// @notice Get the virtual price of the LP token
    /// @return Virtual price scaled by 1e18
    function get_virtual_price() external view returns (uint256);

    /// @notice Get coin address by index
    /// @param i Coin index
    /// @return Coin address
    function coins(uint256 i) external view returns (address);

    /// @notice Get pool balances
    /// @param i Coin index
    /// @return Balance of coin at index
    function balances(uint256 i) external view returns (uint256);

    /// @notice Exchange tokens
    /// @param i Index of input token
    /// @param j Index of output token
    /// @param dx Amount of input token
    /// @param min_dy Minimum amount of output token
    /// @return Amount of output token received
    function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256);

    /// @notice Get expected output amount for exchange
    /// @param i Index of input token
    /// @param j Index of output token
    /// @param dx Amount of input token
    /// @return Expected output amount
    function get_dy(int128 i, int128 j, uint256 dx) external view returns (uint256);
}
IERC20.sol 16 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

/// @title IERC20
/// @notice Standard ERC20 interface
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function decimals() external view returns (uint8);
    function symbol() external view returns (string memory);
    function name() external view returns (string memory);
}
IStakeDaoRewardVault.sol 12 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

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

/// @title IStakeDaoRewardVault
/// @notice Minimal extension for Stake DAO reward vaults exposing the accountant address
interface IStakeDaoRewardVault is ICurveRewardVault {
    /// @notice Stake DAO accountant contract
    /// @return accountant address
    function ACCOUNTANT() external view returns (address);
}
CurveUsdtSwapLib.sol 87 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

import {ICurveStableSwap} from "../interfaces/ICurveStableSwap.sol";
import {ICurveStableSwapWithReceiver} from "../interfaces/ICurveStableSwapWithReceiver.sol";
import {IChainlinkOracle} from "../interfaces/IChainlinkOracle.sol";

/// @title CurveUsdtSwapLib
/// @notice Shared USDT/crvUSD swap + oracle conversion logic
library CurveUsdtSwapLib {
    uint256 internal constant PRECISION = 1e18;

    error ExchangeFailed();

    /// @notice Swap USDT to crvUSD via Curve StableSwap
    function swapUsdtToCrvUsd(
        ICurveStableSwap pool,
        int128 usdtIndex,
        int128 crvUsdIndex,
        uint256 usdtAmount,
        uint256 slippage
    ) internal returns (uint256 crvUsdReceived) {
        uint256 expectedOut = pool.get_dy(usdtIndex, crvUsdIndex, usdtAmount);
        uint256 minOut = (expectedOut * (PRECISION - slippage)) / PRECISION;
        crvUsdReceived = exchange(pool, usdtIndex, crvUsdIndex, usdtAmount, minOut);
    }

    /// @notice Swap crvUSD to USDT via Curve StableSwap
    function swapCrvUsdToUsdt(
        ICurveStableSwap pool,
        int128 crvUsdIndex,
        int128 usdtIndex,
        uint256 crvUsdAmount,
        uint256 slippage
    ) internal returns (uint256 usdtReceived) {
        uint256 expectedOut = pool.get_dy(crvUsdIndex, usdtIndex, crvUsdAmount);
        uint256 minOut = (expectedOut * (PRECISION - slippage)) / PRECISION;
        usdtReceived = exchange(pool, crvUsdIndex, usdtIndex, crvUsdAmount, minOut);
    }

    /// @notice Exchange on Curve with receiver fallback
    function exchange(ICurveStableSwap pool, int128 i, int128 j, uint256 dx, uint256 minOut)
        internal
        returns (uint256 amountOut)
    {
        try ICurveStableSwapWithReceiver(address(pool)).exchange(i, j, dx, minOut, address(this))
        returns (uint256 outWithReceiver) {
            amountOut = outWithReceiver;
        } catch {
            try pool.exchange(i, j, dx, minOut) returns (uint256 outStandard) {
                amountOut = outStandard;
            } catch {
                revert ExchangeFailed();
            }
        }
    }

    /// @notice Convert crvUSD value (18 dec) to USDT value (6 dec) using oracles
    function convertCrvUsdToUsdt(
        uint256 crvUsdValue,
        IChainlinkOracle crvUsdOracle,
        IChainlinkOracle usdtOracle,
        uint256 maxStaleness
    ) internal view returns (uint256) {
        if (crvUsdValue < 1) return 0;

        (uint80 crvRoundId, int256 crvUsdPrice,, uint256 crvUsdUpdatedAt, uint80 crvAnswered) =
            crvUsdOracle.latestRoundData();
        if (
            crvUsdPrice <= 0 || crvAnswered < crvRoundId
                || block.timestamp - crvUsdUpdatedAt > maxStaleness
        ) {
            return crvUsdValue / 1e12; // fallback to 1:1
        }

        (uint80 usdtRoundId, int256 usdtPrice,, uint256 usdtUpdatedAt, uint80 usdtAnswered) =
            usdtOracle.latestRoundData();
        if (
            usdtPrice <= 0 || usdtAnswered < usdtRoundId
                || block.timestamp - usdtUpdatedAt > maxStaleness
        ) {
            return crvUsdValue / 1e12; // fallback to 1:1
        }

        return (crvUsdValue * uint256(crvUsdPrice)) / (uint256(usdtPrice) * 1e12);
    }
}
BaseYieldStrategy.sol 234 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

import {IYieldStrategy} from "../interfaces/IYieldStrategy.sol";
import {IERC20} from "../interfaces/IERC20.sol";
import {SafeTransferLib} from "../libraries/SafeTransferLib.sol";

/// @title BaseYieldStrategy
/// @notice Abstract base contract for yield strategies with cost basis tracking
/// @dev All strategies inherit from this and implement protocol-specific logic
abstract contract BaseYieldStrategy is IYieldStrategy {
    using SafeTransferLib for IERC20;

    // ============ Constants ============

    uint256 public constant PRECISION = 1e18;

    // ============ Immutables ============

    /// @notice The debt asset token (asset accepted/returned by this strategy)
    IERC20 public immutable debtAsset;

    // ============ State ============

    /// @notice The vault that owns this strategy
    address public override vault;

    /// @notice Deployer address for deferred vault initialization
    address public initializer;

    /// @notice Total debt asset deposited (cost basis for profit calculation)
    uint256 internal _costBasis;

    /// @notice Whether the strategy is paused
    bool public override paused;

    /// @notice Reentrancy guard
    uint256 private _status;
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    // ============ Events ============

    event VaultInitialized(address indexed vault);

    // ============ Modifiers ============

    modifier onlyVault() {
        if (msg.sender != vault) revert Unauthorized();
        _;
    }

    modifier whenNotPaused() {
        if (paused) revert StrategyPaused();
        _;
    }

    modifier nonReentrant() {
        if (_status == _ENTERED) revert Unauthorized();
        _status = _ENTERED;
        _;
        _status = _NOT_ENTERED;
    }

    // ============ Constructor ============

    /// @param _debtAsset The debt asset token address
    /// @param _vault The vault address (or zero for deferred initialization)
    constructor(address _debtAsset, address _vault) {
        if (_debtAsset == address(0)) revert InvalidAddress();
        debtAsset = IERC20(_debtAsset);
        if (_vault != address(0)) {
            vault = _vault;
            initializer = address(0);
        } else {
            initializer = msg.sender;
        }
        _status = _NOT_ENTERED;
    }

    // ============ Deferred Initialization ============

    /// @notice Initialize the vault address (can only be called once by deployer)
    function initializeVault(address newVault) external {
        if (vault != address(0)) revert InvalidAddress();
        if (newVault == address(0)) revert InvalidAddress();
        if (msg.sender != initializer) revert Unauthorized();

        vault = newVault;
        initializer = address(0);
        emit VaultInitialized(newVault);
    }

    // ============ Core Functions ============

    /// @inheritdoc IYieldStrategy
    function deposit(uint256 amount)
        external
        override
        onlyVault
        whenNotPaused
        nonReentrant
        returns (uint256 underlyingDeposited)
    {
        if (amount == 0) revert ZeroAmount();

        debtAsset.safeTransferFrom(msg.sender, address(this), amount);
        _costBasis += amount;
        underlyingDeposited = _deposit(amount);
        emit Deposited(amount, underlyingDeposited);
    }

    /// @inheritdoc IYieldStrategy
    function withdraw(uint256 amount)
        external
        override
        onlyVault
        nonReentrant
        returns (uint256 received)
    {
        if (amount == 0) revert ZeroAmount();

        uint256 currentValue = this.balanceOf();
        if (currentValue == 0) return 0;
        if (amount > currentValue) amount = currentValue;

        uint256 basisReduction = (_costBasis * amount) / currentValue;
        received = _withdraw(amount);
        _costBasis = _costBasis > basisReduction ? _costBasis - basisReduction : 0;

        if (received > 0) {
            debtAsset.safeTransfer(vault, received);
        }

        emit Withdrawn(amount, received);
    }

    /// @inheritdoc IYieldStrategy
    function withdrawAll() external override onlyVault nonReentrant returns (uint256 received) {
        received = _withdrawAll();
        _costBasis = 0;

        uint256 balance = debtAsset.balanceOf(address(this));
        if (balance > 0) {
            debtAsset.safeTransfer(vault, balance);
        }

        emit Withdrawn(type(uint256).max, received);
    }

    /// @inheritdoc IYieldStrategy
    function harvest()
        external
        override
        onlyVault
        whenNotPaused
        nonReentrant
        returns (uint256 rewardsValue)
    {
        rewardsValue = _harvest();
        emit Harvested(rewardsValue);
    }

    /// @inheritdoc IYieldStrategy
    function emergencyWithdraw()
        external
        override
        onlyVault
        nonReentrant
        returns (uint256 received)
    {
        received = _emergencyWithdraw();
        _costBasis = 0;

        uint256 balance = debtAsset.balanceOf(address(this));
        if (balance > 0) {
            debtAsset.safeTransfer(vault, balance);
        }

        emit EmergencyWithdrawn(received);
    }

    /// @inheritdoc IYieldStrategy
    function pauseStrategy() external override onlyVault nonReentrant returns (uint256 received) {
        paused = !paused;

        if (paused) {
            received = _withdrawAll();
            _costBasis = 0;

            uint256 balance = debtAsset.balanceOf(address(this));
            if (balance > 0) {
                debtAsset.safeTransfer(vault, balance);
            }
        }

        emit StrategyPauseToggled(paused, received);
    }

    // ============ View Functions ============

    /// @inheritdoc IYieldStrategy
    function asset() external view override returns (address) {
        return address(debtAsset);
    }

    /// @inheritdoc IYieldStrategy
    function costBasis() external view override returns (uint256) {
        return _costBasis;
    }

    /// @inheritdoc IYieldStrategy
    function balanceOf() public view virtual override returns (uint256);

    /// @inheritdoc IYieldStrategy
    function unrealizedProfit() external view override returns (uint256) {
        uint256 currentValue = this.balanceOf();
        return currentValue > _costBasis ? currentValue - _costBasis : 0;
    }

    // ============ Internal Functions (to be implemented by derived contracts) ============

    function _deposit(uint256 amount) internal virtual returns (uint256 underlyingDeposited);
    function _withdraw(uint256 amount) internal virtual returns (uint256 received);
    function _withdrawAll() internal virtual returns (uint256 received);
    function _harvest() internal virtual returns (uint256 rewardsValue);
    function _emergencyWithdraw() internal virtual returns (uint256 received);

    // ============ Helper Functions ============

    function _ensureApprove(address token, address spender, uint256 amount) internal {
        IERC20(token).ensureApproval(spender, amount);
    }
}
IStrategy.sol 35 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.33;

import "src/interfaces/IAllocator.sol";

interface IStrategy {
    /// @notice The policy for harvesting rewards.
    enum HarvestPolicy {
        CHECKPOINT,
        HARVEST
    }

    struct PendingRewards {
        uint128 feeSubjectAmount;
        uint128 totalAmount;
    }

    function deposit(IAllocator.Allocation calldata allocation, HarvestPolicy policy)
        external
        returns (PendingRewards memory pendingRewards);
    function withdraw(
        IAllocator.Allocation calldata allocation,
        HarvestPolicy policy,
        address receiver
    ) external returns (PendingRewards memory pendingRewards);

    function balanceOf(address gauge) external view returns (uint256 balance);

    function harvest(address gauge, bytes calldata extraData)
        external
        returns (PendingRewards memory pendingRewards);
    function flush() external;

    function shutdown(address gauge) external;
}
ICurveRewardVault.sol 49 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

/// @title ICurveRewardVault
/// @notice Interface for Stake DAO ERC4626 reward vaults
/// @dev These vaults wrap Curve LP tokens and distribute CRV/SDT rewards
interface ICurveRewardVault {
    // ============ ERC4626 Functions ============

    function asset() external view returns (address);
    function totalAssets() external view returns (uint256);
    function convertToShares(uint256 assets) external view returns (uint256);
    function convertToAssets(uint256 shares) external view returns (uint256);
    function maxDeposit(address receiver) external view returns (uint256);
    function maxWithdraw(address owner) external view returns (uint256);
    function maxRedeem(address owner) external view returns (uint256);
    function previewDeposit(uint256 assets) external view returns (uint256);
    function previewWithdraw(uint256 assets) external view returns (uint256);
    function previewRedeem(uint256 shares) external view returns (uint256);
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);

    // ============ ERC20 Functions ============

    function balanceOf(address account) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function totalSupply() external view returns (uint256);

    // ============ Reward Functions ============

    /// @notice Claim all pending rewards for the caller
    /// @return Array of reward amounts claimed (ordered by rewardTokens())
    function claim() external returns (uint256[] memory);

    /// @notice Get pending rewards for an account
    /// @param account Address to check
    /// @param token Reward token address
    /// @return Pending reward amount
    function earned(address account, address token) external view returns (uint256);

    /// @notice Get all reward token addresses
    /// @return Array of reward token addresses
    function rewardTokens(uint256 index) external view returns (address);

    /// @notice Get the number of reward tokens
    /// @return Number of reward tokens
    function rewardTokensLength() external view returns (uint256);
}
ICurveStableSwapWithReceiver.sol 17 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

/// @title ICurveStableSwapWithReceiver
/// @notice Optional Curve exchange variant that supports specifying a receiver
interface ICurveStableSwapWithReceiver {
    /// @notice Exchange tokens to a receiver
    /// @param i Index of input token
    /// @param j Index of output token
    /// @param dx Amount of input token
    /// @param min_dy Minimum amount of output token
    /// @param receiver Address to receive the output token
    /// @return Amount of output token received
    function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy, address receiver)
        external
        returns (uint256);
}
SafeTransferLib.sol 57 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;

import { IERC20 } from "../interfaces/IERC20.sol";

/// @title SafeTransferLib
/// @notice Gas-efficient safe transfer functions for ERC20 tokens
library SafeTransferLib {
    error TransferFailed();

    /// @notice Safe transfer that reverts on failure
    function safeTransfer(IERC20 token, address to, uint256 amount) internal {
        (bool success, bytes memory data) =
            address(token).call(abi.encodeWithSelector(token.transfer.selector, to, amount));
        if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
            revert TransferFailed();
        }
    }

    /// @notice Safe transferFrom that reverts on failure
    function safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal {
        (bool success, bytes memory data) = address(token).call(
            abi.encodeWithSelector(token.transferFrom.selector, from, to, amount)
        );
        if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
            revert TransferFailed();
        }
    }

    /// @notice Ensure approval is sufficient, set to max if not
    /// @dev Uses safeApprove pattern for USDT compatibility (non-standard ERC20)
    function ensureApproval(IERC20 token, address spender, uint256 amount) internal {
        if (token.allowance(address(this), spender) < amount) {
            safeApprove(token, spender, type(uint256).max);
        }
    }

    /// @notice Safe approve that handles non-standard ERC20 tokens like USDT
    /// @dev USDT requires setting allowance to 0 first before setting a new non-zero value
    ///      and returns void instead of bool
    function safeApprove(IERC20 token, address spender, uint256 amount) internal {
        // For USDT: first reset to 0 if there's existing allowance and we're setting non-zero
        if (amount > 0 && token.allowance(address(this), spender) > 0) {
            (bool resetSuccess,) =
                address(token).call(abi.encodeWithSelector(token.approve.selector, spender, 0));
            // Ignore return value for void-returning tokens like USDT
            if (!resetSuccess) revert TransferFailed();
        }

        (bool success, bytes memory data) =
            address(token).call(abi.encodeWithSelector(token.approve.selector, spender, amount));
        // Handle both standard (returns bool) and non-standard (returns nothing) ERC20
        if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
            revert TransferFailed();
        }
    }
}
IAllocator.sol 26 lines
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.7;

interface IAllocator {
    struct Allocation {
        address asset;
        address gauge;
        address[] targets;
        uint256[] amounts;
    }

    function getDepositAllocation(address asset, address gauge, uint256 amount)
        external
        view
        returns (Allocation memory);
    function getWithdrawalAllocation(address asset, address gauge, uint256 amount)
        external
        view
        returns (Allocation memory);
    function getRebalancedAllocation(address asset, address gauge, uint256 amount)
        external
        view
        returns (Allocation memory);

    function getAllocationTargets(address gauge) external view returns (address[] memory);
}

Read Contract

DEFAULT_SLIPPAGE 0xa8798510 → uint256
EMERGENCY_SLIPPAGE 0xbb42e5cd → uint256
LP_SLIPPAGE 0x5144107d → uint256
MAX_ORACLE_STALENESS 0x5dc565c2 → uint256
MAX_SLIPPAGE 0xf9759518 → uint256
MIN_HARVEST_THRESHOLD 0xd6ef2365 → uint256
PRECISION 0xaaf5eb68 → uint256
asset 0x38d52e0f → address
balanceOf 0x722713f7 → uint256
costBasis 0x53e3cb5f → uint256
crv 0x6a4874a1 → address
crvSwapper 0xec47df94 → address
crvUSD 0x9bec8288 → address
crvUsdIndex 0x6f000201 → int128
crvUsdOracle 0xab0ebba0 → address
debtAsset 0xa919802d → address
gauge 0xa6f19c84 → address
initializer 0x9ce110d7 → address
lpCrvUsdIndex 0x3f5bbcda → int128
lpPool 0x3737bcb4 → address
lpToken 0x5fcbd285 → address
name 0x06fdde03 → string
paused 0x5c975abb → bool
pendingRewards 0xeded3fda → uint256
rewardVault 0x3a2c6777 → address
slippageTolerance 0xd03153aa → uint256
underlyingAsset 0x7158da7c → address
unrealizedProfit 0x53ee961e → uint256
usdtCrvUsdPool 0x9f19915c → address
usdtIndex 0x5504bd5f → int128
usdtOracle 0x07a79fc7 → address
vault 0xfbfa77cf → address

Write Contract 8 functions

These functions modify contract state and require a wallet transaction to execute.

deposit 0xb6b55f25
uint256 amount
returns: uint256
emergencyWithdraw 0xdb2e21bc
No parameters
returns: uint256
harvest 0x4641257d
No parameters
returns: uint256
initializeVault 0xccb6a270
address newVault
pauseStrategy 0xd8c143f7
No parameters
returns: uint256
setSlippage 0xf0fa55a9
uint256 newSlippage
withdraw 0x2e1a7d4d
uint256 amount
returns: uint256
withdrawAll 0x853828b6
No parameters
returns: uint256

Recent Transactions

This address has 1 on-chain transactions, but only 1.4% of the chain is indexed. Transactions will appear as indexing progresses. View on Etherscan →