Smart Contract
ERC4626Utils
A.04f5ae6bef48c1fc.ERC4626Utils
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