Skip to content

Commit 37e2f75

Browse files
committed
Improve precision of forked tests
1 parent a0a9f06 commit 37e2f75

File tree

8 files changed

+643
-867
lines changed

8 files changed

+643
-867
lines changed

cadence/tests/evm_state_helpers.cdc

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@ import "EVM"
33

44
/* --- ERC4626 Vault State Manipulation --- */
55

6-
/// Set vault share price by setting totalAssets to a specific base value, then multiplying by the price multiplier
7-
/// Manipulates both asset.balanceOf(vault) and vault._totalAssets to bypass maxRate capping
8-
/// Caller should provide baseAssets large enough to prevent slippage during price changes
6+
/// Set vault share price by manipulating totalAssets, totalSupply, and asset.balanceOf(vault)
7+
/// priceMultiplier: share price as a multiplier (e.g. 2.0 for 2x price)
98
access(all) fun setVaultSharePrice(
109
vaultAddress: String,
1110
assetAddress: String,
1211
assetBalanceSlot: UInt256,
1312
totalSupplySlot: UInt256,
1413
vaultTotalAssetsSlot: UInt256,
15-
baseAssets: UFix64,
1614
priceMultiplier: UFix64,
1715
signer: Test.TestAccount
1816
) {
@@ -21,7 +19,7 @@ access(all) fun setVaultSharePrice(
2119
code: Test.readFile("transactions/set_erc4626_vault_price.cdc"),
2220
authorizers: [signer.address],
2321
signers: [signer],
24-
arguments: [vaultAddress, assetAddress, assetBalanceSlot, totalSupplySlot, vaultTotalAssetsSlot, baseAssets, priceMultiplier]
22+
arguments: [vaultAddress, assetAddress, assetBalanceSlot, totalSupplySlot, vaultTotalAssetsSlot, priceMultiplier]
2523
)
2624
)
2725
Test.expect(result, Test.beSucceeded())
@@ -31,16 +29,17 @@ access(all) fun setVaultSharePrice(
3129

3230
/// Set Uniswap V3 pool to a specific price via EVM.store
3331
/// Creates pool if it doesn't exist, then manipulates state
32+
/// Price is specified as UFix128 for high precision (24 decimal places)
3433
access(all) fun setPoolToPrice(
3534
factoryAddress: String,
3635
tokenAAddress: String,
3736
tokenBAddress: String,
3837
fee: UInt64,
39-
priceTokenBPerTokenA: UFix64,
38+
priceTokenBPerTokenA: UFix128,
4039
tokenABalanceSlot: UInt256,
4140
tokenBBalanceSlot: UInt256,
4241
signer: Test.TestAccount
43-
) {
42+
) {
4443
let seedResult = Test.executeTransaction(
4544
Test.Transaction(
4645
code: Test.readFile("transactions/set_uniswap_v3_pool_price.cdc"),
@@ -51,3 +50,17 @@ access(all) fun setPoolToPrice(
5150
)
5251
Test.expect(seedResult, Test.beSucceeded())
5352
}
53+
54+
/* --- Fee Adjustment --- */
55+
56+
/// Adjust a pool price to compensate for Uniswap V3 swap fees.
57+
/// Forward: price / (1 - fee/1e6)
58+
/// Reverse: price * (1 - fee/1e6)
59+
/// Computed in UFix128 for full 24-decimal-place precision.
60+
access(all) fun feeAdjustedPrice(_ price: UFix128, fee: UInt64, reverse: Bool): UFix128 {
61+
let feeRate = UFix128(fee) / 1_000_000.0
62+
if reverse {
63+
return price * (1.0 - feeRate)
64+
}
65+
return price / (1.0 - feeRate)
66+
}
Lines changed: 99 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Tests that EVM state helpers correctly set Uniswap V3 pool price and ERC4626 vault price
2-
#test_fork(network: "mainnet-fork", height: 142251136)
2+
#test_fork(network: "mainnet-fork", height: 143292255)
33

44
import Test
55
import BlockchainHelpers
@@ -9,9 +9,7 @@ import "evm_state_helpers.cdc"
99

1010
import "FlowToken"
1111

12-
// Mainnet addresses (same as forked_rebalance_scenario3c_test.cdc)
1312
access(all) let whaleFlowAccount = Test.getAccount(0x92674150c9213fc9)
14-
access(all) let coaOwnerAccount = Test.getAccount(0xe467b9dd11fa00df)
1513

1614
access(all) let factoryAddress = "0xca6d7Bb03334bBf135902e1d919a5feccb461632"
1715
access(all) let routerAddress = "0xeEDC6Ff75e1b10B903D9013c358e446a73d35341"
@@ -27,123 +25,132 @@ access(all) let wflowBalanceSlot = 3 as UInt256
2725
access(all) let morphoVaultTotalSupplySlot = 11 as UInt256
2826
access(all) let morphoVaultTotalAssetsSlot = 15 as UInt256
2927

30-
// Bridged vault type identifiers (service account prefix may vary; use deployment)
3128
access(all) let pyusd0VaultTypeId = "A.1e4aa0b87d10b141.EVMVMBridgedToken_99af3eea856556646c98c8b9b2548fe815240750.Vault"
32-
access(all) let fusdevVaultTypeId = "A.1e4aa0b87d10b141.EVMVMBridgedToken_d069d989e2f44b70c65347d1853c0c67e10a9f8d.Vault"
3329

34-
access(all)
35-
fun setup() {
36-
deployContractsForFork()
37-
transferFlow(signer: whaleFlowAccount, recipient: coaOwnerAccount.address, amount: 1000.0)
30+
// Vault public paths
31+
access(all) let pyusd0PublicPath = /public/EVMVMBridgedToken_99af3eea856556646c98c8b9b2548fe815240750Vault
32+
access(all) let fusdevPublicPath = /public/EVMVMBridgedToken_d069d989e2f44b70c65347d1853c0c67e10a9f8dVault
3833

39-
// Deposit FLOW to COA to cover bridge/gas fees for swaps (scheduled txs can consume some)
40-
let depositFlowRes = Test.executeTransaction(
41-
Test.Transaction(
42-
code: Test.readFile("transactions/deposit_flow_to_coa.cdc"),
43-
authorizers: [coaOwnerAccount.address],
44-
signers: [coaOwnerAccount],
45-
arguments: [5.0]
46-
)
47-
)
48-
Test.expect(depositFlowRes, Test.beSucceeded())
49-
}
34+
access(all) let univ3PoolFee: UInt64 = 3000
5035

51-
access(all) let univ3PoolFee: UInt64 = 100
36+
access(all) var snapshot: UInt64 = 0
37+
access(all) var testAccount = Test.createAccount()
5238

5339
access(all)
54-
fun test_UniswapV3PriceSetAndSwap() {
40+
fun setup() {
41+
deployContractsForFork()
42+
transferFlow(signer: whaleFlowAccount, recipient: testAccount.address, amount: 10000000.0)
43+
createCOA(testAccount, fundingAmount: 5.0)
44+
45+
// Set up a WFLOW/PYUSD0 pool at 1:1 so we can swap FLOW→PYUSD0 to fund the Cadence vault
5546
setPoolToPrice(
5647
factoryAddress: factoryAddress,
5748
tokenAAddress: wflowAddress,
5849
tokenBAddress: pyusd0Address,
5950
fee: univ3PoolFee,
60-
priceTokenBPerTokenA: 2.0,
51+
priceTokenBPerTokenA: 1.0,
6152
tokenABalanceSlot: wflowBalanceSlot,
6253
tokenBBalanceSlot: pyusd0BalanceSlot,
63-
signer: coaOwnerAccount
54+
signer: testAccount
6455
)
6556

66-
// Set COA WFLOW balance to 100.0 for the swap
67-
let flowAmount = 100.0
68-
let setBalanceRes = Test.executeTransaction(
69-
Test.Transaction(
70-
code: Test.readFile("transactions/set_coa_token_balance.cdc"),
71-
authorizers: [coaOwnerAccount.address],
72-
signers: [coaOwnerAccount],
73-
arguments: [wflowAddress, wflowBalanceSlot, flowAmount]
74-
)
75-
)
76-
Test.expect(setBalanceRes, Test.beSucceeded())
77-
57+
// Swap FLOW→PYUSD0 to create the Cadence-side PYUSD0 vault (needed for ERC4626 deposit test)
7858
let swapRes = Test.executeTransaction(
7959
Test.Transaction(
8060
code: Test.readFile("transactions/execute_univ3_swap.cdc"),
81-
authorizers: [coaOwnerAccount.address],
82-
signers: [coaOwnerAccount],
83-
arguments: [factoryAddress, routerAddress, quoterAddress, wflowAddress, pyusd0Address, univ3PoolFee, flowAmount]
61+
authorizers: [testAccount.address],
62+
signers: [testAccount],
63+
arguments: [factoryAddress, routerAddress, quoterAddress, wflowAddress, pyusd0Address, univ3PoolFee, 11000.0]
8464
)
8565
)
8666
Test.expect(swapRes, Test.beSucceeded())
8767

88-
let balanceRes = Test.executeScript(
89-
Test.readFile("scripts/get_bridged_vault_balance.cdc"),
90-
[coaOwnerAccount.address, pyusd0VaultTypeId]
91-
)
92-
Test.expect(balanceRes, Test.beSucceeded())
93-
let pyusd0Balance = (balanceRes.returnValue as? UFix64) ?? 0.0
94-
let expectedOut = flowAmount * 2.0
95-
let tolerance = expectedOut * forkedPercentTolerance * 0.01
96-
Test.assert(
97-
equalAmounts(a: pyusd0Balance, b: expectedOut, tolerance: tolerance),
98-
message: "PYUSD0 balance \(pyusd0Balance.toString()) not within tolerance of \(expectedOut.toString())"
99-
)
68+
snapshot = getCurrentBlockHeight()
69+
Test.commitBlock()
10070
}
10171

10272
access(all)
103-
fun test_ERC4626PriceSetAndDeposit() {
104-
setVaultSharePrice(
105-
vaultAddress: morphoVaultAddress,
106-
assetAddress: pyusd0Address,
107-
assetBalanceSlot: pyusd0BalanceSlot,
108-
totalSupplySlot: morphoVaultTotalSupplySlot,
109-
vaultTotalAssetsSlot: morphoVaultTotalAssetsSlot,
110-
baseAssets: 1000000000.0,
111-
priceMultiplier: 2.0,
112-
signer: coaOwnerAccount
113-
)
73+
fun test_UniswapV3PriceSetAndSwap() {
74+
let prices = [0.5, 1.0, 2.0, 3.0, 5.0]
75+
let flowAmount = 10000.0
76+
77+
for price in prices {
78+
Test.reset(to: snapshot)
79+
80+
setPoolToPrice(
81+
factoryAddress: factoryAddress,
82+
tokenAAddress: wflowAddress,
83+
tokenBAddress: pyusd0Address,
84+
fee: univ3PoolFee,
85+
priceTokenBPerTokenA: UFix128(price),
86+
tokenABalanceSlot: wflowBalanceSlot,
87+
tokenBBalanceSlot: pyusd0BalanceSlot,
88+
signer: testAccount
89+
)
11490

115-
// Set COA PYUSD0 balance to 1000000000.0 for the deposit
116-
let fundRes = Test.executeTransaction(
117-
Test.Transaction(
118-
code: Test.readFile("transactions/set_coa_token_balance.cdc"),
119-
authorizers: [coaOwnerAccount.address],
120-
signers: [coaOwnerAccount],
121-
arguments: [pyusd0Address, pyusd0BalanceSlot, 1000000000.0]
91+
let balanceBefore = getBalance(address: testAccount.address, vaultPublicPath: pyusd0PublicPath)!
92+
93+
let swapRes = Test.executeTransaction(
94+
Test.Transaction(
95+
code: Test.readFile("transactions/execute_univ3_swap.cdc"),
96+
authorizers: [testAccount.address],
97+
signers: [testAccount],
98+
arguments: [factoryAddress, routerAddress, quoterAddress, wflowAddress, pyusd0Address, univ3PoolFee, flowAmount]
99+
)
122100
)
123-
)
124-
Test.expect(fundRes, Test.beSucceeded())
101+
Test.expect(swapRes, Test.beSucceeded())
125102

126-
let amountIn = 1.0
127-
let depositRes = Test.executeTransaction(
128-
Test.Transaction(
129-
code: Test.readFile("transactions/execute_morpho_deposit.cdc"),
130-
authorizers: [coaOwnerAccount.address],
131-
signers: [coaOwnerAccount],
132-
arguments: [pyusd0VaultTypeId, morphoVaultAddress, amountIn]
103+
let balanceAfter = getBalance(address: testAccount.address, vaultPublicPath: pyusd0PublicPath)!
104+
let swapOutput = balanceAfter - balanceBefore
105+
let expectedOut = feeAdjustedPrice(UFix128(price), fee: univ3PoolFee, reverse: true) * UFix128(flowAmount)
106+
107+
// PYUSD0 has 6 decimals, so we need to use a tolerance of 1e-6
108+
let tolerance = 0.000001
109+
Test.assert(
110+
equalAmounts(a: UFix64(swapOutput), b: UFix64(expectedOut), tolerance: tolerance),
111+
message: "Pool price \(price): swap output \(swapOutput) not within \(tolerance) of expected \(expectedOut)"
133112
)
134-
)
135-
Test.expect(depositRes, Test.beSucceeded())
113+
log("Pool price \(price): expected=\(expectedOut) actual=\(swapOutput)")
114+
}
115+
}
136116

137-
let balanceRes = Test.executeScript(
138-
Test.readFile("scripts/get_bridged_vault_balance.cdc"),
139-
[coaOwnerAccount.address, fusdevVaultTypeId]
140-
)
141-
Test.expect(balanceRes, Test.beSucceeded())
142-
let fusdevBalance = (balanceRes.returnValue as? UFix64) ?? 0.0
143-
let expectedShares = 0.5
144-
let tolerance = expectedShares * forkedPercentTolerance * 0.01
145-
Test.assert(
146-
equalAmounts(a: fusdevBalance, b: expectedShares, tolerance: tolerance),
147-
message: "FUSDEV shares \(fusdevBalance.toString()) not within tolerance of \(expectedShares.toString())"
148-
)
117+
access(all)
118+
fun test_ERC4626PriceSetAndDeposit() {
119+
let multipliers = [0.5, 1.0, 2.0, 3.0, 5.0]
120+
let amountIn = 10000.0
121+
122+
for multiplier in multipliers {
123+
Test.reset(to: snapshot)
124+
125+
setVaultSharePrice(
126+
vaultAddress: morphoVaultAddress,
127+
assetAddress: pyusd0Address,
128+
assetBalanceSlot: pyusd0BalanceSlot,
129+
totalSupplySlot: morphoVaultTotalSupplySlot,
130+
vaultTotalAssetsSlot: morphoVaultTotalAssetsSlot,
131+
priceMultiplier: multiplier,
132+
signer: testAccount
133+
)
134+
135+
let depositRes = Test.executeTransaction(
136+
Test.Transaction(
137+
code: Test.readFile("transactions/execute_morpho_deposit.cdc"),
138+
authorizers: [testAccount.address],
139+
signers: [testAccount],
140+
arguments: [pyusd0VaultTypeId, morphoVaultAddress, amountIn]
141+
)
142+
)
143+
Test.expect(depositRes, Test.beSucceeded())
144+
145+
let fusdevBalance = getBalance(address: testAccount.address, vaultPublicPath: fusdevPublicPath)!
146+
let expectedShares = amountIn / multiplier
147+
148+
// FUSDEV has 18 decimals, so we need to use a tolerance of 1e-8 (Cadence UFix64 precision)
149+
let tolerance: UFix64 = 0.00000001
150+
Test.assert(
151+
equalAmounts(a: fusdevBalance, b: expectedShares, tolerance: tolerance),
152+
message: "Multiplier \(multiplier): FUSDEV shares \(fusdevBalance) not within \(tolerance) of expected \(expectedShares)"
153+
)
154+
log("Multiplier \(multiplier): expected=\(expectedShares) actual=\(fusdevBalance)")
155+
}
149156
}

0 commit comments

Comments
 (0)