diff --git a/fvm/evm/emulator/config.go b/fvm/evm/emulator/config.go index e1bfc0b1375..394c88bc842 100644 --- a/fvm/evm/emulator/config.go +++ b/fvm/evm/emulator/config.go @@ -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 + } +} diff --git a/fvm/evm/emulator/emulator.go b/fvm/evm/emulator/emulator.go index 93561565a59..bddc4e61e73 100644 --- a/fvm/evm/emulator/emulator.go +++ b/fvm/evm/emulator/emulator.go @@ -40,6 +40,7 @@ func newConfig(ctx types.BlockContext) *Config { WithCoinbase(ctx.GasFeeCollector.ToCommon()), WithDirectCallBaseGasUsage(ctx.DirectCallBaseGasUsage), WithExtraPrecompiles(ctx.ExtraPrecompiles), + WithRandom(&ctx.Random), ) } diff --git a/fvm/evm/emulator/emulator_test.go b/fvm/evm/emulator/emulator_test.go index a7c5768cca0..074912f0bde 100644 --- a/fvm/evm/emulator/emulator_test.go +++ b/fvm/evm/emulator/emulator_test.go @@ -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( @@ -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) { diff --git a/fvm/evm/evm_test.go b/fvm/evm/evm_test.go index acd0a3c8289..32f4823df20 100644 --- a/fvm/evm/evm_test.go +++ b/fvm/evm/evm_test.go @@ -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" @@ -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...) diff --git a/fvm/evm/handler/handler.go b/fvm/evm/handler/handler.go index 6d1ab81c882..7c9987f0d10 100644 --- a/fvm/evm/handler/handler.go +++ b/fvm/evm/handler/handler.go @@ -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" @@ -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, } } diff --git a/fvm/evm/handler/handler_test.go b/fvm/evm/handler/handler_test.go index 2001e39323a..1ba0a6b33c4 100644 --- a/fvm/evm/handler/handler_test.go +++ b/fvm/evm/handler/handler_test.go @@ -533,6 +533,39 @@ func TestHandler_BridgedAccount(t *testing.T) { }) }) + 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(types.MakeABalanceInFlow(100)) + foa.Deposit(vault) + + testContract := testutils.GetStorageTestContract(t) + addr := foa.Deploy(testContract.ByteCode, math.MaxUint64, types.EmptyBalance) + require.NotNil(t, addr) + + ret := foa.Call( + addr, + testContract.MakeCallData(t, "random"), + math.MaxUint64, + types.EmptyBalance) + + require.Equal(t, random.Bytes(), []byte(ret)) + }) + }) + }) + // TODO add test with test emulator for unhappy cases (emulator) } diff --git a/fvm/evm/testutils/backend.go b/fvm/evm/testutils/backend.go index 8c73fbf02f3..c25a3626b0c 100644 --- a/fvm/evm/testutils/backend.go +++ b/fvm/evm/testutils/backend.go @@ -1,6 +1,7 @@ package testutils import ( + "crypto/rand" "encoding/binary" "fmt" "math" @@ -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) } @@ -157,6 +159,7 @@ type TestBackend struct { *testMeter *testEventEmitter *TestBlockInfo + *TestRandomGenerator } var _ types.Backend = &TestBackend{} @@ -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 + }, + } +} diff --git a/fvm/evm/testutils/contract.go b/fvm/evm/testutils/contract.go index f67cced4c94..5e9f000b61e 100644 --- a/fvm/evm/testutils/contract.go +++ b/fvm/evm/testutils/contract.go @@ -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 { @@ -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; + } } `, @@ -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", diff --git a/fvm/evm/types/emulator.go b/fvm/evm/types/emulator.go index 01577a4132a..1997624569f 100644 --- a/fvm/evm/types/emulator.go +++ b/fvm/evm/types/emulator.go @@ -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" ) @@ -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 diff --git a/fvm/evm/types/handler.go b/fvm/evm/types/handler.go index bfe187234e8..502e43025fd 100644 --- a/fvm/evm/types/handler.go +++ b/fvm/evm/types/handler.go @@ -48,6 +48,7 @@ type Backend interface { environment.Meter environment.EventEmitter environment.BlockInfo + environment.RandomGenerator } // AddressAllocator allocates addresses, used by the handler diff --git a/fvm/fvm_test.go b/fvm/fvm_test.go index 6fb975b7007..292329ebe6b 100644 --- a/fvm/fvm_test.go +++ b/fvm/fvm_test.go @@ -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...)