Skip to content

Commit

Permalink
Merge pull request #6295 from onflow/ramtin/evm-patch-storage-root-issue
Browse files Browse the repository at this point in the history
[Flow EVM] Patch storage root value issue
  • Loading branch information
j1010001 authored Aug 7, 2024
2 parents 2bd1062 + 1a5b953 commit 698a8d0
Show file tree
Hide file tree
Showing 11 changed files with 519 additions and 2 deletions.
261 changes: 261 additions & 0 deletions fvm/evm/emulator/emulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/onflow/flow-go/fvm/evm/debug"
"github.com/onflow/flow-go/fvm/evm/emulator"
"github.com/onflow/flow-go/fvm/evm/testutils"
"github.com/onflow/flow-go/fvm/evm/testutils/contracts"
"github.com/onflow/flow-go/fvm/evm/types"
"github.com/onflow/flow-go/model/flow"

Expand Down Expand Up @@ -746,6 +747,266 @@ func TestSelfdestruct(t *testing.T) {
})
}

// test factory patterns
func TestFactoryPatterns(t *testing.T) {
testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) {
testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) {

var factoryAddress types.Address
factoryContract := testutils.GetFactoryTestContract(t)
factoryDeployer := types.NewAddressFromString("factoryDeployer")
factoryDeployerBalance := big.NewInt(0).Mul(big.NewInt(1000), big.NewInt(gethParams.Ether))
factoryBalance := big.NewInt(0).Mul(big.NewInt(100), big.NewInt(gethParams.Ether))

// setup the test with funded account and deploying a factory contract.
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
t.Run("test deploying factory", func(t *testing.T) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(types.NewDepositCall(types.EmptyAddress, factoryDeployer, factoryDeployerBalance, 0))
require.NoError(t, err)
requireSuccessfulExecution(t, err, res)
})
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(
types.NewDeployCall(
factoryDeployer,
factoryContract.ByteCode,
math.MaxUint64,
factoryBalance,
0),
)
requireSuccessfulExecution(t, err, res)
require.NotNil(t, res.DeployedContractAddress)
factoryAddress = *res.DeployedContractAddress
})
})

t.Run("test self-destruct to a contract that is already deployed",
func(t *testing.T) {
// first test call deploy and try self destruct later
var deployed types.Address
RunWithNewBlockView(t, env, func(blk types.BlockView) {
salt := [32]byte{1}
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
factoryAddress,
factoryContract.MakeCallData(t, "deploy", salt),
250_000,
big.NewInt(0),
0,
),
)
requireSuccessfulExecution(t, err, res)

// decode address, data is left padded
deployed = types.Address(gethCommon.BytesToAddress(res.ReturnedData[12:]))
})

// deposit money into the contract
depositedBalance := big.NewInt(200)
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(types.NewDepositCall(
types.EmptyAddress,
deployed,
depositedBalance, 1))
require.NoError(t, err)
requireSuccessfulExecution(t, err, res)
})
// check balance of contract
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
bal, err := blk.BalanceOf(deployed)
require.NoError(t, err)
require.Equal(t, depositedBalance, bal)
})

// set storage on deployed contract
storedValue := big.NewInt(12)
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
types.Address(deployed),
testutils.MakeCallData(t,
contracts.FactoryDeployableContractABIJSON,
"set",
storedValue),
120_000,
big.NewInt(0),
0,
),
)
requireSuccessfulExecution(t, err, res)
})

// call self-destruct on the deployed
refundAddress := testutils.RandomAddress(t)
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
types.Address(deployed),
testutils.MakeCallData(t,
contracts.FactoryDeployableContractABIJSON,
"destroy",
refundAddress),
120_000,
big.NewInt(0),
0,
),
)
requireSuccessfulExecution(t, err, res)
})

// check balance of the refund address and the contract
// balance should be transferred to the refund address
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
bal, err := blk.BalanceOf(refundAddress)
require.NoError(t, err)
require.Equal(t, depositedBalance, bal)

bal, err = blk.BalanceOf(deployed)
require.NoError(t, err)
require.True(t, types.BalancesAreEqual(big.NewInt(0), bal))
})

// data should still be there
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
types.Address(deployed),
testutils.MakeCallData(t,
contracts.FactoryDeployableContractABIJSON,
"get"),
120_000,
big.NewInt(0),
0,
),
)
requireSuccessfulExecution(t, err, res)
require.Equal(t, storedValue, new(big.Int).SetBytes(res.ReturnedData))
})
})

t.Run("test deploy and destroy in a single call",
func(t *testing.T) {
var originalFactoryBalance types.Balance
var err error
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
originalFactoryBalance, err = blk.BalanceOf(factoryAddress)
require.NoError(t, err)
})

storedValue := big.NewInt(100)
RunWithNewBlockView(t, env, func(blk types.BlockView) {
salt := [32]byte{2}
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
factoryAddress,
factoryContract.MakeCallData(t,
"deployAndDestroy",
salt,
storedValue),
400_000,
big.NewInt(0),
0,
),
)
requireSuccessfulExecution(t, err, res)
})

// no balance change on the caller
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
ret, err := blk.BalanceOf(factoryAddress)
require.NoError(t, err)
require.True(t, types.BalancesAreEqual(originalFactoryBalance, ret))
})
})
t.Run("test deposit first to an address and then deploy in a single call",
func(t *testing.T) {
storedValue := big.NewInt(120)
balance := big.NewInt(80)
var deployed types.Address
RunWithNewBlockView(t, env, func(blk types.BlockView) {
salt := [32]byte{3}
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
factoryAddress,
factoryContract.MakeCallData(t, "depositAndDeploy", salt, balance, storedValue),
250_000,
big.NewInt(0),
1,
),
)
requireSuccessfulExecution(t, err, res)
// decode address, data is left padded
deployed = types.Address(gethCommon.BytesToAddress(res.ReturnedData[12:]))
})

// no balance change on the caller
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
ret, err := blk.BalanceOf(deployed)
require.NoError(t, err)
require.True(t, types.BalancesAreEqual(balance, ret))
})

// check stored data
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
types.Address(deployed),
testutils.MakeCallData(t,
contracts.FactoryDeployableContractABIJSON,
"get"),
120_000,
big.NewInt(0),
0,
),
)
requireSuccessfulExecution(t, err, res)
require.Equal(t, storedValue, new(big.Int).SetBytes(res.ReturnedData))
})
})

t.Run("test deposit, deploy, destroy in a single call",
func(t *testing.T) {
var originalFactoryBalance types.Balance
var err error
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
originalFactoryBalance, err = blk.BalanceOf(factoryAddress)
require.NoError(t, err)
})

RunWithNewBlockView(t, env, func(blk types.BlockView) {
salt := [32]byte{4}
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
factoryAddress,
factoryContract.MakeCallData(t, "depositDeployAndDestroy", salt, big.NewInt(100), big.NewInt(10)),
250_000,
big.NewInt(0),
1,
),
)
requireSuccessfulExecution(t, err, res)
})
// no balance change on the caller
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
ret, err := blk.BalanceOf(factoryAddress)
require.NoError(t, err)
require.True(t, types.BalancesAreEqual(originalFactoryBalance, ret))
})
})
})
})
})
}

func TestTransfers(t *testing.T) {
testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) {
testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) {
Expand Down
2 changes: 1 addition & 1 deletion fvm/evm/emulator/state/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func (v *BaseView) GetStorageRoot(addr common.Address) (common.Hash, error) {
}

// account is EOA
if account.CollectionID == nil {
if len(account.CollectionID) == 0 {
return gethTypes.EmptyRootHash, nil
}

Expand Down
19 changes: 19 additions & 0 deletions fvm/evm/emulator/state/delta.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ func (d *DeltaView) Exist(addr gethCommon.Address) (bool, error) {
if found {
return true, nil
}
// if is address is dirty it exists
_, found = d.dirtyAddresses[addr]
if found {
return true, nil
}
return d.parent.Exist(addr)
}

Expand Down Expand Up @@ -407,6 +412,20 @@ func (d *DeltaView) SetState(sk types.SlotAddress, value gethCommon.Hash) error
// the way back to the base view. This means that the state root that is returned
// ignores the updates to slots during the transaction.
func (d *DeltaView) GetStorageRoot(addr gethCommon.Address) (gethCommon.Hash, error) {
exist, err := d.Exist(addr)
if err != nil {
return gethCommon.Hash{}, err
}
if exist {
code, err := d.GetCode(addr)
if err != nil {
return gethCommon.Hash{}, err
}
if len(code) == 0 {
return gethTypes.EmptyRootHash, nil
}
// else go back to the parent
}
// go back to parents (until we reach the base view)
// Note that if storage is updated in deltas but not
// committed the expected behavior is to return the root in the base view.
Expand Down
2 changes: 1 addition & 1 deletion fvm/evm/emulator/state/stateDB.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ func (db *StateDB) Finalize() error {

// Prepare is a high level logic that sadly is considered to be part of the
// stateDB interface and not on the layers above.
// based on parameters that are passed it updates access lists
// based on parameters that are passed it updates access-lists
func (db *StateDB) Prepare(rules gethParams.Rules, sender, coinbase gethCommon.Address, dest *gethCommon.Address, precompiles []gethCommon.Address, txAccesses gethTypes.AccessList) {
if rules.IsBerlin {
db.AddAddressToAccessList(sender)
Expand Down
9 changes: 9 additions & 0 deletions fvm/evm/emulator/state/stateDB_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,15 @@ func TestStateDB(t *testing.T) {
require.NoError(t, db.Error())
require.Equal(t, gethTypes.EmptyRootHash, root)

db.AddBalance(addr1, uint256.NewInt(100), tracing.BalanceChangeTouchAccount)
require.NoError(t, db.Error())
err = db.Commit(true)
require.NoError(t, err)

root = db.GetStorageRoot(addr1)
require.NoError(t, db.Error())
require.Equal(t, gethTypes.EmptyRootHash, root)

// add slots to the account
key := testutils.RandomCommonHash(t)
value := testutils.RandomCommonHash(t)
Expand Down
7 changes: 7 additions & 0 deletions fvm/evm/testutils/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ func GetDummyKittyTestContract(t testing.TB) *TestContract {
}
}

func GetFactoryTestContract(t testing.TB) *TestContract {
return &TestContract{
ABI: contracts.FactoryContractABIJSON,
ByteCode: contracts.FactoryContractBytes,
}
}

func RunWithDeployedContract(t testing.TB, tc *TestContract, led atree.Ledger, flowEVMRootAddress flow.Address, f func(*TestContract)) {
DeployContract(t, RandomAddress(t), tc, led, flowEVMRootAddress)
f(tc)
Expand Down
11 changes: 11 additions & 0 deletions fvm/evm/testutils/contracts/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,14 @@ var DummyKittyContractBytes, _ = hex.DecodeString(dummyKittyContractBytesInHex)

//go:embed dummy_kitty_abi.json
var DummyKittyContractABIJSON string

//go:embed factory_bytes.hex
var factoryContractBytesInHex string

var FactoryContractBytes, _ = hex.DecodeString(factoryContractBytesInHex)

//go:embed factory_abi.json
var FactoryContractABIJSON string

//go:embed factory_deployable_abi.json
var FactoryDeployableContractABIJSON string
Loading

0 comments on commit 698a8d0

Please sign in to comment.