0

I am trying to write a test, but I am surprised by an unexpected behavior with the SimulatedBackend.

I am able to deploy the contract, and then call a function. The function runs an internal check, in sol:

    contract IDStore {
      mapping(uint256 => bytes) public ids;
    
      function add(uint256 id, bytes calldata key) external {
        require(ids[id].length == 0, "exists");
        ids[id] = key;
      }

I generated the contract bindings with abigen: abigen --abi ./store.json --pkg store --type IDStore --out ./idstore.abigen.go

Now in the test I am trying to add the same id and key twice, expecting it to fail, but it doesn't.

    backend := backends.NewSimulatedBackend(
        core.GenesisAlloc{
            myaddr: core.GenesisAccount{
                Balance: big.NewInt(9900000000000000000),
            },
        }, 80000000)
    
    defer backend.Close()
    addr := deployTestContract(require)
    store, err := NewIDStore(addr, backend)
    req.NoError(err)
    // nonce 1
    auth := getAuth(1)
    id1 := big.NewInt(99)
    tx, err := store.Add(auth, id1, keybytes)
    backend.Commit()
    // expected
    req.NoError(err)

    // nonce 2
    auth = getAuth(2)
    tx, err = store.Add(auth, id1, keybytes)
    backend.Commit()
    // expected
    req.Error(err)

However the test always returns with An error is expected but got nil.

Am I not seeing something simple? Or does maybe contract storage work differently with SimulatedBackends somehow? Am I using it wrong? Does it not work at all with contract calls? Or is even the sol function bad? I am not responsible for the solidity part.

1 Answer 1

1

Simulating a backend should be the main goal of SimulatedBackends so that I would say yes.

While I'm not an expert with the SimulatedBackends in geth code, I assume errors and failed transactions are treated normally as separate concepts. Hence, you must check the receipts to verify if a transaction failed.

It seems to me the code is testing if the sending of the transaction was successful, and so it was correct in both cases.

Now that the tx is correctly sent and the block is minted with .Commit(), you should check the receipt to get the result. There, you should be able to see the second transaction has failed.

Here, you can see a full guide on using a simulated client. It's based on geth 1.8.10 (2018), but I don't think that part changed too much during the years.

In that guide, you can get the receipt and check the result with code like this:

receipt, err := client.TransactionReceipt(context.Background(), signedTx.Hash())
if err != nil {
  log.Fatal(err)
}
if receipt == nil {
  log.Fatal("receipt is nil. Forgot to commit?")
}

fmt.Printf("status: %v\n", receipt.Status) // status: 1
3
  • That makes a lot of sense! Still, after the 2nd tx, my test fails at require.Equal(uint64(0x0), receipt.Status) - in other words I am expecting the Status to be a failure, but it returns 1, meaning the tx succeeded, which indeed is surprising. Feb 10 at 2:21
  • Nit: changed the code to use directly require.Equal(types.ReceiptStatusFailed, receipt.Status) Feb 10 at 2:33
  • Weird. Looking at the code the backends.NewSimulatedBacked is deprecated now, so maybe it's not a good choice anymore. They suggest using simulated.Backend from package github.com/ethereum/go-ethereum/ethclient/simulated instead (github.com/ethereum/go-ethereum/blob/master/ethclient/simulated/…) Feb 10 at 4:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.