onflow/cadence-tools

[Test] Preview release - Constructing CompositeType using TestAccount fails

Opened this issue · 1 comments

Current Behavior

Attempting to construct a CompositeType from an identifier including the address of a test account results in an error. This can be seen in test runs in ci runs for flow-ft and flow-nft Cadence 1.0 updates where test cases cover events emitted by attempting to construct a CompositeType of the event.

The resulting error outputs as:

- FAIL: testContractInitializedEventEmitted
		Execution failed:
			error: internal error: could not find account with address 0000000000000005

Where the failing test case looks like:

access(all) fun testContractInitializedEventEmitted() {
    let typ = CompositeType(buildTypeIdentifier(admin, "ExampleNFT", "ContractInitialized"))!

    Test.assertEqual(1, blockchain.eventsOfType(typ).length)
}

// helper
access(all) fun buildTypeIdentifier(_ acct: Test.TestAccount, _ contractName: String, _ suffix: String): String {
    let addrString = acct.address.toString()
    return "A.".concat(addrString.slice(from: 2, upTo: addrString.length)).concat(".").concat(contractName).concat(".").concat(suffix)
}

Expected Behavior

I would expect the CompositeType to construct given a properly formatted identifier containing a valid contract address, name, and event type.

Steps To Reproduce

Either of the PRs in flow-ft & flow-nft have failing test cases that showcase this error, but the following simple case might help for easy illustration:

Say you have a project with the contract below:

// ./contracts/Foo.cdc
access(all) contract Foo {

    access(all) event ContractInitialized(blockHeight: UInt64)

    init() {
        emit ContractInitialized(blockHeight: getCurrentBlock().height)
    }
}

The following scripts:

// ./scripts/return_account_address.cdc
access(all) fun main(address: Address): Address {
    return getAccount(address).address
}

// ./scripts/has_foo_deployed.cdc
access(all) fun main(address: Address): Bool {
    return getAccount(address).contracts.get(name: "Foo") != nil
}

And the following test:

// ./tests/test_foo.cdc
import Test

access(all) let blockchain = Test.newEmulatorBlockchain()

access(all) let foo = blockchain.createAccount()

access(all) fun setup() {

    blockchain.useConfiguration(Test.Configuration(
            addresses: {
                "Foo": foo.address
            }
    ))

    let err = blockchain.deployContract(
        name: "Foo",
        code: Test.readFile("../contracts/Foo.cdc"),
        account: foo,
        arguments: [],
    )

    Test.expect(err, Test.beNil())
    if err != nil {
        panic(err!.message)
    }

}

access(all) fun testContractInitialized() {
    // Fails when attempting to construct this type
    let typ = CompositeType(buildTypeIdentifier(foo, "Foo", "ContractInitialized"))!

    Test.assertEqual(1, blockchain.eventsOfType(typ).length)
}

access(all) fun testContractInitializedAlt() {
    // Fails even when hardcoding address string
    let typ = CompositeType("A.0000000000000005.Foo.ContractInitialized")!

    Test.assertEqual(1, blockchain.eventsOfType(typ).length)
}

// Confirms the account is accessible in the test environment
access(all) fun testGetAccountAddress() {
    let code = Test.readFile("../scripts/return_account_address.cdc")
    let scriptResult = blockchain.executeScript(code, [foo.address])

    if let failureError = scriptResult.error {
        panic("Failed to execute the script because -:  ".concat(failureError.message))
    }

    let address = scriptResult.returnValue as! Address? ?? panic("Could not retrieve address")
    Test.assertEqual(foo.address, address)
}

// Confirms Foo is deployed to the account in question
access(all) fun testHasFooDeployed() {
    let code = Test.readFile("../scripts/has_foo_deployed.cdc")
    let scriptResult = blockchain.executeScript(code, [foo.address])

    if let failureError = scriptResult.error {
        panic("Failed to execute the script because -:  ".concat(failureError.message))
    }

    let hasFoo = scriptResult.returnValue as! Bool? ?? panic("Could not retrieve contract names from foo")
    Test.assertEqual(true, hasFoo)
}

access(all) fun buildTypeIdentifier(_ acct: Test.TestAccount, _ contractName: String, _ suffix: String): String {
    let addrString = acct.address.toString()
    return "A.".concat(addrString.slice(from: 2, upTo: addrString.length)).concat(".").concat(contractName).concat(".").concat(suffix)
}

Running the following command:

flow test --cover tests/test_foo.cdc

Shortened results shown below:

Test results: "tests/test_foo.cdc"
- FAIL: testContractInitialized
		Execution failed:
			error: internal error: could not find account with address 0000000000000005
- FAIL: testContractInitializedAlt
		Execution failed:
			error: internal error: could not find account with address 0000000000000005
- PASS: testGetAccountAddress
- PASS: testHasFooDeployed

Environment

- Cadence version: v1.5.0-stable-cadence.3
- Network: Emulator

I think what's happening here is the created account foo is on the blockchain, whereas the composite type creation CompositeType("A.0000000000000005.Foo.ContractInitialized") happens off-chain (in the test script). So the account is not accessible/available off-chain. This is not really a bug rather the expected behavior.

I believe the new changes done to unify the environment (and removing the blockchain concept) and the improvement done in this PR should allow this use-case now. Given the changes are already on master, they would be available with the next CLI preview build.
cc: @m-Peter