Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Flow EVM] populate PREVRANDAO with a random value from the FVM random generator #5281

Merged
merged 10 commits into from
Jan 26, 2024
8 changes: 8 additions & 0 deletions fvm/evm/emulator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,11 @@ func WithExtraPrecompiles(precompiles []types.Precompile) Option {
return c
}
}

// WithRandom sets the block context random field
func WithRandom(rand *gethCommon.Hash) Option {
return func(c *Config) *Config {
c.BlockContext.Random = rand
return c
}
}
1 change: 1 addition & 0 deletions fvm/evm/emulator/emulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func newConfig(ctx types.BlockContext) *Config {
WithCoinbase(ctx.GasFeeCollector.ToCommon()),
WithDirectCallBaseGasUsage(ctx.DirectCallBaseGasUsage),
WithExtraPrecompiles(ctx.ExtraPrecompiles),
WithRandom(&ctx.Random),
)
}

Expand Down
2 changes: 0 additions & 2 deletions fvm/evm/emulator/emulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ func TestContractInteraction(t *testing.T) {

t.Run("call contract", func(t *testing.T) {
num := big.NewInt(10)

RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(
Expand Down Expand Up @@ -184,7 +183,6 @@ func TestContractInteraction(t *testing.T) {
require.Equal(t, blockNumber, ret)
})
})

})

t.Run("test sending transactions (happy case)", func(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions fvm/evm/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/onflow/cadence/encoding/json"
"github.com/stretchr/testify/require"

"github.com/onflow/flow-go/engine/execution/testutil"
"github.com/onflow/flow-go/fvm"
"github.com/onflow/flow-go/fvm/evm/stdlib"
"github.com/onflow/flow-go/fvm/evm/testutils"
Expand Down Expand Up @@ -85,10 +86,12 @@ func TestEVMRun(t *testing.T) {
}

func RunWithNewTestVM(t *testing.T, chain flow.Chain, f func(fvm.Context, fvm.VM, snapshot.SnapshotTree)) {

opts := []fvm.Option{
fvm.WithChain(chain),
fvm.WithAuthorizationChecksEnabled(false),
fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
fvm.WithEntropyProvider(testutil.EntropyProviderFixture(nil)),
}
ctx := fvm.NewContext(opts...)

Expand Down
5 changes: 5 additions & 0 deletions fvm/evm/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"math/big"

gethCommon "github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/onflow/cadence/runtime/common"
Expand Down Expand Up @@ -153,10 +154,14 @@ func (h *ContractHandler) emitEvent(event *types.Event) {
func (h *ContractHandler) getBlockContext() types.BlockContext {
bp, err := h.blockstore.BlockProposal()
handleError(err)
rand := gethCommon.Hash{}
err = h.backend.ReadRandom(rand[:])
handleError(err)
return types.BlockContext{
BlockNumber: bp.Height,
DirectCallBaseGasUsage: types.DefaultDirectCallBaseGasUsage,
ExtraPrecompiles: h.precompiles,
Random: rand,
}
}

Expand Down
33 changes: 33 additions & 0 deletions fvm/evm/handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,39 @@
})
})

t.Run("test block.random call (with integrated emulator)", func(t *testing.T) {
t.Parallel()

testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) {
random := testutils.RandomCommonHash(t)
backend.ReadRandomFunc = func(buffer []byte) error {
copy(buffer, random.Bytes())
return nil
}
testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) {
handler := SetupHandler(t, backend, rootAddr)

foa := handler.AccountByAddress(handler.AllocateAddress(), true)
require.NotNil(t, foa)

vault := types.NewFlowTokenVault(testutils.MakeABalanceInFlow(100))

Check failure on line 551 in fvm/evm/handler/handler_test.go

View workflow job for this annotation

GitHub Actions / Lint (./)

undefined: testutils.MakeABalanceInFlow

Check failure on line 551 in fvm/evm/handler/handler_test.go

View workflow job for this annotation

GitHub Actions / Unit Tests (fvm)

undefined: testutils.MakeABalanceInFlow

Check failure on line 551 in fvm/evm/handler/handler_test.go

View workflow job for this annotation

GitHub Actions / Unit Tests (fvm)

undefined: testutils.MakeABalanceInFlow

Check failure on line 551 in fvm/evm/handler/handler_test.go

View workflow job for this annotation

GitHub Actions / Unit Tests (fvm)

undefined: testutils.MakeABalanceInFlow

Check failure on line 551 in fvm/evm/handler/handler_test.go

View workflow job for this annotation

GitHub Actions / Unit Tests (fvm)

undefined: testutils.MakeABalanceInFlow
foa.Deposit(vault)

testContract := testutils.GetStorageTestContract(t)
addr := foa.Deploy(testContract.ByteCode, math.MaxUint64, types.Balance(0))

Check failure on line 555 in fvm/evm/handler/handler_test.go

View workflow job for this annotation

GitHub Actions / Lint (./)

cannot convert 0 (untyped int constant) to type "github.com/onflow/flow-go/fvm/evm/types".Balance

Check failure on line 555 in fvm/evm/handler/handler_test.go

View workflow job for this annotation

GitHub Actions / Unit Tests (fvm)

cannot convert 0 (untyped int constant) to type "github.com/onflow/flow-go/fvm/evm/types".Balance

Check failure on line 555 in fvm/evm/handler/handler_test.go

View workflow job for this annotation

GitHub Actions / Unit Tests (fvm)

cannot convert 0 (untyped int constant) to type "github.com/onflow/flow-go/fvm/evm/types".Balance

Check failure on line 555 in fvm/evm/handler/handler_test.go

View workflow job for this annotation

GitHub Actions / Unit Tests (fvm)

cannot convert 0 (untyped int constant) to type "github.com/onflow/flow-go/fvm/evm/types".Balance
require.NotNil(t, addr)

ret := foa.Call(
addr,
testContract.MakeCallData(t, "random"),
math.MaxUint64,
types.Balance(0))

Check failure on line 562 in fvm/evm/handler/handler_test.go

View workflow job for this annotation

GitHub Actions / Lint (./)

cannot convert 0 (untyped int constant) to type "github.com/onflow/flow-go/fvm/evm/types".Balance (typecheck)

Check failure on line 562 in fvm/evm/handler/handler_test.go

View workflow job for this annotation

GitHub Actions / Unit Tests (fvm)

cannot convert 0 (untyped int constant) to type "github.com/onflow/flow-go/fvm/evm/types".Balance

Check failure on line 562 in fvm/evm/handler/handler_test.go

View workflow job for this annotation

GitHub Actions / Unit Tests (fvm)

cannot convert 0 (untyped int constant) to type "github.com/onflow/flow-go/fvm/evm/types".Balance

Check failure on line 562 in fvm/evm/handler/handler_test.go

View workflow job for this annotation

GitHub Actions / Unit Tests (fvm)

cannot convert 0 (untyped int constant) to type "github.com/onflow/flow-go/fvm/evm/types".Balance

require.Equal(t, random.Bytes(), []byte(ret))
})
})
})

// TODO add test with test emulator for unhappy cases (emulator)
}

Expand Down
33 changes: 29 additions & 4 deletions fvm/evm/testutils/backend.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package testutils

import (
"crypto/rand"
"encoding/binary"
"fmt"
"math"
Expand Down Expand Up @@ -32,10 +33,11 @@ func RunWithTestFlowEVMRootAddress(t testing.TB, backend atree.Ledger, f func(fl

func RunWithTestBackend(t testing.TB, f func(*TestBackend)) {
tb := &TestBackend{
TestValueStore: GetSimpleValueStore(),
testEventEmitter: getSimpleEventEmitter(),
testMeter: getSimpleMeter(),
TestBlockInfo: &TestBlockInfo{},
TestValueStore: GetSimpleValueStore(),
testEventEmitter: getSimpleEventEmitter(),
testMeter: getSimpleMeter(),
TestBlockInfo: &TestBlockInfo{},
TestRandomGenerator: getSimpleRandomGenerator(),
}
f(tb)
}
Expand Down Expand Up @@ -157,6 +159,7 @@ type TestBackend struct {
*testMeter
*testEventEmitter
*TestBlockInfo
*TestRandomGenerator
}

var _ types.Backend = &TestBackend{}
Expand Down Expand Up @@ -405,3 +408,25 @@ func (tb *TestBlockInfo) GetBlockAtHeight(height uint64) (runtime.Block, bool, e
}
return tb.GetBlockAtHeightFunc(height)
}

type TestRandomGenerator struct {
ReadRandomFunc func([]byte) error
}

var _ environment.RandomGenerator = &TestRandomGenerator{}

func (t *TestRandomGenerator) ReadRandom(buffer []byte) error {
if t.ReadRandomFunc == nil {
panic("ReadRandomFunc method is not set")
}
return t.ReadRandomFunc(buffer)
}

func getSimpleRandomGenerator() *TestRandomGenerator {
return &TestRandomGenerator{
ReadRandomFunc: func(buffer []byte) error {
_, err := rand.Read(buffer)
return err
},
}
}
23 changes: 21 additions & 2 deletions fvm/evm/testutils/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ func (tc *TestContract) SetDeployedAt(deployedAt types.Address) {
}

func GetStorageTestContract(tb testing.TB) *TestContract {
byteCodes, err := hex.DecodeString("608060405261022c806100136000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80632e64cec11461005c57806348b151661461007a57806357e871e7146100985780636057361d146100b657806385df51fd146100d2575b600080fd5b610064610102565b6040516100719190610149565b60405180910390f35b61008261010b565b60405161008f9190610149565b60405180910390f35b6100a0610113565b6040516100ad9190610149565b60405180910390f35b6100d060048036038101906100cb9190610195565b61011b565b005b6100ec60048036038101906100e79190610195565b610125565b6040516100f991906101db565b60405180910390f35b60008054905090565b600042905090565b600043905090565b8060008190555050565b600081409050919050565b6000819050919050565b61014381610130565b82525050565b600060208201905061015e600083018461013a565b92915050565b600080fd5b61017281610130565b811461017d57600080fd5b50565b60008135905061018f81610169565b92915050565b6000602082840312156101ab576101aa610164565b5b60006101b984828501610180565b91505092915050565b6000819050919050565b6101d5816101c2565b82525050565b60006020820190506101f060008301846101cc565b9291505056fea26469706673582212203ee61567a25f0b1848386ae6b8fdbd7733c8a502c83b5ed305b921b7933f4e8164736f6c63430008120033")
byteCodes, err := hex.DecodeString("6080604052610249806100115f395ff3fe608060405234801561000f575f80fd5b5060043610610060575f3560e01c80632e64cec11461006457806348b151661461008257806357e871e7146100a05780635ec01e4d146100be5780636057361d146100dc57806385df51fd146100f8575b5f80fd5b61006c610128565b6040516100799190610170565b60405180910390f35b61008a610130565b6040516100979190610170565b60405180910390f35b6100a8610137565b6040516100b59190610170565b60405180910390f35b6100c661013e565b6040516100d39190610170565b60405180910390f35b6100f660048036038101906100f191906101b7565b610145565b005b610112600480360381019061010d91906101b7565b61014e565b60405161011f91906101fa565b60405180910390f35b5f8054905090565b5f42905090565b5f43905090565b5f44905090565b805f8190555050565b5f81409050919050565b5f819050919050565b61016a81610158565b82525050565b5f6020820190506101835f830184610161565b92915050565b5f80fd5b61019681610158565b81146101a0575f80fd5b50565b5f813590506101b18161018d565b92915050565b5f602082840312156101cc576101cb610189565b5b5f6101d9848285016101a3565b91505092915050565b5f819050919050565b6101f4816101e2565b82525050565b5f60208201905061020d5f8301846101eb565b9291505056fea26469706673582212204e444dbbee71334344ae4d9fe1b45944b0aff9ffd6b8ac8a33cc0f31c6e21d6664736f6c63430008170033")
require.NoError(tb, err)
return &TestContract{
Code: `
pragma solidity >=0.7.0 <0.9.0;

contract Storage {
uint256 number;
constructor() payable {
Expand All @@ -56,11 +58,15 @@ func GetStorageTestContract(tb testing.TB) *TestContract {
return block.number;
}
function blockTime() public view returns (uint) {
return block.timestamp;
return block.timestamp;
}
function blockHash(uint num) public view returns (bytes32) {
return blockhash(num);
}

function random() public view returns (uint256) {
return block.prevrandao;
}
}
`,

Expand Down Expand Up @@ -116,6 +122,19 @@ func GetStorageTestContract(tb testing.TB) *TestContract {
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "random",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "retrieve",
Expand Down
2 changes: 2 additions & 0 deletions fvm/evm/types/emulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package types
import (
"math/big"

gethCommon "github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types"
gethVM "github.com/ethereum/go-ethereum/core/vm"
)
Expand All @@ -26,6 +27,7 @@ type BlockContext struct {
DirectCallBaseGasUsage uint64
DirectCallGasPrice uint64
GasFeeCollector Address
Random gethCommon.Hash

// a set of extra precompiles to be injected
ExtraPrecompiles []Precompile
Expand Down
1 change: 1 addition & 0 deletions fvm/evm/types/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type Backend interface {
environment.Meter
environment.EventEmitter
environment.BlockInfo
environment.RandomGenerator
}

// AddressAllocator allocates addresses, used by the handler
Expand Down
1 change: 1 addition & 0 deletions fvm/fvm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func (vmt vmTest) run(
baseOpts := []fvm.Option{
// default chain is Testnet
fvm.WithChain(flow.Testnet.Chain()),
fvm.WithEntropyProvider(testutil.EntropyProviderFixture(nil)),
}

opts := append(baseOpts, vmt.contextOptions...)
Expand Down
Loading