Skip to content

Commit

Permalink
Starknet getcode (erigontech#3038)
Browse files Browse the repository at this point in the history
* deploy_cairo_smartcontract

* deploy_cairo_smartcontract / 2

Add new transaction type for cairo and vm factory

* starknet_getcode

* deploy_cairo_smartcontract / 3

* deploy_cairo_smartcontract / 4

* deploy_cairo_smartcontract / 5

Co-authored-by: Aleksandr Borodulin <a.borodulin@axioma.lv>
  • Loading branch information
Cript and aleksandrborodulin authored Dec 6, 2021
1 parent 623a576 commit bbb3cc9
Show file tree
Hide file tree
Showing 40 changed files with 1,248 additions and 242 deletions.
2 changes: 1 addition & 1 deletion cmd/evm/internal/t8ntool/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,

// If the transaction created a contract, store the creation address in the receipt.
if msg.To() == nil {
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, txn.GetNonce())
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext().Origin, txn.GetNonce())
}

// Set the receipt logs and create a bloom for filtering
Expand Down
8 changes: 8 additions & 0 deletions cmd/rpcdaemon/commands/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func APIList(ctx context.Context, db kv.RoDB,
}
ethImpl := NewEthAPI(base, db, eth, txPool, mining, cfg.Gascap)
erigonImpl := NewErigonAPI(base, db, eth)
starknetImpl := NewStarknetAPI(base, db, txPool)
txpoolImpl := NewTxPoolAPI(base, db, txPool)
netImpl := NewNetAPIImpl(eth)
debugImpl := NewPrivateDebugAPI(base, db, cfg.Gascap)
Expand Down Expand Up @@ -94,6 +95,13 @@ func APIList(ctx context.Context, db kv.RoDB,
Service: ErigonAPI(erigonImpl),
Version: "1.0",
})
case "starknet":
defaultAPIList = append(defaultAPIList, rpc.API{
Namespace: "starknet",
Public: true,
Service: StarknetAPI(starknetImpl),
Version: "1.0",
})
case "engine":
defaultAPIList = append(defaultAPIList, rpc.API{
Namespace: "engine",
Expand Down
1 change: 0 additions & 1 deletion cmd/rpcdaemon/commands/erigon_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package commands

import (
"context"

"github.com/ledgerwatch/erigon-lib/kv"
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/services"
"github.com/ledgerwatch/erigon/common"
Expand Down
38 changes: 38 additions & 0 deletions cmd/rpcdaemon/commands/starknet_accounts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package commands

import (
"context"
"fmt"
"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/common/hexutil"
"github.com/ledgerwatch/erigon/rpc"
"github.com/ledgerwatch/erigon/turbo/adapter"
"github.com/ledgerwatch/erigon/turbo/rpchelper"
)

// GetCode implements starknet_getCode. Returns the byte code at a given address (if it's a smart contract).
func (api *StarknetImpl) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
tx, err1 := api.db.BeginRo(ctx)
if err1 != nil {
return nil, fmt.Errorf("getCode cannot open tx: %w", err1)
}
defer tx.Rollback()
blockNumber, _, err := rpchelper.GetBlockNumber(blockNrOrHash, tx, api.filters)
if err != nil {
return nil, err
}

reader := adapter.NewStateReader(tx, blockNumber)
acc, err := reader.ReadAccountData(address)
if acc == nil || err != nil {
return hexutil.Bytes(""), nil
}
res, err := reader.ReadAccountCode(address, acc.Incarnation, acc.CodeHash)
if res == nil || err != nil {
return hexutil.Bytes(""), nil
}
if res == nil {
return hexutil.Bytes(""), nil
}
return res, nil
}
30 changes: 30 additions & 0 deletions cmd/rpcdaemon/commands/starknet_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package commands

import (
"context"
"github.com/ledgerwatch/erigon-lib/gointerfaces/txpool"
"github.com/ledgerwatch/erigon/common/hexutil"
"github.com/ledgerwatch/erigon/rpc"

"github.com/ledgerwatch/erigon-lib/kv"
"github.com/ledgerwatch/erigon/common"
)

type StarknetAPI interface {
SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error)
GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error)
}

type StarknetImpl struct {
*BaseAPI
txPool txpool.TxpoolClient
db kv.RoDB
}

func NewStarknetAPI(base *BaseAPI, db kv.RoDB, txPool txpool.TxpoolClient) *StarknetImpl {
return &StarknetImpl{
BaseAPI: base,
db: db,
txPool: txPool,
}
}
47 changes: 47 additions & 0 deletions cmd/rpcdaemon/commands/starknet_send_transaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package commands

import (
"bytes"
"context"
"errors"
"fmt"
txPoolProto "github.com/ledgerwatch/erigon-lib/gointerfaces/txpool"
"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/common/hexutil"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/rlp"
)

var (
ErrOnlyStarknetTx = errors.New("only support starknet transactions")
ErrOnlyContractDeploy = errors.New("only support contract creation")
)

// SendRawTransaction deploy new cairo contract
func (api *StarknetImpl) SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) {
txn, err := types.DecodeTransaction(rlp.NewStream(bytes.NewReader(encodedTx), uint64(len(encodedTx))))

if err != nil {
return common.Hash{}, err
}

if !txn.IsStarkNet() {
return common.Hash{}, ErrOnlyStarknetTx
}

if !txn.IsContractDeploy() {
return common.Hash{}, ErrOnlyContractDeploy
}

hash := txn.Hash()
res, err := api.txPool.Add(ctx, &txPoolProto.AddRequest{RlpTxs: [][]byte{encodedTx}})
if err != nil {
return common.Hash{}, err
}

if res.Imported[0] != txPoolProto.ImportResult_SUCCESS {
return hash, fmt.Errorf("%s: %s", txPoolProto.ImportResult_name[int32(res.Imported[0])], res.Errors[0])
}

return common.Hash{}, err
}
10 changes: 5 additions & 5 deletions cmd/rpcdaemon/commands/trace_adhoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,7 @@ func (api *TraceAPIImpl) Call(ctx context.Context, args TraceCallParam, traceTyp
sdMap := make(map[common.Address]*StateDiffAccount)
traceResult.StateDiff = sdMap
sd := &StateDiff{sdMap: sdMap}
if err = ibs.FinalizeTx(evm.ChainRules, sd); err != nil {
if err = ibs.FinalizeTx(evm.ChainRules(), sd); err != nil {
return nil, err
}
// Create initial IntraBlockState, we will compare it with ibs (IntraBlockState after the transaction)
Expand Down Expand Up @@ -1182,18 +1182,18 @@ func (api *TraceAPIImpl) doCallMany(ctx context.Context, dbtx kv.Tx, msgs []type
sdMap := make(map[common.Address]*StateDiffAccount)
traceResult.StateDiff = sdMap
sd := &StateDiff{sdMap: sdMap}
if err = ibs.FinalizeTx(evm.ChainRules, sd); err != nil {
if err = ibs.FinalizeTx(evm.ChainRules(), sd); err != nil {
return nil, err
}
sd.CompareStates(initialIbs, ibs)
if err = ibs.CommitBlock(evm.ChainRules, cachedWriter); err != nil {
if err = ibs.CommitBlock(evm.ChainRules(), cachedWriter); err != nil {
return nil, err
}
} else {
if err = ibs.FinalizeTx(evm.ChainRules, noop); err != nil {
if err = ibs.FinalizeTx(evm.ChainRules(), noop); err != nil {
return nil, err
}
if err = ibs.CommitBlock(evm.ChainRules, cachedWriter); err != nil {
if err = ibs.CommitBlock(evm.ChainRules(), cachedWriter); err != nil {
return nil, err
}
}
Expand Down
19 changes: 19 additions & 0 deletions cmd/starknet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# How to deploy cairo smart contract

1. Compile cairo smart contract

`starknet-compile contract.cairo --output contract_compiled.json --abi contract_abi.json`


2. Generate payload for `starknet_sendRawTransaction` PRC method

`go run ./cmd/starknet/main.go generateRawTx -c ./cairo/contract.json -o /cairo/send_raw_transaction -k b9a8b19ff082a7f4b943fcbe0da6cce6ce2c860090f05d031f463412ab534e95`

Command syntax: `go run main.go generateRawTx --help`


3. Use command output in RPC call

```json
"params":["0x03f86583127ed80180800180019637623232363136323639323233613230356235643764c080a0b44c2f4e18ca27e621171da5cf3a0c875c0749c7b998ec2759974280d987143aa04f01823122d972baa1a03b113535d9f9057fd9366fd8770e766b91f835b88ea6"],
```
64 changes: 64 additions & 0 deletions cmd/starknet/cmd/generate_raw_tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package cmd

import (
"bytes"
"encoding/hex"
"fmt"
"github.com/ledgerwatch/erigon/cmd/starknet/services"
"github.com/spf13/cobra"
"os"
"strings"
)

type Flags struct {
Contract string
PrivateKey string
Output string
}

var generateRawTxCmd = &cobra.Command{
Use: "generateRawTx",
Short: "Generate data for starknet_sendRawTransaction RPC method",
}

func init() {
flags := Flags{}

generateRawTxCmd.Flags().StringVarP(&flags.Contract, "contract", "c", "", "Path to compiled cairo contract in JSON format")
generateRawTxCmd.MarkFlagRequired("contract")

generateRawTxCmd.Flags().StringVarP(&flags.PrivateKey, "private_key", "k", "", "Private key")
generateRawTxCmd.MarkFlagRequired("private_key")

generateRawTxCmd.Flags().StringVarP(&flags.Output, "output", "o", "", "Path to file where sign transaction will be saved")

generateRawTxCmd.RunE = func(cmd *cobra.Command, args []string) error {
rawTxGenerator := services.NewRawTxGenerator(flags.PrivateKey)

fs := os.DirFS("/")
buf := bytes.NewBuffer(nil)
err := rawTxGenerator.CreateFromFS(fs, strings.Trim(flags.Contract, "/"), buf)
if err != nil {
return err
}

if flags.Output != "" {
outputFile, err := os.Create(flags.Output)
if err != nil {
return fmt.Errorf("could not create output file: %v", flags.Output)
}
defer outputFile.Close()

_, err = outputFile.WriteString(hex.EncodeToString(buf.Bytes()))
if err != nil {
return fmt.Errorf("could not write to output file: %v", flags.Output)
}
} else {
fmt.Println(hex.EncodeToString(buf.Bytes()))
}

return err
}

rootCmd.AddCommand(generateRawTxCmd)
}
19 changes: 19 additions & 0 deletions cmd/starknet/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package cmd

import (
"github.com/spf13/cobra"
)

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "starknet",
Short: "Starknet cli commands",
}

func Execute() {
cobra.CheckErr(rootCmd.Execute())
}

func init() {
cobra.OnInitialize()
}
7 changes: 7 additions & 0 deletions cmd/starknet/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "github.com/ledgerwatch/erigon/cmd/starknet/cmd"

func main() {
cmd.Execute()
}
67 changes: 67 additions & 0 deletions cmd/starknet/services/raw_tx_generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package services

import (
"encoding/hex"
"errors"
"github.com/holiman/uint256"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/crypto"
"github.com/ledgerwatch/erigon/params"
"golang.org/x/crypto/sha3"
"io"
"io/fs"
)

var (
ErrReadContract = errors.New("contract read error")
ErrInvalidPrivateKey = errors.New("invalid private key")
)

func NewRawTxGenerator(privateKey string) RawTxGenerator {
return RawTxGenerator{
privateKey: privateKey,
}
}

type RawTxGenerator struct {
privateKey string
}

func (g RawTxGenerator) CreateFromFS(fileSystem fs.FS, contractFileName string, writer io.Writer) error {
privateKey, err := crypto.HexToECDSA(g.privateKey)
if err != nil {
return ErrInvalidPrivateKey
}

contract, err := fs.ReadFile(fileSystem, contractFileName)
if err != nil {
return ErrReadContract
}

enc := make([]byte, hex.EncodedLen(len(contract)))
hex.Encode(enc, contract)

tx := types.CairoTransaction{
CommonTx: types.CommonTx{
Nonce: 1,
Value: uint256.NewInt(1),
Gas: 1,
Data: enc,
},
}

signature, _ := crypto.Sign(sha3.New256().Sum(nil), privateKey)
signer := types.MakeSigner(params.FermionChainConfig, 1)

signedTx, err := tx.WithSignature(*signer, signature)
if err != nil {
return err
}

err = signedTx.MarshalBinary(writer)
if err != nil {
return errors.New("can not save signed tx")
}

return nil
}
Loading

0 comments on commit bbb3cc9

Please sign in to comment.