Skip to content
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
37 changes: 33 additions & 4 deletions fvm/evm/impl/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ func gethABIType(
}

func goType(
context abiEncodingContext,
staticType interpreter.StaticType,
evmTypeIDs *evmSpecialTypeIDs,
) (reflect.Type, bool) {
Expand Down Expand Up @@ -558,15 +559,15 @@ func goType(

switch staticType := staticType.(type) {
case *interpreter.ConstantSizedStaticType:
elementType, ok := goType(staticType.ElementType(), evmTypeIDs)
elementType, ok := goType(context, staticType.ElementType(), evmTypeIDs)
if !ok {
break
}

return reflect.ArrayOf(int(staticType.Size), elementType), true

case *interpreter.VariableSizedStaticType:
elementType, ok := goType(staticType.ElementType(), evmTypeIDs)
elementType, ok := goType(context, staticType.ElementType(), evmTypeIDs)
if !ok {
break
}
Expand All @@ -585,6 +586,22 @@ func goType(
return reflect.ArrayOf(stdlib.EVMBytes32Length, reflect.TypeOf(byte(0))), true
}

gethABIType, ok := gethABIType(
context,
staticType,
evmTypeIDs,
)
// All user-defined Cadence structs, are ABI encoded/decoded as Solidity tuples.
// Except for the structs defined in the EVM system contract:
// - `EVM.EVMAddress`
// - `EVM.EVMBytes`
// - `EVM.EVMBytes4`
// - `EVM.EVMBytes32`
// These have their own ABI encoding/decoding format.
if ok && gethABIType.T == gethABI.TupleTy {
return gethABIType.TupleType, true
}

return nil, false
}

Expand Down Expand Up @@ -793,7 +810,7 @@ func encodeABI(

elementStaticType := arrayStaticType.ElementType()

elementGoType, ok := goType(elementStaticType, evmTypeIDs)
elementGoType, ok := goType(context, elementStaticType, evmTypeIDs)
if !ok {
break
}
Expand All @@ -810,6 +827,9 @@ func encodeABI(
result = reflect.MakeSlice(reflect.SliceOf(elementGoType), size, size)
}

semaType := interpreter.MustConvertStaticToSemaType(elementStaticType, context)
isTuple := asTupleEncodableCompositeType(semaType) != nil

var index int
value.Iterate(
context,
Expand All @@ -825,7 +845,16 @@ func encodeABI(
panic(err)
}

result.Index(index).Set(reflect.ValueOf(arrayElement))
if isTuple {
// For tuples, the underlying `arrayElement` is a value of
// type *struct { X,Y,Z fields }, so we need to indirect
// the pointer
result.Index(index).Set(
reflect.Indirect(reflect.ValueOf(arrayElement)),
)
} else {
result.Index(index).Set(reflect.ValueOf(arrayElement))
}

index++

Expand Down
68 changes: 68 additions & 0 deletions fvm/evm/stdlib/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,74 @@ func TestEVMEncodeABIBytesRoundtrip(t *testing.T) {

assert.Equal(t, uint64(64), gauge.TotalComputationUsed())
})

t.Run("ABI encode array of structs into tuple Solidity type", func(t *testing.T) {
script := []byte(`
import EVM from 0x1

access(all)
struct S {
access(all) let x: UInt8
access(all) let y: Int16

init(x: UInt8, y: Int16) {
self.x = x
self.y = y
}

access(all) fun toString(): String {
return "S(x: \(self.x), y: \(self.y))"
}
}

access(all)
fun main() {
let s1 = S(x: 4, y: 2)
let s2 = S(x: 5, y: 9)
let structArray = [s1, s2]
let encodedData = EVM.encodeABI([structArray])
assert(encodedData == [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x20,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x4,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x9
], message: String.encodeHex(encodedData))

let values = EVM.decodeABI(types: [Type<[S]>()], data: encodedData)
assert(values.length == 1)
let decodedStructArray = values[0] as! [S]
assert(decodedStructArray.length == 2)

assert(decodedStructArray[0].x == 4)
assert(decodedStructArray[0].y == 2)
assert(decodedStructArray[1].x == 5)
assert(decodedStructArray[1].y == 9)
}
`)

gauge := meter.NewMeter(meter.DefaultParameters().WithComputationWeights(meter.ExecutionEffortWeights{
environment.ComputationKindEVMEncodeABI: 1 << meter.MeterExecutionInternalPrecisionBytes,
}))

// Run script
_, err := rt.ExecuteScript(
runtime.Script{
Source: script,
},
runtime.Context{
Interface: runtimeInterface,
Environment: scriptEnvironment,
Location: nextScriptLocation(),
MemoryGauge: gauge,
ComputationGauge: gauge,
},
)
require.NoError(t, err)

assert.Equal(t, uint64(192), gauge.TotalComputationUsed())
})
}

func TestEVMEncodeABIComputation(t *testing.T) {
Expand Down
Loading