Smart Contract

ERC4626Utils

A.04f5ae6bef48c1fc.ERC4626Utils

Valid From

142,038,776

Deployed

2w ago
Feb 13, 2026, 02:41:56 AM UTC

Dependents

0 imports
1import EVM from 0xe467b9dd11fa00df
2import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141
3import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141
4
5/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
6/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT
7/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
8///
9/// ERC4626Utils
10///
11/// Utility methods commonly used across ERC4626 integrating contracts. The included methods are built on top of the 
12/// OpenZeppelin ERC4626 implementation and support view methods on the underlying ERC4626 contract.
13///
14access(all) contract ERC4626Utils {
15
16    /// COA used to make calls to the ERC4626 vault
17    access(self) let callingCOA: @EVM.CadenceOwnedAccount
18
19    /// Normalizes decimals of the given amount to the target decimals
20    ///
21    /// @param amount The amount to normalize
22    /// @param originalDecimals The original decimals of the amount
23    /// @param targetDecimals The target decimals to normalize to
24    ///
25    /// @return The normalized amount
26    access(all) fun normalizeDecimals(amount: UInt256, originalDecimals: UInt8, targetDecimals: UInt8): UInt256 {
27        var res = amount
28        if originalDecimals > targetDecimals {
29            // decimals is greater than targetDecimals - truncate the fractional part
30            res = amount / FlowEVMBridgeUtils.pow(base: 10, exponent: originalDecimals - targetDecimals)
31        } else if originalDecimals < targetDecimals {
32            // decimals is less than targetDecimals - scale the amount up to targetDecimals
33            res = amount * FlowEVMBridgeUtils.pow(base: 10, exponent: targetDecimals - originalDecimals)
34        }
35        return res
36    }
37
38    /// Returns the EVM address of the underlying asset for the given ERC4626 vault
39    ///
40    /// @param vault The address of the ERC4626 vault
41    ///
42    /// @return The EVM address of the underlying asset for the given ERC4626 vault
43    access(all)
44    fun underlyingAssetEVMAddress(vault: EVM.EVMAddress): EVM.EVMAddress? {
45        let callRes = self._dryCall(to: vault, signature: "asset()", args: [], gasLimit: 5_000_000)
46        if callRes.status != EVM.Status.successful || callRes.data.length == 0 {
47            return nil
48        }
49        let decoded = EVM.decodeABI(types: [Type<EVM.EVMAddress>()], data: callRes.data)
50        return decoded[0] as! EVM.EVMAddress
51    }
52
53    /// Returns the total assets managed by the ERC4626 vault
54    ///
55    /// @param vault The address of the ERC4626 vault
56    ///
57    /// @return The total assets managed by the ERC4626 vault. Callers should anticipate the address of the asset and
58    ///         the decimals of the asset being returned.
59    access(all) fun totalAssets(vault: EVM.EVMAddress): UInt256? {
60        let callRes = self._dryCall(to: vault, signature: "totalAssets()", args: [], gasLimit: 5_000_000)
61        if callRes.status != EVM.Status.successful || callRes.data.length == 0 {
62            return nil
63        }
64        let totalAssets = EVM.decodeABI(types: [Type<UInt256>()], data: callRes.data)
65        return totalAssets[0] as! UInt256
66    }
67
68    /// Returns the total shares issued by the ERC4626 vault
69    ///
70    /// @param vault The address of the ERC4626 vault
71    ///
72    /// @return The total shares issued by the ERC4626 vault. Callers should anticipate the address of the asset and
73    ///         the decimals of the asset being returned.
74    access(all) fun totalShares(vault: EVM.EVMAddress): UInt256? {
75        let callRes = self._dryCall(to: vault, signature: "totalSupply()", args: [], gasLimit: 5_000_000)
76        if callRes.status != EVM.Status.successful || callRes.data.length == 0 {
77            return nil
78        }
79        let totalAssets = EVM.decodeABI(types: [Type<UInt256>()], data: callRes.data)
80        return totalAssets[0] as! UInt256
81    }
82
83    /// Returns the maximum amount of shares that can be redeemed from the given owner's balance in the ERC4626 vault
84    ///
85    /// @param vault The address of the ERC4626 vault
86    /// @param owner The address of the owner of the shares to redeem
87    ///
88    /// @return The maximum amount of shares that can be redeemed from the given owner's balance in the ERC4626 vault.
89    ///         Callers should anticipate the address of the shares and the decimals of the shares being returned.
90    access(all)
91    fun maxRedeem(vault: EVM.EVMAddress, owner: EVM.EVMAddress): UInt256? {
92        let callRes = self._dryCall(to: vault, signature: "maxRedeem(address)", args: [owner], gasLimit: 5_000_000)
93        if callRes.status != EVM.Status.successful || callRes.data.length == 0 {
94            return nil
95        }
96        let maxRedeem = EVM.decodeABI(types: [Type<UInt256>()], data: callRes.data)
97        return maxRedeem[0] as! UInt256
98    }
99
100    /// Returns the maximum amount of assets that can be deposited into the ERC4626 vault
101    ///
102    /// @param vault The address of the ERC4626 vault
103    /// @param receiver The address of the receiver of the deposit
104    ///
105    /// @return The maximum amount of assets that can be deposited into the ERC4626 vault for the receiver, returned in
106    ///         the asset's decimals.
107    access(all)
108    fun maxDeposit(vault: EVM.EVMAddress, receiver: EVM.EVMAddress): UInt256? {
109        let callRes = self._dryCall(to: vault, signature: "maxDeposit(address)", args: [receiver], gasLimit: 5_000_000)
110        if callRes.status != EVM.Status.successful || callRes.data.length == 0 {
111            return nil
112        }
113        let maxDeposit = EVM.decodeABI(types: [Type<UInt256>()], data: callRes.data)
114        return maxDeposit[0] as! UInt256
115    }
116
117    /// Returns the amount of assets that would be required to mint the given amount of shares
118    /// under current conditions.
119    ///
120    /// @param vault The address of the ERC4626 vault
121    /// @param shares The amount of shares to mint (denominated in the share token's decimals)
122    ///
123    /// @return The amount of underlying assets required to mint `shares` (denominated in the
124    ///         underlying asset's decimals), or nil if the call fails.
125    access(all)
126    fun previewMint(vault: EVM.EVMAddress, shares: UInt256): UInt256? {
127        let callRes = self._dryCall(
128            to: vault,
129            signature: "previewMint(uint256)",
130            args: [shares],
131            gasLimit: 5_000_000
132        )
133        if callRes.status != EVM.Status.successful || callRes.data.length == 0 {
134            return nil
135        }
136        let decoded = EVM.decodeABI(types: [Type<UInt256>()], data: callRes.data)
137        return decoded[0] as! UInt256
138    }
139
140    /// Returns the amount of shares that would be minted for depositing the given amount of assets
141    /// under current conditions.
142    ///
143    /// @param vault The address of the ERC4626 vault
144    /// @param assets The amount of assets to deposit (denominated in the underlying asset's decimals)
145    ///
146    /// @return The amount of shares that would be minted (denominated in the share token's decimals),
147    ///         or nil if the call fails.
148    access(all)
149    fun previewDeposit(vault: EVM.EVMAddress, assets: UInt256): UInt256? {
150        let callRes = self._dryCall(
151            to: vault,
152            signature: "previewDeposit(uint256)",
153            args: [assets],
154            gasLimit: 5_000_000
155        )
156        if callRes.status != EVM.Status.successful || callRes.data.length == 0 {
157            return nil
158        }
159        let decoded = EVM.decodeABI(types: [Type<UInt256>()], data: callRes.data)
160        return decoded[0] as! UInt256
161    }
162
163    /// Returns the amount of underlying assets that would be redeemed for the given amount of shares
164    /// under current conditions.
165    ///
166    /// @param vault The address of the ERC4626 vault
167    /// @param shares The amount of shares to redeem (denominated in the share token's decimals)
168    ///
169    /// @return The amount of underlying assets that would be received (denominated in the underlying
170    ///         asset's decimals), or nil if the call fails.
171    access(all)
172    fun previewRedeem(vault: EVM.EVMAddress, shares: UInt256): UInt256? {
173        let callRes = self._dryCall(
174            to: vault,
175            signature: "previewRedeem(uint256)",
176            args: [shares],
177            gasLimit: 5_000_000
178        )
179        if callRes.status != EVM.Status.successful || callRes.data.length == 0 {
180            return nil
181        }
182        let decoded = EVM.decodeABI(types: [Type<UInt256>()], data: callRes.data)
183        return decoded[0] as! UInt256
184    }
185
186    /// Returns the amount of shares that would be required to withdraw the given amount of assets
187    /// under current conditions.
188    ///
189    /// @param vault The address of the ERC4626 vault
190    /// @param assets The amount of assets to withdraw (denominated in the underlying asset's decimals)
191    ///
192    /// @return The amount of shares that would be burned (denominated in the share token's decimals),
193    ///         or nil if the call fails.
194    access(all)
195    fun previewWithdraw(vault: EVM.EVMAddress, assets: UInt256): UInt256? {
196        let callRes = self._dryCall(
197            to: vault,
198            signature: "previewWithdraw(uint256)",
199            args: [assets],
200            gasLimit: 5_000_000
201        )
202        if callRes.status != EVM.Status.successful || callRes.data.length == 0 {
203            return nil
204        }
205        let decoded = EVM.decodeABI(types: [Type<UInt256>()], data: callRes.data)
206        return decoded[0] as! UInt256
207    }
208    /// Performs a dry call using the calling COA
209    ///
210    /// @param to The address of the contract to dry call
211    /// @param signature The signature of the function to dry call
212    /// @param args The arguments to pass to the function
213    /// @param gasLimit The gas limit for the dry call
214    ///
215    /// @return The result of the dry call
216    access(self) fun _dryCall(to: EVM.EVMAddress, signature: String, args: [AnyStruct], gasLimit: UInt64): EVM.Result {
217        return self.callingCOA.dryCall(
218            to: to,
219            data: EVM.encodeABIWithSignature(signature, args),
220            gasLimit: gasLimit,
221            value: EVM.Balance(attoflow: 0)
222        )
223    }
224
225    init() {
226        self.callingCOA <- EVM.createCadenceOwnedAccount()
227    }
228}
229