diff --git a/README.md b/README.md index ae5e54e..e0a040a 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,6 @@ contracts. * [Login](#login) * [Init](#init) * [Push](#push) - * [Export setup](#export-init) - * [Export local transactions to Tenderly](#export) * [Check for updates](#check-for-updates) * [Version](#version) * [Who am I?](#who-am-i) @@ -175,8 +173,7 @@ If you are using Hardhat, take a look at [docs](https://docs.tenderly.co/monitor The `push` command is used to add your contracts to the [Tenderly Dashboard](https://dashboard.tenderly.co). -Note that the `push` command is used **only** for adding contracts that are deployed to a public network. For local -networks see the [export command](#export). +Note that the `push` command is used **only** for adding contracts that are deployed to a public network. ``` tenderly push @@ -202,7 +199,7 @@ projects: # running tenderly push will push the smart contracts to all of the pr my-cool-project: networks: - "1" # mainnet - - "42" # kovan + - "5" # goerli my-other-project: # if the networks property is not provided or is empty the project will be pushed to all of the migrated networks company-account/my-other-project: @@ -210,96 +207,6 @@ projects: # running tenderly push will push the smart contracts to all of the pr # the identifier can be found in your Tenderly dashboard under the projects name ``` -### Export init - -In order to use the [tenderly export](#export) command you need to define a configuration file -(which is described in more detail in the [export command](#export) advanced usage section). - -``` -tenderly export init -``` - -#### Command Flags - -| Flag | Default | Description | -| --- | --- | --- | -| --project | / | The project name used for network configuration | -| --rpc | / | Rpc server address (example: 127.0.0.1:8545) | -| --forked-network | / | In case you forked a public network (example: mainnet) | -| --help | / | Help for export init command | - -### Export - -The `export` command can be used to access transaction debugging tooling available at https://dashboard.tenderly.co/ but -for local transactions. - -Use the -[Transaction Overview](https://dashboard.tenderly.co/tx/main/0x70f28ce44bd58034ac18bec9eb1603350d50e020e4c2cf0b071837699ea1cdb1) -, -[Human-Readable Stack-Traces](https://dashboard.tenderly.co/tx/main/0x30bc65375b2e2b56f97706bccba9b21bc8763cc81a0262351b3373ce49f60ea7) -, -[Debugger](https://dashboard.tenderly.co/tx/main/0x70f28ce44bd58034ac18bec9eb1603350d50e020e4c2cf0b071837699ea1cdb1/debugger) -, -[Gas Profiler](https://dashboard.tenderly.co/tx/main/0x70f28ce44bd58034ac18bec9eb1603350d50e020e4c2cf0b071837699ea1cdb1/gas-usage) -, -[Decoded Events](https://dashboard.tenderly.co/tx/main/0x70f28ce44bd58034ac18bec9eb1603350d50e020e4c2cf0b071837699ea1cdb1/logs) -and [State](https://dashboard.tenderly.co/tx/main/0x70f28ce44bd58034ac18bec9eb1603350d50e020e4c2cf0b071837699ea1cdb1/state-diff) -to boost your local development productivity. - -``` -tenderly export {{transaction_hash}} -``` - -#### Command Arguments - -| Name | Description | -| --- | --- | -| transaction hash | Hash of the local transaction to debug | - -#### Command Flags - -| Flag | Default | Description | -| --- | --- | --- | -| --export-network | / | The name of the exported network in the configuration file | -| --project | / | The project in which the exported transactions will be stored | -| --rpc | 127.0.0.1:8545 | The address and port of the local rpc node | -| --forked-network | / | Optional name of the network which you are forking locally. Can be one of Mainnet, Goerli, Kovan, Ropsten, Rinkeby, xDai | -| --protocol | / | Specify the protocol used for the rpc node. By default `wss`, `https`, `ws`, `http` are tried in that order | -| --help | / | Help for export command | -| --force| false | Export the transaction regardless of gas mismatch| - -#### Advanced usage - -If your local node has different blocks defined for hardforks or you want to generate the configuration file yourself, -you can find the example bellow: - -```yaml -exports: # running tenderly export will export local transaction to the provided project - my-network: - project_slug: my-cool-project - rpc_address: 127.0.0.1:8545 - protocol: http - forked_network: mainnet - chain_config: - homestead_block: 0 # (default 0) - eip150_block: 0 # (default 0) - eip150_hash: 0x0 # (default 0x0) - eip155_block: 0 # (default 0) - eip158_block: 0 # (default 0) - byzantium_block: 0 # (default 0) - constantinople_block: 0 # (default 0) - petersburg_block: 0 # (default 0) - istanbul_block: 0 # (default 0) - berlin_block: 0 # (default 0) - london_block: 0 # (default 0) - - my-company-network: - project_slug: company-account/my-other-project - rpc_address: rpc.ethereum.company:8545 - # if you want to export to a shared project provide the full project identifier - # the identifier can be found in your Tenderly dashboard under the projects name -``` - ### Verify The `verify` command uploads your smart contracts and verifies them on [Tenderly](https://tenderly.co). @@ -344,10 +251,6 @@ The `logout` command disconnects your local Tenderly CLI from your [Tenderly Das tenderly logout ``` -### Proxy Debugging - -The proxy command is deprecated in favor of the [export](#export) command. - ### Global Flags In addition to command specific flags, the following flags can be passed to any command diff --git a/buidler/buidler.go b/buidler/buidler.go index 74dbe6d..ebb5524 100644 --- a/buidler/buidler.go +++ b/buidler/buidler.go @@ -19,7 +19,6 @@ func NewDeploymentProvider() *DeploymentProvider { call.NewUserCalls(), call.NewProjectCalls(), call.NewContractCalls(), - call.NewExportCalls(), call.NewNetworkCalls(), call.NewActionCalls(), call.NewDevNetCalls(), diff --git a/buidler/source.go b/buidler/source.go deleted file mode 100644 index 6b12abb..0000000 --- a/buidler/source.go +++ /dev/null @@ -1,100 +0,0 @@ -package buidler - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/tenderly/tenderly-cli/ethereum" - "github.com/tenderly/tenderly-cli/providers" - "github.com/tenderly/tenderly-cli/stacktrace" -) - -// NewContractSource builds the Contract Source from the provided config, and scoped to the provided network. -func (dp *DeploymentProvider) NewContractSource(path string, networkId string, client ethereum.Client) (stacktrace.ContractSource, error) { - truffleContracts, err := dp.loadBuidlerContracts(path) - if err != nil { - return nil, err - } - - cs := &providers.ContractSource{ - Contracts: dp.mapBuidlerContracts(truffleContracts, networkId), - Client: client, - } - - return cs, nil -} - -func (dp *DeploymentProvider) loadBuidlerContracts(path string) ([]*providers.Contract, error) { - - files, err := os.ReadDir(path) - if err != nil { - return nil, fmt.Errorf("failed listing buidler build files: %s", err) - } - - var contracts []*providers.Contract - for _, file := range files { - if file.IsDir() || !strings.HasSuffix(file.Name(), ".json") { - continue - } - - data, err := os.ReadFile(filepath.Join(path, file.Name())) - if err != nil { - return nil, fmt.Errorf("failed reading buidler build files: %s", err) - } - - var contract providers.Contract - err = json.Unmarshal(data, &contract) - if err != nil { - return nil, fmt.Errorf("failed parsing buidler build files: %s", err) - } - - contracts = append(contracts, &contract) - } - - return contracts, nil -} - -func (dp *DeploymentProvider) mapBuidlerContracts( - buidlerContracts []*providers.Contract, - networkId string, -) map[string]*stacktrace.ContractDetails { - contracts := make(map[string]*stacktrace.ContractDetails) - - for _, buidlerContract := range buidlerContracts { - network, ok := buidlerContract.Networks[networkId] - if !ok { - //@TODO: log.DEBUG Contract X not found in network Y. - continue - } - - bytecode, err := providers.ParseBytecode(buidlerContract.DeployedBytecode) - if err != nil { - //@TODO: log.ERROR Skipping contract because of invalid bytecode. - continue - } - - sourceMap, err := providers.ParseContract(buidlerContract) - if err != nil { - //@TODO: log.ERROR Skipping contract because of invalid source map. - continue - } - - contracts[strings.ToLower(network.Address)] = &stacktrace.ContractDetails{ - Name: buidlerContract.Name, - Hash: network.Address, - - Bytecode: bytecode, - DeployedByteCode: buidlerContract.DeployedBytecode, - - Abi: buidlerContract.Abi, - - Source: buidlerContract.Source, - SourceMap: sourceMap, - } - } - - return contracts -} diff --git a/commands/export.go b/commands/export.go new file mode 100644 index 0000000..ad4118d --- /dev/null +++ b/commands/export.go @@ -0,0 +1,43 @@ +package commands + +import ( + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +func init() { + RootCmd.AddCommand(exportCmd) + RootCmd.AddCommand(exportInitCmd) +} + +var exportCmd = &cobra.Command{ + Use: "export", + Short: "The export feature has been deprecated in favor of the DevNets", + Run: func(cmd *cobra.Command, args []string) { + logrus.Info(Colorizer.Sprintf( + "The export feature has been deprecated in favor of the %s.\n\n"+ + "The %s can be used as a development node infrastructure that allows you to access tooling at %s. "+ + "You can read more about it here: %s.", + Colorizer.Bold(Colorizer.Green("DevNets")), + Colorizer.Bold(Colorizer.Green("DevNets")), + Colorizer.Bold(Colorizer.Green("https://dashboard.tenderly.co")), + Colorizer.Bold(Colorizer.Green("https://blog.tenderly.co/how-to-deploy-smart-contracts-with-hardhat-and-tenderly/")), + )) + }, +} + +var exportInitCmd = &cobra.Command{ + Use: "export init", + Short: "The export feature has been deprecated in favor of the DevNets", + Run: func(cmd *cobra.Command, args []string) { + logrus.Info(Colorizer.Sprintf( + "The export feature has been deprecated in favor of the %s.\n\n"+ + "The %s can be used as a development node infrastructure that allows you to access tooling at %s. "+ + "You can read more about it here: %s.", + Colorizer.Bold(Colorizer.Green("DevNets")), + Colorizer.Bold(Colorizer.Green("DevNets")), + Colorizer.Bold(Colorizer.Green("https://dashboard.tenderly.co")), + Colorizer.Bold(Colorizer.Green("https://blog.tenderly.co/how-to-deploy-smart-contracts-with-hardhat-and-tenderly/")), + )) + }, +} diff --git a/commands/export/export.go b/commands/export/export.go deleted file mode 100644 index ea6a214..0000000 --- a/commands/export/export.go +++ /dev/null @@ -1,397 +0,0 @@ -package export - -import ( - "fmt" - "math/big" - "os" - "regexp" - "strings" - "time" - - "github.com/briandowns/spinner" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/tenderly/tenderly-cli/commands" - "github.com/tenderly/tenderly-cli/config" - "github.com/tenderly/tenderly-cli/ethereum" - evm2 "github.com/tenderly/tenderly-cli/ethereum/evm" - "github.com/tenderly/tenderly-cli/ethereum/types" - "github.com/tenderly/tenderly-cli/model" - "github.com/tenderly/tenderly-cli/providers" - "github.com/tenderly/tenderly-cli/rest/payloads" - "github.com/tenderly/tenderly-cli/userError" -) - -var hash string -var exportNetwork string -var exportProjectName string -var forkedNetwork string -var rpcAddress string -var protocol string -var reExport bool -var forceExport bool - -var network *config.ExportNetwork - -func init() { - exportCmd.PersistentFlags().StringVar(&exportNetwork, "export-network", "", "The name of the exported network in the configuration file.") - exportCmd.PersistentFlags().StringVar(&exportProjectName, "project", "", "The project in which the exported transactions will be stored.") - exportCmd.PersistentFlags().StringVar(&forkedNetwork, "forked-network", "", "The name of the network which you are forking locally.") - exportCmd.PersistentFlags().StringVar(&rpcAddress, "rpc", "", "The address and port of the local rpc node.") - exportCmd.PersistentFlags().StringVar(&protocol, "protocol", "", "Specify protocol for rpc node.") - exportCmd.PersistentFlags().BoolVar(&reExport, "re-init", false, "Force initializes an exported network if it was already initialized.") - exportCmd.PersistentFlags().BoolVar(&forceExport, "force", false, "Forces transaction export without gas cost validation") - commands.RootCmd.AddCommand(exportCmd) -} - -var exportCmd = &cobra.Command{ - Use: "export", - Short: "Exports local transaction to Tenderly for debugging purposes.", - Args: func(cmd *cobra.Command, args []string) error { - commands.InitProvider() - commands.CheckProvider(commands.DeploymentProvider) - commands.CheckLogin() - - if len(args) == 0 { - logrus.Error(commands.Colorizer.Red("Please provide the hash of the transaction you want to export to Tenderly.")) - os.Exit(1) - } - - txRegexp := regexp.MustCompile(`\b0x([A-Fa-f0-9]{64})\b`) - - _, err := hexutil.Decode(args[0]) - if err != nil || !txRegexp.MatchString(args[0]) { - logrus.Error(commands.Colorizer.Red("Invalid transaction hash provided.")) - os.Exit(1) - } - - return nil - }, - PreRun: func(cmd *cobra.Command, args []string) { - logrus.Warn("The export functionality has been deprecated. Please transition to using DevNets.") - }, - Run: func(cmd *cobra.Command, args []string) { - - network = getExportNetwork() - - hash = args[0] - rest := commands.NewRest() - - if network.ProjectSlug == "" { - logrus.Error("Missing project slug in network configuration") - os.Exit(1) - } - - tx, state, networkId, err := transactionWithState(hash, network) - if err != nil { - userError.LogErrorf("Unable to get transaction rerunning information: %s", err) - logrus.Info("Try exporting with --force to ignore some validation") - os.Exit(1) - } - - contracts, providerConfig, err := contractsWithConfig(networkId, state.StateObjects) - if err != nil { - userError.LogErrorf("Unable to get contract: %s", err) - os.Exit(1) - } - - s := spinner.New(spinner.CharSets[33], 100*time.Millisecond) - - s.Start() - - resp, err := rest.Export.ExportTransaction(payloads.ExportTransactionRequest{ - NetworkData: payloads.NetworkData{ - Name: network.Name, - NetworkId: networkId, - ForkedNetwork: network.ForkedNetwork, - ChainConfig: network.ChainConfig, - }, - TransactionData: payloads.TransactionData{ - Transaction: tx, - State: state, - Status: state.Status, - }, - ContractsData: payloads.UploadContractsRequest{ - Contracts: contracts, - Config: providerConfig, - }, - }, network.ProjectSlug) - - s.Stop() - - if err != nil { - userError.LogErrorf( - "Couldn't export transaction to the Tenderly platform", - fmt.Errorf("failed uploading contracts: %s", err), - ) - os.Exit(1) - } - - if resp.Error != nil { - userError.LogError( - userError.NewUserError( - fmt.Errorf("api error exporting transaction: %s", resp.Error.Slug), - resp.Error.Message, - ), - ) - os.Exit(1) - } - - var exportedContracts []string - for _, contract := range resp.Contracts { - exportedContracts = append(exportedContracts, commands.Colorizer.Sprintf( - "\t• %s with address %s", - commands.Colorizer.Bold(commands.Colorizer.Green(contract.Name)), - commands.Colorizer.Bold(commands.Colorizer.Green(contract.Address)), - )) - } - - logrus.Infof("Successfully exported transaction with hash %s", commands.Colorizer.Bold(commands.Colorizer.Green(hash))) - - if len(exportedContracts) != 0 { - logrus.Infof("Using contracts: \n%s", - strings.Join(exportedContracts, "\n"), - ) - } - - username := config.GetString(config.Username) - if strings.Contains(network.ProjectSlug, "/") { - projectInfo := strings.Split(network.ProjectSlug, "/") - username = projectInfo[0] - network.ProjectSlug = projectInfo[1] - } - - logrus.Infof("You can view your transaction at %s", - commands.Colorizer.Bold(commands.Colorizer.Green(fmt.Sprintf("https://dashboard.tenderly.co/%s/%s/local-transactions/%s", username, network.ProjectSlug, resp.Export.ID))), - ) - }, -} - -func getExportNetwork() *config.ExportNetwork { - network := GetNetwork(exportNetwork) - - logrus.Info("Collecting network information...\n") - - if exportProjectName != "" { - rest := commands.NewRest() - - accountID := config.GetString(config.AccountID) - - projectsResponse, err := rest.Project.GetProjects(accountID) - if err != nil { - userError.LogErrorf("failed fetching projects: %s", - userError.NewUserError( - err, - "Fetching projects for account failed. This can happen if you are running an older version of the Tenderly CLI.", - ), - ) - - commands.CheckVersion(true, true) - - os.Exit(1) - } - if projectsResponse.Error != nil { - userError.LogErrorf("get projects call: %s", projectsResponse.Error) - os.Exit(1) - } - - project := commands.GetProjectFromFlag(exportProjectName, projectsResponse.Projects, rest) - - if project == nil { - userError.LogErrorf("get projects call: %s", projectsResponse.Error) - os.Exit(1) - } - } - - if rpcAddress != "" { - network.RpcAddress = rpcAddress - } - - if protocol != "" { - network.Protocol = protocol - } - - if forkedNetwork != "" { - network.ForkedNetwork = forkedNetwork - } - - return network -} - -func transactionWithState(hash string, network *config.ExportNetwork) (types.Transaction, *model.TransactionState, string, error) { - logrus.Info("Collecting transaction information...\n") - - client, err := ethereum.Dial(network.RpcAddress, network.Protocol) - if err != nil { - return nil, nil, "", userError.NewUserError( - errors.Wrap(err, "unable to dial rpc server"), - commands.Colorizer.Sprintf("Make sure that rpc server is running at: %s.", - commands.Colorizer.Bold(commands.Colorizer.Red(network.RpcAddress)), - ), - ) - } - - networkId, err := client.GetNetworkID() - if err != nil { - return nil, nil, "", userError.NewUserError( - errors.Wrap(err, "unable to get network id"), - commands.Colorizer.Sprintf("Unable to get network id from rpc node."), - ) - } - - var ok bool - network.ChainConfig.ChainID, ok = new(big.Int).SetString(networkId, 10) - if !ok { - return nil, nil, "", userError.NewUserError( - errors.Wrap(err, "unable to decode network id"), - commands.Colorizer.Sprintf("Unable to decode network id from rpc node."), - ) - } - - tx, err := client.GetTransaction(hash) - if err != nil { - return nil, nil, "", userError.NewUserError( - errors.Wrap(err, "unable to find transaction"), - commands.Colorizer.Sprintf("Transaction with hash %s not found.", - commands.Colorizer.Bold(commands.Colorizer.Red(hash)), - ), - ) - } - - state, err := evm2.NewProcessor(client, network.ChainConfig).ProcessTransaction(hash, forceExport) - if err != nil { - return nil, nil, "", userError.NewUserError( - errors.Wrap(err, "error processing transaction"), - commands.Colorizer.Sprintf( - "Transaction processing failed. To see more info about this error, please run this command with the %s flag.", - commands.Colorizer.Bold(commands.Colorizer.Red("--debug")), - ), - ) - } - - return tx, state, networkId, nil -} - -func contractsWithConfig( - networkId string, - objects []*model.StateObject, -) ([]providers.Contract, *payloads.Config, error) { - logrus.Info("Collecting contracts...") - - providerConfig, err := commands.DeploymentProvider.MustGetConfig() - if err != nil { - return nil, nil, err - } - - contracts, _, err := commands.DeploymentProvider.GetContracts(providerConfig.AbsoluteBuildDirectoryPath(), []string{networkId}, objects...) - - configPayload := commands.GetConfigPayload(providerConfig) - - return contracts, configPayload, nil -} - -func GetNetwork(networkId string) *config.ExportNetwork { - var networks map[string]*struct { - Name string `mapstructure:"-"` - ProjectSlug string `mapstructure:"project_slug"` - RpcAddress string `mapstructure:"rpc_address"` - Protocol string `mapstructure:"protocol"` - ForkedNetwork string `mapstructure:"forked_network"` - ChainConfig *config.ChainConfig `mapstructure:"chain_config"` - } - - err := config.UnmarshalKey(config.Exports, &networks) - if err != nil { - userError.LogErrorf("failed unmarshaling export network config: %s", - userError.NewUserError( - err, - "Failed parsing exported networks configuration. This can happen if you are running an older version of the Tenderly CLI.", - ), - ) - - os.Exit(1) - } - - var network *struct { - Name string `mapstructure:"-"` - ProjectSlug string `mapstructure:"project_slug"` - RpcAddress string `mapstructure:"rpc_address"` - Protocol string `mapstructure:"protocol"` - ForkedNetwork string `mapstructure:"forked_network"` - ChainConfig *config.ChainConfig `mapstructure:"chain_config"` - } - - if networkId == "" { - if len(networks) == 0 { - logrus.Error("You need to set up at least one exported network first.\n\n", - "You can do this by using the ", commands.Colorizer.Bold(commands.Colorizer.Green("tenderly export init")), " command.") - os.Exit(1) - } else { - if len(networks) == 1 { - for networkId, network = range networks { - network.Name = networkId - } - } else { - logrus.Error(commands.Colorizer.Sprintf( - "You have multiple exported network configured. Please use the %s flag to specify on which network was the transaction mined.", - commands.Colorizer.Bold(commands.Colorizer.Green("--export-network")), - )) - os.Exit(1) - } - } - } else { - network = networks[networkId] - } - - if network == nil { - logrus.Error(commands.Colorizer.Sprintf("Couldn't find network %s in the configuration file. Please use the %s command to set up a new network.", - commands.Colorizer.Bold(commands.Colorizer.Red(networkId)), - commands.Colorizer.Bold(commands.Colorizer.Green("tenderly export init")), - )) - os.Exit(1) - } - - network.Name = networkId - - if network.ChainConfig == nil { - network.ChainConfig = &config.ChainConfig{ - HomesteadBlock: 0, - EIP150Block: 0, - EIP150Hash: common.Hash{}, - EIP155Block: 0, - EIP158Block: 0, - ByzantiumBlock: 0, - ConstantinopleBlock: 0, - PetersburgBlock: 0, - IstanbulBlock: 0, - BerlinBlock: 0, - //LondonBlock: 0, - } - } - - chainConfig, err := network.ChainConfig.Config() - if err != nil { - userError.LogErrorf("unable to read chain_config", - userError.NewUserError( - err, - commands.Colorizer.Sprintf( - "Failed parsing exported networks chain configuration. To see more info about this error, please run this command with the %s flag.", - commands.Colorizer.Bold(commands.Colorizer.Red("--debug")), - ), - ), - ) - os.Exit(1) - } - - return &config.ExportNetwork{ - Name: network.Name, - ProjectSlug: network.ProjectSlug, - RpcAddress: network.RpcAddress, - Protocol: network.Protocol, - ForkedNetwork: network.ForkedNetwork, - ChainConfig: chainConfig, - } -} diff --git a/commands/export/init.go b/commands/export/init.go deleted file mode 100644 index 230d055..0000000 --- a/commands/export/init.go +++ /dev/null @@ -1,121 +0,0 @@ -package export - -import ( - "fmt" - "os" - - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/tenderly/tenderly-cli/commands" - "github.com/tenderly/tenderly-cli/config" - "github.com/tenderly/tenderly-cli/userError" -) - -func init() { - exportCmd.AddCommand(initCmd) -} - -var initCmd = &cobra.Command{ - Use: "init", - Short: "Export init is a helper subcommand for creating exported network configuration", - PreRun: func(cmd *cobra.Command, args []string) { - logrus.Warn("The export functionality has been deprecated. Please transition to using DevNets.") - }, - Run: func(cmd *cobra.Command, args []string) { - commands.CheckLogin() - - if exportNetwork == "" { - exportNetwork = promptExportNetwork() - } - - if config.IsNetworkConfigured(exportNetwork) && !reExport { - logrus.Info(commands.Colorizer.Sprintf("The network %s is already configured. If you want to set up the network again, rerun this command with the %s flag.", - commands.Colorizer.Bold(commands.Colorizer.Green(exportNetwork)), - commands.Colorizer.Bold(commands.Colorizer.Green("--re-init")), - )) - os.Exit(0) - } - - if config.IsNetworkConfigured(exportNetwork) { - network = GetNetwork(exportNetwork) - } else { - network = &config.ExportNetwork{} - } - - rest := commands.NewRest() - - networks, err := rest.Networks.GetPublicNetworks() - if err != nil { - userError.LogErrorf("failed fetching public networks: %s", - userError.NewUserError( - err, - "Fetching public networks failed. This can happen if you are running an older version of the Tenderly CLI.", - ), - ) - - commands.CheckVersion(true, true) - - os.Exit(1) - } - - accountID := config.GetString(config.AccountID) - - projectsResponse, err := rest.Project.GetProjects(accountID) - if err != nil { - userError.LogErrorf("failed fetching projects: %s", - userError.NewUserError( - err, - "Fetching projects for account failed. This can happen if you are running an older version of the Tenderly CLI.", - ), - ) - - commands.CheckVersion(true, true) - - os.Exit(1) - } - if projectsResponse.Error != nil { - userError.LogErrorf("get projects call: %s", projectsResponse.Error) - os.Exit(1) - } - - project := commands.GetProjectFromFlag(exportProjectName, projectsResponse.Projects, rest) - - if project == nil { - project = commands.PromptProjectSelect(projectsResponse.Projects, rest, true) - } - if project != nil { - slug := project.Slug - if project.OwnerInfo != nil { - slug = fmt.Sprintf("%s/%s", project.OwnerInfo.Username, slug) - } - network.ProjectSlug = slug - } - - if rpcAddress == "" { - rpcAddress = promptRpcAddress() - } - if network.RpcAddress != rpcAddress { - network.RpcAddress = rpcAddress - } - - if forkedNetwork == "" { - networkNames := []string{"None"} - for _, network := range *networks { - networkNames = append(networkNames, network.Name) - } - forkedNetwork = promptForkedNetwork(networkNames) - } - if network.ForkedNetwork != forkedNetwork { - network.ForkedNetwork = forkedNetwork - } - - err = config.WriteExportNetwork(exportNetwork, network) - if err != nil { - userError.LogErrorf( - "write project config: %s", - userError.NewUserError(err, "Couldn't write project config file"), - ) - os.Exit(1) - } - }, -} diff --git a/commands/export/prompt.go b/commands/export/prompt.go deleted file mode 100644 index cfd6863..0000000 --- a/commands/export/prompt.go +++ /dev/null @@ -1,70 +0,0 @@ -package export - -import ( - "os" - - "github.com/manifoldco/promptui" - "github.com/pkg/errors" - "github.com/tenderly/tenderly-cli/userError" -) - -func promptExportNetwork() string { - prompt := promptui.Prompt{ - Label: "Choose the name for the exported network", - Validate: func(input string) error { - if len(input) == 0 { - return errors.New("please enter the exported network name") - } - - return nil - }, - } - - result, err := prompt.Run() - - if err != nil { - userError.LogErrorf("prompt export network failed: %s", err) - os.Exit(1) - } - - return result -} - -func promptRpcAddress() string { - prompt := promptui.Prompt{ - Label: "Enter rpc address (default: 127.0.0.1:8545)", - } - - result, err := prompt.Run() - - if err != nil { - userError.LogErrorf("prompt rpc address failed: %s", err) - os.Exit(1) - } - - if result == "" { - result = "127.0.0.1:8545" - } - - return result -} - -func promptForkedNetwork(forkedNetworkNames []string) string { - promptNetworks := promptui.Select{ - Label: "If you are forking a public network, please define which one", - Items: forkedNetworkNames, - } - - index, _, err := promptNetworks.Run() - - if err != nil { - userError.LogErrorf("prompt forked network failed: %s", err) - os.Exit(1) - } - - if index == 0 { - return "" - } - - return forkedNetworkNames[index] -} diff --git a/commands/proxy.go b/commands/proxy.go deleted file mode 100644 index 461ec28..0000000 --- a/commands/proxy.go +++ /dev/null @@ -1,26 +0,0 @@ -package commands - -import ( - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -func init() { - RootCmd.AddCommand(proxyCmd) -} - -var proxyCmd = &cobra.Command{ - Use: "proxy", - Short: "The proxy command is deprecated in favor of the export command", - Run: func(cmd *cobra.Command, args []string) { - logrus.Info(Colorizer.Sprintf( - "The proxy command is deprecated in favor of the %s command.\n\n"+ - "The %s command can be used to access all of the tooling available at %s but for local transactions. "+ - "You can read more about it here: %s.", - Colorizer.Bold(Colorizer.Green("export")), - Colorizer.Bold(Colorizer.Green("export")), - Colorizer.Bold(Colorizer.Green("https://dashboard.tenderly.co")), - Colorizer.Bold(Colorizer.Green("https://github.com/Tenderly/tenderly-cli#export")), - )) - }, -} diff --git a/commands/util.go b/commands/util.go index 5f6e82d..807e29c 100644 --- a/commands/util.go +++ b/commands/util.go @@ -31,7 +31,6 @@ func NewRest() *rest.Rest { call.NewUserCalls(), call.NewProjectCalls(), call.NewContractCalls(), - call.NewExportCalls(), call.NewNetworkCalls(), call.NewActionCalls(), call.NewDevNetCalls(), diff --git a/config/config.go b/config/config.go index 83c134d..4a106c9 100644 --- a/config/config.go +++ b/config/config.go @@ -3,13 +3,10 @@ package config import ( "flag" "fmt" - "math/big" "os" "os/user" "path/filepath" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/params" "github.com/tenderly/tenderly-cli/model/actions" extensionsModel "github.com/tenderly/tenderly-cli/model/extensions" "github.com/tenderly/tenderly-cli/userError" @@ -30,7 +27,6 @@ const ( OrganizationName = "org_name" - Exports = "exports" Actions = "actions" Extensions = "node_extensions" Projects = "projects" @@ -40,147 +36,6 @@ var defaultsGlobal = map[string]interface{}{ Token: "", } -type EthashConfig struct{} - -type CliqueConfig struct { - Period uint64 `mapstructure:"period"` - Epoch uint64 `mapstructure:"epoch"` -} - -type BigInt interface{} - -func toInt(x BigInt) (*big.Int, error) { - if x == nil { - return nil, nil - } - - if stringVal, ok := x.(string); ok { - i := &big.Int{} - _, ok := i.SetString(stringVal, 10) - if !ok { - return nil, fmt.Errorf("failed parsing big int: %s", stringVal) - } - - return i, nil - } - - if numberVal, ok := x.(int64); ok { - return big.NewInt(numberVal), nil - } - - if numberVal, ok := x.(int); ok { - return big.NewInt(int64(numberVal)), nil - } - - return nil, fmt.Errorf("unrecognized value: %s", x) -} - -type ChainConfig struct { - HomesteadBlock BigInt `mapstructure:"homestead_block,omitempty" yaml:"homestead_block,omitempty"` - - EIP150Block BigInt `mapstructure:"eip150_block,omitempty" yaml:"eip150_block,omitempty"` - EIP150Hash common.Hash `mapstructure:"eip150_hash,omitempty" yaml:"eip150_hash,omitempty"` - - EIP155Block BigInt `mapstructure:"eip155_block,omitempty" yaml:"eip155_block,omitempty"` - EIP158Block BigInt `mapstructure:"eip158_block,omitempty" yaml:"eip158_block,omitempty"` - - ByzantiumBlock BigInt `mapstructure:"byzantium_block,omitempty" yaml:"byzantium_block,omitempty"` - ConstantinopleBlock BigInt `mapstructure:"constantinople_block,omitempty" yaml:"constantinople_block,omitempty"` - PetersburgBlock BigInt `mapstructure:"petersburg_block,omitempty" yaml:"petersburg_block,omitempty"` - IstanbulBlock BigInt `mapstructure:"istanbul_block,omitempty" yaml:"istanbul_block,omitempty"` - BerlinBlock BigInt `mapstructure:"berlin_block,omitempty" yaml:"berlin_block,omitempty"` - LondonBlock BigInt `mapstructure:"london_block,omitempty" yaml:"london_block,omitempty"` -} - -var DefaultChainConfig = &ChainConfig{ - HomesteadBlock: 0, - EIP150Block: 0, - EIP150Hash: common.Hash{}, - EIP155Block: 0, - EIP158Block: 0, - ByzantiumBlock: 0, - ConstantinopleBlock: 0, - PetersburgBlock: 0, - IstanbulBlock: 0, - BerlinBlock: 0, - LondonBlock: 0, -} - -func (c *ChainConfig) Config() (*params.ChainConfig, error) { - homesteadBlock, err := toInt(c.HomesteadBlock) - if err != nil { - return nil, err - } - - eip150Block, err := toInt(c.EIP150Block) - if err != nil { - return nil, err - } - - eip155Block, err := toInt(c.EIP155Block) - if err != nil { - return nil, err - } - - eip158Block, err := toInt(c.EIP158Block) - if err != nil { - return nil, err - } - - byzantiumBlock, err := toInt(c.ByzantiumBlock) - if err != nil { - return nil, err - } - - constantinopleBlock, err := toInt(c.ConstantinopleBlock) - if err != nil { - return nil, err - } - - petersburgBlock, err := toInt(c.PetersburgBlock) - if err != nil { - return nil, err - } - - istanbulBlock, err := toInt(c.IstanbulBlock) - if err != nil { - return nil, err - } - - berlinBlock, err := toInt(c.BerlinBlock) - if err != nil { - return nil, err - } - - londonBlock, err := toInt(c.LondonBlock) - if err != nil { - return nil, err - } - - return ¶ms.ChainConfig{ - HomesteadBlock: homesteadBlock, - EIP150Block: eip150Block, - EIP150Hash: c.EIP150Hash, - EIP155Block: eip155Block, - EIP158Block: eip158Block, - ByzantiumBlock: byzantiumBlock, - ConstantinopleBlock: constantinopleBlock, - PetersburgBlock: petersburgBlock, - IstanbulBlock: istanbulBlock, - BerlinBlock: berlinBlock, - LondonBlock: londonBlock, - }, nil -} - -type ExportNetwork struct { - Name string `mapstructure:"-"` - ProjectSlug string `mapstructure:"project_slug"` - RpcAddress string `mapstructure:"rpc_address"` - Protocol string `mapstructure:"protocol"` - ForkedNetwork string `mapstructure:"forked_network"` - ChainConfig *params.ChainConfig `mapstructure:"chain_config"` -} - var defaultsProject = map[string]interface{}{ AccountID: "", ProjectSlug: "", @@ -238,11 +93,6 @@ func Init() { } } -func GetBool(key string) bool { - check(key) - return getBool(key) -} - func GetString(key string) string { check(key) return getString(key) @@ -293,52 +143,6 @@ func IsProjectInit() bool { return getString(ProjectSlug) != "" || len(MaybeGetMap(Projects)) > 0 } -func IsNetworkConfigured(network string) bool { - if _, ok := getStringMapString(Exports)[network]; ok { - return true - } - - return false -} - -func WriteExportNetwork(networkId string, network *ExportNetwork) error { - exports := projectConfig.GetStringMap(Exports) - - chainConfig := DefaultChainConfig - if network.ChainConfig != nil { - chainConfig = &ChainConfig{ - HomesteadBlock: network.ChainConfig.HomesteadBlock, - EIP150Block: network.ChainConfig.EIP150Block, - EIP150Hash: network.ChainConfig.EIP150Hash, - EIP155Block: network.ChainConfig.EIP158Block, - EIP158Block: network.ChainConfig.EIP158Block, - ByzantiumBlock: network.ChainConfig.ByzantiumBlock, - ConstantinopleBlock: network.ChainConfig.ConstantinopleBlock, - PetersburgBlock: network.ChainConfig.PetersburgBlock, - IstanbulBlock: network.ChainConfig.IstanbulBlock, - BerlinBlock: network.ChainConfig.BerlinBlock, - LondonBlock: network.ChainConfig.LondonBlock, - } - } - - exports[networkId] = struct { - ProjectSlug string `mapstructure:"project_slug" yaml:"project_slug"` - RpcAddress string `mapstructure:"rpc_address" yaml:"rpc_address"` - Protocol string `mapstructure:"protocol" yaml:"protocol"` - ForkedNetwork string `mapstructure:"forked_network" yaml:"forked_network"` - ChainConfig *ChainConfig `mapstructure:"chain_config" yaml:"chain_config"` - }{ - ProjectSlug: network.ProjectSlug, - RpcAddress: network.RpcAddress, - Protocol: network.Protocol, - ForkedNetwork: network.ForkedNetwork, - ChainConfig: chainConfig, - } - - projectConfig.Set(Exports, exports) - return WriteProjectConfig() -} - func IsAnyActionsInit() bool { act := projectConfig.GetStringMap(Actions) return len(act) > 0 @@ -445,30 +249,6 @@ func getString(key string) string { return globalConfig.GetString(key) } -func getBool(key string) bool { - if projectConfig.IsSet(key) { - return projectConfig.GetBool(key) - } - - return globalConfig.GetBool(key) -} - -func getStringMapString(key string) map[string]interface{} { - if projectConfig.IsSet(key) { - return projectConfig.GetStringMap(key) - } - - return globalConfig.GetStringMap(key) -} - -func UnmarshalKey(key string, val interface{}) error { - if projectConfig.IsSet(key) { - return projectConfig.UnmarshalKey(key, val) - } - - return globalConfig.UnmarshalKey(key, val) -} - func check(key string) { if !globalConfig.IsSet(key) && !projectConfig.IsSet(key) { fmt.Printf("Could not find value for config: %s\n", key) diff --git a/ethereum/README.md b/ethereum/README.md deleted file mode 100644 index f799eaa..0000000 --- a/ethereum/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Ethereum Client - -Ethereum client is supposed to be a general interface over any ethereum node. \ No newline at end of file diff --git a/ethereum/client.go b/ethereum/client.go deleted file mode 100644 index de486ba..0000000 --- a/ethereum/client.go +++ /dev/null @@ -1,285 +0,0 @@ -package ethereum - -import ( - "encoding/json" - "fmt" - "log" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/tenderly/tenderly-cli/ethereum/geth" - "github.com/tenderly/tenderly-cli/ethereum/parity" - "github.com/tenderly/tenderly-cli/ethereum/schema" - "github.com/tenderly/tenderly-cli/ethereum/types" - - "github.com/tenderly/tenderly-cli/jsonrpc2" -) - -// Client represents an implementation agnostic interface to the Ethereum node. -// It is able connect to both different protocols (http, ws) and implementations (geth, parity). -type Client struct { - rpc *jsonrpc2.Client - schema schema.Schema - - openChannels []chan int64 -} - -func Dial(target string, protocol string) (*Client, error) { - rpcClient, err := jsonrpc2.DiscoverAndDial(target, protocol) - if err != nil { - return nil, fmt.Errorf("dial ethereum rpc: %s", err) - } - - nodeType := "geth" - - req, resp := parity.DefaultSchema.Parity().VersionInfo() - if err = rpcClient.CallRequest(resp, req); err == nil { - nodeType = "parity" - } - - var schema schema.Schema - switch nodeType { - case "geth": - schema = &geth.DefaultSchema - case "parity": - schema = &parity.DefaultSchema - default: - return nil, fmt.Errorf("unsupported node type: %s", err) - } - - return &Client{ - rpc: rpcClient, - schema: schema, - }, nil -} - -func (c *Client) Call(message *jsonrpc2.Message) error { - var params []interface{} - if message.Params != nil { - err := json.Unmarshal(message.Params, ¶ms) - if err != nil { - return err - } - } - - req := jsonrpc2.NewRequest(message.Method, params...) - - resMsg, err := c.rpc.SendRawRequest(req) - if err != nil { - return fmt.Errorf("proxy calling failed method: [%s], parameters [%s], error: %s", - req.Method, - req.Params, - err) - } - - message.Result = resMsg.Result - message.Error = resMsg.Error - return nil -} - -func (c *Client) CurrentBlockNumber() (int64, error) { - req, resp := c.schema.Eth().BlockNumber() - - err := c.rpc.CallRequest(resp, req) - if err != nil { - return 0, fmt.Errorf("current block number: %s", err) - } - - return resp.Value(), nil -} - -func (c *Client) GetBlock(number int64) (types.Block, error) { - req, resp := c.schema.Eth().GetBlockByNumber(types.Number(number)) - - if err := c.rpc.CallRequest(resp, req); err != nil { - return nil, fmt.Errorf("get block by number [%d]: %s", number, err) - } - - return resp, nil -} - -func (c *Client) GetBlockByHash(hash string) (types.BlockHeader, error) { - req, resp := c.schema.Eth().GetBlockByHash(hash) - - if err := c.rpc.CallRequest(resp, req); err != nil { - return nil, fmt.Errorf("get block by hash [%s]: %s", hash, err) - } - - return resp, nil -} - -func (c *Client) GetTransaction(hash string) (types.Transaction, error) { - req, resp := c.schema.Eth().GetTransaction(hash) - - if err := c.rpc.CallRequest(resp, req); err != nil { - return nil, fmt.Errorf("get transaction [%s]: %s", hash, err) - } - - return resp, nil -} - -func (c *Client) GetTransactionReceipt(hash string) (types.TransactionReceipt, error) { - req, resp := c.schema.Eth().GetTransactionReceipt(hash) - - if err := c.rpc.CallRequest(resp, req); err != nil { - return nil, fmt.Errorf("get transaction receipt [%s]: %s", hash, err) - } - - return resp, nil -} - -func (c *Client) GetNetworkID() (string, error) { - req, resp := c.schema.Net().Version() - - if err := c.rpc.CallRequest(resp, req); err != nil { - return "", fmt.Errorf("get network ID: %s", err) - } - - return *resp, nil -} - -func (c *Client) GetTransactionVMTrace(hash string) (types.TransactionStates, error) { - req, resp := c.schema.Trace().VMTrace(hash) - - if err := c.rpc.CallRequest(resp, req); err != nil { - return nil, fmt.Errorf("get transaction trace [%s]: %s", hash, err) - } - - resp.ProcessTrace() - - return resp, nil -} - -func (c *Client) GetTransactionCallTrace(hash string) (types.CallTraces, error) { - req, resp := c.schema.Trace().CallTrace(hash) - - if err := c.rpc.CallRequest(resp, req); err != nil { - return nil, fmt.Errorf("get transaction pretty trace [%s]: %s", hash, err) - } - - return resp, nil -} - -func (c *Client) GetBalance(address string, block *types.Number) (*big.Int, error) { - req, resp := c.schema.Eth().GetBalance(address, block) - - if err := c.rpc.CallRequest(resp, req); err != nil { - return nil, fmt.Errorf("get balance [%s]: %s", address, err) - } - - return resp.ToInt(), nil -} - -func (c *Client) GetCode(address string, block *types.Number) (string, error) { - req, resp := c.schema.Eth().GetCode(address, block) - - if err := c.rpc.CallRequest(resp, req); err != nil { - return "", fmt.Errorf("get code [%s]: %s", address, err) - } - - return *resp, nil -} - -func (c *Client) GetNonce(address string, block *types.Number) (uint64, error) { - req, resp := c.schema.Eth().GetNonce(address, block) - - if err := c.rpc.CallRequest(resp, req); err != nil { - return 0, fmt.Errorf("get nonce [%s]: %s", address, err) - } - - return uint64(*resp), nil -} - -func (c *Client) GetStorageAt(hash string, offset common.Hash, block *types.Number) (*common.Hash, error) { - req, resp := c.schema.Eth().GetStorage(hash, offset, block) - - if err := c.rpc.CallRequest(resp, req); err != nil { - return nil, fmt.Errorf("get storage at [%s]: %s", hash, err) - } - - respHash := common.HexToHash(*resp) - return &respHash, nil -} - -func (c *Client) Subscribe(forcePoll bool) (chan int64, error) { - if forcePoll { - log.Printf("Forcing polling subscription...") - return c.subscribeViaPoll() - } - - //@TODO: Manage closing of the subscription. - req, subscription := c.schema.PubSub().Subscribe() - err := c.rpc.CallRequest(subscription, req) - if err != nil { - //@TODO: Do specific check if subscription not supported. - log.Printf("Subscription not supported, falling back to polling") - return c.subscribeViaPoll() - } - - return c.subscribe(subscription) -} - -func (c *Client) subscribeViaPoll() (chan int64, error) { - outCh := make(chan int64) - - go func() { - var lastBlock int64 - - for { - blockNumber, err := c.CurrentBlockNumber() - if err != nil { - log.Printf("failed pollig for last block number: %s", err) - time.Sleep(1 * time.Second) - continue - } - - if lastBlock == 0 { - lastBlock = blockNumber - continue - } - - for lastBlock < blockNumber { - outCh <- blockNumber - - lastBlock++ - } - - time.Sleep(200 * time.Millisecond) - } - }() - - return outCh, nil -} - -func (c *Client) subscribe(id *types.SubscriptionID) (chan int64, error) { - outCh := make(chan int64) - - inCh, err := c.rpc.Subscribe(id.String()) - if err != nil { - return nil, fmt.Errorf("listen for subscriptions: %s", err) - } - - go func() { - for msg := range inCh { - var resp geth.SubscriptionResult - err = json.Unmarshal(msg.Params, &resp) - if err != nil { - log.Printf("failed reading notification: %s", err) - continue - } - - outCh <- resp.Result.Number().Value() - } - - close(outCh) - }() - - return outCh, nil -} - -func (c *Client) Close() error { - c.rpc.Close() - - return nil -} diff --git a/ethereum/evm/chain.go b/ethereum/evm/chain.go deleted file mode 100644 index 853ba17..0000000 --- a/ethereum/evm/chain.go +++ /dev/null @@ -1,110 +0,0 @@ -package evm - -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/core/types" - "github.com/tenderly/tenderly-cli/ethereum" -) - -type Chain struct { - Header *types.Header - client *ethereum.Client - - engine consensus.Engine - - cachedHeaders map[int64]*types.Header -} - -func newChain(header *types.Header, client *ethereum.Client, cachedHeaders map[int64]*types.Header, engine consensus.Engine) *Chain { - h := &types.Header{ - Number: header.Number, - ParentHash: header.ParentHash, - UncleHash: header.UncleHash, - Coinbase: header.Coinbase, - Root: header.Root, - TxHash: header.TxHash, - ReceiptHash: header.ReceiptHash, - Bloom: header.Bloom, - Difficulty: header.Difficulty, - GasLimit: header.GasLimit, - GasUsed: header.GasUsed, - Time: header.Time, - Extra: header.Extra, - MixDigest: header.MixDigest, - Nonce: header.Nonce, - BaseFee: header.BaseFee, - } - if engine != nil { - h.Coinbase, _ = engine.Author(header) - } - - cachedHeaders[header.Number.Int64()] = h - - return &Chain{ - Header: h, - client: client, - - engine: engine, - - cachedHeaders: cachedHeaders, - } -} - -func (c *Chain) Engine() consensus.Engine { - if c.engine == nil { - panic("engine not implemented") - } - - return c.engine -} - -func (c *Chain) GetHeader(hash common.Hash, number uint64) *types.Header { - if number == c.Header.Number.Uint64() { - c.cachedHeaders[int64(number)] = c.Header - return c.Header - } - - if c.cachedHeaders[int64(number)] != nil { - return c.cachedHeaders[int64(number)] - } - - if c.client == nil { - panic("client not initiated") - } - - blockHeader, err := c.client.GetBlockByHash(hash.String()) - if err != nil { - return &types.Header{} - } - - header := &types.Header{ - ParentHash: blockHeader.ParentHash(), - UncleHash: blockHeader.UncleHash(), - Root: blockHeader.StateRoot(), - TxHash: blockHeader.TxHash(), - ReceiptHash: blockHeader.ReceiptHash(), - Bloom: blockHeader.Bloom(), - Number: blockHeader.Number().Big(), - Time: blockHeader.Time().ToInt().Uint64(), - Difficulty: maybeDifficulty(blockHeader.Difficulty()), - GasLimit: blockHeader.GasLimit().ToInt().Uint64(), - GasUsed: blockHeader.GasUsed().ToInt().Uint64(), - Coinbase: blockHeader.Coinbase(), - Extra: blockHeader.ExtraData(), - MixDigest: blockHeader.MixDigest(), - Nonce: blockHeader.Nonce(), - BaseFee: blockHeader.BaseFeePerGas().ToInt(), - } - - if c.engine != nil { - header.Coinbase, _ = c.engine.Author(header) - } - - c.cachedHeaders[int64(number)] = header - return header -} - -func (c *Chain) GetHeaders() map[int64]*types.Header { - return c.cachedHeaders -} diff --git a/ethereum/evm/difficulty.go b/ethereum/evm/difficulty.go deleted file mode 100644 index 38124f2..0000000 --- a/ethereum/evm/difficulty.go +++ /dev/null @@ -1,17 +0,0 @@ -package evm - -import ( - "encoding/json" - "math/big" - - "github.com/ethereum/go-ethereum/common/hexutil" -) - -func maybeDifficulty(hexBytes []byte) *big.Int { - var maybeHex *hexutil.Big - err := json.Unmarshal(hexBytes, maybeHex) - if err != nil { - return big.NewInt(0) - } - return maybeHex.ToInt() -} diff --git a/ethereum/evm/processor.go b/ethereum/evm/processor.go deleted file mode 100644 index bb5bc83..0000000 --- a/ethereum/evm/processor.go +++ /dev/null @@ -1,258 +0,0 @@ -package evm - -import ( - "encoding/binary" - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/consensus/clique" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/params" - "github.com/pkg/errors" - "github.com/tenderly/tenderly-cli/ethereum" - state2 "github.com/tenderly/tenderly-cli/ethereum/state" - tenderlyTypes "github.com/tenderly/tenderly-cli/ethereum/types" - "github.com/tenderly/tenderly-cli/model" - "github.com/tenderly/tenderly-cli/userError" -) - -type Processor struct { - client *ethereum.Client - - chainConfig *params.ChainConfig -} - -func NewProcessor(client *ethereum.Client, chainConfig *params.ChainConfig) *Processor { - return &Processor{ - client: client, - chainConfig: chainConfig, - } -} - -func (p *Processor) ProcessTransaction(hash string, force bool) (*model.TransactionState, error) { - _, err := p.client.GetTransaction(hash) - if err != nil { - return nil, userError.NewUserError( - errors.Wrap(err, "unable to find transaction"), - fmt.Sprintf("Transaction with hash %s not found.", hash), - ) - } - - receipt, err := p.client.GetTransactionReceipt(hash) - if err != nil { - return nil, userError.NewUserError( - errors.Wrap(err, "unable to find transaction receipt"), - fmt.Sprintf("Transaction receipt with hash %s not found.", hash), - ) - } - - block, err := p.client.GetBlock(receipt.BlockNumber().Value()) - if err != nil { - return nil, userError.NewUserError( - errors.Wrap(err, "unable to get block by number"), - fmt.Sprintf("Block with number %d not found.", receipt.BlockNumber()), - ) - } - - return p.processTransactions(block, receipt.TransactionIndex().Value(), force) -} - -func (p *Processor) processTransactions(ethBlock tenderlyTypes.Block, ti int64, force bool) (*model.TransactionState, error) { - stateDB := state2.NewState(p.client, ethBlock.Number().Value()) - - blockHeader, err := p.client.GetBlockByHash(ethBlock.Hash().String()) - if err != nil { - return nil, userError.NewUserError( - errors.Wrap(err, "unable to get block by hash"), - fmt.Sprintf("Block with hash %s not found.", ethBlock.Hash()), - ) - } - - var author *common.Address - if p.chainConfig.Clique == nil || blockHeader.Coinbase() != common.BytesToAddress([]byte{}) { - coinbase := blockHeader.Coinbase() - author = &coinbase - } - - if p.chainConfig.IsLondon(ethBlock.Number().Big()) && blockHeader.BaseFeePerGas() == nil { - return nil, userError.NewUserError( - errors.Wrap(err, "missing block base fee"), - fmt.Sprintf("Missing block base fee parameter for block %d, london hard fork is probabbly not activated.", ethBlock.Number().Big()), - ) - } - - header := types.Header{ - Number: blockHeader.Number().Big(), - ParentHash: blockHeader.ParentHash(), - UncleHash: blockHeader.UncleHash(), - Coinbase: blockHeader.Coinbase(), - Root: blockHeader.StateRoot(), - TxHash: blockHeader.TxHash(), - ReceiptHash: blockHeader.ReceiptHash(), - Bloom: blockHeader.Bloom(), - Difficulty: maybeDifficulty(blockHeader.Difficulty()), - GasLimit: blockHeader.GasLimit().ToInt().Uint64(), - GasUsed: blockHeader.GasUsed().ToInt().Uint64(), - Time: blockHeader.Time().ToInt().Uint64(), - Extra: blockHeader.ExtraData(), - MixDigest: blockHeader.MixDigest(), - Nonce: blockHeader.Nonce(), - BaseFee: blockHeader.BaseFeePerGas().ToInt(), - } - - return p.applyTransactions(ethBlock.Hash(), ethBlock.Transactions()[:ti+1], stateDB, header, author, force) -} - -func (p Processor) applyTransactions(blockHash common.Hash, txs []tenderlyTypes.Transaction, - stateDB *state2.StateDB, header types.Header, author *common.Address, force bool, -) (*model.TransactionState, error) { - var txState *model.TransactionState - for ti := 0; ti < len(txs); ti++ { - tx := txs[ti] - - receipt, err := p.client.GetTransactionReceipt(tx.Hash().String()) - if err != nil { - return nil, userError.NewUserError( - errors.Wrap(err, "unable to find transaction receipt"), - fmt.Sprintf("Transaction receipt with hash %s not found.", tx.Hash()), - ) - } - - stateDB.Prepare(tx.Hash(), blockHash, ti) - snapshotId := stateDB.Snapshot() - txState, err = p.applyTransaction(tx, stateDB, header, author) - if err := stateDB.GetDbErr(); err != nil { - ti -= 1 - stateDB.RevertToSnapshot(snapshotId) - stateDB.CleanErr() - continue - } - if err != nil { - return nil, err - } - - if txState.GasUsed != receipt.GasUsed().ToInt().Uint64() && !force { - return nil, userError.NewUserError( - errors.New("gas mismatch between receipt and actual gas used"), - fmt.Sprintf("Rerun gas mismatch for transaction %s. This can happen when the chain config is incorrect or the local node is not running the latest version.\n\n"+ - "Please check which hardfork is active on your local node. If you are not running the newest fork, comment out the forks block in tenderly.yaml.\n", - tx.Hash().String(), - ), - ) - } - - stateDB.Finalise(true) - } - - return txState, nil -} - -func (p Processor) applyTransaction(tx tenderlyTypes.Transaction, stateDB *state2.StateDB, - header types.Header, author *common.Address, -) (*model.TransactionState, error) { - message := newMessage(tx) - - var engine consensus.Engine - if p.chainConfig.Clique != nil { - engine = clique.New(p.chainConfig.Clique, nil) - } - chain := newChain(&header, p.client, make(map[int64]*types.Header), engine) - context := core.NewEVMBlockContext(&header, chain, author) - txContext := core.NewEVMTxContext(message) - - evm := vm.NewEVM(context, txContext, stateDB, p.chainConfig, vm.Config{}) - - executionResult, err := core.ApplyMessage(evm, message, new(core.GasPool).AddGas(message.Gas())) - if err != nil { - return nil, userError.NewUserError( - errors.Wrap(err, "unable to apply message"), - fmt.Sprintf("Transaction applying error with hash %s.", tx.Hash()), - ) - } - - return &model.TransactionState{ - GasUsed: executionResult.UsedGas, - Status: !executionResult.Failed(), - - StateObjects: stateObjects(stateDB), - Headers: headers(chain), - }, nil -} - -func newMessage(tx tenderlyTypes.Transaction) types.Message { - var accessList []types.AccessTuple - for _, v := range tx.AccessList() { - accessList = append(accessList, types.AccessTuple{ - Address: v.Address(), - StorageKeys: v.StorageKeys(), - }) - } - - gasFeeCap := tx.GasFeeCap().ToInt() - if gasFeeCap == nil { - gasFeeCap = tx.GasPrice().ToInt() - } - gasTipCap := tx.GasTipCap().ToInt() - if gasTipCap == nil { - gasTipCap = tx.GasPrice().ToInt() - } - - return types.NewMessage(tx.From(), tx.To(), tx.Nonce().ToInt().Uint64(), - tx.Value().ToInt(), tx.Gas().ToInt().Uint64(), tx.GasPrice().ToInt(), gasFeeCap, gasTipCap, - tx.Input(), accessList, false) -} - -func stateObjects(stateDB *state2.StateDB) (stateObjects []*model.StateObject) { - for _, stateObject := range stateDB.GetStateObjects() { - if stateObject.Used() { - stateObjects = append(stateObjects, &model.StateObject{ - Address: stateObject.Address().String(), - Data: &model.Data{ - Nonce: stateObject.OriginalNonce(), - Balance: stateObject.OriginalBalance().Bytes(), - CodeHash: stateObject.OriginalCodeHash(), - }, - Code: stateObject.GetCode(), - Storage: stateObject.GetStorage(), - }) - } - } - - return stateObjects -} - -func headers(chain *Chain) (headers []*model.Header) { - for _, header := range chain.GetHeaders() { - gasLimit := make([]byte, 8) - binary.LittleEndian.PutUint64(gasLimit, header.GasLimit) - - var baseFee []byte - if header.BaseFee != nil { - baseFee = header.BaseFee.Bytes() - } - - headers = append(headers, &model.Header{ - Number: header.Number.Int64(), - ReceiptHash: header.ReceiptHash.Bytes(), - ParentHash: header.ParentHash.Bytes(), - Root: header.Root.Bytes(), - UncleHash: header.UncleHash.Bytes(), - GasLimit: gasLimit, - TxHash: header.TxHash.Bytes(), - Timestamp: int64(header.Time), - Difficulty: header.Difficulty.Bytes(), - Coinbase: header.Coinbase.Bytes(), - Bloom: header.Bloom.Bytes(), - GasUsed: header.GasUsed, - Extra: header.Extra, - MixDigest: header.MixDigest.Bytes(), - Nonce: header.Nonce[:], - BaseFee: baseFee, - }) - } - - return headers -} diff --git a/ethereum/geth/schema.go b/ethereum/geth/schema.go deleted file mode 100644 index 8edbd54..0000000 --- a/ethereum/geth/schema.go +++ /dev/null @@ -1,173 +0,0 @@ -package geth - -import ( - "fmt" - "regexp" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/tenderly/tenderly-cli/ethereum/schema" - "github.com/tenderly/tenderly-cli/ethereum/types" - "github.com/tenderly/tenderly-cli/jsonrpc2" -) - -var DefaultSchema = Schema{ - ValueEth: ethSchema{}, - ValueNet: netSchema{}, - ValueTrace: traceSchema{}, - ValuePubSub: pubSubSchema{}, -} - -type Schema struct { - ValueEth schema.EthSchema - ValueNet schema.NetSchema - ValueTrace schema.TraceSchema - ValuePubSub schema.PubSubSchema -} - -func (s *Schema) Eth() schema.EthSchema { - return s.ValueEth -} - -func (s *Schema) Net() schema.NetSchema { - return s.ValueNet -} - -func (s *Schema) Trace() schema.TraceSchema { - return s.ValueTrace -} - -func (s *Schema) PubSub() schema.PubSubSchema { - return s.ValuePubSub -} - -// Eth - -type ethSchema struct { -} - -func (ethSchema) BlockNumber() (*jsonrpc2.Request, *types.Number) { - var num types.Number - - return jsonrpc2.NewRequest("eth_blockNumber"), &num -} - -func (ethSchema) GetBlockByNumber(num types.Number) (*jsonrpc2.Request, types.Block) { - var block Block - - return jsonrpc2.NewRequest("eth_getBlockByNumber", num.Hex(), true), &block -} - -func (ethSchema) GetBlockByHash(hash string) (*jsonrpc2.Request, types.BlockHeader) { - var block BlockHeader - - return jsonrpc2.NewRequest("eth_getBlockByHash", hash, false), &block -} - -func (ethSchema) GetTransaction(hash string) (*jsonrpc2.Request, types.Transaction) { - var t Transaction - - return jsonrpc2.NewRequest("eth_getTransactionByHash", hash), &t -} - -func (ethSchema) GetTransactionReceipt(hash string) (*jsonrpc2.Request, types.TransactionReceipt) { - var receipt TransactionReceipt - - return jsonrpc2.NewRequest("eth_getTransactionReceipt", hash), &receipt -} - -func (ethSchema) GetBalance(address string, block *types.Number) (*jsonrpc2.Request, *hexutil.Big) { - var balance hexutil.Big - - param := "latest" - if block != nil { - param = fmt.Sprintf("0x%x", *block) - } - - return jsonrpc2.NewRequest("eth_getBalance", address, param), &balance // "latest" -} - -func (ethSchema) GetCode(address string, block *types.Number) (*jsonrpc2.Request, *string) { - var code string - - param := "latest" - if block != nil { - param = fmt.Sprintf("0x%x", *block) - } - - return jsonrpc2.NewRequest("eth_getCode", address, param), &code -} - -func (ethSchema) GetNonce(address string, block *types.Number) (*jsonrpc2.Request, *hexutil.Uint64) { - var nonce hexutil.Uint64 - - param := "latest" - if block != nil { - param = fmt.Sprintf("0x%x", *block) - } - - return jsonrpc2.NewRequest("eth_getTransactionCount", address, param), &nonce -} - -func (ethSchema) GetStorage(address string, offset common.Hash, block *types.Number) (*jsonrpc2.Request, *string) { - var data string - - param := "latest" - if block != nil { - param = fmt.Sprintf("0x%x", *block) - } - - re := regexp.MustCompile("^(0x)0*([0-9a-fA-F]+)$") - slot := re.ReplaceAllString(offset.String(), "$1$2") - - return jsonrpc2.NewRequest("eth_getStorageAt", address, slot, param), &data -} - -// Net - -type netSchema struct { -} - -func (netSchema) Version() (*jsonrpc2.Request, *string) { - var v string - - return jsonrpc2.NewRequest("net_version"), &v -} - -// States - -type traceSchema struct { -} - -func (traceSchema) VMTrace(hash string) (*jsonrpc2.Request, types.TransactionStates) { - var trace TraceResult - - return jsonrpc2.NewRequest("debug_traceTransaction", hash, struct{}{}), &trace -} -func (traceSchema) CallTrace(hash string) (*jsonrpc2.Request, types.CallTraces) { - var trace CallTrace - - return jsonrpc2.NewRequest("debug_traceTransaction", hash, map[string]string{"tracer": "callTracer"}), &trace -} - -// PubSub - -type PubSubSchema interface { - Subscribe() (*jsonrpc2.Request, *types.SubscriptionID) - Unsubscribe(id types.SubscriptionID) (*jsonrpc2.Request, *types.UnsubscribeSuccess) -} - -type pubSubSchema struct { -} - -func (pubSubSchema) Subscribe() (*jsonrpc2.Request, *types.SubscriptionID) { - id := types.NewNilSubscriptionID() - - return jsonrpc2.NewRequest("eth_subscribe", "newHeads"), &id -} - -func (pubSubSchema) Unsubscribe(id types.SubscriptionID) (*jsonrpc2.Request, *types.UnsubscribeSuccess) { - var success types.UnsubscribeSuccess - - return jsonrpc2.NewRequest("eth_unsubscribe", id.String()), &success -} diff --git a/ethereum/geth/types.go b/ethereum/geth/types.go deleted file mode 100644 index 1cfe26d..0000000 --- a/ethereum/geth/types.go +++ /dev/null @@ -1,508 +0,0 @@ -package geth - -import ( - "encoding/json" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - - "github.com/tenderly/tenderly-cli/ethereum/types" -) - -// Core Types - -type Header struct { - HNumber *types.Number `json:"number"` -} - -func (h *Header) Number() *types.Number { - return h.HNumber -} - -type Block struct { - ValuesNumber types.Number `json:"number"` - ValuesHash common.Hash `json:"hash"` - ValueParentHash common.Hash `json:"parentHash"` - ValueTimestamp *hexutil.Big `json:"timestamp"` - ValueDifficulty *hexutil.Big `json:"difficulty"` - ValueGasLimit *hexutil.Big `json:"gasLimit"` - ValuesTransactions []*Transaction `json:"transactions"` - ValueBaseFeePerGas *hexutil.Big `json:"baseFeePerGas"` -} - -func (b Block) Number() types.Number { - return b.ValuesNumber -} - -func (b Block) Hash() common.Hash { - return b.ValuesHash -} - -func (b *Block) ParentHash() common.Hash { - return b.ValueParentHash -} - -func (b *Block) Time() *hexutil.Big { - return b.ValueTimestamp -} - -func (b *Block) Timestamp() time.Time { - return time.Unix(b.ValueTimestamp.ToInt().Int64(), 0) -} - -func (b *Block) Difficulty() []byte { - return []byte(b.ValueDifficulty.String()) -} - -func (b *Block) GasLimit() *hexutil.Big { - return b.ValueGasLimit -} - -func (b Block) Transactions() []types.Transaction { - transactions := make([]types.Transaction, len(b.ValuesTransactions)) - for k, v := range b.ValuesTransactions { - transactions[k] = v - } - - return transactions -} - -func (b *Block) BaseFeePerGas() *hexutil.Big { - return b.ValueBaseFeePerGas -} - -type BlockHeader struct { - ValueNumber types.Number `json:"number"` - ValueBlockHash common.Hash `json:"hash"` - ValueStateRoot common.Hash `json:"stateRoot"` - ValueParentHash common.Hash `json:"parentHash"` - ValueUncleHash common.Hash `json:"sha3Uncles"` - ValueTxHash common.Hash `json:"transactionsRoot"` - ValueReceiptHash common.Hash `json:"receiptsRoot"` - ValueBloom hexutil.Bytes `json:"logsBloom"` - ValueTimestamp *hexutil.Big `json:"timestamp"` - ValueDifficulty *hexutil.Big `json:"difficulty"` - ValueGasLimit *hexutil.Big `json:"gasLimit"` - ValueGasUsed *hexutil.Big `json:"gasUsed"` - ValueCoinbase common.Address `json:"miner"` - ValueExtraData hexutil.Bytes `json:"extraData"` - ValueMixDigest common.Hash `json:"mixDigest"` - ValueNonce hexutil.Bytes `json:"nonce"` - ValueBaseFeePerGas *hexutil.Big `json:"baseFeePerGas"` -} - -func (b *BlockHeader) Number() types.Number { - return b.ValueNumber -} - -func (b *BlockHeader) Hash() common.Hash { - return b.ValueBlockHash -} - -func (b *BlockHeader) StateRoot() common.Hash { - return b.ValueStateRoot -} - -func (b *BlockHeader) ParentHash() common.Hash { - return b.ValueParentHash -} - -func (b *BlockHeader) UncleHash() common.Hash { - return b.ValueUncleHash -} - -func (b *BlockHeader) TxHash() common.Hash { - return b.ValueTxHash -} - -func (b *BlockHeader) ReceiptHash() common.Hash { - return b.ValueReceiptHash -} - -func (b *BlockHeader) Bloom() [256]byte { - var arr [256]byte - copy(arr[:], b.ValueBloom[:256]) - return arr -} - -func (b *BlockHeader) Time() *hexutil.Big { - return b.ValueTimestamp -} - -func (b *BlockHeader) Timestamp() time.Time { - return time.Unix(b.ValueTimestamp.ToInt().Int64(), 0) -} - -func (b *BlockHeader) Difficulty() []byte { - return []byte(b.ValueDifficulty.String()) -} - -func (b *BlockHeader) GasLimit() *hexutil.Big { - return b.ValueGasLimit -} - -func (b *BlockHeader) GasUsed() *hexutil.Big { - return b.ValueGasUsed -} - -func (b *BlockHeader) Coinbase() common.Address { - return b.ValueCoinbase -} - -func (b *BlockHeader) ExtraData() hexutil.Bytes { - return b.ValueExtraData -} - -func (b *BlockHeader) MixDigest() common.Hash { - return b.ValueMixDigest -} - -func (b *BlockHeader) Nonce() [8]byte { - var arr [8]byte - copy(arr[:], b.ValueNonce[:8]) - return arr -} - -func (b *BlockHeader) BaseFeePerGas() *hexutil.Big { - return b.ValueBaseFeePerGas -} - -type AccessTuple struct { - ValueAddress common.Address `json:"address"` - ValueStorageKeys []common.Hash `json:"storageKeys"` -} - -func (a AccessTuple) Address() common.Address { - return a.ValueAddress -} - -func (a AccessTuple) StorageKeys() []common.Hash { - return a.ValueStorageKeys -} - -type Transaction struct { - ValueHash common.Hash `json:"hash"` - ValueFrom common.Address `json:"from"` - ValueTo *common.Address `json:"to"` - ValueInput hexutil.Bytes `json:"input"` - ValueValue *hexutil.Big `json:"value"` - ValueGas *hexutil.Big `json:"gas"` - ValueGasTipCap *hexutil.Big `json:"maxPriorityFeePerGas"` - ValueGasFeeCap *hexutil.Big `json:"maxFeePerGas"` - ValueGasPrice *hexutil.Big `json:"gasPrice"` - ValueBlockNumber *hexutil.Big `json:"blockNumber"` - ValueBlockHash *common.Hash `json:"blockHash"` - ValueNonce *hexutil.Big `json:"nonce"` - - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` - - ValueAccessList []*AccessTuple `json:"accessList"` -} - -func (t *Transaction) Hash() common.Hash { - return t.ValueHash -} - -func (t *Transaction) From() common.Address { - return t.ValueFrom -} - -func (t *Transaction) To() *common.Address { - return t.ValueTo -} - -func (t *Transaction) Input() hexutil.Bytes { - return t.ValueInput -} - -func (t *Transaction) Value() *hexutil.Big { - return t.ValueValue -} - -func (t *Transaction) Gas() *hexutil.Big { - return t.ValueGas -} - -func (t *Transaction) GasTipCap() *hexutil.Big { - return t.ValueGasTipCap -} - -func (t *Transaction) GasFeeCap() *hexutil.Big { - return t.ValueGasFeeCap -} - -func (t *Transaction) GasPrice() *hexutil.Big { - return t.ValueGasPrice -} - -func (t *Transaction) BlockNumber() *hexutil.Big { - return t.ValueBlockNumber -} - -func (t *Transaction) BlockHash() *common.Hash { - return t.ValueBlockHash -} - -func (t *Transaction) Nonce() *hexutil.Big { - return t.ValueNonce -} - -func (t *Transaction) AccessList() (list []types.AccessTuple) { - for _, accessTuple := range t.ValueAccessList { - list = append( - list, &AccessTuple{ - ValueAddress: accessTuple.ValueAddress, - ValueStorageKeys: accessTuple.ValueStorageKeys, - }, - ) - } - - return -} - -type Log struct { - ValueAddress string `json:"address"` - ValueBlockHash string `json:"blockHash"` - ValueBlockNumber string `json:"blockNumber"` - ValueData string `json:"data"` - ValueLogIndex string `json:"logIndex"` - ValueRemoved bool `json:"removed"` - ValueTopics []string `json:"topics"` - ValueTransactionHash string `json:"transactionHash"` - ValueTransactionIndex string `json:"transactionIndex"` - ValueTransactionLogIndex string `json:"transactionLogIndex"` - ValueType string `json:"type"` -} - -func (l *Log) Data() string { - return l.ValueData -} - -func (l *Log) Topics() []string { - return l.ValueTopics -} - -type TransactionReceipt struct { - TTransactionHash string `json:"transactionHash"` - TTransactionIndex types.Number `json:"transactionIndex"` - TBlockHash common.Hash `json:"blockHash"` - TBlockNumber types.Number `json:"blockNumber"` - - TFrom common.Address `json:"from"` - TTo *common.Address `json:"to"` - - TGasUsed *hexutil.Big `json:"gasUsed"` - TCumulativeGasUsed *hexutil.Big `json:"cumulativeGasUsed"` - TEffectiveGasPrice hexutil.Uint64 `json:"effectiveGasPrice"` - TContractAddress *common.Address `json:"contractAddress"` - - TStatus string `json:"status"` // Can be null, if null do a check anyways. 0x0 fail, 0x1 success - TLogs []*Log `json:"logs"` - TLogsBloom hexutil.Bytes `json:"logsBloom"` - TRoot *string `json:"root"` -} - -func (t *TransactionReceipt) SetStatus(trace string) { - t.TStatus = "0x0 " + trace -} - -func (t *TransactionReceipt) Hash() string { - return t.TTransactionHash -} - -func (t *TransactionReceipt) TransactionIndex() types.Number { - return t.TTransactionIndex -} - -func (t *TransactionReceipt) BlockHash() common.Hash { - return t.TBlockHash -} - -func (t *TransactionReceipt) BlockNumber() types.Number { - return t.TBlockNumber -} - -func (t *TransactionReceipt) From() common.Address { - return t.TFrom -} - -func (t *TransactionReceipt) To() *common.Address { - return t.TTo -} - -func (t *TransactionReceipt) GasUsed() *hexutil.Big { - return t.TGasUsed -} - -func (t *TransactionReceipt) CumulativeGasUsed() *hexutil.Big { - return t.TCumulativeGasUsed -} - -func (t *TransactionReceipt) EffectiveGasPrice() hexutil.Uint64 { - return t.TEffectiveGasPrice -} - -func (t *TransactionReceipt) ContractAddress() *common.Address { - return t.TContractAddress -} - -func (t *TransactionReceipt) Status() string { - return t.TStatus -} - -func (t *TransactionReceipt) Logs() []types.Log { - var logs []types.Log - - for _, log := range t.TLogs { - logs = append(logs, log) - } - - return logs -} - -func (t *TransactionReceipt) LogsBloom() hexutil.Bytes { - return t.TLogsBloom -} - -// States Types - -type EvmState struct { - ValuePc uint64 `json:"pc"` - ValueOp string `json:"op"` - ValueGas uint64 `json:"gas"` - ValueGasCost int64 `json:"gasCost"` - ValueDepth int `json:"depth"` - ValueError json.RawMessage `json:"error,omitempty"` - ValueStack *[]string `json:"stack,omitempty"` - ValueMemory *[]string `json:"memory,omitempty"` - ValueStorage *map[string]string `json:"storage,omitempty"` -} - -func (s *EvmState) Pc() uint64 { - return s.ValuePc -} - -func (s *EvmState) Depth() int { - return s.ValueDepth -} - -func (s *EvmState) Op() string { - return s.ValueOp -} - -func (s *EvmState) Stack() []string { - return *s.ValueStack -} - -type TraceResult struct { - Gas uint64 `json:"gas"` - Failed bool `json:"failed"` - ReturnValue string `json:"returnValue"` - StructLogs []*EvmState `json:"structLogs"` -} - -type CallTrace struct { - ValueHash *common.Hash `json:"hash"` - ValueParentHash *common.Hash `json:"parentHash"` - ValueTransactionHash *common.Hash `json:"transactionHash"` - ValueType string `json:"type"` - ValueFrom common.Address `json:"from"` - ValueTo common.Address `json:"to"` - ValueInput hexutil.Bytes `json:"input"` - ValueOutput hexutil.Bytes `json:"output"` - ValueGas *hexutil.Uint64 `json:"gas,omitempty"` - ValueGasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"` - ValueValue *hexutil.Big `json:"value,omitempty"` - ValueError string `json:"error,omitempty"` - ValueCalls []CallTrace `json:"calls,omitempty"` -} - -func (c *CallTrace) Hash() *common.Hash { - return c.ValueHash -} - -func (c *CallTrace) ParentHash() *common.Hash { - return c.ValueParentHash -} - -func (c *CallTrace) TransactionHash() *common.Hash { - return c.ValueTransactionHash -} - -func (c *CallTrace) Type() string { - return c.ValueType -} - -func (c *CallTrace) From() common.Address { - return c.ValueFrom -} - -func (c *CallTrace) To() common.Address { - return c.ValueTo -} - -func (c *CallTrace) Input() hexutil.Bytes { - return c.ValueInput -} - -func (c *CallTrace) Output() hexutil.Bytes { - return c.ValueOutput -} - -func (c *CallTrace) Gas() *hexutil.Uint64 { - return c.ValueGas -} - -func (c *CallTrace) GasUsed() *hexutil.Uint64 { - return c.ValueGasUsed -} - -func (c *CallTrace) Value() *hexutil.Big { - return c.ValueValue -} - -func (c *CallTrace) Error() string { - return c.ValueError -} - -func (c *CallTrace) Traces() []types.Trace { - ch := make(chan *CallTrace) - Walk(c, ch) - - var traces []types.Trace - for callTrace := range ch { - traces = append(traces, callTrace) - } - - return traces -} - -func Walk(c *CallTrace, ch chan *CallTrace) { - if c == nil { - return - } - ch <- c - for _, callTrace := range c.ValueCalls { - Walk(&callTrace, ch) - } -} - -func (gtr *TraceResult) States() []types.EvmState { - traces := make([]types.EvmState, len(gtr.StructLogs)) - for k, v := range gtr.StructLogs { - traces[k] = v - } - - return traces -} - -func (gtr *TraceResult) ProcessTrace() { -} - -type SubscriptionResult struct { - Subscription types.SubscriptionID `json:"subscription"` - Result Header `json:"result"` -} diff --git a/ethereum/parity/schema.go b/ethereum/parity/schema.go deleted file mode 100644 index ceb050b..0000000 --- a/ethereum/parity/schema.go +++ /dev/null @@ -1,203 +0,0 @@ -package parity - -import ( - "fmt" - "regexp" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/tenderly/tenderly-cli/ethereum/schema" - "github.com/tenderly/tenderly-cli/ethereum/types" - "github.com/tenderly/tenderly-cli/jsonrpc2" -) - -var DefaultSchema = Schema{ - ValueEth: ethSchema{}, - ValueNet: netSchema{}, - ValueTrace: traceSchema{}, - ValuePubSub: pubSubSchema{}, - ValueParity: ParitySchema{}, -} - -type Schema struct { - ValueEth schema.EthSchema - ValueNet schema.NetSchema - ValueTrace schema.TraceSchema - ValuePubSub schema.PubSubSchema - ValueParity ParitySchema -} - -func (s *Schema) Eth() schema.EthSchema { - return s.ValueEth -} - -func (s *Schema) Net() schema.NetSchema { - return s.ValueNet -} - -func (s *Schema) Trace() schema.TraceSchema { - return s.ValueTrace -} - -func (s *Schema) PubSub() schema.PubSubSchema { - return s.ValuePubSub -} - -func (s *Schema) Parity() ParitySchema { - return s.ValueParity -} - -// Eth - -type ethSchema struct { -} - -func (ethSchema) BlockNumber() (*jsonrpc2.Request, *types.Number) { - var num types.Number - - return jsonrpc2.NewRequest("eth_blockNumber"), &num -} - -func (ethSchema) GetBlockByNumber(num types.Number) (*jsonrpc2.Request, types.Block) { - var block Block - - return jsonrpc2.NewRequest("eth_getBlockByNumber", num.Hex(), true), &block -} - -func (ethSchema) GetBlockByHash(hash string) (*jsonrpc2.Request, types.BlockHeader) { - var block BlockHeader - - return jsonrpc2.NewRequest("eth_getBlockByHash", hash, false), &block -} - -func (ethSchema) GetTransaction(hash string) (*jsonrpc2.Request, types.Transaction) { - var t Transaction - - return jsonrpc2.NewRequest("eth_getTransactionByHash", hash), &t -} - -func (ethSchema) GetTransactionReceipt(hash string) (*jsonrpc2.Request, types.TransactionReceipt) { - var receipt TransactionReceipt - - return jsonrpc2.NewRequest("eth_getTransactionReceipt", hash), &receipt -} - -func (ethSchema) GetBalance(address string, block *types.Number) (*jsonrpc2.Request, *hexutil.Big) { - var balance hexutil.Big - - param := "latest" - if block != nil { - param = fmt.Sprintf("0x%x", *block) - } - - return jsonrpc2.NewRequest("eth_getBalance", address, param), &balance -} - -func (ethSchema) GetCode(address string, block *types.Number) (*jsonrpc2.Request, *string) { - var code string - - param := "latest" - if block != nil { - param = fmt.Sprintf("0x%x", *block) - } - - return jsonrpc2.NewRequest("eth_getCode", address, param), &code -} - -func (ethSchema) GetNonce(address string, block *types.Number) (*jsonrpc2.Request, *hexutil.Uint64) { - var nonce hexutil.Uint64 - - param := "latest" - if block != nil { - param = fmt.Sprintf("0x%x", *block) - } - - return jsonrpc2.NewRequest("eth_getTransactionCount", address, param), &nonce -} - -func (ethSchema) GetStorage(address string, offset common.Hash, block *types.Number) (*jsonrpc2.Request, *string) { - var data string - - param := "latest" - if block != nil { - param = fmt.Sprintf("0x%x", *block) - } - - re := regexp.MustCompile("^(0x)0*([0-9a-fA-F]+)$") - slot := re.ReplaceAllString(offset.String(), "$1$2") - - return jsonrpc2.NewRequest("eth_getStorageAt", address, slot, param), &data -} - -// Net - -type netSchema struct { -} - -func (netSchema) Version() (*jsonrpc2.Request, *string) { - var v string - - return jsonrpc2.NewRequest("net_version"), &v -} - -// States - -type traceSchema struct { -} - -func (traceSchema) VMTrace(hash string) (*jsonrpc2.Request, types.TransactionStates) { - var trace TraceResult - - return jsonrpc2.NewRequest("trace_replayTransaction", hash, []string{"vmTrace"}), &trace -} - -func (traceSchema) CallTrace(hash string) (*jsonrpc2.Request, types.CallTraces) { - var trace TraceResult - - return jsonrpc2.NewRequest("trace_replayTransaction", hash, []string{"traceSchema"}), &trace -} - -type codeSchema struct { -} - -func (codeSchema) GetCode(address string) (*jsonrpc2.Request, *string) { - var code string - - return jsonrpc2.NewRequest("eth_getCode", address, "latest"), &code -} - -// PubSub - -type PubSubSchema interface { - Subscribe() (*jsonrpc2.Request, *types.SubscriptionID) - Unsubscribe(id types.SubscriptionID) (*jsonrpc2.Request, *types.UnsubscribeSuccess) -} - -type pubSubSchema struct { -} - -func (pubSubSchema) Subscribe() (*jsonrpc2.Request, *types.SubscriptionID) { - id := types.NewNilSubscriptionID() - - return jsonrpc2.NewRequest("eth_subscribe", "newHeads"), &id -} - -func (pubSubSchema) Unsubscribe(id types.SubscriptionID) (*jsonrpc2.Request, *types.UnsubscribeSuccess) { - var success types.UnsubscribeSuccess - - return jsonrpc2.NewRequest("eth_unsubscribe", id.String()), &success -} - -// Parity Schema - -type ParityVersionInfo struct { -} - -type ParitySchema struct { -} - -func (ParitySchema) VersionInfo() (*jsonrpc2.Request, *ParityVersionInfo) { - var info ParityVersionInfo - - return jsonrpc2.NewRequest("parity_versionInfo"), &info -} diff --git a/ethereum/parity/types.go b/ethereum/parity/types.go deleted file mode 100644 index 7c6038f..0000000 --- a/ethereum/parity/types.go +++ /dev/null @@ -1,593 +0,0 @@ -package parity - -import ( - "encoding/json" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/tenderly/tenderly-cli/ethereum/types" -) - -// Core Types - -type Header struct { - HNumber *types.Number `json:"number"` -} - -func (h *Header) Number() *types.Number { - return h.HNumber -} - -type Block struct { - ValuesNumber types.Number `json:"number"` - ValuesHash common.Hash `json:"hash"` - ValueParentHash common.Hash `json:"parentHash"` - ValueTimestamp *hexutil.Big `json:"timestamp"` - ValueDifficulty []byte `json:"difficulty"` - ValueGasLimit *hexutil.Big `json:"gasLimit"` - ValuesTransactions []*Transaction `json:"transactions"` - ValueBaseFeePerGas *hexutil.Big `json:"baseFeePerGas"` -} - -func (b Block) Number() types.Number { - return b.ValuesNumber -} - -func (b Block) Hash() common.Hash { - return b.ValuesHash -} - -func (b *Block) ParentHash() common.Hash { - return b.ValueParentHash -} - -func (b *Block) Time() *hexutil.Big { - return b.ValueTimestamp -} - -func (b *Block) Timestamp() time.Time { - return time.Unix(b.ValueTimestamp.ToInt().Int64(), 0) -} - -func (b *Block) Difficulty() []byte { - return b.ValueDifficulty -} - -func (b *Block) GasLimit() *hexutil.Big { - return b.ValueGasLimit -} - -func (b *Block) Transactions() []types.Transaction { - if b.ValuesTransactions == nil { - return []types.Transaction{} - } - - traces := make([]types.Transaction, len(b.ValuesTransactions)) - for k, v := range b.ValuesTransactions { - traces[k] = v - } - - return traces -} - -func (b *Block) BaseFeePerGas() *hexutil.Big { - return b.ValueBaseFeePerGas -} - -type BlockHeader struct { - ValueNumber types.Number `json:"number"` - ValueBlockHash common.Hash `json:"hash"` - ValueStateRoot common.Hash `json:"stateRoot"` - ValueParentHash common.Hash `json:"parentHash"` - ValueUncleHash common.Hash `json:"sha3Uncles"` - ValueTxHash common.Hash `json:"transactionsRoot"` - ValueReceiptHash common.Hash `json:"receiptsRoot"` - ValueBloom hexutil.Bytes `json:"logsBloom"` - ValueTimestamp *hexutil.Big `json:"timestamp"` - ValueDifficulty []byte `json:"difficulty"` - ValueGasLimit *hexutil.Big `json:"gasLimit"` - ValueGasUsed *hexutil.Big `json:"gasUsed"` - ValueCoinbase common.Address `json:"miner"` - ValueExtraData hexutil.Bytes `json:"extraData"` - ValueMixDigest common.Hash `json:"mixDigest"` - ValueNonce hexutil.Bytes `json:"nonce"` - ValueBaseFeePerGas *hexutil.Big `json:"baseFeePerGas"` -} - -func (b *BlockHeader) Number() types.Number { - return b.ValueNumber -} - -func (b *BlockHeader) Hash() common.Hash { - return b.ValueBlockHash -} - -func (b *BlockHeader) StateRoot() common.Hash { - return b.ValueStateRoot -} - -func (b *BlockHeader) ParentHash() common.Hash { - return b.ValueParentHash -} - -func (b *BlockHeader) UncleHash() common.Hash { - return b.ValueUncleHash -} - -func (b *BlockHeader) TxHash() common.Hash { - return b.ValueTxHash -} - -func (b *BlockHeader) ReceiptHash() common.Hash { - return b.ValueReceiptHash -} - -func (b *BlockHeader) Bloom() [256]byte { - var arr [256]byte - copy(arr[:], b.ValueBloom[:256]) - return arr -} - -func (b *BlockHeader) Time() *hexutil.Big { - return b.ValueTimestamp -} - -func (b *BlockHeader) Timestamp() time.Time { - return time.Unix(b.ValueTimestamp.ToInt().Int64(), 0) -} - -func (b *BlockHeader) Difficulty() []byte { - return b.ValueDifficulty -} - -func (b *BlockHeader) GasLimit() *hexutil.Big { - return b.ValueGasLimit -} - -func (b *BlockHeader) GasUsed() *hexutil.Big { - return b.ValueGasUsed -} - -func (b *BlockHeader) Coinbase() common.Address { - return b.ValueCoinbase -} - -func (b *BlockHeader) ExtraData() hexutil.Bytes { - return b.ValueExtraData -} - -func (b *BlockHeader) MixDigest() common.Hash { - return b.ValueMixDigest -} - -func (b *BlockHeader) Nonce() [8]byte { - if len(b.ValueNonce) == 0 { - return [8]byte{} - } - - var arr [8]byte - copy(arr[:], b.ValueNonce[:8]) - return arr -} - -func (b *BlockHeader) BaseFeePerGas() *hexutil.Big { - return b.ValueBaseFeePerGas -} - -type AccessTuple struct { - ValueAddress common.Address `json:"address"` - ValueStorageKeys []common.Hash `json:"storageKeys"` -} - -func (a AccessTuple) Address() common.Address { - return a.ValueAddress -} - -func (a AccessTuple) StorageKeys() []common.Hash { - return a.ValueStorageKeys -} - -type Transaction struct { - ValueHash common.Hash `json:"hash"` - ValueFrom common.Address `json:"from"` - ValueTo *common.Address `json:"to"` - ValueInput hexutil.Bytes `json:"input"` - ValueValue *hexutil.Big `json:"value"` - ValueGas *hexutil.Big `json:"gas"` - ValueGasTipCap *hexutil.Big `json:"maxPriorityFeePerGas"` - ValueGasFeeCap *hexutil.Big `json:"maxFeePerGas"` - ValueGasPrice *hexutil.Big `json:"gasPrice"` - ValueBlockNumber *hexutil.Big `json:"blockNumber"` - ValueBlockHash *common.Hash `json:"blockHash"` - ValueNonce *hexutil.Big `json:"nonce"` - - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` - - ValueAccessList []*AccessTuple `json:"accessList"` -} - -func (t *Transaction) Hash() common.Hash { - return t.ValueHash -} - -func (t *Transaction) From() common.Address { - return t.ValueFrom -} - -func (t *Transaction) To() *common.Address { - return t.ValueTo -} - -func (t *Transaction) Input() hexutil.Bytes { - return t.ValueInput -} - -func (t *Transaction) Value() *hexutil.Big { - return t.ValueValue -} - -func (t *Transaction) Gas() *hexutil.Big { - return t.ValueGas -} - -func (t *Transaction) GasTipCap() *hexutil.Big { - return t.ValueGasTipCap -} - -func (t *Transaction) GasFeeCap() *hexutil.Big { - return t.ValueGasFeeCap -} - -func (t *Transaction) GasPrice() *hexutil.Big { - return t.ValueGasPrice -} - -func (t *Transaction) BlockNumber() *hexutil.Big { - return t.ValueBlockNumber -} - -func (t *Transaction) BlockHash() *common.Hash { - return t.ValueBlockHash -} - -func (t *Transaction) Nonce() *hexutil.Big { - return t.ValueNonce -} - -func (t *Transaction) AccessList() (list []types.AccessTuple) { - for _, accessTuple := range t.ValueAccessList { - list = append(list, &AccessTuple{ - ValueAddress: accessTuple.ValueAddress, - ValueStorageKeys: accessTuple.ValueStorageKeys, - }) - } - - return -} - -type Log struct { - ValueAddress string `json:"address"` - ValueBlockHash string `json:"blockHash"` - ValueBlockNumber string `json:"blockNumber"` - ValueData string `json:"data"` - ValueLogIndex string `json:"logIndex"` - ValueRemoved bool `json:"removed"` - ValueTopics []string `json:"topics"` - ValueTransactionHash string `json:"transactionHash"` - ValueTransactionIndex string `json:"transactionIndex"` - ValueTransactionLogIndex string `json:"transactionLogIndex"` - ValueType string `json:"type"` -} - -func (l *Log) Data() string { - return l.ValueData -} - -func (l *Log) Topics() []string { - return l.ValueTopics -} - -type TransactionReceipt struct { - TTransactionHash string `json:"transactionHash"` - TTransactionIndex types.Number `json:"transactionIndex"` - TBlockHash common.Hash `json:"blockHash"` - TBlockNumber types.Number `json:"blockNumber"` - - TFrom common.Address `json:"from"` - TTo *common.Address `json:"to"` - - TGasUsed *hexutil.Big `json:"gasUsed"` - TCumulativeGasUsed *hexutil.Big `json:"cumulativeGasUsed"` - TEffectiveGasPrice hexutil.Uint64 `json:"effectiveGasPrice"` - TContractAddress *common.Address `json:"contractAddress"` - - TStatus string `json:"status"` // Can be null, if null do a check anyways. 0x0 fail, 0x1 success - TLogs []*Log `json:"logs"` - TLogsBloom hexutil.Bytes `json:"logsBloom"` - TRoot *string `json:"root"` -} - -func (t *TransactionReceipt) SetStatus(trace string) { - t.TStatus = "0x0 " + trace -} - -func (t *TransactionReceipt) Hash() string { - return t.TTransactionHash -} - -func (t *TransactionReceipt) TransactionIndex() types.Number { - return t.TTransactionIndex -} - -func (t *TransactionReceipt) BlockHash() common.Hash { - return t.TBlockHash -} - -func (t *TransactionReceipt) BlockNumber() types.Number { - return t.TBlockNumber -} - -func (t *TransactionReceipt) From() common.Address { - return t.TFrom -} - -func (t *TransactionReceipt) To() *common.Address { - return t.TTo -} - -func (t *TransactionReceipt) GasUsed() *hexutil.Big { - return t.TGasUsed -} - -func (t *TransactionReceipt) CumulativeGasUsed() *hexutil.Big { - return t.TCumulativeGasUsed -} - -func (t *TransactionReceipt) EffectiveGasPrice() hexutil.Uint64 { - return t.TEffectiveGasPrice -} - -func (t *TransactionReceipt) ContractAddress() *common.Address { - return t.TContractAddress -} - -func (t *TransactionReceipt) Status() string { - return t.TStatus -} - -func (t *TransactionReceipt) Logs() []types.Log { - var logs []types.Log - - for _, log := range t.TLogs { - logs = append(logs, log) - } - - return logs -} - -func (t *TransactionReceipt) LogsBloom() hexutil.Bytes { - return t.TLogsBloom -} - -type Version struct { - Major int `json:"major"` - Minor int `json:"minor"` - Patch int `json:"patch"` -} - -type VersionInfo struct { - Hash string `json:"hash"` - Track string `json:"track"` - Version Version `json:"version"` -} - -// States Types - -type Mem struct { - Data hexutil.Bytes `json:"data"` - Off int64 `json:"off"` -} - -type Ex struct { - Mem Mem `json:"mem"` - Push []string `json:"push"` - Used uint64 `json:"used"` -} - -type VmState struct { - ValuePc uint64 `json:"pc"` - ValueOp string `json:"op"` - ValueEx Ex `json:"ex"` - ValueSub *VmTrace `json:"sub"` - ValueGas uint64 `json:"gas"` - ValueGasCost int64 `json:"cost"` - ValueDepth int `json:"depth"` - ValueError json.RawMessage `json:"error,omitempty"` - ValueStack *[]string `json:"stack,omitempty"` - ValueMemory *[]string `json:"memory,omitempty"` - ValueStorage *map[string]string `json:"storage,omitempty"` - Terminating bool -} - -func (pvs *VmState) Pc() uint64 { - return pvs.ValuePc -} - -func (pvs *VmState) Depth() int { - return pvs.ValueDepth + 1 -} - -func (pvs *VmState) Op() string { - return "Not implemented" -} - -func (pvs *VmState) Stack() []string { - return *pvs.ValueStack -} - -type TraceResult struct { - VmTrace *VmTrace `json:"vmTrace"` - CallTrace []*Trace `json:"traceSchema"` -} - -type VmTrace struct { - Logs []*VmState `json:"ops"` - Code hexutil.Bytes `json:"code"` -} - -func (tr *TraceResult) States() []types.EvmState { - if tr.VmTrace == nil { - return []types.EvmState{} - } - - traces := make([]types.EvmState, len(tr.VmTrace.Logs)) - for k, v := range tr.VmTrace.Logs { - traces[k] = v - } - - return traces -} - -func (tr *TraceResult) Traces() []types.Trace { - if tr.VmTrace == nil { - return []types.Trace{} - } - - traces := make([]types.Trace, len(tr.CallTrace)) - for k, v := range tr.CallTrace { - traces[k] = v - } - - return traces -} - -func (tr *TraceResult) ProcessTrace() { - if tr.VmTrace == nil { - return - } - - tr.VmTrace.Logs = Walk(tr.VmTrace) -} - -func Walk(vmt *VmTrace) []*VmState { - var traces []*VmState - - vmt.Logs[0].ValueOp = vm.OpCode(vmt.Code[vmt.Logs[0].ValuePc]).String() - for i := 0; i < len(vmt.Logs); i++ { - if i > 0 { - vmt.Logs[i].ValueStack = vmt.Logs[i-1].ValueStack - - if vmt.Logs[i-1].ValueOp == "CALL" { - vmt.Logs[i].ValueStack = nil - } - } - - if i < len(vmt.Logs)-1 { - opCode := vm.OpCode(vmt.Code[vmt.Logs[i+1].ValuePc]) - vmt.Logs[i+1].ValueOp = opCode.String() - - if vmt.Logs[i+1].ValueOp == "EXTCODESIZE" { - vmt.Logs[i].ValueStack = &[]string{} - for j := 0; j < len(vmt.Logs[i].ValueEx.Push); j++ { - vmt.Logs[i].ValueEx.Push[j] = "000000000000000000000000" + vmt.Logs[i].ValueEx.Push[j][2:] - for len(vmt.Logs[i].ValueEx.Push[j]) < 64 { - vmt.Logs[i].ValueEx.Push[j] = "0" + vmt.Logs[i].ValueEx.Push[j] - } - } - - *vmt.Logs[i].ValueStack = append(*vmt.Logs[i].ValueStack, vmt.Logs[i].ValueEx.Push...) - } - } - - traces = append(traces, vmt.Logs[i]) - if vmt.Logs[i].ValueSub != nil { - subTraces := Walk(vmt.Logs[i].ValueSub) - subTraces[len(subTraces)-1].Terminating = true - - traces = append(traces, subTraces...) - } - } - - traces[len(traces)-1].Terminating = true - - return traces -} - -type Action struct { - CallType string `json:"callType"` - Hash *common.Hash `json:"hash"` - ParentHash *common.Hash `json:"parentHash"` - TransactionHash *common.Hash `json:"transactionHash"` - From common.Address `json:"from"` - To common.Address `json:"to"` - Input hexutil.Bytes `json:"input"` - Gas *hexutil.Uint64 `json:"gas,omitempty"` - Value *hexutil.Big `json:"value,omitempty"` -} - -type Result struct { - GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"` - Output hexutil.Bytes `json:"output"` -} - -type Trace struct { - ValueAction Action `json:"action"` - ValueResult Result `json:"result"` - ValueLogs []Log `json:"logs"` - ValueSubtraces int `json:"subtraces"` - ValueError string `json:"error"` - ValueTraceAddress []int `json:"traceAddress"` - ValueType string `json:"type"` -} - -func (t *Trace) Hash() *common.Hash { - return t.ValueAction.Hash -} - -func (t *Trace) ParentHash() *common.Hash { - return t.ValueAction.ParentHash -} - -func (t *Trace) TransactionHash() *common.Hash { - return t.ValueAction.TransactionHash -} - -func (t *Trace) Type() string { - return t.ValueType -} - -func (t *Trace) From() common.Address { - return t.ValueAction.From -} - -func (t *Trace) To() common.Address { - return t.ValueAction.To -} - -func (t *Trace) Input() hexutil.Bytes { - return t.ValueAction.Input -} - -func (t *Trace) Output() hexutil.Bytes { - return t.ValueResult.Output -} - -func (t *Trace) Gas() *hexutil.Uint64 { - return t.ValueAction.Gas -} - -func (t *Trace) GasUsed() *hexutil.Uint64 { - return t.ValueResult.GasUsed -} - -func (t *Trace) Value() *hexutil.Big { - return t.ValueAction.Value -} - -func (t *Trace) Error() string { - return t.ValueError -} diff --git a/ethereum/schema/schema.go b/ethereum/schema/schema.go deleted file mode 100644 index 13020a6..0000000 --- a/ethereum/schema/schema.go +++ /dev/null @@ -1,70 +0,0 @@ -package schema - -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/tenderly/tenderly-cli/ethereum/types" - "github.com/tenderly/tenderly-cli/jsonrpc2" -) - -type Schema interface { - Eth() EthSchema - Net() NetSchema - Trace() TraceSchema - PubSub() PubSubSchema -} - -// Eth - -type EthSchema interface { - BlockNumber() (*jsonrpc2.Request, *types.Number) - GetBlockByNumber(num types.Number) (*jsonrpc2.Request, types.Block) - GetBlockByHash(hash string) (*jsonrpc2.Request, types.BlockHeader) - GetTransaction(hash string) (*jsonrpc2.Request, types.Transaction) - GetTransactionReceipt(hash string) (*jsonrpc2.Request, types.TransactionReceipt) - GetBalance(address string, block *types.Number) (*jsonrpc2.Request, *hexutil.Big) - GetCode(address string, block *types.Number) (*jsonrpc2.Request, *string) - GetNonce(address string, block *types.Number) (*jsonrpc2.Request, *hexutil.Uint64) - GetStorage(address string, offset common.Hash, block *types.Number) (*jsonrpc2.Request, *string) -} - -// Net - -type NetSchema interface { - Version() (*jsonrpc2.Request, *string) -} - -// States - -type TraceSchema interface { - VMTrace(hash string) (*jsonrpc2.Request, types.TransactionStates) - CallTrace(hash string) (*jsonrpc2.Request, types.CallTraces) -} - -// Code - -type CodeSchema interface { - GetCode(address string) (*jsonrpc2.Request, *string) -} - -// PubSub - -type PubSubSchema interface { - Subscribe() (*jsonrpc2.Request, *types.SubscriptionID) - Unsubscribe(id types.SubscriptionID) (*jsonrpc2.Request, *types.UnsubscribeSuccess) -} - -type pubSubSchema struct { -} - -func (pubSubSchema) Subscribe() (*jsonrpc2.Request, *types.SubscriptionID) { - id := types.NewNilSubscriptionID() - - return jsonrpc2.NewRequest("eth_subscribe", "newHeads"), &id -} - -func (pubSubSchema) Unsubscribe(id types.SubscriptionID) (*jsonrpc2.Request, *types.UnsubscribeSuccess) { - var success types.UnsubscribeSuccess - - return jsonrpc2.NewRequest("eth_unsubscribe", id.String()), &success -} diff --git a/ethereum/state/access_list.go b/ethereum/state/access_list.go deleted file mode 100644 index 7e17a55..0000000 --- a/ethereum/state/access_list.go +++ /dev/null @@ -1,120 +0,0 @@ -package state - -import ( - "github.com/ethereum/go-ethereum/common" -) - -type accessList struct { - addresses map[common.Address]int - slots []map[common.Hash]struct{} -} - -// ContainsAddress returns true if the address is in the access list. -func (al *accessList) ContainsAddress(address common.Address) bool { - _, ok := al.addresses[address] - return ok -} - -// Contains checks if a slot within an account is present in the access list, returning -// separate flags for the presence of the account and the slot respectively. -func (al *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { - idx, ok := al.addresses[address] - if !ok { - // no such address (and hence zero slots) - return false, false - } - if idx == -1 { - // address yes, but no slots - return true, false - } - _, slotPresent = al.slots[idx][slot] - return true, slotPresent -} - -// newAccessList creates a new accessList. -func newAccessList() *accessList { - return &accessList{ - addresses: make(map[common.Address]int), - } -} - -// Copy creates an independent copy of an accessList. -func (a *accessList) Copy() *accessList { - cp := newAccessList() - for k, v := range a.addresses { - cp.addresses[k] = v - } - cp.slots = make([]map[common.Hash]struct{}, len(a.slots)) - for i, slotMap := range a.slots { - newSlotmap := make(map[common.Hash]struct{}, len(slotMap)) - for k := range slotMap { - newSlotmap[k] = struct{}{} - } - cp.slots[i] = newSlotmap - } - return cp -} - -// AddAddress adds an address to the access list, and returns 'true' if the operation -// caused a change (addr was not previously in the list). -func (al *accessList) AddAddress(address common.Address) bool { - if _, present := al.addresses[address]; present { - return false - } - al.addresses[address] = -1 - return true -} - -// AddSlot adds the specified (addr, slot) combo to the access list. -// Return values are: -// - address added -// - slot added -// For any 'true' value returned, a corresponding journal entry must be made. -func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) { - idx, addrPresent := al.addresses[address] - if !addrPresent || idx == -1 { - // Address not present, or addr present but no slots there - al.addresses[address] = len(al.slots) - slotmap := map[common.Hash]struct{}{slot: {}} - al.slots = append(al.slots, slotmap) - return !addrPresent, true - } - // There is already an (address,slot) mapping - slotmap := al.slots[idx] - if _, ok := slotmap[slot]; !ok { - slotmap[slot] = struct{}{} - // Journal add slot change - return false, true - } - // No changes required - return false, false -} - -// DeleteSlot removes an (address, slot)-tuple from the access list. -// This operation needs to be performed in the same order as the addition happened. -// This method is meant to be used by the journal, which maintains ordering of -// operations. -func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) { - idx, addrOk := al.addresses[address] - // There are two ways this can fail - if !addrOk { - panic("reverting slot change, address not present in list") - } - slotmap := al.slots[idx] - delete(slotmap, slot) - // If that was the last (first) slot, remove it - // Since additions and rollbacks are always performed in order, - // we can delete the item without worrying about screwing up later indices - if len(slotmap) == 0 { - al.slots = al.slots[:idx] - al.addresses[address] = -1 - } -} - -// DeleteAddress removes an address from the access list. This operation -// needs to be performed in the same order as the addition happened. -// This method is meant to be used by the journal, which maintains ordering of -// operations. -func (al *accessList) DeleteAddress(address common.Address) { - delete(al.addresses, address) -} diff --git a/ethereum/state/journal.go b/ethereum/state/journal.go deleted file mode 100644 index af1d39e..0000000 --- a/ethereum/state/journal.go +++ /dev/null @@ -1,251 +0,0 @@ -package state - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" -) - -// journalEntry is a modification entry in the state change journal that can be -// reverted on demand. -type journalEntry interface { - // revert undoes the changes introduced by this journal entry. - revert(*StateDB) - - // dirtied returns the Ethereum address modified by this journal entry. - dirtied() *common.Address -} - -// journal contains the list of state modifications applied since the last state -// commit. These are tracked to be able to be reverted in case of an execution -// exception or revertal request. -type journal struct { - entries []journalEntry // Current changes tracked by the journal - dirties map[common.Address]int // Dirty accounts and the number of changes -} - -// newJournal create a new initialized journal. -func newJournal() *journal { - return &journal{ - dirties: make(map[common.Address]int), - } -} - -// append inserts a new modification entry to the end of the change journal. -func (j *journal) append(entry journalEntry) { - j.entries = append(j.entries, entry) - if addr := entry.dirtied(); addr != nil { - j.dirties[*addr]++ - } -} - -// revert undoes a batch of journalled modifications along with any reverted -// dirty handling too. -func (j *journal) revert(statedb *StateDB, snapshot int) { - for i := len(j.entries) - 1; i >= snapshot; i-- { - // Undo the changes made by the operation - j.entries[i].revert(statedb) - - // Drop any dirty tracking induced by the change - if addr := j.entries[i].dirtied(); addr != nil { - if j.dirties[*addr]--; j.dirties[*addr] == 0 { - delete(j.dirties, *addr) - } - } - } - j.entries = j.entries[:snapshot] -} - -// dirty explicitly sets an address to dirty, even if the change entries would -// otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD -// precompile consensus exception. -func (j *journal) dirty(addr common.Address) { - j.dirties[addr]++ -} - -// length returns the current number of entries in the journal. -func (j *journal) length() int { - return len(j.entries) -} - -type ( - // Changes to the account trie. - createObjectChange struct { - account *common.Address - } - resetObjectChange struct { - prev *stateObject - } - suicideChange struct { - account *common.Address - prev bool // whether account had already suicided - prevbalance *big.Int - } - - // Changes to individual accounts. - balanceChange struct { - account *common.Address - prev *big.Int - } - nonceChange struct { - account *common.Address - prev uint64 - } - storageChange struct { - account *common.Address - key, prevalue common.Hash - } - codeChange struct { - account *common.Address - prevcode, prevhash []byte - } - - // Changes to other state values. - refundChange struct { - prev uint64 - } - addLogChange struct { - txhash common.Hash - } - addPreimageChange struct { - hash common.Hash - } - touchChange struct { - account *common.Address - prev bool - prevDirty bool - } - // Changes to the access list - accessListAddAccountChange struct { - address *common.Address - } - accessListAddSlotChange struct { - address *common.Address - slot *common.Hash - } -) - -func (ch createObjectChange) revert(s *StateDB) { - delete(s.stateObjects, *ch.account) - delete(s.stateObjectsDirty, *ch.account) -} - -func (ch createObjectChange) dirtied() *common.Address { - return ch.account -} - -func (ch resetObjectChange) revert(s *StateDB) { - s.setStateObject(ch.prev) -} - -func (ch resetObjectChange) dirtied() *common.Address { - return nil -} - -func (ch suicideChange) revert(s *StateDB) { - obj := s.getStateObject(*ch.account) - if obj != nil { - obj.suicided = ch.prev - obj.setBalance(ch.prevbalance) - } -} - -func (ch suicideChange) dirtied() *common.Address { - return ch.account -} - -var ripemd = common.HexToAddress("0000000000000000000000000000000000000003") - -func (ch touchChange) revert(s *StateDB) { -} - -func (ch touchChange) dirtied() *common.Address { - return ch.account -} - -func (ch balanceChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setBalance(ch.prev) -} - -func (ch balanceChange) dirtied() *common.Address { - return ch.account -} - -func (ch nonceChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setNonce(ch.prev) -} - -func (ch nonceChange) dirtied() *common.Address { - return ch.account -} - -func (ch codeChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) -} - -func (ch codeChange) dirtied() *common.Address { - return ch.account -} - -func (ch storageChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setState(ch.key, ch.prevalue) -} - -func (ch storageChange) dirtied() *common.Address { - return ch.account -} - -func (ch refundChange) revert(s *StateDB) { - s.refund = ch.prev -} - -func (ch refundChange) dirtied() *common.Address { - return nil -} - -func (ch addLogChange) revert(s *StateDB) { - logs := s.logs[ch.txhash] - if len(logs) == 1 { - delete(s.logs, ch.txhash) - } else { - s.logs[ch.txhash] = logs[:len(logs)-1] - } - s.logSize-- -} - -func (ch addLogChange) dirtied() *common.Address { - return nil -} - -func (ch addPreimageChange) revert(s *StateDB) { - delete(s.preimages, ch.hash) -} - -func (ch addPreimageChange) dirtied() *common.Address { - return nil -} - -func (ch accessListAddAccountChange) revert(s *StateDB) { - /* - One important invariant here, is that whenever a (addr, slot) is added, if the - addr is not already present, the add causes two journal entries: - - one for the address, - - one for the (address,slot) - Therefore, when unrolling the change, we can always blindly delete the - (addr) at this point, since no storage adds can remain when come upon - a single (addr) change. - */ - s.accessList.DeleteAddress(*ch.address) -} - -func (ch accessListAddAccountChange) dirtied() *common.Address { - return nil -} - -func (ch accessListAddSlotChange) revert(s *StateDB) { - s.accessList.DeleteSlot(*ch.address, *ch.slot) -} - -func (ch accessListAddSlotChange) dirtied() *common.Address { - return nil -} diff --git a/ethereum/state/state_object.go b/ethereum/state/state_object.go deleted file mode 100644 index 5c18b4f..0000000 --- a/ethereum/state/state_object.go +++ /dev/null @@ -1,373 +0,0 @@ -package state - -import ( - "bytes" - "encoding/hex" - "fmt" - "io" - "math/big" - "strings" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" - "github.com/tenderly/tenderly-cli/ethereum" - "github.com/tenderly/tenderly-cli/ethereum/types" -) - -var emptyCodeHash = crypto.Keccak256(nil) - -type Code []byte - -func (c Code) String() string { - return string(c) //strings.Join(Disassemble(c), " ") -} - -type Storage map[common.Hash]common.Hash - -func (s Storage) String() (str string) { - for key, value := range s { - str += fmt.Sprintf("%X : %X\n", key, value) - } - - return -} - -func (s Storage) Copy() Storage { - cpy := make(Storage) - for key, value := range s { - cpy[key] = value - } - - return cpy -} - -// stateObject represents an Ethereum account which is being modified. -// -// The usage pattern is as follows: -// First you need to obtain a state object. -// Account values can be accessed and modified through the object. -// Finally, call CommitTrie to write the modified storage trie into a database. -type stateObject struct { - address common.Address - addrHash common.Hash // hash of ethereum address of the account - data Account - db *StateDB - - used bool - // DB error. - // State objects are used by the consensus core and VM which are - // unable to deal with database-level errors. Any error that occurs - // during a database read is memoized here and will eventually be returned - // by StateDB.Commit. - dbErr error - - // Write caches. - code Code // contract bytecode, which gets set when code is loaded - - originStorage Storage // Storage cache of original entries to dedup rewrites - dirtyStorage Storage // Storage entries that need to be flushed to disk - - // Cache flags. - // When an object is marked suicided it will be delete from the trie - // during the "update" phase of the state transition. - dirtyCode bool // true if the code was updated - suicided bool - deleted bool -} - -// empty returns whether the account is considered empty. -func (s *stateObject) empty() bool { - return s.data.DirtyNonce == 0 && s.data.DirtyBalance.Sign() == 0 && bytes.Equal(s.data.DirtyCodeHash, emptyCodeHash) -} - -func (s *stateObject) Used() bool { - return s.used -} - -// Account is the Ethereum consensus representation of accounts. -// These objects are stored in the main account trie. -type Account struct { - OriginalNonce uint64 - DirtyNonce uint64 - OriginalBalance *big.Int - DirtyBalance *big.Int - Root common.Hash // merkle root of the storage trie - OriginalCodeHash []byte - DirtyCodeHash []byte -} - -// newObject creates a state object. -func newObject(db *StateDB, address common.Address, data Account, code Code) *stateObject { - if data.OriginalBalance == nil { - data.OriginalBalance = new(big.Int) - } - if data.OriginalCodeHash == nil { - data.OriginalCodeHash = emptyCodeHash - } - - data.DirtyNonce = data.OriginalNonce - data.DirtyBalance = data.OriginalBalance - data.DirtyCodeHash = crypto.Keccak256Hash(code).Bytes() - - return &stateObject{ - used: true, - db: db, - address: address, - addrHash: crypto.Keccak256Hash(address[:]), - data: data, - code: code, - originStorage: make(Storage), - dirtyStorage: make(Storage), - } -} - -// EncodeRLP implements rlp.Encoder. -func (s *stateObject) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, s.data) -} - -// setError remembers the first non-nil error it is called with. -func (s *stateObject) setError(err error) { - if s.dbErr == nil { - s.dbErr = err - } -} - -func (s *stateObject) markSuicided() { - s.suicided = true -} - -func (s *stateObject) touch() { - s.db.journal.append(touchChange{ - account: &s.address, - }) - if s.address == ripemd { - // Explicitly put it in the dirty-cache, which is otherwise generated from - // flattened journals. - s.db.journal.dirty(s.address) - } -} - -// GetState retrieves a value from the account storage trie. -func (s *stateObject) GetState(client *ethereum.Client, blockNumber int64, key common.Hash) common.Hash { - // If we have a dirty value for this state entry, return it - value, dirty := s.dirtyStorage[key] - if dirty { - return value - } - // Otherwise return the entry's original value - return s.GetCommittedState(client, blockNumber, key) -} - -// GetCommittedState retrieves a value from the committed account storage trie. -func (s *stateObject) GetCommittedState(client *ethereum.Client, blockNumber int64, key common.Hash) common.Hash { - // If we have the original value cached, return that - value, cached := s.originStorage[key] - if cached { - return value - } - // Otherwise load the value from the database - number := types.Number(blockNumber) - state, err := client.GetStorageAt(s.address.String(), key, &number) - if err != nil { - s.db.setDbErr(err) - return common.Hash{} - } - s.originStorage[key] = *state - return *state -} - -// SetState updates a value in account storage. -func (s *stateObject) SetState(client *ethereum.Client, blockNumber int64, key, value common.Hash) { - // If the new value is the same as old, don't set - prev := s.GetState(client, blockNumber, key) - if prev == value { - return - } - // New value is different, update and journal the change - s.db.journal.append(storageChange{ - account: &s.address, - key: key, - prevalue: prev, - }) - s.setState(key, value) -} - -func (s *stateObject) setState(key, value common.Hash) { - s.dirtyStorage[key] = value -} - -// AddBalance removes amount from c's balance. -// It is used to add funds to the destination account of a transfer. -func (s *stateObject) AddBalance(amount *big.Int) { - // EIP158: We must check emptiness for the objects such that the account - // clearing (0,0,0 objects) can take effect. - if amount.Sign() == 0 { - if s.empty() { - s.touch() - } - - return - } - s.SetBalance(new(big.Int).Add(s.Balance(), amount)) -} - -// SubBalance removes amount from c's balance. -// It is used to remove funds from the origin account of a transfer. -func (s *stateObject) SubBalance(amount *big.Int) { - if amount.Sign() == 0 { - return - } - s.SetBalance(new(big.Int).Sub(s.Balance(), amount)) -} - -func (s *stateObject) SetBalance(amount *big.Int) { - s.db.journal.append(balanceChange{ - account: &s.address, - prev: new(big.Int).Set(s.data.DirtyBalance), - }) - s.setBalance(amount) -} - -func (s *stateObject) setBalance(amount *big.Int) { - s.data.DirtyBalance = amount -} - -// Return the gas back to the origin. Used by the Virtual machine or Closures -func (s *stateObject) ReturnGas(gas *big.Int) {} - -// -// Attribute accessors -// - -// Returns the address of the contract/account -func (s *stateObject) Address() common.Address { - return s.address -} - -// Code returns the contract code associated with this object, if any. -func (s *stateObject) Code(client *ethereum.Client, blockNumber int64) []byte { - if s.code != nil { - return s.code - } - if bytes.Equal(s.CodeHash(), emptyCodeHash) { - return nil - } - - number := types.Number(blockNumber) - code, err := client.GetCode(s.address.String(), &number) - if err != nil { - s.db.setDbErr(err) - } - - raw := code - if strings.HasPrefix(raw, "0x") { - raw = raw[2:] - } - bin, err := hex.DecodeString(raw) - if err != nil { - return []byte{} - } - - s.code = Code(bin) - return bin -} - -func (s *stateObject) SetCode(client *ethereum.Client, blockNumber int64, codeHash common.Hash, code []byte) { - prevcode := s.Code(client, blockNumber) - s.db.journal.append(codeChange{ - account: &s.address, - prevhash: s.CodeHash(), - prevcode: prevcode, - }) - s.setCode(codeHash, code) -} - -func (s *stateObject) setCode(codeHash common.Hash, code []byte) { - s.code = code - s.data.DirtyCodeHash = codeHash[:] - s.dirtyCode = true -} - -func (s *stateObject) SetNonce(nonce uint64) { - s.db.journal.append(nonceChange{ - account: &s.address, - prev: s.data.DirtyNonce, - }) - s.setNonce(nonce) -} - -func (s *stateObject) setNonce(nonce uint64) { - s.data.DirtyNonce = nonce -} - -func (s *stateObject) OriginalCodeHash() []byte { - return s.data.OriginalCodeHash -} - -func (s *stateObject) CodeHash() []byte { - return s.data.DirtyCodeHash -} - -func (s *stateObject) Root() common.Hash { - return s.data.Root -} - -func (s *stateObject) OriginalBalance() *big.Int { - return s.data.OriginalBalance -} - -func (s *stateObject) Balance() *big.Int { - return s.data.DirtyBalance -} - -func (s *stateObject) OriginalNonce() uint64 { - return s.data.OriginalNonce -} - -func (s *stateObject) Nonce() uint64 { - return s.data.DirtyNonce -} - -func (s *stateObject) GetStorage() map[string][]byte { - storage := make(map[string][]byte) - for k, v := range s.originStorage { - storage[k.String()] = v.Bytes() - } - - return storage -} - -func (s *stateObject) GetCode() []byte { - if bytes.Compare(s.OriginalCodeHash(), emptyCodeHash) == 0 { - return []byte{} - } - - return s.code -} - -func (s *stateObject) finalise() { - if !s.used { - return - } - - s.used = false - - s.data.OriginalNonce = s.data.DirtyNonce - s.data.OriginalBalance = s.data.DirtyBalance - s.data.OriginalCodeHash = s.data.DirtyCodeHash - - if len(s.dirtyStorage) > 0 { - for k, v := range s.dirtyStorage { - s.originStorage[k] = v - } - } -} - -// Never called, but must be present to allow stateObject to be used -// as a vm.Account interface that also satisfies the vm.ContractRef -// interface. Interfaces are awesome. -func (s *stateObject) Value() *big.Int { - panic("Value on stateObject should never be called") -} diff --git a/ethereum/state/statedb.go b/ethereum/state/statedb.go deleted file mode 100644 index 184352f..0000000 --- a/ethereum/state/statedb.go +++ /dev/null @@ -1,521 +0,0 @@ -package state - -import ( - "encoding/hex" - "fmt" - "math/big" - "sort" - "strings" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/tenderly/tenderly-cli/ethereum" - clientTypes "github.com/tenderly/tenderly-cli/ethereum/types" -) - -type revision struct { - id int - journalIndex int -} - -// StateDBs within the ethereum protocol are used to store anything -// within the merkle trie. StateDBs take care of caching and storing -// nested states. It's the general query interface to retrieve: -// * ContractsInformation -// * Accounts -type StateDB struct { - client *ethereum.Client - blockNumber int64 - - // This map holds 'live' objects, which will getAccount modified while processing a state transition. - stateObjects map[common.Address]*stateObject - stateObjectsDirty map[common.Address]struct{} - - // DB error. - // State objects are used by the consensus core and VM which are - // unable to deal with database-level errors. Any error that occurs - // during a database read is memoized here and will eventually be returned - // by StateDB.Commit. - dbErr error - - // The refund counter, also used by state transitioning. - refund uint64 - - thash, bhash common.Hash - txIndex int - logs map[common.Hash][]*types.Log - logSize uint - - preimages map[common.Hash][]byte - - accessList *accessList - - // Journal of state modifications. This is the backbone of - // Snapshot and RevertToSnapshot. - journal *journal - validRevisions []revision - nextRevisionId int -} - -// PrepareAccessList handles the preparatory steps for executing a state transition with -// regards to both EIP-2929 and EIP-2930: -// -// - Add sender to access list (2929) -// - Add destination to access list (2929) -// - Add precompiles to access list (2929) -// - Add the contents of the optional tx access list (2930) -// -// This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number. -func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { - s.AddAddressToAccessList(sender) - if dst != nil { - s.AddAddressToAccessList(*dst) - // If it's a create-tx, the destination will be added inside evm.create - } - for _, addr := range precompiles { - s.AddAddressToAccessList(addr) - } - for _, el := range list { - s.AddAddressToAccessList(el.Address) - for _, key := range el.StorageKeys { - s.AddSlotToAccessList(el.Address, key) - } - } -} - -// AddAddressToAccessList adds the given address to the access list -func (s *StateDB) AddAddressToAccessList(addr common.Address) { - if s.accessList.AddAddress(addr) { - s.journal.append(accessListAddAccountChange{&addr}) - } -} - -// AddSlotToAccessList adds the given (address, slot)-tuple to the access list -func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { - addrMod, slotMod := s.accessList.AddSlot(addr, slot) - if addrMod { - // In practice, this should not happen, since there is no way to enter the - // scope of 'address' without having the 'address' become already added - // to the access list (via call-variant, create, etc). - // Better safe than sorry, though - s.journal.append(accessListAddAccountChange{&addr}) - } - if slotMod { - s.journal.append(accessListAddSlotChange{ - address: &addr, - slot: &slot, - }) - } -} - -// AddressInAccessList returns true if the given address is in the access list. -func (s *StateDB) AddressInAccessList(addr common.Address) bool { - return s.accessList.ContainsAddress(addr) -} - -// SlotInAccessList returns true if the given (address, slot)-tuple is in the access list. -func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { - return s.accessList.Contains(addr, slot) -} - -// Create a new state from a given trie. -func NewState(client *ethereum.Client, blockNumber int64) *StateDB { - return &StateDB{ - client: client, - blockNumber: blockNumber, - - stateObjects: make(map[common.Address]*stateObject), - stateObjectsDirty: make(map[common.Address]struct{}), - logs: make(map[common.Hash][]*types.Log), - preimages: make(map[common.Hash][]byte), - accessList: newAccessList(), - journal: newJournal(), - } -} - -func (s *StateDB) AddLog(log *types.Log) { - s.journal.append(addLogChange{txhash: s.thash}) - - log.TxHash = s.thash - log.BlockHash = s.bhash - log.TxIndex = uint(s.txIndex) - log.Index = s.logSize - s.logs[s.thash] = append(s.logs[s.thash], log) - s.logSize++ -} - -// AddPreimage records a SHA3 preimage seen by the VM. -func (s *StateDB) AddPreimage(hash common.Hash, preimage []byte) { - if _, ok := s.preimages[hash]; !ok { - s.journal.append(addPreimageChange{hash: hash}) - pi := make([]byte, len(preimage)) - copy(pi, preimage) - s.preimages[hash] = pi - } -} - -// AddRefund adds gas to the refund counter -func (s *StateDB) AddRefund(gas uint64) { - s.journal.append(refundChange{prev: s.refund}) - s.refund += gas -} - -// SubRefund removes gas from the refund counter. -// This method will panic if the refund counter goes below zero -func (s *StateDB) SubRefund(gas uint64) { - s.journal.append(refundChange{prev: s.refund}) - if gas > s.refund { - panic(fmt.Sprintf("Refund counter below zero in tx %s in block %d", s.thash.String(), s.blockNumber)) - } - s.refund -= gas -} - -// Exist reports whether the given account address exists in the state. -// Notably this also returns true for suicided accounts. -func (s *StateDB) Exist(addr common.Address) bool { - return s.getStateObject(addr) != nil -} - -// Empty returns whether the state object is either non-existent -// or empty according to the EIP161 specification (balance = nonce = code = 0) -func (s *StateDB) Empty(addr common.Address) bool { - so := s.getStateObject(addr) - return so == nil || so.empty() -} - -// Retrieve the balance from the given address or 0 if object not found -func (s *StateDB) GetBalance(addr common.Address) *big.Int { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.Balance() - } - return common.Big0 -} - -func (s *StateDB) GetNonce(addr common.Address) uint64 { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.Nonce() - } - - return 0 -} - -func (s *StateDB) GetCode(addr common.Address) []byte { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.Code(s.client, s.blockNumber-1) - } - return nil -} - -func (s *StateDB) GetCodeSize(addr common.Address) int { - stateObject := s.getStateObject(addr) - if stateObject == nil { - return 0 - } - if stateObject.code != nil { - return len(stateObject.code) - } - - return 0 -} - -func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { - stateObject := s.getStateObject(addr) - if stateObject == nil { - return common.Hash{} - } - return common.BytesToHash(stateObject.CodeHash()) -} - -// GetState retrieves a value from the given account's storage trie. -func (s *StateDB) GetState(addr common.Address, bhash common.Hash) common.Hash { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.GetState(s.client, s.blockNumber-1, bhash) - } - return common.Hash{} -} - -// GetCommittedState retrieves a value from the given account's committed storage trie. -func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.GetCommittedState(s.client, s.blockNumber-1, hash) - } - return common.Hash{} -} - -func (s *StateDB) HasSuicided(addr common.Address) bool { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.suicided - } - return false -} - -// AddBalance adds amount to the account associated with addr. -func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) { - stateObject := s.GetOrNewStateObject(addr) - if stateObject != nil { - stateObject.AddBalance(amount) - } -} - -// SubBalance subtracts amount from the account associated with addr. -func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) { - stateObject := s.GetOrNewStateObject(addr) - if stateObject != nil { - stateObject.SubBalance(amount) - } -} - -func (s *StateDB) SetNonce(addr common.Address, nonce uint64) { - stateObject := s.GetOrNewStateObject(addr) - if stateObject != nil { - stateObject.SetNonce(nonce) - } -} - -func (s *StateDB) SetCode(addr common.Address, code []byte) { - stateObject := s.GetOrNewStateObject(addr) - if stateObject != nil { - stateObject.SetCode(s.client, s.blockNumber-1, crypto.Keccak256Hash(code), code) - } -} - -func (s *StateDB) SetState(addr common.Address, key, value common.Hash) { - stateObject := s.GetOrNewStateObject(addr) - if stateObject != nil { - stateObject.SetState(s.client, s.blockNumber-1, key, value) - } -} - -// Suicide marks the given account as suicided. -// This clears the account balance. -// -// The account's state object is still available until the state is committed, -// getStateObject will return a non-nil account after Suicide. -func (s *StateDB) Suicide(addr common.Address) bool { - stateObject := s.getStateObject(addr) - if stateObject == nil { - return false - } - s.journal.append(suicideChange{ - account: &addr, - prev: stateObject.suicided, - prevbalance: new(big.Int).Set(stateObject.Balance()), - }) - stateObject.markSuicided() - stateObject.data.DirtyBalance = new(big.Int) - - return true -} - -// -// Setting, updating & deleting state object methods. -// - -// Retrieve a state object given by the address. Returns nil if not found. -func (s *StateDB) getStateObject(addr common.Address) (stateObject *stateObject) { - // Prefer 'live' objects. - if obj := s.stateObjects[addr]; obj != nil { - obj.used = true - if obj.deleted { - return nil - } - return obj - } - - number := clientTypes.Number(s.blockNumber - 1) - code, err := s.client.GetCode(addr.String(), &number) - if err != nil { - s.setDbErr(err) - } - - raw := code - if strings.HasPrefix(raw, "0x") { - raw = raw[2:] - } - bin, err := hex.DecodeString(raw) - if err != nil { - bin = []byte{} - } - - // Load the object from ethereum rpc server. - acc := s.getAccount(addr, bin) - - // Insert into the live set. - obj := newObject(s, addr, acc, Code(bin)) - s.setStateObject(obj) - return obj -} - -func (s *StateDB) setStateObject(object *stateObject) { - object.used = true - - s.stateObjects[object.Address()] = object -} - -// Retrieve a state object or create a new state object if nil. -func (s *StateDB) GetOrNewStateObject(addr common.Address) *stateObject { - stateObject := s.getStateObject(addr) - if stateObject == nil || stateObject.deleted { - stateObject, _ = s.createObject(addr) - } - return stateObject -} - -// createObject creates a new state object. If there is an existing account with -// the given address, it is overwritten and returned as the second return value. -func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) { - prev = s.getStateObject(addr) - newobj = newObject(s, addr, Account{}, Code{}) - newobj.setNonce(0) // sets the object to dirty - if prev == nil { - s.journal.append(createObjectChange{account: &addr}) - } else { - s.journal.append(resetObjectChange{prev: prev}) - } - s.setStateObject(newobj) - return newobj, prev -} - -func (s *StateDB) getAccount(addr common.Address, bin []byte) Account { - number := clientTypes.Number(s.blockNumber - 1) - nonce, err := s.client.GetNonce(addr.String(), &number) - if err != nil { - s.setDbErr(err) - } - - balance, err := s.client.GetBalance(addr.String(), &number) - if err != nil { - s.setDbErr(err) - } - - var codeHash []byte - if len(bin) != 0 { - codeHash = crypto.Keccak256Hash(bin).Bytes() - } - - return Account{ - OriginalNonce: nonce, - OriginalBalance: balance, - OriginalCodeHash: codeHash, - } -} - -// CreateAccount explicitly creates a state object. If a state object with the address -// already exists the balance is carried over to the new account. -// -// CreateAccount is called during the EVM CREATE operation. The situation might arise that -// a contract does the following: -// -// 1. sends funds to sha(account ++ (nonce + 1)) -// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1) -// -// Carrying over the balance ensures that Ether doesn't disappear. -func (s *StateDB) CreateAccount(addr common.Address) { - newObj, prev := s.createObject(addr) - if prev != nil { - newObj.setBalance(prev.data.DirtyBalance) - } -} - -func (s *StateDB) ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error { - return nil -} - -// Snapshot returns an identifier for the current revision of the state. -func (s *StateDB) Snapshot() int { - id := s.nextRevisionId - s.nextRevisionId++ - s.validRevisions = append(s.validRevisions, revision{id, s.journal.length()}) - return id -} - -// RevertToSnapshot reverts all state changes made since the given revision. -func (s *StateDB) RevertToSnapshot(revid int) { - // Find the snapshot in the stack of valid snapshots. - idx := sort.Search(len(s.validRevisions), func(i int) bool { - return s.validRevisions[i].id >= revid - }) - if idx == len(s.validRevisions) || s.validRevisions[idx].id != revid { - panic(fmt.Errorf("revision id %v cannot be reverted", revid)) - } - snapshot := s.validRevisions[idx].journalIndex - - // Replay the journal to undo changes and remove invalidated snapshots - s.journal.revert(s, snapshot) - s.validRevisions = s.validRevisions[:idx] -} - -// GetRefund returns the current value of the refund counter. -func (s *StateDB) GetRefund() uint64 { - return s.refund -} - -func (s *StateDB) Prepare(thash, bhash common.Hash, ti int) { - s.thash = thash - s.bhash = bhash - s.txIndex = ti - s.accessList = newAccessList() -} - -func (s *StateDB) setDbErr(err error) { - if s.dbErr == nil { - s.dbErr = err - } -} - -func (s *StateDB) CleanErr() { - s.dbErr = nil -} - -func (s *StateDB) GetDbErr() error { - return s.dbErr -} - -func (s *StateDB) GetLogs(hash common.Hash) []*types.Log { - return s.logs[hash] -} - -func (s *StateDB) GetStateObjects() []*stateObject { - var stateObjects []*stateObject - for _, stateObject := range s.stateObjects { - stateObjects = append(stateObjects, stateObject) - } - - return stateObjects -} - -func (s *StateDB) GetStateObject(addr common.Address) *stateObject { - return s.stateObjects[addr] -} - -func (s *StateDB) Finalise(deleteEmptyObjects bool) { - for addr := range s.journal.dirties { - obj, exist := s.stateObjects[addr] - if !exist { - continue - } - if obj.suicided || (deleteEmptyObjects && obj.empty()) { - obj.deleted = true - } else { - obj.finalise() - } - s.stateObjectsDirty[addr] = struct{}{} - } - // Invalidate journal because reverting across transactions is not allowed. - s.clearJournalAndRefund() -} - -func (s *StateDB) clearJournalAndRefund() { - if len(s.journal.entries) > 0 { - s.journal = newJournal() - s.refund = 0 - } - s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entires -} diff --git a/ethereum/types/types.go b/ethereum/types/types.go deleted file mode 100644 index 8917e71..0000000 --- a/ethereum/types/types.go +++ /dev/null @@ -1,187 +0,0 @@ -package types - -import ( - "encoding/json" - "fmt" - "math/big" - "strconv" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" -) - -// Core Types - -type Number int64 - -func (n Number) Value() int64 { - return int64(n) -} - -func (n Number) Big() *big.Int { - return big.NewInt(int64(n)) -} - -func (n *Number) Hex() string { - return fmt.Sprintf("%#x", int64(*n)) -} - -func (n *Number) UnmarshalJSON(b []byte) error { - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - num, err := strconv.ParseInt(s, 0, 64) - if err != nil { - return err - } - - *n = Number(num) - - return nil -} - -func (n *Number) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf("\"%s\"", n.Hex())), nil -} - -type Header interface { - Number() *Number -} - -type Block interface { - Number() Number - Hash() common.Hash - Transactions() []Transaction - ParentHash() common.Hash - Time() *hexutil.Big - Timestamp() time.Time - Difficulty() []byte - GasLimit() *hexutil.Big - BaseFeePerGas() *hexutil.Big -} - -type BlockHeader interface { - Number() Number - Hash() common.Hash - StateRoot() common.Hash - ParentHash() common.Hash - UncleHash() common.Hash - TxHash() common.Hash - ReceiptHash() common.Hash - Bloom() [256]byte - Difficulty() []byte - GasLimit() *hexutil.Big - GasUsed() *hexutil.Big - Coinbase() common.Address - Time() *hexutil.Big - Timestamp() time.Time - ExtraData() hexutil.Bytes - MixDigest() common.Hash - Nonce() [8]byte - BaseFeePerGas() *hexutil.Big -} - -type AccessTuple interface { - Address() common.Address - StorageKeys() []common.Hash -} - -type Transaction interface { - Hash() common.Hash - BlockNumber() *hexutil.Big - BlockHash() *common.Hash - - From() common.Address - To() *common.Address - - Input() hexutil.Bytes - Value() *hexutil.Big - Gas() *hexutil.Big - GasTipCap() *hexutil.Big - GasFeeCap() *hexutil.Big - GasPrice() *hexutil.Big - Nonce() *hexutil.Big - - AccessList() []AccessTuple -} - -type Log interface { - Topics() []string - Data() string -} - -type TransactionReceipt interface { - Hash() string - TransactionIndex() Number - - BlockHash() common.Hash - BlockNumber() Number - - From() common.Address - To() *common.Address - - GasUsed() *hexutil.Big - CumulativeGasUsed() *hexutil.Big - EffectiveGasPrice() hexutil.Uint64 - ContractAddress() *common.Address - - Status() string - SetStatus(trace string) - Logs() []Log - LogsBloom() hexutil.Bytes -} - -// States Types - -type TransactionStates interface { - States() []EvmState - ProcessTrace() -} - -type EvmState interface { - Pc() uint64 - Depth() int - Op() string - Stack() []string -} - -type CallTraces interface { - Traces() []Trace -} - -type Trace interface { - Hash() *common.Hash - ParentHash() *common.Hash - TransactionHash() *common.Hash - Type() string - From() common.Address - To() common.Address - Input() hexutil.Bytes - Output() hexutil.Bytes - Gas() *hexutil.Uint64 - GasUsed() *hexutil.Uint64 - Value() *hexutil.Big - Error() string -} - -// Subscription Types - -type SubscriptionID string - -func NewNilSubscriptionID() SubscriptionID { - return "" -} - -func (id SubscriptionID) String() string { - return string(id) -} - -type SubscriptionResult struct { - Subscription SubscriptionID `json:"subscription"` - Result Header `json:"result"` -} - -type UnsubscribeSuccess bool diff --git a/hardhat/hardhat.go b/hardhat/hardhat.go index b1efb9a..267b7c1 100644 --- a/hardhat/hardhat.go +++ b/hardhat/hardhat.go @@ -19,7 +19,6 @@ func NewDeploymentProvider() *DeploymentProvider { call.NewUserCalls(), call.NewProjectCalls(), call.NewContractCalls(), - call.NewExportCalls(), call.NewNetworkCalls(), call.NewActionCalls(), call.NewDevNetCalls(), diff --git a/hardhat/source.go b/hardhat/source.go deleted file mode 100644 index 18b8300..0000000 --- a/hardhat/source.go +++ /dev/null @@ -1,100 +0,0 @@ -package hardhat - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/tenderly/tenderly-cli/ethereum" - "github.com/tenderly/tenderly-cli/providers" - "github.com/tenderly/tenderly-cli/stacktrace" -) - -// NewContractSource builds the Contract Source from the provided config, and scoped to the provided network. -func (dp *DeploymentProvider) NewContractSource(path string, networkId string, client ethereum.Client) (stacktrace.ContractSource, error) { - truffleContracts, err := dp.loadHardhatContracts(path) - if err != nil { - return nil, err - } - - cs := &providers.ContractSource{ - Contracts: dp.mapHardhatContracts(truffleContracts, networkId), - Client: client, - } - - return cs, nil -} - -func (dp *DeploymentProvider) loadHardhatContracts(path string) ([]*providers.Contract, error) { - - files, err := os.ReadDir(path) - if err != nil { - return nil, fmt.Errorf("failed listing hardhat build files: %s", err) - } - - var contracts []*providers.Contract - for _, file := range files { - if file.IsDir() || !strings.HasSuffix(file.Name(), ".json") { - continue - } - - data, err := os.ReadFile(filepath.Join(path, file.Name())) - if err != nil { - return nil, fmt.Errorf("failed reading hardhat build files: %s", err) - } - - var contract providers.Contract - err = json.Unmarshal(data, &contract) - if err != nil { - return nil, fmt.Errorf("failed parsing hardhat build files: %s", err) - } - - contracts = append(contracts, &contract) - } - - return contracts, nil -} - -func (dp *DeploymentProvider) mapHardhatContracts( - hardhatContracts []*providers.Contract, - networkId string, -) map[string]*stacktrace.ContractDetails { - contracts := make(map[string]*stacktrace.ContractDetails) - - for _, hardhatContract := range hardhatContracts { - network, ok := hardhatContract.Networks[networkId] - if !ok { - //@TODO: log.DEBUG Contract X not found in network Y. - continue - } - - bytecode, err := providers.ParseBytecode(hardhatContract.DeployedBytecode) - if err != nil { - //@TODO: log.ERROR Skipping contract because of invalid bytecode. - continue - } - - sourceMap, err := providers.ParseContract(hardhatContract) - if err != nil { - //@TODO: log.ERROR Skipping contract because of invalid source map. - continue - } - - contracts[strings.ToLower(network.Address)] = &stacktrace.ContractDetails{ - Name: hardhatContract.Name, - Hash: network.Address, - - Bytecode: bytecode, - DeployedByteCode: hardhatContract.DeployedBytecode, - - Abi: hardhatContract.Abi, - - Source: hardhatContract.Source, - SourceMap: sourceMap, - } - } - - return contracts -} diff --git a/jsonrpc2/client.go b/jsonrpc2/client.go deleted file mode 100644 index 0d3e4aa..0000000 --- a/jsonrpc2/client.go +++ /dev/null @@ -1,289 +0,0 @@ -package jsonrpc2 - -import ( - "context" - "encoding/json" - "fmt" - "io" - "log" - "strings" - "sync" - "sync/atomic" - "time" -) - -var id int64 - -func nextID() int64 { - atomic.AddInt64(&id, 1) - - return id -} - -type Request struct { - ID int64 `json:"id,omitempty"` - Version string `json:"jsonrpc"` - - Method string `json:"method"` - Params []interface{} `json:"params,omitempty"` -} - -func NewRequest(method string, params ...interface{}) *Request { - return &Request{ - ID: nextID(), - Version: "2.0", - - Method: method, - Params: params, - } -} - -type Message struct { - ID int64 `json:"id,omitempty"` - Version string `json:"jsonrpc"` - - Method string `json:"method,omitempty"` - Params json.RawMessage `json:"params,omitempty"` - - Result json.RawMessage `json:"result,omitempty"` - Error *Error `json:"error,omitempty"` -} - -type Error struct { - Code int `json:"code"` - Message string `json:"message"` - Data json.RawMessage `json:"data,omitempty"` -} - -func (msg *Message) Reset() { - msg.ID = 0 - msg.Version = "2.0" - msg.Method = "" - msg.Params = nil - msg.Result = nil - msg.Error = nil -} - -type Connection interface { - Write(msg *Request) error - Read() (*Message, error) - Close() error -} - -type Client struct { - conn Connection - - flying sync.Map - subscribers sync.Map -} - -func DiscoverAndDial(target, protocol string) (client *Client, err error) { - protocols := []string{protocol} - if protocol != "wss" && protocol != "https" && protocol != "ws" && protocol != "http" { - protocols = []string{"wss", "https", "ws", "http"} - } - - for _, protocol := range protocols { - addr := fmt.Sprintf("%s://%s", protocol, target) - - client, err = Dial(addr) - if err != nil { - continue - } - - return client, nil - } - - return nil, fmt.Errorf("could not determine protocol") -} - -func Dial(addr string) (*Client, error) { - conn, err := dialConn(addr) - if err != nil { - return nil, fmt.Errorf("dial connection: %s", err) - } - - client := &Client{ - conn: conn, - } - - go client.listen() - - return client, nil -} - -func dialConn(addr string) (Connection, error) { - if strings.HasPrefix(addr, "ws") { - return DialWebsocketConnection(addr) - } - - if strings.HasPrefix(addr, "http") { - return DialHttpConnection(addr) - } - - return nil, fmt.Errorf("unrecognized protocol") -} - -func (c *Client) Call(res interface{}, method string, params ...interface{}) error { - req := NewRequest(method, params...) - - return c.CallRequest(res, req) -} - -func (c *Client) CallRequest(res interface{}, req *Request) error { - resMsg, err := c.SendRawRequest(req) - if err != nil { - return err - } - - if resMsg.Error != nil { - return fmt.Errorf("request failed: [ %d ] %s", resMsg.Error.Code, resMsg.Error.Message) - } - - if _, ok := res.(*Message); ok { - res.(*Message).Result = resMsg.Result - return nil - } - - if string(resMsg.Result) == "null" { - return fmt.Errorf("resource not found") - } - - err = json.Unmarshal(resMsg.Result, res) - if err != nil { - return fmt.Errorf("read result: %s", err) - } - - return nil -} - -func (c *Client) SendRawRequest(req *Request) (*Message, error) { - resCh := make(chan *Message, 1) - c.setFlying(req.ID, resCh) - defer c.deleteFlying(req.ID) - - err := c.conn.Write(req) - if err != nil { - return nil, fmt.Errorf("write message to socket: %s", err) - } - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - select { - case <-ctx.Done(): - return nil, fmt.Errorf("request timed out") - case r := <-resCh: - return r, nil - } -} - -func (c *Client) Subscribe(id string) (chan *Message, error) { - subCh := make(chan *Message, 256) - - if c.hasSubscription(id) { - return nil, fmt.Errorf("subscription %s already exists", id) - } - - c.setSubscription(id, subCh) - - return subCh, nil -} - -func (c *Client) Unsubscribe(id string) error { - subCh, ok := c.getSubscription(id) - if !ok { - return fmt.Errorf("subscription %s does not exist", id) - } - - c.deleteSubscription(id) - close(subCh) - - return nil -} - -func (c *Client) listen() { - for { - msg, err := c.conn.Read() - if err == io.EOF { - break - } - if err != nil { - log.Fatalf("failed reading message from connection: %s", err) - } - - c.processMsg(msg) - } -} - -func (c *Client) processMsg(msg *Message) error { - if msg.ID == 0 { - // notification - c.subscribers.Range( - func(_, val interface{}) bool { - subCh := val.(chan *Message) - - subCh <- msg - - return true - }, - ) - - return nil - } - - // response - // @TODO: We don't have to send ID and JSONRPC version back to caller. - - resCh, ok := c.getFlying(msg.ID) - - if !ok { - log.Printf("dropped message: %d", msg.ID) - return nil - } - - resCh <- msg - - return nil -} - -func (c *Client) setFlying(id int64, msg chan *Message) { - c.flying.Store(id, msg) -} - -func (c *Client) getFlying(id int64) (chan *Message, bool) { - msg, ok := c.flying.Load(id) - if !ok { - return nil, ok - } - - return msg.(chan *Message), ok -} - -func (c *Client) deleteFlying(id int64) { - c.flying.Delete(id) -} - -func (c *Client) hasSubscription(id string) bool { - _, ok := c.subscribers.Load(id) - return ok -} - -func (c *Client) setSubscription(id string, msg chan *Message) { - c.subscribers.Store(id, msg) -} - -func (c *Client) getSubscription(id string) (chan *Message, bool) { - msg, ok := c.subscribers.Load(id) - if !ok { - return nil, ok - } - return msg.(chan *Message), ok -} - -func (c *Client) deleteSubscription(id string) { - c.subscribers.Delete(id) -} - -func (c *Client) Close() error { - return c.conn.Close() -} diff --git a/jsonrpc2/http.go b/jsonrpc2/http.go deleted file mode 100644 index f42d272..0000000 --- a/jsonrpc2/http.go +++ /dev/null @@ -1,108 +0,0 @@ -package jsonrpc2 - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "sync" -) - -type httpConnection struct { - addr string - - respCh chan *Message - - once sync.Once - ctx context.Context - cancel context.CancelFunc -} - -func DialHttpConnection(addr string) (Connection, error) { - ctx, cancel := context.WithCancel(context.Background()) - - conn := &httpConnection{ - addr: addr, - - respCh: make(chan *Message, 256), - - ctx: ctx, - cancel: cancel, - } - - msg := NewRequest("ping") - - err := conn.Write(msg) - if err != nil { - return nil, fmt.Errorf("connect via http: %s", err) - } - - _, err = conn.Read() - if err != nil { - return nil, fmt.Errorf("connect via http: %s", err) - } - - return conn, nil -} - -func (conn *httpConnection) Write(msg *Request) error { - // Setup request, possibly wasteful. - req, err := http.NewRequest(http.MethodPost, conn.addr, nil) - if err != nil { - return fmt.Errorf("write request setup: %s", err) - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - - data, err := json.Marshal(msg) - if err != nil { - return fmt.Errorf("write request body: %s", err) - } - - req.Body = io.NopCloser(bytes.NewReader(data)) - req.ContentLength = int64(len(data)) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return fmt.Errorf("write request send: %s", err) - } - if resp.StatusCode < 200 && resp.StatusCode >= 300 { - return fmt.Errorf("write request unsuccessful: %s", err) - } - - var respMsg Message - - err = json.NewDecoder(resp.Body).Decode(&respMsg) - if err != nil { - return fmt.Errorf("write request read: %s", err) - } - - conn.respCh <- &respMsg - - err = resp.Body.Close() - if err != nil { - return fmt.Errorf("write request cleanup: %s", err) - } - - return nil -} - -func (conn *httpConnection) Read() (*Message, error) { - select { - case resp := <-conn.respCh: - return resp, nil - case <-conn.ctx.Done(): - return nil, io.EOF - } -} - -func (conn *httpConnection) Close() error { - conn.once.Do(func() { - conn.cancel() - }) - - return nil -} diff --git a/jsonrpc2/websocket.go b/jsonrpc2/websocket.go deleted file mode 100644 index b5c9efd..0000000 --- a/jsonrpc2/websocket.go +++ /dev/null @@ -1,50 +0,0 @@ -package jsonrpc2 - -import ( - "fmt" - "io" - - "github.com/gorilla/websocket" -) - -type websocketConnection struct { - ws *websocket.Conn -} - -func DialWebsocketConnection(host string) (Connection, error) { - ws, _, err := websocket.DefaultDialer.Dial(host, nil) - if err != nil { - return nil, fmt.Errorf("open websocket connection: %s", err) - } - - return &websocketConnection{ - ws: ws, - }, nil -} - -func (conn *websocketConnection) Write(r *Request) error { - err := conn.ws.WriteJSON(r) - if err != nil { - return fmt.Errorf("write websocket: %s", err) - } - - return nil -} - -func (conn *websocketConnection) Read() (*Message, error) { - var msg Message - - err := conn.ws.ReadJSON(&msg) - if err == io.ErrUnexpectedEOF { - return nil, io.EOF - } - if err != nil { - return nil, fmt.Errorf("read websocket: %s", err) - } - - return &msg, nil -} - -func (conn *websocketConnection) Close() error { - return conn.ws.Close() -} diff --git a/main.go b/main.go index d175d8c..40f32d7 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,6 @@ import ( _ "github.com/tenderly/tenderly-cli/commands/actions" _ "github.com/tenderly/tenderly-cli/commands/contract" _ "github.com/tenderly/tenderly-cli/commands/devnet" - _ "github.com/tenderly/tenderly-cli/commands/export" _ "github.com/tenderly/tenderly-cli/commands/extensions" ) diff --git a/openzeppelin/source.go b/openzeppelin/source.go deleted file mode 100644 index fd3d35c..0000000 --- a/openzeppelin/source.go +++ /dev/null @@ -1,100 +0,0 @@ -package openzeppelin - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/tenderly/tenderly-cli/ethereum" - "github.com/tenderly/tenderly-cli/providers" - "github.com/tenderly/tenderly-cli/stacktrace" -) - -// NewContractSource builds the Contract Source from the provided config, and scoped to the provided network. -func (dp *DeploymentProvider) NewContractSource(path string, networkId string, client ethereum.Client) (stacktrace.ContractSource, error) { - truffleContracts, err := dp.loadOpenZeppelinContracts(path) - if err != nil { - return nil, err - } - - cs := &providers.ContractSource{ - Contracts: dp.mapTruffleContracts(truffleContracts, networkId), - Client: client, - } - - return cs, nil -} - -func (dp *DeploymentProvider) loadOpenZeppelinContracts(path string) ([]*providers.Contract, error) { - - files, err := os.ReadDir(path) - if err != nil { - return nil, fmt.Errorf("failed listing openzeppelin build files: %s", err) - } - - var contracts []*providers.Contract - for _, file := range files { - if file.IsDir() || !strings.HasSuffix(file.Name(), ".json") { - continue - } - - data, err := os.ReadFile(filepath.Join(path, file.Name())) - if err != nil { - return nil, fmt.Errorf("failed reading openzeppelin build files: %s", err) - } - - var contract providers.Contract - err = json.Unmarshal(data, &contract) - if err != nil { - return nil, fmt.Errorf("failed parsing openzeppelin build files: %s", err) - } - - contracts = append(contracts, &contract) - } - - return contracts, nil -} - -func (dp *DeploymentProvider) mapTruffleContracts( - truffleContracts []*providers.Contract, - networkId string, -) map[string]*stacktrace.ContractDetails { - contracts := make(map[string]*stacktrace.ContractDetails) - - for _, truffleContract := range truffleContracts { - network, ok := truffleContract.Networks[networkId] - if !ok { - //@TODO: log.DEBUG Contract X not found in network Y. - continue - } - - bytecode, err := providers.ParseBytecode(truffleContract.DeployedBytecode) - if err != nil { - //@TODO: log.ERROR Skipping contract because of invalid bytecode. - continue - } - - sourceMap, err := providers.ParseContract(truffleContract) - if err != nil { - //@TODO: log.ERROR Skipping contract because of invalid source map. - continue - } - - contracts[strings.ToLower(network.Address)] = &stacktrace.ContractDetails{ - Name: truffleContract.Name, - Hash: network.Address, - - Bytecode: bytecode, - DeployedByteCode: truffleContract.DeployedBytecode, - - Abi: truffleContract.Abi, - - Source: truffleContract.Source, - SourceMap: sourceMap, - } - } - - return contracts -} diff --git a/providers/deployment_provider.go b/providers/deployment_provider.go index 0263ab0..ffe160f 100644 --- a/providers/deployment_provider.go +++ b/providers/deployment_provider.go @@ -1,14 +1,10 @@ package providers import ( - "encoding/hex" - "fmt" "path/filepath" "time" - "github.com/tenderly/tenderly-cli/ethereum" "github.com/tenderly/tenderly-cli/model" - "github.com/tenderly/tenderly-cli/stacktrace" ) type DeploymentProvider interface { @@ -231,45 +227,3 @@ type ApiDeploymentInformation struct { NetworkID string `json:"network_id"` Address string `json:"address"` } - -type ContractSource struct { - Contracts map[string]*stacktrace.ContractDetails - Client ethereum.Client -} - -func (cs *ContractSource) Get(id string) (*stacktrace.ContractDetails, error) { - contract, ok := cs.Contracts[id] - if ok { - return contract, nil - } - - code, err := cs.Client.GetCode(id, nil) - if err != nil { - return nil, fmt.Errorf("failed fetching code on address %s\n", id) - } - - for _, c := range cs.Contracts { - if c.DeployedByteCode == code { - return c, nil - } - } - - bytecode, err := ParseBytecode(code) - if err != nil { - return nil, fmt.Errorf("failed parsing bytecode %s", err) - } - - return &stacktrace.ContractDetails{ - Bytecode: bytecode, - DeployedByteCode: code, - }, nil -} - -func ParseBytecode(raw string) ([]byte, error) { - bin, err := hex.DecodeString(raw[2:]) - if err != nil { - return nil, fmt.Errorf("failed decoding runtime binary: %s", err) - } - - return bin, nil -} diff --git a/providers/sourcemap.go b/providers/sourcemap.go deleted file mode 100644 index 43c8642..0000000 --- a/providers/sourcemap.go +++ /dev/null @@ -1,190 +0,0 @@ -package providers - -import ( - "encoding/hex" - "fmt" - "strconv" - "strings" - - "github.com/ethereum/go-ethereum/core/vm" - "github.com/tenderly/tenderly-cli/stacktrace" -) - -func ParseContract(contract *Contract) (stacktrace.SourceMap, error) { - rawSrcMap := contract.DeployedSourceMap - instructionSrcMap, err := parseInstructionSourceMap(rawSrcMap) - if err != nil { - return nil, fmt.Errorf("sourcemap.Parse: %s", err) - } - - rawBytecode := contract.DeployedBytecode - - memSrcMap, err := convertToMemoryMap(instructionSrcMap, rawBytecode) - if err != nil { - return nil, fmt.Errorf("sourcemap.Parse: %s", err) - } - - rawSrc := contract.Source - - for _, instruction := range memSrcMap { - if instruction == nil { - // Instruction does not map to source - continue - } - - i := 0 - line := 1 - column := 1 - - for i < instruction.Start { - if rawSrc[i] == '\n' { - line++ - column = 0 - } - - column++ - i++ - - if i == len(rawSrc)-1 { - break - } - } - - instruction.Line = line - instruction.Column = column - } - - return memSrcMap, nil -} - -func Parse(contracts map[string]*Contract) (map[string]stacktrace.SourceMap, map[string][]byte, error) { - sourceMaps := make(map[string]stacktrace.SourceMap) - binaries := make(map[string][]byte) - for key, contract := range contracts { - if contract != nil { - rawSrcMap := contract.DeployedSourceMap - instructionSrcMap, err := parseInstructionSourceMap(rawSrcMap) - if err != nil { - return nil, nil, fmt.Errorf("sourcemap.Parse: %s", err) - } - - rawBytecode := contract.DeployedBytecode - - bin, err := hex.DecodeString(rawBytecode[2:]) - if err != nil { - return nil, nil, fmt.Errorf("failed decoding runtime binary: %s", err) - } - - memSrcMap, err := convertToMemoryMap(instructionSrcMap, rawBytecode) - if err != nil { - return nil, nil, fmt.Errorf("sourcemap.Parse: %s", err) - } - - rawSrc := contract.Source - - for _, instruction := range memSrcMap { - if instruction == nil { - // Instruction does not map to source - continue - } - - i := 0 - line := 1 - column := 1 - - for i < instruction.Start { - if rawSrc[i] == '\n' { - line++ - column = 0 - } - - column++ - i++ - } - - instruction.Line = line - instruction.Column = column - } - - sourceMaps[key] = memSrcMap - binaries[key] = bin - } - } - - return sourceMaps, binaries, nil -} - -func parseInstructionSourceMap(rawSrcMap string) (stacktrace.SourceMap, error) { - instructionSrcMap := make(stacktrace.SourceMap) - - var err error - var s int64 - var l int64 - var f int64 - var j string - for index, mapping := range strings.Split(rawSrcMap, ";") { - if mapping == "" { - instructionSrcMap[index] = instructionSrcMap[index-1] - continue - } - - info := strings.Split(mapping, ":") - if len(info) > 0 && info[0] != "" { - s, err = strconv.ParseInt(info[0], 0, 64) - if err != nil { - return nil, fmt.Errorf("failed parsing integer: %s", err) - } - } - if len(info) > 1 && info[1] != "" { - l, err = strconv.ParseInt(info[1], 0, 64) - if err != nil { - return nil, fmt.Errorf("failed parsing integer: %s", err) - } - } - if len(info) > 2 && info[2] != "" { - f, err = strconv.ParseInt(info[2], 0, 64) - if err != nil { - return nil, fmt.Errorf("failed parsing integer: %s", err) - } - } - if len(info) > 3 && info[3] != "" { - j = info[3] - } - - instructionSrcMap[index] = &stacktrace.InstructionMapping{ - Start: int(s), - Length: int(l), - FileIndex: int(f), - Jump: j, - } - } - - return instructionSrcMap, nil -} - -func convertToMemoryMap(sourceMap stacktrace.SourceMap, binData string) (stacktrace.SourceMap, error) { - memSrcMap := make(stacktrace.SourceMap) - - bin, err := hex.DecodeString(binData[2:]) - if err != nil { - return nil, fmt.Errorf("failed decoding runtime binary: %s", err) - } - - instruction := 0 - for i := 0; i < len(bin); i++ { - - op := vm.OpCode(bin[i]) - extraPush := 0 - if op.IsPush() { - // Skip more here - extraPush = int(op - vm.PUSH1 + 1) - } - - memSrcMap[i] = sourceMap[instruction] - - instruction++ - i += extraPush - } - - return memSrcMap, nil -} diff --git a/rest/call/export.go b/rest/call/export.go deleted file mode 100644 index b023f49..0000000 --- a/rest/call/export.go +++ /dev/null @@ -1,42 +0,0 @@ -package call - -import ( - "encoding/json" - "strings" - - "github.com/tenderly/tenderly-cli/config" - "github.com/tenderly/tenderly-cli/rest/client" - "github.com/tenderly/tenderly-cli/rest/payloads" -) - -type ExportCalls struct { -} - -func NewExportCalls() *ExportCalls { - return &ExportCalls{} -} - -func (rest *ExportCalls) ExportTransaction(request payloads.ExportTransactionRequest, projectSlug string) (*payloads.ExportTransactionResponse, error) { - uploadJson, err := json.Marshal(request) - if err != nil { - return nil, err - } - - accountID := config.GetGlobalString(config.AccountID) - if strings.Contains(projectSlug, "/") { - projectInfo := strings.Split(projectSlug, "/") - accountID = projectInfo[0] - projectSlug = projectInfo[1] - } - - var contracts *payloads.ExportTransactionResponse - - response := client.Request( - "POST", - "api/v1/account/"+accountID+"/project/"+projectSlug+"/export", - uploadJson, - ) - - err = json.NewDecoder(response).Decode(&contracts) - return contracts, err -} diff --git a/rest/payloads/transactionPayloads.go b/rest/payloads/transactionPayloads.go deleted file mode 100644 index bbbbe7a..0000000 --- a/rest/payloads/transactionPayloads.go +++ /dev/null @@ -1,49 +0,0 @@ -package payloads - -import ( - "github.com/ethereum/go-ethereum/params" - "github.com/tenderly/tenderly-cli/ethereum/types" - "github.com/tenderly/tenderly-cli/model" - "github.com/tenderly/tenderly-cli/providers" -) - -type ExportTransactionRequest struct { - NetworkData NetworkData `json:"network_data"` - TransactionData TransactionData `json:"transaction_data"` - ContractsData UploadContractsRequest `json:"contracts_data"` -} - -type NetworkData struct { - Name string `json:"name"` - NetworkId string `json:"network_id"` - ForkedNetwork string `json:"forked_network"` - ChainConfig *params.ChainConfig `json:"chain_config"` -} - -type TransactionData struct { - Transaction types.Transaction `json:"transaction"` - State *model.TransactionState `json:"state"` - Status bool `json:"status"` -} - -type Export struct { - ID string `json:"id"` - - Hash string `json:"hash"` - BlockHash string `json:"block_hash"` - BlockNumber int64 `json:"block_number"` - From string `json:"from"` - Gas int64 `json:"gas"` - GasPrice int64 `json:"gas_price"` - Input string `json:"input"` - Nonce int64 `json:"nonce"` - To *string `json:"to"` - Value string `json:"value"` - Status bool `json:"status"` -} - -type ExportTransactionResponse struct { - Export *Export `json:"export"` - Contracts []providers.ApiContract `json:"contracts"` - Error *ApiError `json:"error"` -} diff --git a/rest/rest.go b/rest/rest.go index dd3360b..48a0287 100755 --- a/rest/rest.go +++ b/rest/rest.go @@ -30,10 +30,6 @@ type ContractRoutes interface { RenameContract(request payloads.RenameContractRequest, projectSlug, networkID, address string) (*payloads.RenameContractResponse, error) } -type ExportRoutes interface { - ExportTransaction(request payloads.ExportTransactionRequest, projectSlug string) (*payloads.ExportTransactionResponse, error) -} - type NetworkRoutes interface { GetPublicNetworks() (*payloads.NetworksResponse, error) } @@ -62,7 +58,6 @@ type Rest struct { User UserRoutes Project ProjectRoutes Contract ContractRoutes - Export ExportRoutes Networks NetworkRoutes Actions ActionRoutes DevNet DevNetRoutes @@ -75,7 +70,6 @@ func NewRest( user UserRoutes, project ProjectRoutes, contract ContractRoutes, - export ExportRoutes, networks NetworkRoutes, actions ActionRoutes, devnet DevNetRoutes, @@ -87,7 +81,6 @@ func NewRest( User: user, Project: project, Contract: contract, - Export: export, Networks: networks, Actions: actions, DevNet: devnet, diff --git a/stacktrace/contract.go b/stacktrace/contract.go deleted file mode 100644 index b1d8bb6..0000000 --- a/stacktrace/contract.go +++ /dev/null @@ -1,51 +0,0 @@ -package stacktrace - -import ( - "fmt" - "time" -) - -type ContractID string -type NetworkID string -type AccountID string - -func (id ContractID) String() string { - return string(id) -} - -type ContractAddress string - -func NewContractAddress(address string) ContractAddress { - return ContractAddress(address) -} - -func (address ContractAddress) String() string { - return string(address) -} - -type DeploymentInformation struct { - NetworkID NetworkID `json:"network_id"` - Address ContractAddress `json:"address"` -} - -func (deployment DeploymentInformation) String() string { - return fmt.Sprintf("[Network ID: %s, Address: %s]", deployment.NetworkID, deployment.Address) -} - -type Contract struct { - ID ContractID - - AccountID *AccountID `json:"account_id"` - - Name string `json:"contract_name"` - LowercaseName string `json:"lowercase_contract_name"` - Abi string `json:"abi"` - Bytecode string `json:"bytecode"` - SourceMap string `json:"source_map"` - Source string `json:"source"` - NumberOfExceptions int `json:"number_of_exceptions"` - LastEventOccurredAt *time.Time `json:"last_event_occurred_at"` - - VerificationDate time.Time `json:"verification_date"` - CreatedAt time.Time `json:"created_at"` -} diff --git a/stacktrace/core.go b/stacktrace/core.go deleted file mode 100644 index ea3d0fc..0000000 --- a/stacktrace/core.go +++ /dev/null @@ -1,236 +0,0 @@ -package stacktrace - -import ( - "errors" - "fmt" - "log" - "strings" - - "github.com/ethereum/go-ethereum/core/vm" - "github.com/tenderly/tenderly-cli/ethereum/parity" - "github.com/tenderly/tenderly-cli/ethereum/types" -) - -const ( - INVALID_OPCODE vm.OpCode = 0xfe -) - -var ( - ErrNotExist = errors.New("contract does not exist") -) - -var ( - EnableDebugLogging bool = false -) - -type ContractDetails struct { - ID ContractID - Name string - Hash string - - Bytecode []byte - DeployedByteCode string - - Abi interface{} - - Source string - SourceMap SourceMap - ProjectID string -} - -type ContractSource interface { - Get(id string) (*ContractDetails, error) -} - -type ContractStack struct { - contracts []*ContractDetails -} - -func NewContractStack(initialContract *ContractDetails) *ContractStack { - return &ContractStack{ - contracts: []*ContractDetails{ - initialContract, - }, - } -} - -func (cs *ContractStack) Push(contract *ContractDetails) { - cs.contracts = append(cs.contracts, contract) -} - -func (cs *ContractStack) Pop() { - if len(cs.contracts) == 1 { - return - } - - cs.contracts = cs.contracts[:len(cs.contracts)-1] -} - -func (cs *ContractStack) Get() *ContractDetails { - return cs.contracts[len(cs.contracts)-1] -} - -type Core struct { - Contracts ContractSource - // Way to access contracts (runtimeBin, runtimeSrcMap) - // Input of transactions - // Way to determine if transaction should be traced - // Trace transaction - - // Needs to be initialized before use. - stack *ContractStack -} - -func NewCore(contracts ContractSource) *Core { - return &Core{ - Contracts: contracts, - } -} - -// Listen on the provided transaction channel and parse traces -func (c *Core) Listen() { - //@TODO: Process block from here, not from main program. -} - -// Process a single transaction -// @TODO: remove client! -func (c *Core) GenerateStackTrace(contractHash string, txTrace types.TransactionStates) ([]*StackFrame, error) { - if err := c.initStack(contractHash); err != nil { - return nil, fmt.Errorf("process trace: %s", err) - } - - var stackTrace StackTrace - var stackFrames []*StackFrame - recordStackFrames := false - - for _, state := range txTrace.States() { - contract := c.stack.Get() - - op := vm.OpCode(contract.Bytecode[state.Pc()]) - - switch op { - case vm.CALL: - stack := state.Stack() - if stack == nil { - log.Println("didn't find stack but expected one: ", contractHash) - c.stack.Push(contract) - break - } - - newAddress := "0x" + stack[len(stack)-2][24:] - - newContract, err := c.Contracts.Get(newAddress) - if err != nil { - return nil, fmt.Errorf("cannot call contract [%s]: %s", newAddress, err) - } - - c.stack.Push(newContract) - case vm.RETURN, INVALID_OPCODE, vm.REVERT, vm.STOP: - c.stack.Pop() - } - - // If last in calling block && not a terminating op, it's an invalid opcode situation. - if parityState, ok := state.(*parity.VmState); ok && parityState.Terminating { - switch op { - case vm.RETURN, vm.REVERT, vm.STOP: - default: - log.Printf("Previous opcode: %s, changing to INVALID OPCODE", op.String()) - op = INVALID_OPCODE - } - } - - if op == vm.REVERT || op == INVALID_OPCODE { - recordStackFrames = true - } - - im := contract.SourceMap[int(state.Pc())] - if im == nil { - //@TODO: Abort, with error message. - log.Printf("MISSING SOURCE MAP: %s %d", contractHash, int(state.Pc())) - continue - } - - if im.FileIndex == -1 { - //@TODO: Check if reverts keep getting excluded due to this. - //fmt.Printf("INTERNAL => %d\t%s\n", state.Pc(), op.String()) - continue - } - - code := string(contract.Source[im.Start : im.Start+im.Length]) - - frame := &Frame{ - File: contract.Name, - - Line: im.Line, - Column: im.Column, - - Start: im.Start, - Length: im.Length, - - Text: strings.Split(code, "\n")[0], - - Op: op.String(), - - State: &state, - Mapping: im, - } - - if EnableDebugLogging { - log.Printf("%d\t%s\n", state.Pc(), frame) - } - - if recordStackFrames { - stackFrames = append(stackFrames, &StackFrame{ - ContractAddress: NewContractAddress(contract.Hash), - ContractName: contract.Name, - Line: frame.Line, - - Code: getLineFromContract(contract.Source, frame.Line), - Op: op.String(), - Start: frame.Start, - Length: frame.Length, - }) - } - - stackTrace.PushFrame(frame) - } - - return stackFrames, nil -} - -func (c *Core) initStack(contractHash string) error { - contract, err := c.Contracts.Get(contractHash) - if err != nil { - return err - } - - c.stack = NewContractStack(contract) - - return nil -} - -func getLineFromContract(source string, line int) string { - currentLine := 1 - recording := false - var text string - - for _, c := range source { - if currentLine == line && !recording { - recording = true - } - - if recording { - if currentLine != line { - break - } - - text = text + string(c) - } - - if c == '\n' { - currentLine++ - } - } - - return strings.TrimSpace(text) -} diff --git a/stacktrace/exception.go b/stacktrace/exception.go deleted file mode 100644 index 5a6becd..0000000 --- a/stacktrace/exception.go +++ /dev/null @@ -1,98 +0,0 @@ -package stacktrace - -import ( - "fmt" - "reflect" - "time" -) - -//@TODO: Figure out if this is how we want to name it. -type StackFrame struct { - ContractAddress ContractAddress `json:"contract"` - ContractName string `json:"name"` - Line int `json:"line"` - - Code string `json:"code"` - Op string `json:"op"` - Start int `json:"start"` - Length int `json:"length"` -} - -type DecodedCallData struct { - Signature string - Name string - Inputs []DecodedArgument -} - -type DecodedArgument struct { - Soltype Argument - Value interface{} -} - -type Argument struct { - Name string - Type Type - Indexed bool // indexed is only used by events -} - -type Type struct { - Elem *Type - - Kind reflect.Kind - Type reflect.Type - Size int - T byte // Our own type checking - - stringKind string // holds the unparsed string for deriving signatures -} - -type ABI struct { - Constructor Method - Methods map[string]Method - Events map[string]Event -} - -type Method struct { - Name string - Const bool - Inputs Arguments - Outputs Arguments -} - -type Arguments []Argument - -type Event struct { - Name string - Anonymous bool - Inputs Arguments -} - -type ArgumentsUint struct { - Position int `json:"position"` - Value int `json:"value"` -} - -type ArgumentsString struct { - Position int `json:"position"` - Value string `json:"value"` -} - -func (f StackFrame) String() string { - return fmt.Sprintf("\n\tat %s\n\t\tin %s:%d\n", f.Code, f.ContractName, f.Line) -} - -type Exception struct { - ContractID ContractID `json:"contract_id"` - - BlockNumber int64 `json:"block_number"` - TransactionID string `json:"transaction_id"` - - Method string - Parameters []DecodedArgument - - StackTrace []*StackFrame - - CreatedAt time.Time - - Projects []string -} diff --git a/stacktrace/source.go b/stacktrace/source.go deleted file mode 100644 index 36090c7..0000000 --- a/stacktrace/source.go +++ /dev/null @@ -1,41 +0,0 @@ -package stacktrace - -import ( - "sync" -) - -type TenderlyContractSource struct { - networkID NetworkID - - cache sync.Map -} - -func (cs *TenderlyContractSource) Get(id string) (*ContractDetails, error) { - res, ok := cs.cache.Load(id) - if ok { - return res.(*ContractDetails), nil - } - - return &ContractDetails{}, nil - //contract, err := cs.fetch(id) - //if err != nil { - // return nil, err - //} - // - //cs.cache.Store(id, contract) - // - //return contract, nil -} - -//func parseBytecode(raw string) ([]byte, error) { -// if strings.HasPrefix(raw, "0x") { -// raw = raw[2:] -// } -// -// bin, err := hex.DecodeString(raw) -// if err != nil { -// return nil, fmt.Errorf("failed decoding runtime binary: %s", err) -// } -// -// return bin, nil -//} diff --git a/stacktrace/sourcemap.go b/stacktrace/sourcemap.go deleted file mode 100644 index f97a77f..0000000 --- a/stacktrace/sourcemap.go +++ /dev/null @@ -1,135 +0,0 @@ -package stacktrace - -import ( - "encoding/hex" - "fmt" - "strconv" - "strings" - - "github.com/ethereum/go-ethereum/core/vm" -) - -//@TODO: Use a more memory efficient method of representing source maps. How costly are InstructionMapping refs? - -type InstructionMapping struct { - Start int - Length int - - // WHAT WE REALLY NEED - Line int - Column int - - FileIndex int - Jump string -} - -// SourceMap is the memory address to instruction information map. -type SourceMap map[int]*InstructionMapping - -func ParseSourceMap(sourceMap string, source string, bytecode string) (*SourceMap, error) { - instructionSrcMap := make(SourceMap) - - var err error - var s int64 - var l int64 - var f int64 - var j string - for index, mapping := range strings.Split(sourceMap, ";") { - if mapping == "" { - instructionSrcMap[index] = instructionSrcMap[index-1] - continue - } - - info := strings.Split(mapping, ":") - if len(info) > 0 && info[0] != "" { - s, err = strconv.ParseInt(info[0], 0, 64) - if err != nil { - return nil, fmt.Errorf("failed parsing integer: %s", err) - } - } - if len(info) > 1 && info[1] != "" { - l, err = strconv.ParseInt(info[1], 0, 64) - if err != nil { - return nil, fmt.Errorf("failed parsing integer: %s", err) - } - } - if len(info) > 2 && info[2] != "" { - f, err = strconv.ParseInt(info[2], 0, 64) - if err != nil { - return nil, fmt.Errorf("failed parsing integer: %s", err) - } - } - if len(info) > 3 && info[3] != "" { - j = info[3] - } - - instructionSrcMap[index] = &InstructionMapping{ - Start: int(s), - Length: int(l), - FileIndex: int(f), - Jump: j, - } - } - - for _, instruction := range instructionSrcMap { - if instruction == nil { - // Instruction does not map to source - continue - } - - i := 0 - line := 1 - column := 1 - - for i < instruction.Start { - if source[i] == '\n' { - line++ - column = 0 - } - - column++ - i++ - } - - instruction.Line = line - instruction.Column = column - } - - memSrcMap, err := convertToMemoryMap(instructionSrcMap, bytecode) - if err != nil { - return nil, fmt.Errorf("failed mapping source map to memory: %s", err) - } - - return &memSrcMap, nil -} - -func convertToMemoryMap(sourceMap SourceMap, binData string) (SourceMap, error) { - memSrcMap := make(SourceMap) - - if strings.HasPrefix(binData, "0x") { - binData = binData[2:] - } - - bin, err := hex.DecodeString(binData) - if err != nil { - return nil, fmt.Errorf("failed decoding runtime binary: %s", err) - } - - instruction := 0 - for i := 0; i < len(bin); i++ { - - op := vm.OpCode(bin[i]) - extraPush := 0 - if op.IsPush() { - // Skip more here - extraPush = int(op - vm.PUSH1 + 1) - } - - memSrcMap[i] = sourceMap[instruction] - - instruction++ - i += extraPush - } - - return memSrcMap, nil -} diff --git a/stacktrace/stacktrace.go b/stacktrace/stacktrace.go deleted file mode 100644 index 6f69416..0000000 --- a/stacktrace/stacktrace.go +++ /dev/null @@ -1,43 +0,0 @@ -package stacktrace - -import ( - "fmt" - - "github.com/tenderly/tenderly-cli/ethereum/types" -) - -type Frame struct { - File string `json:"file"` - Line int `json:"line"` - Column int `json:"column"` - - Start int `json:"start"` - Length int `json:"length"` - - Text string `json:"text"` - - Op string `json:"op"` - - State *types.EvmState - Mapping *InstructionMapping -} - -func (frame Frame) String() string { - return fmt.Sprintf("at %s:%03d %s %s // %s", frame.File, frame.Line, frame.Mapping.Jump, frame.Text, frame.Op) -} - -type StackTrace struct { - Frames []*Frame -} - -func (st *StackTrace) PushFrame(frame *Frame) { - st.Frames = append(st.Frames, frame) -} - -func (st *StackTrace) PopFrame() *Frame { - frame := st.Frames[len(st.Frames)-1] - - st.Frames = st.Frames[:len(st.Frames)-1] - - return frame -} diff --git a/truffle/source.go b/truffle/source.go deleted file mode 100644 index 17589a3..0000000 --- a/truffle/source.go +++ /dev/null @@ -1,100 +0,0 @@ -package truffle - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/tenderly/tenderly-cli/ethereum" - "github.com/tenderly/tenderly-cli/providers" - "github.com/tenderly/tenderly-cli/stacktrace" -) - -// NewContractSource builds the Contract Source from the provided config, and scoped to the provided network. -func (dp *DeploymentProvider) NewContractSource(path string, networkId string, client ethereum.Client) (stacktrace.ContractSource, error) { - truffleContracts, err := dp.loadTruffleContracts(path) - if err != nil { - return nil, err - } - - cs := &providers.ContractSource{ - Contracts: dp.mapTruffleContracts(truffleContracts, networkId), - Client: client, - } - - return cs, nil -} - -func (dp *DeploymentProvider) loadTruffleContracts(path string) ([]*providers.Contract, error) { - - files, err := os.ReadDir(path) - if err != nil { - return nil, fmt.Errorf("failed listing truffle build files: %s", err) - } - - var contracts []*providers.Contract - for _, file := range files { - if file.IsDir() || !strings.HasSuffix(file.Name(), ".json") { - continue - } - - data, err := os.ReadFile(filepath.Join(path, file.Name())) - if err != nil { - return nil, fmt.Errorf("failed reading truffle build files: %s", err) - } - - var contract providers.Contract - err = json.Unmarshal(data, &contract) - if err != nil { - return nil, fmt.Errorf("failed parsing truffle build files: %s", err) - } - - contracts = append(contracts, &contract) - } - - return contracts, nil -} - -func (dp *DeploymentProvider) mapTruffleContracts( - truffleContracts []*providers.Contract, - networkId string, -) map[string]*stacktrace.ContractDetails { - contracts := make(map[string]*stacktrace.ContractDetails) - - for _, truffleContract := range truffleContracts { - network, ok := truffleContract.Networks[networkId] - if !ok { - //@TODO: log.DEBUG Contract X not found in network Y. - continue - } - - bytecode, err := providers.ParseBytecode(truffleContract.DeployedBytecode) - if err != nil { - //@TODO: log.ERROR Skipping contract because of invalid bytecode. - continue - } - - sourceMap, err := providers.ParseContract(truffleContract) - if err != nil { - //@TODO: log.ERROR Skipping contract because of invalid source map. - continue - } - - contracts[strings.ToLower(network.Address)] = &stacktrace.ContractDetails{ - Name: truffleContract.Name, - Hash: network.Address, - - Bytecode: bytecode, - DeployedByteCode: truffleContract.DeployedBytecode, - - Abi: truffleContract.Abi, - - Source: truffleContract.Source, - SourceMap: sourceMap, - } - } - - return contracts -}