Skip to content
This repository has been archived by the owner on Dec 23, 2024. It is now read-only.

Fix and test optimistic requires #35

Merged
merged 3 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 229 additions & 0 deletions fhevm/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3152,3 +3152,232 @@ func TestFheRandEthCall(t *testing.T) {
t.Fatalf("fheRand expected 0 verified ciphertexts on EthCall")
}
}

func interpreterRunWithStopContract(environment *MockEVMEnvironment, interpreter *vm.EVMInterpreter, contract *vm.Contract, input []byte, readOnly bool) (ret []byte, err error) {
ret, _ = interpreter.Run(contract, input, readOnly)
// the following functions are meant to be ran from within interpreter.run so we increment depth to emulate that
environment.depth++
RemoveVerifiedCipherextsAtCurrentDepth(environment)
err = EvalRemOptReqWhenStopToken(environment)
environment.depth--
return ret, err
}

func newInterpreterFromEnvironment(environment *MockEVMEnvironment) *vm.EVMInterpreter {
cfg := vm.Config{}
evm := &vm.EVM{Config: cfg}
evm.Context = vm.BlockContext{}
evm.Context.Transfer = func(vm.StateDB, common.Address, common.Address, *big.Int) {}
evm.Context.CanTransfer = func(vm.StateDB, common.Address, *big.Int) bool { return true }
evm.StateDB = environment.stateDb
interpreter := vm.NewEVMInterpreter(evm)
return interpreter

}

func newStopOpcodeContract() *vm.Contract {
addr := vm.AccountRef{}
c := vm.NewContract(addr, addr, big.NewInt(0), 100000)
c.Code = make([]byte, 1)
c.Code[0] = byte(vm.STOP)
return c
}

func TestLibOneTrueOptimisticRequire(t *testing.T) {
var value uint64 = 1
signature := "optimisticRequire(uint256)"
hashRes := crypto.Keccak256([]byte(signature))
signatureBytes := hashRes[0:4]
depth := 1
environment := newTestEVMEnvironment()
environment.depth = depth
addr := common.Address{}
readOnly := false
input := make([]byte, 0)
hash := verifyCiphertextInTestMemory(environment, value, depth, FheUint8).getHash()
input = append(input, signatureBytes...)
input = append(input, hash.Bytes()...)
out, err := FheLibRun(environment, addr, addr, input, readOnly)
if err != nil {
t.Fatalf(err.Error())
} else if len(out) != 0 {
t.Fatalf("require expected output len of 0, got %v", len(out))
}

interpreter := newInterpreterFromEnvironment(environment)
// Call the interpreter with a single STOP opcode and expect that the optimistic require doesn't revert.
out, err = interpreterRunWithStopContract(environment, interpreter, newStopOpcodeContract(), make([]byte, 0), readOnly)
if err != nil {
t.Fatalf(err.Error())
} else if out != nil {
t.Fatalf("expected empty response")
}
}

func TestOneFalseOptimisticRequire(t *testing.T) {
var value uint64 = 0
depth := 0
environment := newTestEVMEnvironment()
environment.depth = depth
addr := common.Address{}
readOnly := false
hash := verifyCiphertextInTestMemory(environment, value, depth, FheUint8).getHash()
out, err := optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly)
if err != nil {
t.Fatalf(err.Error())
} else if len(out) != 0 {
t.Fatalf("require expected output len of 0, got %v", len(out))
}
interpreter := newInterpreterFromEnvironment(environment)
// Call the interpreter with a single STOP opcode and expect that the optimistic require reverts.
out, err = interpreterRunWithStopContract(environment, interpreter, newStopOpcodeContract(), make([]byte, 0), readOnly)
if err == nil || err != ErrExecutionReverted {
t.Fatalf("require expected reversal on value 0")
} else if out != nil {
t.Fatalf("expected empty response")
}
}

func TestTwoTrueOptimisticRequires(t *testing.T) {
var value uint64 = 1
depth := 1
environment := newTestEVMEnvironment()
environment.depth = depth
addr := common.Address{}
readOnly := false
hash := verifyCiphertextInTestMemory(environment, value, depth, FheUint8).getHash()
out, err := optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly)
if err != nil {
t.Fatalf(err.Error())
} else if len(out) != 0 {
t.Fatalf("require expected output len of 0, got %v", len(out))
}
hash = verifyCiphertextInTestMemory(environment, value, depth, FheUint8).getHash()
out, err = optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly)
if err != nil {
t.Fatalf(err.Error())
} else if len(out) != 0 {
t.Fatalf("require expected output len of 0, got %v", len(out))
}
interpreter := newInterpreterFromEnvironment(environment)
// Call the interpreter with a single STOP opcode and expect that the optimistic require doesn't revert.
out, err = interpreterRunWithStopContract(environment, interpreter, newStopOpcodeContract(), make([]byte, 0), readOnly)
if err != nil {
t.Fatalf(err.Error())
} else if out != nil {
t.Fatalf("expected empty response")
}
}

func TestOptimisticRequireTwiceOnSameCiphertext(t *testing.T) {
var value uint64 = 1
depth := 1
environment := newTestEVMEnvironment()
environment.depth = depth
addr := common.Address{}
readOnly := false
ct := verifyCiphertextInTestMemory(environment, value, depth, FheUint8)
hash := ct.getHash()
out, err := optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly)
if err != nil {
t.Fatalf(err.Error())
} else if len(out) != 0 {
t.Fatalf("require expected output len of 0, got %v", len(out))
}
out, err = optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly)
if err != nil {
t.Fatalf(err.Error())
} else if len(out) != 0 {
t.Fatalf("require expected output len of 0, got %v", len(out))
}
interpreter := newInterpreterFromEnvironment(environment)
// Call the interpreter with a single STOP opcode and expect that the optimistic require doesn't revert.
out, err = interpreterRunWithStopContract(environment, interpreter, newStopOpcodeContract(), make([]byte, 0), readOnly)
if err != nil {
t.Fatalf(err.Error())
} else if out != nil {
t.Fatalf("expected empty response")
}
}

func TestOneFalseAndOneTrueOptimisticRequire(t *testing.T) {
depth := 0
environment := newTestEVMEnvironment()
environment.depth = depth
addr := common.Address{}
readOnly := false
hash := verifyCiphertextInTestMemory(environment, 0, depth, FheUint8).getHash()
out, err := optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly)
if err != nil {
t.Fatalf(err.Error())
} else if len(out) != 0 {
t.Fatalf("require expected output len of 0, got %v", len(out))
}
hash = verifyCiphertextInTestMemory(environment, 1, depth, FheUint8).getHash()
out, err = optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly)
if err != nil {
t.Fatalf(err.Error())
} else if len(out) != 0 {
t.Fatalf("require expected output len of 0, got %v", len(out))
}
interpreter := newInterpreterFromEnvironment(environment)
// Call the interpreter with a single STOP opcode and expect that the optimistic require reverts.
out, err = interpreterRunWithStopContract(environment, interpreter, newStopOpcodeContract(), make([]byte, 0), readOnly)
if err == nil || err != ErrExecutionReverted {
t.Fatalf("require expected reversal on value 0")
} else if out != nil {
t.Fatalf("expected empty response")
}
}

func TestDecryptWithFalseOptimisticRequire(t *testing.T) {
depth := 0
environment := newTestEVMEnvironment()
environment.depth = depth
addr := common.Address{}
readOnly := false
// Call optimistic require with a false value and expect it succeeds.
hash := verifyCiphertextInTestMemory(environment, 0, depth, FheUint8).getHash()
out, err := optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly)
if err != nil {
t.Fatalf(err.Error())
} else if len(out) != 0 {
t.Fatalf("require expected output len of 0, got %v", len(out))
}
// Call decrypt and expect it to fail due to the optimistic require being false.
_, err = decryptRun(environment, addr, addr, hash.Bytes(), readOnly)
if err == nil {
t.Fatalf("expected decrypt fails due to false optimistic require")
}
// Make sure there are no more optimistic requires after the decrypt call.
if len(environment.FhevmData().optimisticRequires) != 0 {
t.Fatalf("expected that there are no optimistic requires after decrypt")
}
}

func TestDecryptWithTrueOptimisticRequire(t *testing.T) {
depth := 0
environment := newTestEVMEnvironment()
environment.depth = depth
addr := common.Address{}
readOnly := false
// Call optimistic require with a false value and expect it succeeds.
hash := verifyCiphertextInTestMemory(environment, 1, depth, FheUint8).getHash()
out, err := optimisticRequireRun(environment, addr, addr, hash.Bytes(), readOnly)
if err != nil {
t.Fatalf(err.Error())
} else if len(out) != 0 {
t.Fatalf("require expected output len of 0, got %v", len(out))
}
// Call decrypt and expect it to succeed due to the optimistic require being true.
out, err = decryptRun(environment, addr, addr, hash.Bytes(), readOnly)
if err != nil {
t.Fatalf(err.Error())
} else if len(out) != 32 {
t.Fatalf("decrypt expected output len of 32, got %v", len(out))
}
// Make sure there are no more optimistic requires after the decrypt call.
if len(environment.FhevmData().optimisticRequires) != 0 {
t.Fatalf("expected that there are no optimistic requires after decrypt")
}
}
23 changes: 22 additions & 1 deletion fhevm/interpreter.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package fhevm

import "github.com/ethereum/go-ethereum/common"
import (
"github.com/ethereum/go-ethereum/common"
)

type ScopeContext interface {
GetMemory() Memory
Expand Down Expand Up @@ -60,3 +62,22 @@ var PrivilegedMempory *PrivilegedMemory = &PrivilegedMemory{
make(map[common.Hash]*verifiedCiphertext),
make([]*tfheCiphertext, 0),
}

// Evaluate remaining optimistic requires when Interpreter.Run get an errStopToken
//
// This function is meant to be integrated as part of vm.EVMInterpreter.Run in the case
// there was an errStopToken
func EvalRemOptReqWhenStopToken(env EVMEnvironment) (err error) {
err = nil
// If we are finishing execution (about to go to from depth 1 to depth 0), evaluate
// any remaining optimistic requires.
if env.GetDepth() == 1 {
result, evalErr := evaluateRemainingOptimisticRequires(env)
if evalErr != nil {
err = evalErr
} else if !result {
err = ErrExecutionReverted
}
}
return err
}
2 changes: 1 addition & 1 deletion fhevm/precompiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -2003,7 +2003,7 @@ func decryptValue(ct *tfheCiphertext) (uint64, error) {
func evaluateRemainingOptimisticRequires(environment EVMEnvironment) (bool, error) {
requires := environment.FhevmData().optimisticRequires
len := len(requires)
defer func() { requires = make([]*tfheCiphertext, 0) }()
defer func() { environment.FhevmData().optimisticRequires = make([]*tfheCiphertext, 0) }()
if len != 0 {
var cumulative *tfheCiphertext = requires[0]
var err error
Expand Down