Skip to content

Commit

Permalink
Merge pull request #5742 from onflow/gregor/evm/revertible-random
Browse files Browse the repository at this point in the history
[EVM] Add get random source to Cadence Arch
  • Loading branch information
sideninja authored Apr 25, 2024
2 parents 83724f7 + a0ed6b5 commit 9a6781f
Show file tree
Hide file tree
Showing 10 changed files with 353 additions and 16 deletions.
7 changes: 7 additions & 0 deletions fvm/evm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ func StorageAccountAddress(chainID flow.ChainID) (flow.Address, error) {
return sc.EVMStorage.Address, nil
}

func RandomBeaconAddress(chainID flow.ChainID) flow.Address {
return systemcontracts.SystemContractsForChain(chainID).RandomBeaconHistory.Address
}

func SetupEnvironment(
chainID flow.ChainID,
fvmEnv environment.Environment,
Expand All @@ -39,6 +43,8 @@ func SetupEnvironment(
return err
}

randomBeaconAddress := RandomBeaconAddress(chainID)

backend := backends.NewWrappedEnvironment(fvmEnv)

emulator := evm.NewEmulator(backend, evmStorageAccountAddress)
Expand All @@ -51,6 +57,7 @@ func SetupEnvironment(
chainID,
evmContractAccountAddress,
common.Address(flowToken),
randomBeaconAddress,
blockStore,
addressAllocator,
backend,
Expand Down
188 changes: 188 additions & 0 deletions fvm/evm/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,193 @@ func TestCadenceArch(t *testing.T) {
})
})

t.Run("testing calling Cadence arch - random source (happy case)", 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,
) {
entropy := []byte{13, 37}
source := []byte{91, 161, 206, 171, 100, 17, 141, 44} // coresponding out to the above entropy

// we must record a new heartbeat with a fixed block, we manually execute a transaction to do so,
// since doing this automatically would require a block computer and whole execution setup
height := uint64(1)
block1 := unittest.BlockFixture()
block1.Header.Height = height
ctx.BlockHeader = block1.Header
ctx.EntropyProvider = testutil.EntropyProviderFixture(entropy) // fix the entropy

txBody := flow.NewTransactionBody().
SetScript([]byte(fmt.Sprintf(`
import RandomBeaconHistory from %s
transaction {
prepare(serviceAccount: AuthAccount) {
let randomBeaconHistoryHeartbeat = serviceAccount.borrow<&RandomBeaconHistory.Heartbeat>(
from: RandomBeaconHistory.HeartbeatStoragePath)
?? panic("Couldn't borrow RandomBeaconHistory.Heartbeat Resource")
randomBeaconHistoryHeartbeat.heartbeat(randomSourceHistory: randomSourceHistory())
}
}`, sc.RandomBeaconHistory.Address.HexWithPrefix())),
).
AddAuthorizer(sc.FlowServiceAccount.Address)

s, out, err := vm.Run(ctx, fvm.Transaction(txBody, 0), snapshot)
require.NoError(t, err)
require.NoError(t, out.Err)

snapshot = snapshot.Append(s)

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: "evm tx wrong status")
return res.data
}
`,
sc.EVMContract.Address.HexWithPrefix(),
))

// we fake progressing to new block height since random beacon does the check the
// current height (2) is bigger than the height requested (1)
block1.Header.Height = 2
ctx.BlockHeader = block1.Header

innerTxBytes := testAccount.PrepareSignAndEncodeTx(t,
testContract.DeployedAt.ToCommon(),
testContract.MakeCallData(t, "verifyArchCallToRandomSource", height),
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] = vals[i].ToGoValue().(byte)
}
require.Equal(t, source, res)
})
})

t.Run("testing calling Cadence arch - random source (failed due to incorrect height)", 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,
) {
// we must record a new heartbeat with a fixed block, we manually execute a transaction to do so,
// since doing this automatically would require a block computer and whole execution setup
height := uint64(1)
block1 := unittest.BlockFixture()
block1.Header.Height = height
ctx.BlockHeader = block1.Header

txBody := flow.NewTransactionBody().
SetScript([]byte(fmt.Sprintf(`
import RandomBeaconHistory from %s
transaction {
prepare(serviceAccount: AuthAccount) {
let randomBeaconHistoryHeartbeat = serviceAccount.borrow<&RandomBeaconHistory.Heartbeat>(
from: RandomBeaconHistory.HeartbeatStoragePath)
?? panic("Couldn't borrow RandomBeaconHistory.Heartbeat Resource")
randomBeaconHistoryHeartbeat.heartbeat(randomSourceHistory: randomSourceHistory())
}
}`, sc.RandomBeaconHistory.Address.HexWithPrefix())),
).
AddAuthorizer(sc.FlowServiceAccount.Address)

s, out, err := vm.Run(ctx, fvm.Transaction(txBody, 0), snapshot)
require.NoError(t, err)
require.NoError(t, out.Err)

snapshot = snapshot.Append(s)

height = 1337 // invalid
// we make sure the transaction fails, due to requested height being invalid
code := []byte(fmt.Sprintf(
`
import EVM from %s
access(all)
fun main(tx: [UInt8], coinbaseBytes: [UInt8; 20]) {
let coinbase = EVM.EVMAddress(bytes: coinbaseBytes)
let res = EVM.run(tx: tx, coinbase: coinbase)
}
`,
sc.EVMContract.Address.HexWithPrefix(),
))

// we fake progressing to new block height since random beacon does the check the
// current height (2) is bigger than the height requested (1)
block1.Header.Height = 2
ctx.BlockHeader = block1.Header

innerTxBytes := testAccount.PrepareSignAndEncodeTx(t,
testContract.DeployedAt.ToCommon(),
testContract.MakeCallData(t, "verifyArchCallToRandomSource", height),
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)
// make sure the error is correct
require.ErrorContains(t, output.Err, "Source of randomness not yet recorded")
})
})

t.Run("testing calling Cadence arch - COA ownership proof (happy case)", func(t *testing.T) {
chain := flow.Emulator.Chain()
sc := systemcontracts.SystemContractsForChain(chain.ChainID())
Expand Down Expand Up @@ -1121,6 +1308,7 @@ func RunWithNewEnvironment(
fvm.WithAuthorizationChecksEnabled(false),
fvm.WithSequenceNumberCheckAndIncrementEnabled(false),
fvm.WithEntropyProvider(testutil.EntropyProviderFixture(nil)),
fvm.WithRandomSourceHistoryCallAllowed(true),
fvm.WithBlocks(blocks),
}
ctx := fvm.NewContext(opts...)
Expand Down
3 changes: 2 additions & 1 deletion fvm/evm/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func NewContractHandler(
flowChainID flow.ChainID,
evmContractAddress flow.Address,
flowTokenAddress common.Address,
randomBeaconAddress flow.Address,
blockStore types.BlockStore,
addressAllocator types.AddressAllocator,
backend types.Backend,
Expand All @@ -56,7 +57,7 @@ func NewContractHandler(
addressAllocator: addressAllocator,
backend: backend,
emulator: emulator,
precompiles: preparePrecompiles(evmContractAddress, addressAllocator, backend),
precompiles: preparePrecompiles(evmContractAddress, randomBeaconAddress, addressAllocator, backend),
}
}

Expand Down
26 changes: 13 additions & 13 deletions fvm/evm/handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestHandler_TransactionRunOrPanic(t *testing.T) {
return result, nil
},
}
handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, bs, aa, backend, em)
handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em)

coinbase := types.NewAddress(gethCommon.Address{})

Expand Down Expand Up @@ -163,7 +163,7 @@ func TestHandler_TransactionRunOrPanic(t *testing.T) {
}, nil
},
}
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, bs, aa, backend, em)
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em)

coinbase := types.NewAddress(gethCommon.Address{})

Expand Down Expand Up @@ -220,7 +220,7 @@ func TestHandler_TransactionRunOrPanic(t *testing.T) {
return &types.Result{}, types.NewFatalError(fmt.Errorf("Fatal error"))
},
}
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, bs, aa, backend, em)
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em)
assertPanic(t, errors.IsFailure, func() {
tx := eoa.PrepareSignAndEncodeTx(
t,
Expand Down Expand Up @@ -464,7 +464,7 @@ func TestHandler_COA(t *testing.T) {
},
}

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

account := handler.AccountByAddress(testutils.RandomAddress(t), false)
account.Withdraw(types.NewBalanceFromUFix64(1))
Expand All @@ -481,7 +481,7 @@ func TestHandler_COA(t *testing.T) {
},
}

handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, bs, aa, backend, em)
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em)
account := handler.AccountByAddress(testutils.RandomAddress(t), true)

account.Withdraw(types.NewBalanceFromUFix64(1))
Expand All @@ -498,7 +498,7 @@ func TestHandler_COA(t *testing.T) {
},
}

handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, bs, aa, backend, em)
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em)
account := handler.AccountByAddress(testutils.RandomAddress(t), true)

account.Withdraw(types.NewBalanceFromUFix64(0))
Expand All @@ -515,7 +515,7 @@ func TestHandler_COA(t *testing.T) {
},
}

handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, bs, aa, backend, em)
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em)
account := handler.AccountByAddress(testutils.RandomAddress(t), true)

account.Withdraw(types.NewBalanceFromUFix64(0))
Expand Down Expand Up @@ -545,7 +545,7 @@ func TestHandler_COA(t *testing.T) {
},
}

handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, bs, aa, backend, em)
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em)
account := handler.AccountByAddress(testutils.RandomAddress(t), true)

account.Deposit(types.NewFlowTokenVault(types.NewBalanceFromUFix64(1)))
Expand All @@ -562,7 +562,7 @@ func TestHandler_COA(t *testing.T) {
},
}

handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, bs, aa, backend, em)
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em)
account := handler.AccountByAddress(testutils.RandomAddress(t), true)

account.Deposit(types.NewFlowTokenVault(types.NewBalanceFromUFix64(1)))
Expand Down Expand Up @@ -700,7 +700,7 @@ func TestHandler_TransactionRun(t *testing.T) {
return result, nil
},
}
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, bs, aa, backend, em)
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em)
tx := eoa.PrepareSignAndEncodeTx(
t,
gethCommon.Address{},
Expand Down Expand Up @@ -746,7 +746,7 @@ func TestHandler_TransactionRun(t *testing.T) {
return result, nil
},
}
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, bs, aa, backend, em)
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em)

tx := eoa.PrepareSignAndEncodeTx(
t,
Expand Down Expand Up @@ -781,7 +781,7 @@ func TestHandler_TransactionRun(t *testing.T) {
return &types.Result{ValidationError: evmErr}, nil
},
}
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, bs, aa, backend, em)
handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em)

coinbase := types.NewAddress(gethCommon.Address{})

Expand Down Expand Up @@ -845,6 +845,6 @@ func SetupHandler(t testing.TB, backend types.Backend, rootAddr flow.Address) *h
aa := handler.NewAddressAllocator()
emulator := emulator.NewEmulator(backend, rootAddr)

handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, bs, aa, backend, emulator)
handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, emulator)
return handler
}
Loading

0 comments on commit 9a6781f

Please sign in to comment.