Skip to content

Commit 9846bf6

Browse files
committed
Implement ABI encoding/decoding for arrays of Solidity tuples
1 parent cc0505f commit 9846bf6

File tree

2 files changed

+105
-4
lines changed

2 files changed

+105
-4
lines changed

fvm/evm/impl/abi.go

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ func gethABIType(
516516
}
517517

518518
func goType(
519+
context abiEncodingContext,
519520
staticType interpreter.StaticType,
520521
evmTypeIDs *evmSpecialTypeIDs,
521522
) (reflect.Type, bool) {
@@ -558,15 +559,15 @@ func goType(
558559

559560
switch staticType := staticType.(type) {
560561
case *interpreter.ConstantSizedStaticType:
561-
elementType, ok := goType(staticType.ElementType(), evmTypeIDs)
562+
elementType, ok := goType(context, staticType.ElementType(), evmTypeIDs)
562563
if !ok {
563564
break
564565
}
565566

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

568569
case *interpreter.VariableSizedStaticType:
569-
elementType, ok := goType(staticType.ElementType(), evmTypeIDs)
570+
elementType, ok := goType(context, staticType.ElementType(), evmTypeIDs)
570571
if !ok {
571572
break
572573
}
@@ -585,6 +586,26 @@ func goType(
585586
return reflect.ArrayOf(stdlib.EVMBytes32Length, reflect.TypeOf(byte(0))), true
586587
}
587588

589+
// This check for Cadence structs, has to be after the above checks,
590+
// which are also structs defined in the EVM system contract:
591+
// - `EVM.EVMAddress`
592+
// - `EVM.EVMBytes`
593+
// - `EVM.EVMBytes4`
594+
// - `EVM.EVMBytes32`
595+
semaType := interpreter.MustConvertStaticToSemaType(staticType, context)
596+
if compositeType := asTupleEncodableCompositeType(semaType); compositeType != nil {
597+
tupleGethABIType, ok := gethABIType(
598+
context,
599+
staticType,
600+
evmTypeIDs,
601+
)
602+
if !ok {
603+
return nil, false
604+
}
605+
606+
return tupleGethABIType.TupleType, true
607+
}
608+
588609
return nil, false
589610
}
590611

@@ -793,7 +814,7 @@ func encodeABI(
793814

794815
elementStaticType := arrayStaticType.ElementType()
795816

796-
elementGoType, ok := goType(elementStaticType, evmTypeIDs)
817+
elementGoType, ok := goType(context, elementStaticType, evmTypeIDs)
797818
if !ok {
798819
break
799820
}
@@ -810,6 +831,9 @@ func encodeABI(
810831
result = reflect.MakeSlice(reflect.SliceOf(elementGoType), size, size)
811832
}
812833

834+
semaType := interpreter.MustConvertStaticToSemaType(elementStaticType, context)
835+
isTuple := asTupleEncodableCompositeType(semaType) != nil
836+
813837
var index int
814838
value.Iterate(
815839
context,
@@ -825,7 +849,16 @@ func encodeABI(
825849
panic(err)
826850
}
827851

828-
result.Index(index).Set(reflect.ValueOf(arrayElement))
852+
if isTuple {
853+
// For tuples, the underlying `arrayElement` is a value of
854+
// type *struct { X,Y,Z fields }, so we need to indirect
855+
// the pointer
856+
result.Index(index).Set(
857+
reflect.Indirect(reflect.ValueOf(arrayElement)),
858+
)
859+
} else {
860+
result.Index(index).Set(reflect.ValueOf(arrayElement))
861+
}
829862

830863
index++
831864

fvm/evm/stdlib/contract_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,74 @@ func TestEVMEncodeABIBytesRoundtrip(t *testing.T) {
12891289

12901290
assert.Equal(t, uint64(64), gauge.TotalComputationUsed())
12911291
})
1292+
1293+
t.Run("ABI encode array of structs into tuple Solidity type", func(t *testing.T) {
1294+
script := []byte(`
1295+
import EVM from 0x1
1296+
1297+
access(all)
1298+
struct S {
1299+
access(all) let x: UInt8
1300+
access(all) let y: Int16
1301+
1302+
init(x: UInt8, y: Int16) {
1303+
self.x = x
1304+
self.y = y
1305+
}
1306+
1307+
access(all) fun toString(): String {
1308+
return "S(x: \(self.x), y: \(self.y))"
1309+
}
1310+
}
1311+
1312+
access(all)
1313+
fun main() {
1314+
let s1 = S(x: 4, y: 2)
1315+
let s2 = S(x: 5, y: 9)
1316+
let structArray = [s1, s2]
1317+
let encodedData = EVM.encodeABI([structArray])
1318+
assert(encodedData == [
1319+
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,
1320+
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,
1321+
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,
1322+
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,
1323+
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,
1324+
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
1325+
], message: String.encodeHex(encodedData))
1326+
1327+
let values = EVM.decodeABI(types: [Type<[S]>()], data: encodedData)
1328+
assert(values.length == 1)
1329+
let decodedStructArray = values[0] as! [S]
1330+
assert(decodedStructArray.length == 2)
1331+
1332+
assert(decodedStructArray[0].x == 4)
1333+
assert(decodedStructArray[0].y == 2)
1334+
assert(decodedStructArray[1].x == 5)
1335+
assert(decodedStructArray[1].y == 9)
1336+
}
1337+
`)
1338+
1339+
gauge := meter.NewMeter(meter.DefaultParameters().WithComputationWeights(meter.ExecutionEffortWeights{
1340+
environment.ComputationKindEVMEncodeABI: 1 << meter.MeterExecutionInternalPrecisionBytes,
1341+
}))
1342+
1343+
// Run script
1344+
_, err := rt.ExecuteScript(
1345+
runtime.Script{
1346+
Source: script,
1347+
},
1348+
runtime.Context{
1349+
Interface: runtimeInterface,
1350+
Environment: scriptEnvironment,
1351+
Location: nextScriptLocation(),
1352+
MemoryGauge: gauge,
1353+
ComputationGauge: gauge,
1354+
},
1355+
)
1356+
require.NoError(t, err)
1357+
1358+
assert.Equal(t, uint64(192), gauge.TotalComputationUsed())
1359+
})
12921360
}
12931361

12941362
func TestEVMEncodeABIComputation(t *testing.T) {

0 commit comments

Comments
 (0)