Skip to content

Commit

Permalink
Merge pull request #5369 from onflow/gregor/evm/direct-call
Browse files Browse the repository at this point in the history
[EVM] Direct call hash calculation
  • Loading branch information
ramtinms authored Feb 20, 2024
2 parents a260653 + 5340029 commit 53dc73e
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 30 deletions.
51 changes: 35 additions & 16 deletions fvm/evm/emulator/emulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ func TestNativeTokenBridging(t *testing.T) {
testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) {
originalBalance := big.NewInt(10000)
testAccount := types.NewAddressFromString("test")
nonce := uint64(0)

t.Run("mint tokens to the first account", func(t *testing.T) {
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(types.NewDepositCall(testAccount, originalBalance))
res, err := blk.DirectCall(types.NewDepositCall(testAccount, originalBalance, nonce))
require.NoError(t, err)
require.Equal(t, defaultCtx.DirectCallBaseGasUsage, res.GasConsumed)
nonce += 1
})
})
})
Expand All @@ -64,9 +66,10 @@ func TestNativeTokenBridging(t *testing.T) {
})
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(types.NewWithdrawCall(testAccount, amount))
res, err := blk.DirectCall(types.NewWithdrawCall(testAccount, amount, nonce))
require.NoError(t, err)
require.Equal(t, defaultCtx.DirectCallBaseGasUsage, res.GasConsumed)
nonce += 1
})
})
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
Expand All @@ -89,14 +92,17 @@ func TestContractInteraction(t *testing.T) {
testContract := testutils.GetStorageTestContract(t)

testAccount := types.NewAddressFromString("test")
nonce := uint64(0)

amount := big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(gethParams.Ether))
amountToBeTransfered := big.NewInt(0).Mul(big.NewInt(100), big.NewInt(gethParams.Ether))

// fund test account
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(testAccount, amount))
_, err := blk.DirectCall(types.NewDepositCall(testAccount, amount, nonce))
require.NoError(t, err)
nonce += 1
})
})

Expand All @@ -110,10 +116,12 @@ func TestContractInteraction(t *testing.T) {
testAccount,
testContract.ByteCode,
math.MaxUint64,
amountToBeTransfered),
amountToBeTransfered,
nonce),
)
require.NoError(t, err)
contractAddr = res.DeployedContractAddress
nonce += 1
})
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
require.NotNil(t, contractAddr)
Expand Down Expand Up @@ -143,10 +151,12 @@ func TestContractInteraction(t *testing.T) {
testContract.MakeCallData(t, "store", num),
1_000_000,
big.NewInt(0), // this should be zero because the contract doesn't have receiver
nonce,
),
)
require.NoError(t, err)
require.GreaterOrEqual(t, res.GasConsumed, uint64(40_000))
nonce += 1
})
})

Expand All @@ -159,9 +169,11 @@ func TestContractInteraction(t *testing.T) {
testContract.MakeCallData(t, "retrieve"),
1_000_000,
big.NewInt(0), // this should be zero because the contract doesn't have receiver
nonce,
),
)
require.NoError(t, err)
nonce += 1

ret := new(big.Int).SetBytes(res.ReturnedValue)
require.Equal(t, num, ret)
Expand All @@ -178,9 +190,11 @@ func TestContractInteraction(t *testing.T) {
testContract.MakeCallData(t, "blockNumber"),
1_000_000,
big.NewInt(0), // this should be zero because the contract doesn't have receiver
nonce,
),
)
require.NoError(t, err)
nonce += 1

ret := new(big.Int).SetBytes(res.ReturnedValue)
require.Equal(t, blockNumber, ret)
Expand All @@ -193,7 +207,7 @@ func TestContractInteraction(t *testing.T) {
fAddr := account.Address()
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(fAddr, amount))
_, err := blk.DirectCall(types.NewDepositCall(fAddr, amount, account.Nonce()))
require.NoError(t, err)
})
})
Expand All @@ -204,7 +218,7 @@ func TestContractInteraction(t *testing.T) {
coinbaseOrgBalance := gethCommon.Big1
// small amount of money to create account
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(ctx.GasFeeCollector, coinbaseOrgBalance))
_, err := blk.DirectCall(types.NewDepositCall(ctx.GasFeeCollector, coinbaseOrgBalance, 0))
require.NoError(t, err)
})

Expand Down Expand Up @@ -240,7 +254,7 @@ func TestContractInteraction(t *testing.T) {
fAddr := account.Address()
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(fAddr, amount))
_, err := blk.DirectCall(types.NewDepositCall(fAddr, amount, account.Nonce()))
require.NoError(t, err)
})
})
Expand Down Expand Up @@ -301,7 +315,7 @@ func TestDeployAtFunctionality(t *testing.T) {
// fund test account
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(testAccount, amount))
_, err := blk.DirectCall(types.NewDepositCall(testAccount, amount, 0))
require.NoError(t, err)
})
})
Expand All @@ -316,7 +330,9 @@ func TestDeployAtFunctionality(t *testing.T) {
target,
testContract.ByteCode,
math.MaxUint64,
amountToBeTransfered),
amountToBeTransfered,
0,
),
)
require.NoError(t, err)
require.Equal(t, target, res.DeployedContractAddress)
Expand All @@ -343,7 +359,8 @@ func TestDeployAtFunctionality(t *testing.T) {
target,
testContract.ByteCode,
math.MaxUint64,
amountToBeTransfered),
amountToBeTransfered,
0),
)
require.NoError(t, err)
require.Equal(t, gethVM.ErrContractAddressCollision, res.VMError)
Expand All @@ -356,7 +373,8 @@ func TestDeployAtFunctionality(t *testing.T) {
types.Address{3, 4, 5},
testContract.ByteCode,
100,
new(big.Int)),
new(big.Int),
0),
)
require.NoError(t, err)
require.Equal(t, fmt.Errorf("out of gas"), res.VMError)
Expand All @@ -379,14 +397,14 @@ func TestTransfers(t *testing.T) {

RunWithNewEmulator(t, backend, rootAddr, func(em *emulator.Emulator) {
RunWithNewBlockView(t, em, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(testAccount1, amount))
_, err := blk.DirectCall(types.NewDepositCall(testAccount1, amount, 0))
require.NoError(t, err)
})
})

RunWithNewEmulator(t, backend, rootAddr, func(em *emulator.Emulator) {
RunWithNewBlockView(t, em, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewTransferCall(testAccount1, testAccount2, amountToBeTransfered))
_, err := blk.DirectCall(types.NewTransferCall(testAccount1, testAccount2, amountToBeTransfered, 0))
require.NoError(t, err)
})
})
Expand Down Expand Up @@ -414,13 +432,13 @@ func TestStorageNoSideEffect(t *testing.T) {
testAccount := types.NewAddressFromString("test")
amount := big.NewInt(10)
RunWithNewBlockView(t, em, func(blk types.BlockView) {
_, err = blk.DirectCall(types.NewDepositCall(testAccount, amount))
_, err = blk.DirectCall(types.NewDepositCall(testAccount, amount, 0))
require.NoError(t, err)
})

orgSize := backend.TotalStorageSize()
RunWithNewBlockView(t, em, func(blk types.BlockView) {
_, err = blk.DirectCall(types.NewDepositCall(testAccount, amount))
_, err = blk.DirectCall(types.NewDepositCall(testAccount, amount, 0))
require.NoError(t, err)
})
require.Equal(t, orgSize, backend.TotalStorageSize())
Expand All @@ -436,7 +454,7 @@ func TestCallingExtraPrecompiles(t *testing.T) {
testAccount := types.NewAddressFromString("test")
amount := big.NewInt(10_000_000)
RunWithNewBlockView(t, em, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(testAccount, amount))
_, err := blk.DirectCall(types.NewDepositCall(testAccount, amount, 0))
require.NoError(t, err)
})

Expand Down Expand Up @@ -469,6 +487,7 @@ func TestCallingExtraPrecompiles(t *testing.T) {
input,
1_000_000,
big.NewInt(0), // this should be zero because the contract doesn't have receiver
0,
),
)
require.NoError(t, err)
Expand Down
4 changes: 4 additions & 0 deletions fvm/evm/handler/addressAllocator.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ func (aa *AddressAllocator) COAFactoryAddress() types.Address {
return MakeCOAAddress(0)
}

func (aa *AddressAllocator) NativeTokenBridgeAddress() types.Address {
return MakePrecompileAddress(0)
}

// AllocateCOAAddress allocates an address for COA
func (aa *AddressAllocator) AllocateCOAAddress(uuid uint64) types.Address {
return MakeCOAAddress(uuid)
Expand Down
27 changes: 27 additions & 0 deletions fvm/evm/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,14 @@ func (h *ContractHandler) deployCOA(uuid uint64) (types.Address, error) {
}

factory := h.addressAllocator.COAFactoryAddress()
factoryAccount := h.AccountByAddress(factory, false)
call := types.NewDeployCallWithTargetAddress(
factory,
target,
coa.ContractBytes,
uint64(gaslimit),
new(big.Int),
factoryAccount.Nonce(),
)

ctx, err := h.getBlockContext()
Expand Down Expand Up @@ -388,6 +390,23 @@ func (a *Account) Address() types.Address {
return a.address
}

// Nonce returns the nonce of this account
//
// TODO: we might need to meter computation for read only operations as well
// currently the storage limits is enforced
func (a *Account) Nonce() uint64 {
ctx, err := a.fch.getBlockContext()
panicOnAnyError(err)

blk, err := a.fch.emulator.NewReadOnlyBlockView(ctx)
panicOnAnyError(err)

nonce, err := blk.NonceOf(a.address)
panicOnAnyError(err)

return nonce
}

// Balance returns the balance of this account
//
// TODO: we might need to meter computation for read only operations as well
Expand Down Expand Up @@ -462,9 +481,13 @@ func (a *Account) Deposit(v *types.FLOWTokenVault) {
}

func (a *Account) deposit(v *types.FLOWTokenVault) error {
bridge := a.fch.addressAllocator.NativeTokenBridgeAddress()
bridgeAccount := a.fch.AccountByAddress(bridge, false)

call := types.NewDepositCall(
a.address,
v.Balance(),
bridgeAccount.Nonce(),
)
ctx, err := a.precheck(false, types.GasLimit(call.GasLimit))
if err != nil {
Expand All @@ -486,6 +509,7 @@ func (a *Account) withdraw(b types.Balance) (*types.FLOWTokenVault, error) {
call := types.NewWithdrawCall(
a.address,
b,
a.Nonce(),
)

ctx, err := a.precheck(true, types.GasLimit(call.GasLimit))
Expand Down Expand Up @@ -517,6 +541,7 @@ func (a *Account) transfer(to types.Address, balance types.Balance) error {
a.address,
to,
balance,
a.Nonce(),
)
ctx, err := a.precheck(true, types.GasLimit(call.GasLimit))
if err != nil {
Expand Down Expand Up @@ -546,6 +571,7 @@ func (a *Account) deploy(code types.Code, gaslimit types.GasLimit, balance types
code,
uint64(gaslimit),
balance,
a.Nonce(),
)
res, err := a.fch.executeAndHandleCall(ctx, call, nil, false)
if err != nil {
Expand Down Expand Up @@ -575,6 +601,7 @@ func (a *Account) call(to types.Address, data types.Data, gaslimit types.GasLimi
data,
uint64(gaslimit),
balance,
a.Nonce(),
)

res, err := a.fch.executeAndHandleCall(ctx, call, nil, false)
Expand Down
21 changes: 20 additions & 1 deletion fvm/evm/handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,11 @@ func TestHandler_COA(t *testing.T) {

// Withdraw calls are only possible within FOA accounts
assertPanic(t, types.IsAUnAuthroizedMethodCallError, func() {
em := &testutils.TestEmulator{}
em := &testutils.TestEmulator{
NonceOfFunc: func(address types.Address) (uint64, error) {
return 0, nil
},
}

handler := handler.NewContractHandler(rootAddr, flowTokenAddress, bs, aa, backend, em)

Expand All @@ -466,6 +470,9 @@ func TestHandler_COA(t *testing.T) {
// test insufficient total supply error
assertPanic(t, types.IsAInsufficientTotalSupplyError, func() {
em := &testutils.TestEmulator{
NonceOfFunc: func(address types.Address) (uint64, error) {
return 0, nil
},
DirectCallFunc: func(call *types.DirectCall) (*types.Result, error) {
return &types.Result{}, nil
},
Expand All @@ -480,6 +487,9 @@ func TestHandler_COA(t *testing.T) {
// test non fatal error of emulator
assertPanic(t, isNotFatal, func() {
em := &testutils.TestEmulator{
NonceOfFunc: func(address types.Address) (uint64, error) {
return 0, nil
},
DirectCallFunc: func(call *types.DirectCall) (*types.Result, error) {
return &types.Result{}, types.NewEVMValidationError(fmt.Errorf("some sort of error"))
},
Expand All @@ -494,6 +504,9 @@ func TestHandler_COA(t *testing.T) {
// test fatal error of emulator
assertPanic(t, types.IsAFatalError, func() {
em := &testutils.TestEmulator{
NonceOfFunc: func(address types.Address) (uint64, error) {
return 0, nil
},
DirectCallFunc: func(call *types.DirectCall) (*types.Result, error) {
return &types.Result{}, types.NewFatalError(fmt.Errorf("some sort of fatal error"))
},
Expand Down Expand Up @@ -521,6 +534,9 @@ func TestHandler_COA(t *testing.T) {
// test non fatal error of emulator
assertPanic(t, isNotFatal, func() {
em := &testutils.TestEmulator{
NonceOfFunc: func(address types.Address) (uint64, error) {
return 0, nil
},
DirectCallFunc: func(call *types.DirectCall) (*types.Result, error) {
return &types.Result{}, fmt.Errorf("some sort of error")
},
Expand All @@ -535,6 +551,9 @@ func TestHandler_COA(t *testing.T) {
// test fatal error of emulator
assertPanic(t, types.IsAFatalError, func() {
em := &testutils.TestEmulator{
NonceOfFunc: func(address types.Address) (uint64, error) {
return 0, nil
},
DirectCallFunc: func(call *types.DirectCall) (*types.Result, error) {
return &types.Result{}, types.NewFatalError(fmt.Errorf("some sort of fatal error"))
},
Expand Down
8 changes: 8 additions & 0 deletions fvm/evm/stdlib/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ type testFlowAccount struct {
balance func() types.Balance
code func() types.Code
codeHash func() []byte
nonce func() uint64
transfer func(address types.Address, balance types.Balance)
deposit func(vault *types.FLOWTokenVault)
withdraw func(balance types.Balance) *types.FLOWTokenVault
Expand Down Expand Up @@ -107,6 +108,13 @@ func (t *testFlowAccount) CodeHash() []byte {
return t.codeHash()
}

func (t *testFlowAccount) Nonce() uint64 {
if t.nonce == nil {
return 0
}
return t.nonce()
}

func (t *testFlowAccount) Transfer(address types.Address, balance types.Balance) {
if t.transfer == nil {
panic("unexpected Transfer")
Expand Down
Loading

0 comments on commit 53dc73e

Please sign in to comment.