Skip to content

Commit

Permalink
Introduce the EVM.dryCall method
Browse files Browse the repository at this point in the history
This new method can be used instead of COA.call, in a Cadence transaction,
when the users wants to execute a contract call, without committing any
state changes. This is useful for reading a contract's state.
  • Loading branch information
m-Peter committed Dec 27, 2024
1 parent 941178d commit efd6cc6
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 0 deletions.
92 changes: 92 additions & 0 deletions fvm/evm/impl/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"github.com/onflow/flow-go/fvm/evm/stdlib"
"github.com/onflow/flow-go/fvm/evm/types"
"github.com/onflow/flow-go/model/flow"

gethTypes "github.com/onflow/go-ethereum/core/types"
)

var internalEVMContractStaticType = interpreter.ConvertSemaCompositeTypeToStaticCompositeType(
Expand Down Expand Up @@ -50,6 +52,7 @@ func NewInternalEVMContractValue(
stdlib.InternalEVMTypeCastToFLOWFunctionName: newInternalEVMTypeCastToFLOWFunction(gauge),
stdlib.InternalEVMTypeGetLatestBlockFunctionName: newInternalEVMTypeGetLatestBlockFunction(gauge, handler),
stdlib.InternalEVMTypeDryRunFunctionName: newInternalEVMTypeDryRunFunction(gauge, handler),
stdlib.InternalEVMTypeDryCallFunctionName: newInternalEVMTypeDryCallFunction(gauge, handler),
stdlib.InternalEVMTypeCommitBlockProposalFunctionName: newInternalEVMTypeCommitBlockProposalFunction(gauge, handler),
},
nil,
Expand Down Expand Up @@ -895,6 +898,95 @@ func newInternalEVMTypeDryRunFunction(
)
}

func newInternalEVMTypeDryCallFunction(
gauge common.MemoryGauge,
handler types.ContractHandler,
) *interpreter.HostFunctionValue {
return interpreter.NewStaticHostFunctionValue(
gauge,
stdlib.InternalEVMTypeDryCallFunctionType,
func(invocation interpreter.Invocation) interpreter.Value {
inter := invocation.Interpreter
locationRange := invocation.LocationRange

// Get from address

fromAddressValue, ok := invocation.Arguments[0].(*interpreter.ArrayValue)
if !ok {
panic(errors.NewUnreachableError())
}

fromAddress, err := AddressBytesArrayValueToEVMAddress(inter, locationRange, fromAddressValue)
if err != nil {
panic(err)
}

// Get to address

toAddressValue, ok := invocation.Arguments[1].(*interpreter.ArrayValue)
if !ok {
panic(errors.NewUnreachableError())
}

toAddress, err := AddressBytesArrayValueToEVMAddress(inter, locationRange, toAddressValue)
if err != nil {
panic(err)
}

to := toAddress.ToCommon()

// Get data

dataValue, ok := invocation.Arguments[2].(*interpreter.ArrayValue)
if !ok {
panic(errors.NewUnreachableError())
}

data, err := interpreter.ByteArrayValueToByteSlice(inter, dataValue, locationRange)
if err != nil {
panic(err)
}

// Get gas limit

gasLimitValue, ok := invocation.Arguments[3].(interpreter.UInt64Value)
if !ok {
panic(errors.NewUnreachableError())
}

gasLimit := types.GasLimit(gasLimitValue)

// Get balance

balanceValue, ok := invocation.Arguments[4].(interpreter.UIntValue)
if !ok {
panic(errors.NewUnreachableError())
}

balance := types.NewBalance(balanceValue.BigInt)

tx := gethTypes.NewTx(&gethTypes.LegacyTx{
Nonce: 0,
To: &to,
Gas: uint64(gasLimit),
Data: data,
GasPrice: big.NewInt(0),
Value: balance,
})

txPayload, err := tx.MarshalBinary()
if err != nil {
panic(err)
}

// call contract function

res := handler.DryRun(txPayload, fromAddress)
return NewResultValue(handler, gauge, inter, locationRange, res)
},
)
}

func newInternalEVMTypeBatchRunFunction(
gauge common.MemoryGauge,
handler types.ContractHandler,
Expand Down
20 changes: 20 additions & 0 deletions fvm/evm/stdlib/contract.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,26 @@ contract EVM {
) as! Result
}

/// Calls a contract function with the given data.
/// The execution is limited by the given amount of gas.
/// The transaction state changes are not persisted.
access(all)
fun dryCall(
from: EVMAddress,
to: EVMAddress,
data: [UInt8],
gasLimit: UInt64,
value: Balance,
): Result {
return InternalEVM.dryCall(
from: from.bytes,
to: to.bytes,
data: data,
gasLimit: gasLimit,
value: value.attoflow
) as! Result
}

/// Runs a batch of RLP-encoded EVM transactions, deducts the gas fees,
/// and deposits the gas fees into the provided coinbase address.
/// An invalid transaction is not executed and not included in the block.
Expand Down
37 changes: 37 additions & 0 deletions fvm/evm/stdlib/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,37 @@ var InternalEVMTypeDryRunFunctionType = &sema.FunctionType{
ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.AnyStructType),
}

// InternalEVM.dryCall

const InternalEVMTypeDryCallFunctionName = "dryCall"

var InternalEVMTypeDryCallFunctionType = &sema.FunctionType{
Parameters: []sema.Parameter{
{
Label: "from",
TypeAnnotation: sema.NewTypeAnnotation(EVMAddressBytesType),
},
{
Label: "to",
TypeAnnotation: sema.NewTypeAnnotation(EVMAddressBytesType),
},
{
Label: "data",
TypeAnnotation: sema.NewTypeAnnotation(sema.ByteArrayType),
},
{
Label: "gasLimit",
TypeAnnotation: sema.NewTypeAnnotation(sema.UInt64Type),
},
{
Label: "value",
TypeAnnotation: sema.NewTypeAnnotation(sema.UIntType),
},
},
// Actually EVM.Result, but cannot refer to it here
ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.AnyStructType),
}

// InternalEVM.batchRun

const InternalEVMTypeBatchRunFunctionName = "batchRun"
Expand Down Expand Up @@ -434,6 +465,12 @@ var InternalEVMContractType = func() *sema.CompositeType {
InternalEVMTypeDryRunFunctionType,
"",
),
sema.NewUnmeteredPublicFunctionMember(
ty,
InternalEVMTypeDryCallFunctionName,
InternalEVMTypeDryCallFunctionType,
"",
),
sema.NewUnmeteredPublicFunctionMember(
ty,
InternalEVMTypeBatchRunFunctionName,
Expand Down

0 comments on commit efd6cc6

Please sign in to comment.