Skip to content

Commit

Permalink
WIP: Implement RemoteCadenceArch type for fetching precompiled calls …
Browse files Browse the repository at this point in the history
…in contract calls
  • Loading branch information
m-Peter committed Nov 5, 2024
1 parent c4b329b commit b27b097
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 7 deletions.
154 changes: 154 additions & 0 deletions services/requester/remote_cadence_arch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package requester

import (
"context"
"encoding/hex"
"fmt"
"math/big"
"strings"

"github.com/onflow/cadence"
errs "github.com/onflow/flow-evm-gateway/models/errors"
evmImpl "github.com/onflow/flow-go/fvm/evm/impl"
evmTypes "github.com/onflow/flow-go/fvm/evm/types"
"github.com/onflow/flow-go/fvm/systemcontracts"
flowGo "github.com/onflow/flow-go/model/flow"
gethCommon "github.com/onflow/go-ethereum/common"
"github.com/onflow/go-ethereum/core/types"
"github.com/onflow/go-ethereum/crypto"
)

var cadenceArchAddress = gethCommon.HexToAddress("0x0000000000000000000000010000000000000001")

type RemoteCadenceArch struct {
blockHeight uint64
client *CrossSporkClient
chainID flowGo.ChainID
cachedCalls map[string]evmTypes.Data
}

var _ evmTypes.PrecompiledContract = (*RemoteCadenceArch)(nil)

func NewRemoteCadenceArch(
blockHeight uint64,
client *CrossSporkClient,
chainID flowGo.ChainID,
) *RemoteCadenceArch {
return &RemoteCadenceArch{
blockHeight: blockHeight,
client: client,
chainID: chainID,
cachedCalls: map[string]evmTypes.Data{},
}
}

func (rca *RemoteCadenceArch) Address() evmTypes.Address {
return evmTypes.NewAddress(cadenceArchAddress)
}

func (rca *RemoteCadenceArch) RequiredGas(input []byte) uint64 {
evmResult, err := rca.runCall(input)
if err != nil {
return 0
}

key := hex.EncodeToString(crypto.Keccak256(input))
rca.cachedCalls[key] = evmResult.ReturnedData

return evmResult.GasConsumed
}

func (rca *RemoteCadenceArch) Run(input []byte) ([]byte, error) {
key := hex.EncodeToString(crypto.Keccak256(input))
result, ok := rca.cachedCalls[key]

if !ok {
evmResult, err := rca.runCall(input)
if err != nil {
return nil, err
}
return evmResult.ReturnedData, nil
}

return result, nil
}

func (rca *RemoteCadenceArch) replaceAddresses(script []byte) []byte {
// make the list of all contracts we should replace address for
sc := systemcontracts.SystemContractsForChain(rca.chainID)
contracts := []systemcontracts.SystemContract{sc.EVMContract, sc.FungibleToken, sc.FlowToken}

s := string(script)
// iterate over all the import name and address pairs and replace them in script
for _, contract := range contracts {
s = strings.ReplaceAll(s,
fmt.Sprintf("import %s", contract.Name),
fmt.Sprintf("import %s from %s", contract.Name, contract.Address.HexWithPrefix()),
)
}

return []byte(s)
}

func (rca *RemoteCadenceArch) runCall(input []byte) (*evmTypes.ResultSummary, error) {
tx := types.NewTx(
&types.LegacyTx{
Nonce: 0,
To: &cadenceArchAddress,
Value: big.NewInt(0),
Gas: 55_000,
GasPrice: big.NewInt(0),
Data: input,
},
)
encodedTx, err := tx.MarshalBinary()
if err != nil {
return nil, err
}
hexEncodedTx, err := cadence.NewString(hex.EncodeToString(encodedTx))
if err != nil {
return nil, err
}

hexEncodedAddress, err := addressToCadenceString(evmTypes.CoinbaseAddress.ToCommon())
if err != nil {
return nil, err
}

scriptResult, err := rca.client.ExecuteScriptAtBlockHeight(
context.Background(),
rca.blockHeight,
rca.replaceAddresses(dryRunScript),
[]cadence.Value{hexEncodedTx, hexEncodedAddress},
)
if err != nil {
return nil, err
}

evmResult, err := parseResult(scriptResult)
if err != nil {
return nil, err
}

return evmResult, nil
}

func addressToCadenceString(address gethCommon.Address) (cadence.String, error) {
return cadence.NewString(strings.TrimPrefix(address.Hex(), "0x"))
}

func parseResult(res cadence.Value) (*evmTypes.ResultSummary, error) {
result, err := evmImpl.ResultSummaryFromEVMResultValue(res)
if err != nil {
return nil, fmt.Errorf("failed to decode EVM result of type: %s, with: %w", res.Type().ID(), err)
}

if result.ErrorCode != 0 {
if result.ErrorCode == evmTypes.ExecutionErrCodeExecutionReverted {
return nil, errs.NewRevertError(result.ReturnedData)
}
return nil, errs.NewFailedTransactionError(result.ErrorMessage)
}

return result, err
}
25 changes: 25 additions & 0 deletions services/requester/requester.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,12 +398,18 @@ func (e *EVM) Call(
if tx.To != nil {
to = *tx.To
}
cdcHeight, err := e.evmToCadenceHeight(evmHeight)
if err != nil {
return nil, err
}
rca := NewRemoteCadenceArch(cdcHeight, e.client, e.config.FlowNetworkID)
result, err := view.DryCall(
from,
to,
tx.Data,
tx.Value,
tx.Gas,
query.WithExtraPrecompiledContracts([]evmTypes.PrecompiledContract{rca}),
)

resultSummary := result.ResultSummary()
Expand Down Expand Up @@ -432,12 +438,18 @@ func (e *EVM) EstimateGas(
if tx.To != nil {
to = *tx.To
}
cdcHeight, err := e.evmToCadenceHeight(evmHeight)
if err != nil {
return 0, err
}
rca := NewRemoteCadenceArch(cdcHeight, e.client, e.config.FlowNetworkID)
result, err := view.DryCall(
from,
to,
tx.Data,
tx.Value,
tx.Gas,
query.WithExtraPrecompiledContracts([]evmTypes.PrecompiledContract{rca}),
)
if err != nil {
return 0, err
Expand Down Expand Up @@ -669,3 +681,16 @@ func AddOne64th(n uint64) uint64 {
// NOTE: Go's integer division floors, but that is desirable here
return n + (n / 64)
}

func (e *EVM) evmToCadenceHeight(height int64) (uint64, error) {
cadenceHeight, err := e.blocks.GetCadenceHeight(uint64(height))
if err != nil {
return 0, fmt.Errorf(
"failed to map evm height: %d to cadence height: %w",
height,
err,
)
}

return cadenceHeight, nil
}
1 change: 0 additions & 1 deletion tests/e2e_web3js_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ func TestWeb3_E2E(t *testing.T) {
})

t.Run("verify Cadence arch calls", func(t *testing.T) {
t.Skip("not implemented yet")
runWeb3Test(t, "verify_cadence_arch_calls_test")
})

Expand Down
12 changes: 6 additions & 6 deletions tests/web3js/verify_cadence_arch_calls_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ it('should be able to use Cadence Arch calls', async () => {
let contractAddress = deployed.receipt.contractAddress

// submit a transaction that calls verifyArchCallToRandomSource(uint64 height)
let getRandomSourceData = deployed.contract.methods.verifyArchCallToRandomSource(120).encodeABI()
let getRandomSourceData = deployed.contract.methods.verifyArchCallToRandomSource(2).encodeABI()
res = await helpers.signAndSend({
from: conf.eoa.address,
to: contractAddress,
Expand All @@ -23,7 +23,7 @@ it('should be able to use Cadence Arch calls', async () => {
assert.equal(res.receipt.status, conf.successStatus)

// make a contract call for verifyArchCallToRandomSource(uint64 height)
res = await web3.eth.call({ to: contractAddress, data: getRandomSourceData }, latest)
res = await web3.eth.call({ to: contractAddress, data: getRandomSourceData }, 'latest')
assert.notEqual(
res,
'0x0000000000000000000000000000000000000000000000000000000000000000'
Expand All @@ -42,7 +42,7 @@ it('should be able to use Cadence Arch calls', async () => {
assert.equal(res.receipt.status, conf.successStatus)

// make a contract call for verifyArchCallToRevertibleRandom()
res = await web3.eth.call({ to: contractAddress, data: revertibleRandomData }, latest)
res = await web3.eth.call({ to: contractAddress, data: revertibleRandomData }, 'latest')
assert.notEqual(
res,
'0x0000000000000000000000000000000000000000000000000000000000000000'
Expand All @@ -61,10 +61,10 @@ it('should be able to use Cadence Arch calls', async () => {
assert.equal(res.receipt.status, conf.successStatus)

// make a contract call for verifyArchCallToFlowBlockHeight()
res = await web3.eth.call({ to: contractAddress, data: flowBlockHeightData }, latest)
res = await web3.eth.call({ to: contractAddress, data: flowBlockHeightData }, 'latest')
assert.equal(
web3.eth.abi.decodeParameter('uint64', res),
latest,
7n,
)

// submit a transaction that calls verifyArchCallToVerifyCOAOwnershipProof(address,bytes32,bytes)
Expand All @@ -84,7 +84,7 @@ it('should be able to use Cadence Arch calls', async () => {
assert.equal(res.receipt.status, conf.successStatus)

// make a contract call for verifyArchCallToVerifyCOAOwnershipProof(address,bytes32,bytes)
res = await web3.eth.call({ to: contractAddress, data: verifyCOAOwnershipProofData }, latest)
res = await web3.eth.call({ to: contractAddress, data: verifyCOAOwnershipProofData }, 'latest')
assert.equal(
web3.eth.abi.decodeParameter('bool', res),
false,
Expand Down

0 comments on commit b27b097

Please sign in to comment.