Skip to content

Commit

Permalink
Report computation for EVM.encodeABI while computing it
Browse files Browse the repository at this point in the history
  • Loading branch information
m-Peter committed Dec 15, 2023
1 parent bc736dd commit 7863c84
Show file tree
Hide file tree
Showing 2 changed files with 248 additions and 25 deletions.
64 changes: 44 additions & 20 deletions fvm/evm/stdlib/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,53 +99,75 @@ func (e abiDecodingError) Error() string {
return b.String()
}

func calculateComputation(
func reportABIEncodingComputation(
inter *interpreter.Interpreter,
values *interpreter.ArrayValue,
evmAddressTypeID common.TypeID,
) uint {
computation := uint(0)

reportComputation func(compKind common.ComputationKind, intensity uint),
) {
values.Iterate(inter, func(element interpreter.Value) (resume bool) {
switch value := element.(type) {
case *interpreter.StringValue:
// Dynamic variables, such as strings, are encoded
// in 3 chunks of 32 bytes. The first chunk contains
// in 2+ chunks of 32 bytes. The first chunk contains
// the index where information for the string begin,
// the second chunk contains the number of bytes the
// string occupies, and the third chunk contains the
// value of the string itself.
computation += 2 * abiEncodingByteSize
computation := uint(2 * abiEncodingByteSize)
stringLength := len(value.Str)
chunks := stringLength / abiEncodingByteSize
remainder := stringLength % abiEncodingByteSize
if remainder > 0 {
chunks += 1
}
computation += uint(chunks * abiEncodingByteSize)
reportComputation(environment.ComputationKindEVMEncodeABI, computation)
case interpreter.BoolValue,
interpreter.UInt8Value,
interpreter.UInt16Value,
interpreter.UInt32Value,
interpreter.UInt64Value,
interpreter.UInt128Value,
interpreter.UInt256Value,
interpreter.Int8Value,
interpreter.Int16Value,
interpreter.Int32Value,
interpreter.Int64Value,
interpreter.Int128Value,
interpreter.Int256Value:
// Numeric and bool variables are also static variables
// with a fixed size of 32 bytes.
reportComputation(environment.ComputationKindEVMEncodeABI, abiEncodingByteSize)
case *interpreter.CompositeValue:
if value.TypeID() == evmAddressTypeID {
// EVM addresses are static variables with a fixed
// size of 32 bytes.
computation += abiEncodingByteSize
reportComputation(environment.ComputationKindEVMEncodeABI, abiEncodingByteSize)
} else {
panic(abiEncodingError{
Type: value.StaticType(inter),
})
}
case *interpreter.ArrayValue:
// Dynamic variables, such as arrays and slices, encode
// an extra chunk of 32 bytes, for each element, except
// for the last one.
computation += uint((value.Count()-1)*abiEncodingByteSize) +
calculateComputation(inter, value, evmAddressTypeID)
// Dynamic variables, such as arrays & slices, are encoded
// in 2+ chunks of 32 bytes. The first chunk contains
// the index where information for the array begin,
// the second chunk contains the number of bytes the
// array occupies, and the third chunk contains the
// values of the array itself.
computation := uint(2 * abiEncodingByteSize)
reportComputation(environment.ComputationKindEVMEncodeABI, computation)
reportABIEncodingComputation(inter, value, evmAddressTypeID, reportComputation)
default:
// Numeric and bool variables are also static variables
// with a fixed size of 32 bytes.
computation += abiEncodingByteSize
panic(abiEncodingError{
Type: element.StaticType(inter),
})
}

// continue iteration
return true
})

return computation
}

// EVM.encodeABI
Expand Down Expand Up @@ -186,9 +208,11 @@ func newInternalEVMTypeEncodeABIFunction(
panic(errors.NewUnreachableError())
}

invocation.Interpreter.ReportComputation(
environment.ComputationKindEVMEncodeABI,
calculateComputation(inter, valuesArray, evmAddressTypeID),
reportABIEncodingComputation(
inter,
valuesArray,
evmAddressTypeID,
invocation.Interpreter.ReportComputation,
)

size := valuesArray.Count()
Expand Down
209 changes: 204 additions & 5 deletions fvm/evm/stdlib/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ func TestEVMEncodeABI(t *testing.T) {
},
OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error {
if compKind == environment.ComputationKindEVMEncodeABI {
computation = intensity
computation += intensity
}
return nil
},
Expand Down Expand Up @@ -415,7 +415,206 @@ func TestEVMEncodeABIComputation(t *testing.T) {
},
OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error {
if compKind == environment.ComputationKindEVMEncodeABI {
computation = intensity
computation += intensity
}
return nil
},
}

nextTransactionLocation := NewTransactionLocationGenerator()
nextScriptLocation := NewScriptLocationGenerator()

// Deploy contracts

deployContracts(
t,
rt,
contractsAddress,
runtimeInterface,
transactionEnvironment,
nextTransactionLocation,
)

// Run script

result, err := rt.ExecuteScript(
runtime.Script{
Source: script,
Arguments: [][]byte{},
},
runtime.Context{
Interface: runtimeInterface,
Environment: scriptEnvironment,
Location: nextScriptLocation(),
},
)
require.NoError(t, err)

cdcBytes, ok := result.(cadence.Array)
require.True(t, ok)
// computation & len(cdcBytes.Values) is equal to 832
assert.Equal(t, computation, uint(len(cdcBytes.Values)))
}

func TestEVMEncodeABIComputationEmptyDynamicVariables(t *testing.T) {

t.Parallel()

handler := &testContractHandler{}

contractsAddress := flow.BytesToAddress([]byte{0x1})

transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress)
scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress)

rt := runtime.NewInterpreterRuntime(runtime.Config{})

script := []byte(`
import EVM from 0x1
access(all)
fun main(): [UInt8] {
return EVM.encodeABI([
"",
[[""], [] as [String]],
[] as [UInt8],
["", "", ""]
])
}
`)

accountCodes := map[common.Location][]byte{}
var events []cadence.Event

computation := uint(0)
runtimeInterface := &TestRuntimeInterface{
Storage: NewTestLedger(nil, nil),
OnGetSigningAccounts: func() ([]runtime.Address, error) {
return []runtime.Address{runtime.Address(contractsAddress)}, nil
},
OnResolveLocation: SingleIdentifierLocationResolver(t),
OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error {
accountCodes[location] = code
return nil
},
OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) {
code = accountCodes[location]
return code, nil
},
OnEmitEvent: func(event cadence.Event) error {
events = append(events, event)
return nil
},
OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) {
return json.Decode(nil, b)
},
OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error {
if compKind == environment.ComputationKindEVMEncodeABI {
computation += intensity
}
return nil
},
}

nextTransactionLocation := NewTransactionLocationGenerator()
nextScriptLocation := NewScriptLocationGenerator()

// Deploy contracts

deployContracts(
t,
rt,
contractsAddress,
runtimeInterface,
transactionEnvironment,
nextTransactionLocation,
)

// Run script

result, err := rt.ExecuteScript(
runtime.Script{
Source: script,
Arguments: [][]byte{},
},
runtime.Context{
Interface: runtimeInterface,
Environment: scriptEnvironment,
Location: nextScriptLocation(),
},
)
require.NoError(t, err)

cdcBytes, ok := result.(cadence.Array)
require.True(t, ok)
// computation & len(cdcBytes.Values) is equal to 832
assert.Equal(t, computation, uint(len(cdcBytes.Values)))
}

func TestEVMEncodeABIComputationDynamicVariablesAboveChunkSize(t *testing.T) {

t.Parallel()

handler := &testContractHandler{}

contractsAddress := flow.BytesToAddress([]byte{0x1})

transactionEnvironment := newEVMTransactionEnvironment(handler, contractsAddress)
scriptEnvironment := newEVMScriptEnvironment(handler, contractsAddress)

rt := runtime.NewInterpreterRuntime(runtime.Config{})

script := []byte(`
import EVM from 0x1
access(all)
fun main(): [UInt8] {
let str = "abcdefghijklmnopqrstuvwxyz"
let arr: [UInt64] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53
]
return EVM.encodeABI([
str,
str.concat(str).concat(str),
[[str]],
arr,
[arr],
arr.concat(arr).concat(arr)
])
}
`)

accountCodes := map[common.Location][]byte{}
var events []cadence.Event

computation := uint(0)
runtimeInterface := &TestRuntimeInterface{
Storage: NewTestLedger(nil, nil),
OnGetSigningAccounts: func() ([]runtime.Address, error) {
return []runtime.Address{runtime.Address(contractsAddress)}, nil
},
OnResolveLocation: SingleIdentifierLocationResolver(t),
OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error {
accountCodes[location] = code
return nil
},
OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) {
code = accountCodes[location]
return code, nil
},
OnEmitEvent: func(event cadence.Event) error {
events = append(events, event)
return nil
},
OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) {
return json.Decode(nil, b)
},
OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error {
if compKind == environment.ComputationKindEVMEncodeABI {
computation += intensity
}
return nil
},
Expand Down Expand Up @@ -513,7 +712,7 @@ func TestEVMDecodeABI(t *testing.T) {
},
OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error {
if compKind == environment.ComputationKindEVMDecodeABI {
computation = intensity
computation += intensity
}
return nil
},
Expand Down Expand Up @@ -647,7 +846,7 @@ func TestEVMDecodeABIComputation(t *testing.T) {
},
OnMeterComputation: func(compKind common.ComputationKind, intensity uint) error {
if compKind == environment.ComputationKindEVMDecodeABI {
computation = intensity
computation += intensity
}
return nil
},
Expand Down Expand Up @@ -1304,7 +1503,7 @@ func TestEVMEncodeDecodeABIErrors(t *testing.T) {
assert.ErrorContains(
t,
err,
"failed to ABI encode value of type [Character]",
"failed to ABI encode value of type Character",
)
})

Expand Down

0 comments on commit 7863c84

Please sign in to comment.