polkadot-evm / frontier

Ethereum compatibility layer for Substrate.
Apache License 2.0
574 stars 487 forks source link

Contract nonce resetting to 0 on new runner when deploying with value within contract. #286

Open jnaviask opened 3 years ago

jnaviask commented 3 years ago

Description

In certain cases, when calling a method on a contract that spawns another contract within solidity, the nonce of the creating contract will reliably be reset to 0.

Steps to Reproduce

Consider the following solidity contract:

pragma solidity ^0.5.0;

contract SubContract {
  constructor() public payable { }
  function getAddress() external view returns (address ownAddress) {
    return address(this);
  }

  function getValue() external view returns (uint) {
    return address(this).balance;
  }
}

contract CreateContract {
  address public deployed;

  constructor() public { }

  function spawn() external returns (SubContract subAddress) {
    SubContract result = new SubContract();
    deployed = address(result);
    return result;
  }

  function spawnWithValue() external payable returns (SubContract subAddress) {
    SubContract result = (new SubContract).value(msg.value)();
    deployed = address(result);
    return result;
  }
}

The following test case works:

const contract = require("@truffle/contract");
const { assert } = require("chai");
const CreateContract = require('../build/contracts/CreateContract.json');

let Create = contract({
  abi: CreateContract.abi,
  unlinked_binary: CreateContract.bytecode,
});
Create.setProvider(web3.currentProvider);

let account = 'some funded account';
let c = await Create.new({ from: account });
let startNonce = await web3.eth.getTransactionCount(c.address);
let receipt = await c.spawn({ from: account });

// check nonce
let nonce = await web3.eth.getTransactionCount(c.address);
assert.equal(nonce, startNonce + 1, 'contract nonce should increment');

But replacing c.spawn with c.spawnWithValue set to some reasonable amount causes c's nonce to become 0 instead of 2 as expected.

JoshOrndorff commented 3 years ago

Is this related to the existential deposit?

https://github.com/paritytech/frontier/blob/52cfbc4e88ad5c36db68891f20a6540613b81141/template/runtime/src/lib.rs#L230

jnaviask commented 3 years ago

Is this related to the existential deposit?

https://github.com/paritytech/frontier/blob/52cfbc4e88ad5c36db68891f20a6540613b81141/template/runtime/src/lib.rs#L230

Still investigating... how would this cause a contract's nonce to reset?

JoshOrndorff commented 3 years ago

It may not be related, but I know that when people have problems with nonces resetting in Substrate chains it is often due to accounts getting "reaped".

For normal substrate accounts, if your balance goes below this amount, the account is "reaped" and the nonce reset to zero. The point is that we shouldn't waste state space on accounts that just have "dust" values.

I don't know how or whether this relates to accounts inside the evm. But the fact that the resetting happens only when you send value was a further clue that made me suggest it.

An easy thing to test would be setting this value to zero and seeing whether it solves your problem. If it does then you ca nstart to explore why.

And again I could be 100% wrong about this, I just wanted to share the idea.

jnaviask commented 3 years ago

This issue still persists, and your guess was correct @JoshOrndorff, the contract's underlying account is getting reaped. I see system:KilledAccount emitted when the contract spawns another with value.