Skip to content

Commit b19b3f9

Browse files
authored
Update to latest Fork Testing Changes (#2171)
* Update to latest test framework * Update test framework * Update dependencies * Update to latest dependencies & new pragma API * Add metrics for fork testing (#2186)
1 parent f4e17a6 commit b19b3f9

4 files changed

Lines changed: 219 additions & 64 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// The #test_fork pragma configures this test to run against a snapshot of mainnet state.
2+
// This allows testing against real deployed contracts and production data.
3+
#test_fork(network: "mainnet")
4+
5+
import Test
6+
import "DeFiActions"
7+
8+
// Executes a minimal swap from FLOW -> stFlow using IncrementFi on a forked mainnet.
9+
// Withdraws a tiny amount from a known FLOW holder and swaps via IncrementFi router.
10+
access(all) fun testIncrementFi_SwapOnFork() {
11+
let HOLDER = Test.getAccount(0x42a06f24a1049154)
12+
let AMOUNT_IN: UFix64 = 0.001
13+
14+
let txCode = Test.readFile("../transactions/incrementfi_swap_flow.cdc")
15+
16+
let res = Test.executeTransaction(
17+
Test.Transaction(
18+
code: txCode,
19+
authorizers: [HOLDER.address],
20+
signers: [HOLDER],
21+
arguments: [AMOUNT_IN]
22+
)
23+
)
24+
25+
Test.expect(res, Test.beSucceeded())
26+
27+
// Log all swap events emitted during the transaction
28+
let swapEvents = Test.eventsOfType(Type<DeFiActions.Swapped>())
29+
log("Swap events:")
30+
for event in swapEvents {
31+
log(event)
32+
}
33+
}
34+
35+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import "DeFiActions"
2+
import "FungibleToken"
3+
import "FlowToken"
4+
import "IncrementFiSwapConnectors"
5+
6+
transaction(amountIn: UFix64) {
7+
prepare(acct: auth(BorrowValue) &Account) {
8+
let opID = DeFiActions.createUniqueIdentifier()
9+
let swapper = IncrementFiSwapConnectors.Swapper(
10+
path: ["A.1654653399040a61.FlowToken", "A.d6f80565193ad727.stFlowToken"],
11+
inVault: Type<@FlowToken.Vault>(),
12+
outVault: CompositeType("A.d6f80565193ad727.stFlowToken.Vault")!,
13+
uniqueID: opID
14+
)
15+
let flowVaultRef = acct.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
16+
?? panic("Holder missing FlowToken vault")
17+
let payment <- flowVaultRef.withdraw(amount: amountIn)
18+
let out <- swapper.swap(quote: nil, inVault: <-payment)
19+
assert(out.balance > 0.0, message: "Expected positive output")
20+
destroy out
21+
}
22+
}
23+
24+

internal/super/projecttypes.go

Lines changed: 14 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ func getProjectTypeConfigs() map[ProjectType]*ProjectTypeConfig {
124124
CoreContracts: []string{"FungibleToken", "FlowToken"},
125125
CustomDependencies: []flowkitConfig.Dependency{
126126
{
127-
Name: "DeFiActionsMathUtils",
127+
Name: "DeFiActions",
128128
Source: flowkitConfig.Source{
129129
NetworkName: flowkitConfig.MainnetNetwork.Name,
130130
Address: flowsdk.HexToAddress("92195d814edf9cb0"),
131-
ContractName: "DeFiActionsMathUtils",
131+
ContractName: "DeFiActions",
132132
},
133133
Aliases: flowkitConfig.Aliases{
134134
{
@@ -146,48 +146,13 @@ func getProjectTypeConfigs() map[ProjectType]*ProjectTypeConfig {
146146
},
147147
},
148148
{
149-
Name: "DeFiActionsUtils",
149+
Name: "IncrementFiSwapConnectors",
150150
Source: flowkitConfig.Source{
151151
NetworkName: flowkitConfig.MainnetNetwork.Name,
152-
Address: flowsdk.HexToAddress("92195d814edf9cb0"),
153-
ContractName: "DeFiActionsUtils",
154-
},
155-
Aliases: flowkitConfig.Aliases{
156-
{
157-
Network: "mainnet",
158-
Address: flowsdk.HexToAddress("92195d814edf9cb0"),
159-
},
160-
{
161-
Network: "testnet",
162-
Address: flowsdk.HexToAddress("4c2ff9dd03ab442f"),
163-
},
164-
{
165-
Network: "testing",
166-
Address: flowsdk.HexToAddress("0000000000000006"),
167-
},
168-
},
169-
},
170-
{
171-
Name: "DeFiActions",
172-
Source: flowkitConfig.Source{
173-
NetworkName: flowkitConfig.MainnetNetwork.Name,
174-
Address: flowsdk.HexToAddress("92195d814edf9cb0"),
175-
ContractName: "DeFiActions",
176-
},
177-
Aliases: flowkitConfig.Aliases{
178-
{
179-
Network: "mainnet",
180-
Address: flowsdk.HexToAddress("92195d814edf9cb0"),
181-
},
182-
{
183-
Network: "testnet",
184-
Address: flowsdk.HexToAddress("4c2ff9dd03ab442f"),
185-
},
186-
{
187-
Network: "testing",
188-
Address: flowsdk.HexToAddress("0000000000000006"),
189-
},
152+
Address: flowsdk.HexToAddress("efa9bd7d1b17f1ed"),
153+
ContractName: "IncrementFiSwapConnectors",
190154
},
155+
Aliases: flowkitConfig.Aliases{{Network: "mainnet", Address: flowsdk.HexToAddress("efa9bd7d1b17f1ed")}},
191156
},
192157
},
193158
ContractNames: []string{"DeFiActionsMathUtils", "DeFiActionsUtils", "DeFiActions", "ExampleConnectors"},
@@ -320,6 +285,14 @@ func getProjectTemplates(projectType ProjectType, targetDir string, state *flowk
320285
Name: "DepositViaSink",
321286
TemplatePath: "transaction_deposit_via_sink.cdc.tmpl",
322287
},
288+
generator.TransactionTemplate{
289+
Name: "incrementfi_swap_flow",
290+
TemplatePath: "transaction_incrementfi_swap_flow.cdc.tmpl",
291+
},
292+
generator.TestTemplate{
293+
Name: "incrementfi_swap_on_fork",
294+
TemplatePath: "test_incrementfi_swap_on_fork.cdc.tmpl",
295+
},
323296
generator.FileTemplate{
324297
TemplatePath: "README_defi_actions.md.tmpl",
325298
TargetPath: getReadmeFileName(targetDir),

internal/test/test.go

Lines changed: 146 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ import (
2626
"os"
2727
"path/filepath"
2828
"regexp"
29+
goRuntime "runtime"
2930
"strings"
3031

3132
cdcTests "github.com/onflow/cadence-tools/test"
3233
"github.com/onflow/cadence/common"
3334
"github.com/onflow/cadence/runtime"
35+
flowGo "github.com/onflow/flow-go/model/flow"
3436
"github.com/rs/zerolog"
3537
"github.com/spf13/cobra"
3638

@@ -40,6 +42,7 @@ import (
4042

4143
"github.com/onflow/flow-cli/common/branding"
4244

45+
"github.com/onflow/flow-cli/build"
4346
"github.com/onflow/flow-cli/internal/command"
4447
"github.com/onflow/flow-cli/internal/util"
4548
)
@@ -183,7 +186,37 @@ func testCode(
183186
flags flagsTests,
184187
) (*result, error) {
185188
logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger()
186-
runner := cdcTests.NewTestRunner().WithLogger(logger)
189+
190+
// Track network resolutions per file for pragma-based fork detection
191+
// Map: filename -> resolved network name
192+
fileNetworkResolutions := make(map[string]string)
193+
var currentTestFile string
194+
195+
// Resolve network labels using flow.json state
196+
resolveNetworkFromState := func(label string) (string, bool) {
197+
network, err := state.Networks().ByName(strings.ToLower(strings.TrimSpace(label)))
198+
if err != nil || network == nil {
199+
return "", false
200+
}
201+
if strings.TrimSpace(network.Host) == "" {
202+
return "", false
203+
}
204+
205+
// Track network resolution for current test file (indicates pragma-based fork usage)
206+
// Only track if it's not the default "testing" network
207+
normalizedLabel := strings.ToLower(strings.TrimSpace(label))
208+
if currentTestFile != "" && normalizedLabel != "testing" {
209+
if _, exists := fileNetworkResolutions[currentTestFile]; !exists {
210+
fileNetworkResolutions[currentTestFile] = normalizedLabel
211+
}
212+
}
213+
214+
return network.Host, true
215+
}
216+
217+
runner := cdcTests.NewTestRunner().
218+
WithLogger(logger).
219+
WithNetworkResolver(resolveNetworkFromState)
187220

188221
// Configure fork mode if requested
189222
var effectiveForkHost string
@@ -204,20 +237,42 @@ func testCode(
204237
}
205238
}
206239

240+
// Determine network label (used by resolver/addresses); default to testing
241+
networkLabel := "testing"
242+
if strings.TrimSpace(flags.Fork) != "" {
243+
networkLabel = strings.ToLower(flags.Fork)
244+
}
245+
207246
// If fork mode is enabled, query the host to get chain ID
247+
var forkCfg *cdcTests.ForkConfig
208248
if effectiveForkHost != "" {
209249
forkChainID, err := util.GetChainIDFromHost(effectiveForkHost)
210250
if err != nil {
211251
return nil, fmt.Errorf("failed to get chain ID from fork host %q: %w", effectiveForkHost, err)
212252
}
213253

214-
runner = runner.WithFork(cdcTests.ForkConfig{
254+
cfg := cdcTests.ForkConfig{
215255
ForkHost: effectiveForkHost,
216256
ChainID: forkChainID,
217257
ForkHeight: flags.ForkHeight,
218-
})
258+
}
259+
forkCfg = &cfg
260+
runner = runner.WithFork(cfg)
261+
262+
// Map chain ID to a sensible network label if not provided explicitly
263+
if strings.TrimSpace(flags.Fork) == "" {
264+
switch forkChainID {
265+
case flowGo.Mainnet:
266+
networkLabel = "mainnet"
267+
case flowGo.Testnet:
268+
networkLabel = "testnet"
269+
}
270+
}
219271
}
220272

273+
// Apply the network label on the base runner now that it is known
274+
runner = runner.WithNetworkLabel(networkLabel)
275+
221276
var coverageReport *runtime.CoverageReport
222277
if flags.Cover {
223278
coverageReport = state.CreateCoverageReport("testing")
@@ -244,30 +299,43 @@ func testCode(
244299
runner = runner.WithRandomSeed(seed)
245300
}
246301

247-
contractsConfig := *state.Contracts()
248-
contracts := make(map[string]common.Address, len(contractsConfig))
249-
// Choose alias network: default to "testing", but in fork mode use selected chain (mainnet/testnet)
250-
aliasNetwork := "testing"
251-
if strings.TrimSpace(flags.Fork) != "" {
252-
aliasNetwork = strings.ToLower(flags.Fork)
253-
}
254-
for _, contract := range contractsConfig {
255-
alias := contract.Aliases.ByNetwork(aliasNetwork)
256-
if alias != nil {
257-
contracts[contract.Name] = common.Address(alias.Address)
258-
}
259-
}
260-
261302
testResults := make(map[string]cdcTests.Results, 0)
262303
exitCode := 0
263304
for scriptPath, code := range testFiles {
264-
runner := runner.
305+
// Set current test file for network resolution tracking
306+
currentTestFile = scriptPath
307+
308+
fileRunner := runner.
265309
WithImportResolver(importResolver(scriptPath, state)).
266310
WithFileResolver(fileResolver(scriptPath, state)).
267-
WithContracts(contracts)
311+
WithContractAddressResolver(func(network string, contractName string) (common.Address, error) {
312+
// Build name -> contract map once per file run
313+
contractsByName := make(map[string]config.Contract)
314+
for _, c := range *state.Contracts() {
315+
contractsByName[c.Name] = c
316+
}
317+
318+
contract, exists := contractsByName[contractName]
319+
if !exists {
320+
return common.Address{}, fmt.Errorf("contract not found: %s", contractName)
321+
}
322+
323+
alias := contract.Aliases.ByNetwork(network)
324+
if alias != nil {
325+
return common.Address(alias.Address), nil
326+
}
327+
328+
return common.Address{}, fmt.Errorf("no address for contract %s on network %s", contractName, network)
329+
})
330+
331+
// Ensure the file runner has the correct network label and fork config
332+
fileRunner = fileRunner.WithNetworkLabel(networkLabel)
333+
if forkCfg != nil {
334+
fileRunner = fileRunner.WithFork(*forkCfg)
335+
}
268336

269337
if flags.Name != "" {
270-
testFunctions, err := runner.GetTests(string(code))
338+
testFunctions, err := fileRunner.GetTests(string(code))
271339
if err != nil {
272340
return nil, err
273341
}
@@ -277,14 +345,14 @@ func testCode(
277345
continue
278346
}
279347

280-
result, err := runner.RunTest(string(code), flags.Name)
348+
result, err := fileRunner.RunTest(string(code), flags.Name)
281349
if err != nil {
282350
return nil, err
283351
}
284352
testResults[scriptPath] = []cdcTests.Result{*result}
285353
}
286354
} else {
287-
results, err := runner.RunTests(string(code))
355+
results, err := fileRunner.RunTests(string(code))
288356
if err != nil {
289357
return nil, err
290358
}
@@ -297,6 +365,61 @@ func testCode(
297365
break
298366
}
299367
}
368+
369+
// Clear current test file after processing
370+
currentTestFile = ""
371+
}
372+
373+
// Track fork test usage metrics - aggregate into single event
374+
hasPragmaFiles := len(fileNetworkResolutions) > 0
375+
hasStaticFork := forkCfg != nil
376+
377+
if hasPragmaFiles || hasStaticFork {
378+
// Determine primary fork source
379+
forkSource := "none"
380+
var primaryNetwork string
381+
var chainID string
382+
hasHeight := false
383+
384+
if hasPragmaFiles {
385+
// Pragma takes priority - collect unique networks
386+
forkSource = "pragma"
387+
networkSet := make(map[string]bool)
388+
for _, network := range fileNetworkResolutions {
389+
networkSet[network] = true
390+
}
391+
// Use first resolved network as primary (for single-value tracking)
392+
for _, network := range fileNetworkResolutions {
393+
primaryNetwork = network
394+
break
395+
}
396+
// If multiple networks, note that in source
397+
if len(networkSet) > 1 {
398+
forkSource = "pragma-mixed"
399+
}
400+
} else if hasStaticFork {
401+
// Static flags
402+
if flags.ForkHost != "" {
403+
forkSource = "fork-host-flag"
404+
} else if flags.Fork != "" {
405+
forkSource = "fork-flag"
406+
}
407+
primaryNetwork = networkLabel
408+
chainID = forkCfg.ChainID.String()
409+
hasHeight = forkCfg.ForkHeight > 0
410+
}
411+
412+
command.TrackEvent("test-fork", map[string]any{
413+
"fork_source": forkSource,
414+
"network": primaryNetwork,
415+
"chain_id": chainID,
416+
"has_height": hasHeight,
417+
"pragma_files": len(fileNetworkResolutions),
418+
"total_files": len(testFiles),
419+
"version": build.Semver(),
420+
"os": goRuntime.GOOS,
421+
"ci": os.Getenv("CI") != "",
422+
})
300423
}
301424

302425
return &result{
@@ -313,7 +436,7 @@ func importResolver(scriptPath string, state *flowkit.State) cdcTests.ImportReso
313436
contracts[contract.Name] = contract
314437
}
315438

316-
return func(location common.Location) (string, error) {
439+
return func(network string, location common.Location) (string, error) {
317440
contract := config.Contract{}
318441

319442
switch location := location.(type) {

0 commit comments

Comments
 (0)