Skip to content

Commit

Permalink
Merge pull request onflow#5934 from onflow/gergor/evm/revertible-random
Browse files Browse the repository at this point in the history
[EVM] Revertible random Cadence arch function
  • Loading branch information
sideninja authored May 17, 2024
2 parents a430211 + 5b63d8e commit 91097e3
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 6 deletions.
74 changes: 72 additions & 2 deletions fvm/evm/evm_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package evm_test

import (
"encoding/binary"
"encoding/hex"
"fmt"
"math"
Expand Down Expand Up @@ -1554,7 +1555,8 @@ func TestCadenceArch(t *testing.T) {
access(all)
fun main(tx: [UInt8], coinbaseBytes: [UInt8; 20]) {
let coinbase = EVM.EVMAddress(bytes: coinbaseBytes)
EVM.run(tx: tx, coinbase: coinbase)
let res = EVM.run(tx: tx, coinbase: coinbase)
assert(res.status == EVM.Status.successful, message: "test failed: ".concat(res.errorCode.toString()))
}
`,
sc.EVMContract.Address.HexWithPrefix(),
Expand Down Expand Up @@ -1587,6 +1589,73 @@ func TestCadenceArch(t *testing.T) {
})
})

t.Run("testing calling Cadence arch - revertible random", func(t *testing.T) {
chain := flow.Emulator.Chain()
sc := systemcontracts.SystemContractsForChain(chain.ChainID())
RunWithNewEnvironment(t,
chain, func(
ctx fvm.Context,
vm fvm.VM,
snapshot snapshot.SnapshotTree,
testContract *TestContract,
testAccount *EOATestAccount,
) {
code := []byte(fmt.Sprintf(
`
import EVM from %s
access(all)
fun main(tx: [UInt8], coinbaseBytes: [UInt8; 20]): [UInt8] {
let coinbase = EVM.EVMAddress(bytes: coinbaseBytes)
let res = EVM.run(tx: tx, coinbase: coinbase)
assert(res.status == EVM.Status.successful, message: "test failed: ".concat(res.errorCode.toString()))
return res.data
}
`,
sc.EVMContract.Address.HexWithPrefix(),
))
innerTxBytes := testAccount.PrepareSignAndEncodeTx(t,
testContract.DeployedAt.ToCommon(),
testContract.MakeCallData(t, "verifyArchCallToRevertibleRandom"),
big.NewInt(0),
uint64(10_000_000),
big.NewInt(0),
)
script := fvm.Script(code).WithArguments(
json.MustEncode(
cadence.NewArray(
ConvertToCadence(innerTxBytes),
).WithType(stdlib.EVMTransactionBytesCadenceType),
),
json.MustEncode(
cadence.NewArray(
ConvertToCadence(testAccount.Address().Bytes()),
).WithType(stdlib.EVMAddressBytesCadenceType),
),
)
_, output, err := vm.Run(
ctx,
script,
snapshot)
require.NoError(t, err)
require.NoError(t, output.Err)

res := make([]byte, 8)
vals := output.Value.(cadence.Array).Values
vals = vals[len(vals)-8:] // only last 8 bytes is the value
for i := range res {
res[i] = byte(vals[i].(cadence.UInt8))
}

actualRand := binary.BigEndian.Uint64(res)
// because PRG uses script ID and random source we can not predict the random
// we can set the random source but since script ID is generated by hashing
// script and args, and since arg is a signed transaction which always changes
// we can't fix the value
require.Greater(t, actualRand, uint64(0))
})
})

t.Run("testing calling Cadence arch - random source (happy case)", func(t *testing.T) {
chain := flow.Emulator.Chain()
sc := systemcontracts.SystemContractsForChain(chain.ChainID())
Expand Down Expand Up @@ -1833,7 +1902,8 @@ func TestCadenceArch(t *testing.T) {
access(all)
fun main(tx: [UInt8], coinbaseBytes: [UInt8; 20]) {
let coinbase = EVM.EVMAddress(bytes: coinbaseBytes)
EVM.run(tx: tx, coinbase: coinbase)
let res = EVM.run(tx: tx, coinbase: coinbase)
assert(res.status == EVM.Status.successful, message: "test failed: ".concat(res.errorCode.toString()))
}
`,
sc.EVMContract.Address.HexWithPrefix(),
Expand Down
13 changes: 13 additions & 0 deletions fvm/evm/handler/precompiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func preparePrecompiles(
blockHeightProvider(backend),
coaOwnershipProofValidator(evmContractAddress, backend),
randomSourceProvider(randomBeaconAddress, backend),
revertibleRandomGenerator(backend),
)
return []types.Precompile{archContract}
}
Expand Down Expand Up @@ -80,6 +81,18 @@ func randomSourceProvider(contractAddress flow.Address, backend types.Backend) f
}
}

func revertibleRandomGenerator(backend types.Backend) func() (uint64, error) {
return func() (uint64, error) {
rand := make([]byte, 8)
err := backend.ReadRandom(rand)
if err != nil {
return 0, err
}

return binary.BigEndian.Uint64(rand), nil
}
}

const ValidationResultTypeIsValidFieldName = "isValid"

func coaOwnershipProofValidator(contractAddress flow.Address, backend types.Backend) func(proof *types.COAOwnershipProofInContext) (bool, error) {
Expand Down
40 changes: 38 additions & 2 deletions fvm/evm/precompiles/arch.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ var (

RandomSourceFuncSig = ComputeFunctionSelector("getRandomSource", []string{"uint64"})

RevertibleRandomFuncSig = ComputeFunctionSelector("revertibleRandom", nil)

// FlowBlockHeightFixedGas is set to match the `number` opCode (0x43)
FlowBlockHeightFixedGas = uint64(2)
// ProofVerifierBaseGas covers the cost of decoding, checking capability the resource
Expand All @@ -25,8 +27,11 @@ var (
// but we might increase this in the future
ProofVerifierGasMultiplerPerSignature = uint64(3_000)

// RandomSourceGas covers the cost of calculating a revertible random bytes
RandomSourceGas = uint64(1_000) // todo define
// RandomSourceGas covers the cost of obtaining random sournce bytes
RandomSourceGas = uint64(1_000)

// RevertibleRandomGas covers the cost of calculating a revertible random bytes
RevertibleRandomGas = uint64(1_000)

// errUnexpectedInput is returned when the function that doesn't expect an input
// argument, receives one
Expand All @@ -41,13 +46,15 @@ func ArchContract(
heightProvider func() (uint64, error),
proofVer func(*types.COAOwnershipProofInContext) (bool, error),
randomSourceProvider func(uint64) (uint64, error),
revertibleRandomGenerator func() (uint64, error),
) types.Precompile {
return MultiFunctionPrecompileContract(
address,
[]Function{
&flowBlockHeight{heightProvider},
&proofVerifier{proofVer},
&randomnessSource{randomSourceProvider},
&revertibleRandom{revertibleRandomGenerator},
},
)
}
Expand Down Expand Up @@ -162,6 +169,35 @@ func (r *randomnessSource) Run(input []byte) ([]byte, error) {
return buf, nil
}

var _ Function = &revertibleRandom{}

type revertibleRandom struct {
revertibleRandomGenerator func() (uint64, error)
}

func (r *revertibleRandom) FunctionSelector() FunctionSelector {
return RevertibleRandomFuncSig
}

func (r *revertibleRandom) ComputeGas(input []byte) uint64 {
return RevertibleRandomGas
}

func (r *revertibleRandom) Run(input []byte) ([]byte, error) {
rand, err := r.revertibleRandomGenerator()
if err != nil {
return nil, err
}

buf := make([]byte, EncodedUint64Size)
err = EncodeUint64(rand, buf, 0)
if err != nil {
return nil, err
}

return buf, nil
}

func DecodeABIEncodedProof(input []byte) (*types.COAOwnershipProofInContext, error) {
index := 0
caller, err := ReadAddress(input, index)
Expand Down
29 changes: 29 additions & 0 deletions fvm/evm/precompiles/arch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func TestArchContract(t *testing.T) {
},
nil,
nil,
nil,
)

input := precompiles.FlowBlockHeightFuncSig.Bytes()
Expand All @@ -48,6 +49,7 @@ func TestArchContract(t *testing.T) {
func(u uint64) (uint64, error) {
return rand, nil
},
nil,
)

require.Equal(t, address, pc.Address())
Expand All @@ -66,6 +68,32 @@ func TestArchContract(t *testing.T) {
require.Equal(t, rand, resultRand)
})

t.Run("test revertible random", func(t *testing.T) {
address := testutils.RandomAddress(t)
rand := uint64(1337)
pc := precompiles.ArchContract(
address,
nil,
nil,
nil,
func() (uint64, error) {
return rand, nil
},
)

require.Equal(t, address, pc.Address())

input := precompiles.RevertibleRandomFuncSig.Bytes()
require.Equal(t, precompiles.RevertibleRandomGas, pc.RequiredGas(input))

ret, err := pc.Run(input)
require.NoError(t, err)

resultRand, err := precompiles.ReadUint64(ret, 0)
require.NoError(t, err)
require.Equal(t, rand, resultRand)
})

t.Run("test proof verification", func(t *testing.T) {
proof := testutils.COAOwnershipProofInContextFixture(t)
pc := precompiles.ArchContract(
Expand All @@ -76,6 +104,7 @@ func TestArchContract(t *testing.T) {
return true, nil
},
nil,
nil,
)

abiEncodedData, err := precompiles.ABIEncodeProof(proof)
Expand Down
7 changes: 7 additions & 0 deletions fvm/evm/testutils/contracts/test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ contract Storage {
return output;
}

function verifyArchCallToRevertibleRandom() public view returns (uint64) {
(bool ok, bytes memory data) = cadenceArch.staticcall(abi.encodeWithSignature("revertibleRandom()"));
require(ok, "unsuccessful call to arch");
uint64 output = abi.decode(data, (uint64));
return output;
}

function verifyArchCallToFlowBlockHeight(uint64 expected) public view returns (uint64){
(bool ok, bytes memory data) = cadenceArch.staticcall(abi.encodeWithSignature("flowBlockHeight()"));
require(ok, "unsuccessful call to arch ");
Expand Down
15 changes: 14 additions & 1 deletion fvm/evm/testutils/contracts/test_abi.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,19 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "verifyArchCallToRevertibleRandom",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
Expand Down Expand Up @@ -238,4 +251,4 @@
"stateMutability": "view",
"type": "function"
}
]
]
Loading

0 comments on commit 91097e3

Please sign in to comment.