Skip to content

Commit

Permalink
LFVM: Add test for log operation (#856)
Browse files Browse the repository at this point in the history
* Add test for log operation

* remove old log test

new test covers error reporting, expansion errors are off-loaded to memory testing, new test tests arguments passed to emitLog and size cost

* Apply review comments

- test log topics order
- test mem copied
  • Loading branch information
LuisPH3 authored Oct 14, 2024
1 parent 9af1426 commit 86b61f7
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 60 deletions.
7 changes: 5 additions & 2 deletions go/interpreter/lfvm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -1143,9 +1143,12 @@ func opLog(c *context, n int) error {
return errStaticContextViolation
}

topics := make([]tosca.Hash, n)
offset, size := c.stack.pop(), c.stack.pop()
var (
offset = c.stack.pop()
size = c.stack.pop()
)

topics := make([]tosca.Hash, n)
for i := 0; i < n; i++ {
addr := c.stack.pop()
topics[i] = addr.Bytes32()
Expand Down
130 changes: 72 additions & 58 deletions go/interpreter/lfvm/instructions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"crypto/rand"
"fmt"
"math"
"slices"
"testing"

"github.com/Fantom-foundation/Tosca/go/tosca"
Expand Down Expand Up @@ -281,64 +282,6 @@ func TestCreateChecksBalance(t *testing.T) {
}
}

func TestLogOpSizeOverflow(t *testing.T) {

originalBugValue := uint256.MustFromHex("0x3030303030303030")
maxUint64 := uint256.NewInt(math.MaxUint64)
zero := uint256.NewInt(0)

tests := map[string]struct {
logn int
size *uint256.Int
logCalls int
expectedError error
}{
"log0_zero": {logn: 0, size: zero, logCalls: 1, expectedError: nil},
"log1_zero": {logn: 1, size: zero, logCalls: 1, expectedError: nil},
"log2_zero": {logn: 2, size: zero, logCalls: 1, expectedError: nil},
"log3_zero": {logn: 3, size: zero, logCalls: 1, expectedError: nil},
"log4_zero": {logn: 4, size: zero, logCalls: 1, expectedError: nil},
"log0_max": {logn: 0, size: maxUint64, logCalls: 0, expectedError: errOverflow},
"log1_max": {logn: 1, size: maxUint64, logCalls: 0, expectedError: errOverflow},
"log2_max": {logn: 2, size: maxUint64, logCalls: 0, expectedError: errOverflow},
"log3_max": {logn: 3, size: maxUint64, logCalls: 0, expectedError: errOverflow},
"log4_max": {logn: 4, size: maxUint64, logCalls: 0, expectedError: errOverflow},
"log0_much_larger": {logn: 0, size: originalBugValue, logCalls: 0, expectedError: errMaxMemoryExpansionSize},
"log1_much_larger": {logn: 1, size: originalBugValue, logCalls: 0, expectedError: errMaxMemoryExpansionSize},
"log2_much_larger": {logn: 2, size: originalBugValue, logCalls: 0, expectedError: errMaxMemoryExpansionSize},
"log3_much_larger": {logn: 3, size: originalBugValue, logCalls: 0, expectedError: errMaxMemoryExpansionSize},
"log4_much_larger": {logn: 4, size: originalBugValue, logCalls: 0, expectedError: errMaxMemoryExpansionSize},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {

ctrl := gomock.NewController(t)
runContext := tosca.NewMockRunContext(ctrl)
runContext.EXPECT().EmitLog(gomock.Any()).Times(test.logCalls)

stack := NewStack()
for i := 0; i < test.logn; i++ {
stack.push(uint256.NewInt(0))
}
stack.push(test.size)
stack.push(uint256.NewInt(0))

ctxt := context{
gas: 392,
context: runContext,
stack: stack,
memory: NewMemory(),
}

err := opLog(&ctxt, test.logn)
if got, want := err, test.expectedError; got != want {
t.Fatalf("unexpected result, wanted %v, got %v", want, got)
}
})
}
}

func TestBlobHash(t *testing.T) {

hash := tosca.Hash{1}
Expand Down Expand Up @@ -2036,6 +1979,77 @@ func TestOpExtCodeHash_WritesHashOnStackIfAccountExists(t *testing.T) {
}
}

func TestInstructions_opLog(t *testing.T) {

tests := map[string]struct {
offset, size *uint256.Int
expectedError error
reduceGas uint64
}{
"returns error if memory expansion fails": {
offset: uint256.NewInt(math.MaxUint64), size: uint256.NewInt(1),
expectedError: errOverflow,
},
"returns error if word gas cost is more than available gas": {
offset: uint256.NewInt(10), size: uint256.NewInt(128),
reduceGas: 1,
expectedError: errOutOfGas,
},
"calls emitLog with recipient, defined order of topics and memory copy": {
offset: uint256.NewInt(1), size: uint256.NewInt(2),
},
}
for name, test := range tests {
for n := 0; n < 4; n++ {
t.Run(fmt.Sprintf("%v/LOG%d", name, n), func(t *testing.T) {

ctxt := getEmptyContext()
for i := n - 1; i >= 0; i-- {
ctxt.stack.push(uint256.NewInt(uint64(i)))
}
ctxt.stack.push(test.size)
ctxt.stack.push(test.offset)
ctxt.gas = tosca.Gas(test.size.Uint64()*8 - test.reduceGas)
ctxt.params.Recipient = tosca.Address{1}
// ignore the expansion error to focus on log operation
// this expansion is done to remove expansion costs (if any) and
// test word count cost only.
memoryContents := []byte{0, 1, 2, 3}
_ = ctxt.memory.set(uint256.NewInt(0), memoryContents, &context{gas: math.MaxInt64})

if test.expectedError == nil {
runContext := tosca.NewMockRunContext(gomock.NewController(t))
runContext.EXPECT().EmitLog(gomock.Any()).Do(func(log tosca.Log) {
if want, got := ctxt.params.Recipient, log.Address; want != got {
t.Errorf("unexpected log address, wanted %v, got %v", want, got)
}
if want, got := n, len(log.Topics); want != got {
t.Errorf("unexpected number of topics, wanted %v, got %v", want, got)
}

for i := n; i > n; i++ {
if want, got := tosca.Hash(uint256.NewInt(uint64(i)).Bytes32()), log.Topics[i]; want != got {
t.Errorf("unexpected topic #%d, wanted %v, got %v", i, want, got)
}
}
from := test.offset.Uint64()
to := from + test.size.Uint64()
if want, got := memoryContents[from:to], log.Data; !slices.Equal(want, got) {
t.Errorf("unexpected log data, wanted %v,got %v", want, got)
}
})
ctxt.context = runContext
}

err := opLog(&ctxt, n)
if want, got := test.expectedError, err; want != got {
t.Fatalf("unexpected error, wanted %v, got %v", want, got)
}
})
}
}
}

////////////////////////////////////////////////////////////////////////////////
// Helper functions

Expand Down

0 comments on commit 86b61f7

Please sign in to comment.