diff --git a/client/genutil/evm/evm.go b/client/genutil/evm/evm.go index db610fd3..6d498eca 100644 --- a/client/genutil/evm/evm.go +++ b/client/genutil/evm/evm.go @@ -12,7 +12,6 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/piplabs/story/client/genutil/evm/predeploys" - "github.com/piplabs/story/lib/anvil" "github.com/piplabs/story/lib/errors" "github.com/piplabs/story/lib/netconf" ) @@ -94,18 +93,6 @@ func precompilesAlloc() types.GenesisAlloc { // devPrefundAlloc returns allocs for pre-funded geth dev accounts. func stagingPrefundAlloc() types.GenesisAlloc { return types.GenesisAlloc{ - // anvil pre-funded accounts - anvil.DevAccount0(): {Balance: eth1m}, - anvil.DevAccount1(): {Balance: eth1m}, - anvil.DevAccount2(): {Balance: eth1m}, - anvil.DevAccount3(): {Balance: eth1m}, - anvil.DevAccount4(): {Balance: eth1m}, - anvil.DevAccount5(): {Balance: eth1m}, - anvil.DevAccount6(): {Balance: eth1m}, - anvil.DevAccount7(): {Balance: eth1m}, - anvil.DevAccount8(): {Balance: eth1m}, - anvil.DevAccount9(): {Balance: eth1m}, - // TODO: team accounts common.HexToAddress("0x0000000000000000000000000000000000000000"): {Balance: eth1m}, } diff --git a/client/genutil/evm/evm_test.go b/client/genutil/evm/evm_test.go deleted file mode 100644 index fa77552f..00000000 --- a/client/genutil/evm/evm_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package evm_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/client/genutil/evm" - "github.com/piplabs/story/lib/netconf" - "github.com/piplabs/story/lib/tutil" - - _ "github.com/piplabs/story/client/app" // To init SDK config. -) - -//go:generate go test . -golden -clean - -func TestMakeGenesis(t *testing.T) { - t.Parallel() - - genesis, err := evm.MakeGenesis(netconf.Staging) - require.NoError(t, err) - tutil.RequireGoldenJSON(t, genesis) -} diff --git a/e2e/.gitignore b/e2e/.gitignore deleted file mode 100644 index 577774da..00000000 --- a/e2e/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -runs -failed-logs* diff --git a/e2e/README.md b/e2e/README.md deleted file mode 100644 index 8a1782b0..00000000 --- a/e2e/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# End-to-End Tests - -Spins up and tests Iliad devnets in Docker Compose based on a testnet manifest. To run the CI testnet: - -```sh -# In repo root -# Install the e2e app -go install github.com/piplabs/story/e2e - -# Build docker image of the code to test. -make build-docker - -# Run one of the "manifests" in networks/ directory: e2e -f -e2e -f e2e/networks/single.toml -``` - -This creates and runs a testnet named `single` under `e2e/runs/single/`. - -## Conceptual Overview - -Please refer to the [cometBFT E2E test framework](https://github.com/cometbft/cometbft/tree/main/test/e2e) for more details. - -In order to perform any action on a network (deploy/test/show logs), the following process is followed to create a network `Definition`: -1. A network is initially declared in a `manifest` file, see [manifests/](./manifests) folder. It defines the desired network topology. See the `e2e/types#Manifest` type for details. -2. Then the infrastructure provider (only `docker compose` supported at the moment) subsequently generates the `e2e/types#InfrastructureData` from the manifest. This defines the instance IPs and ports of everything we will deploy. -3. Subsequently, we generate a `Testnet` struct which is basically contains all the configuration/keys/peers/images/files/folders required to deploy a network. See `e2e/types#Testnet` for details. -4. We then instantiate a `netman.Manager` which is responsible for deploying portals. It takes a `Testnet` struct as input. -5. Finally, we instantiate new `InfrastructureProvider` which can deploy the network. It takes a `Testnet` struct and `InfrastructureData` as input. - -These objects are then wrapped in a `e2e/app#Definition` that can be used to perform any action on a network. - -## Test Stages - -The e2e test has the following stages, which can also be executed explicitly by running `e2e -f `: - -* `setup`: generates configuration files. - -* `start`: starts Docker containers. - -* `wait`: waits for a few blocks to be produced, and for all nodes to catch up to it. - -* `stop`: stops Docker containers. - -* `cleanup`: removes configuration files and Docker containers/networks. - -Auxiliary commands: - -* `logs`: outputs all node logs. - -* `tail`: tails (follows) node logs until canceled. diff --git a/e2e/app/agent/prometheus.go b/e2e/app/agent/prometheus.go deleted file mode 100644 index fdbc8146..00000000 --- a/e2e/app/agent/prometheus.go +++ /dev/null @@ -1,157 +0,0 @@ -package agent - -import ( - "bytes" - "context" - "fmt" - "os" - "path/filepath" - "regexp" - "strings" - "text/template" - - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/log" - "github.com/piplabs/story/lib/netconf" - - _ "embed" -) - -type Secrets struct { - URL string - User string - Pass string -} - -const promPort = 26660 // Default metrics port for all iliad apps (from cometBFT) -const gethPromPort = 6060 - -//go:embed prometheus.yml.tmpl -var promConfigTmpl []byte - -func WriteConfig(ctx context.Context, testnet types.Testnet, secrets Secrets) error { - hostname, err := os.Hostname() - if err != nil { - hostname = "unknown" - } - - bz, err := genPromConfig(ctx, testnet, secrets, hostname) - if err != nil { - return errors.Wrap(err, "generating prometheus config") - } - - promFile := filepath.Join(testnet.Dir, "prometheus", "prometheus.yml") - if err := os.MkdirAll(filepath.Dir(promFile), 0755); err != nil { - return errors.Wrap(err, "creating prometheus dir") - } - - if err := os.WriteFile(promFile, bz, 0644); err != nil { - return errors.Wrap(err, "writing prometheus config") - } - - return nil -} - -func genPromConfig(ctx context.Context, testnet types.Testnet, secrets Secrets, hostname string) ([]byte, error) { - var nodeTargets []string - for _, node := range testnet.Nodes { - // Prometheus is always inside the same docker-compose, so use service names. - nodeTargets = append(nodeTargets, fmt.Sprintf("%s:%d", node.Name, promPort)) - } - - var evmTargets []string - for _, iliadEVM := range testnet.IliadEVMs { - evmTargets = append(evmTargets, fmt.Sprintf("%s:%d", iliadEVM.InstanceName, gethPromPort)) - } - - network := string(testnet.Network) - if testnet.Network == netconf.Devnet { - network = fmt.Sprintf("%s-%s", testnet.Name, hostname) - } - - if secrets.URL == "" { - log.Warn(ctx, "Prometheus remote URL not set, metrics not being pushed to Grafana cloud", nil) - } else { - log.Info(ctx, "Prometheus metrics pushed to Grafana cloud", "network", network) - } - - data := promTmplData{ - Network: network, - Host: hostname, - RemoteURL: secrets.URL, - RemoteUsername: secrets.User, - RemotePassword: secrets.Pass, - ScrapeConfigs: []promScrapConfig{ - { - JobName: "iliad", - MetricsPath: "/metrics", - targets: nodeTargets, - }, - { - JobName: "geth", - MetricsPath: "/debug/metrics/prometheus", - targets: evmTargets, - }, - }, - } - - t, err := template.New("").Parse(string(promConfigTmpl)) - if err != nil { - return nil, errors.Wrap(err, "parsing template") - } - - var bz bytes.Buffer - if err := t.Execute(&bz, data); err != nil { - return nil, errors.Wrap(err, "executing template") - } - - return bz.Bytes(), nil -} - -type promTmplData struct { - Network string // Used a "network" label to all metrics - Host string // Hostname of the docker host machine - RemoteURL string // URL to the Grafana cloud server - RemoteUsername string // Username to the Grafana cloud server - RemotePassword string // Password to the Grafana cloud server - ScrapeConfigs []promScrapConfig // List of scrape configs -} - -type promScrapConfig struct { - JobName string - MetricsPath string - targets []string -} - -func (c promScrapConfig) Targets() string { - return strings.Join(c.targets, ",") -} - -// ConfigForHost returns a new prometheus agent config with the given host and iliad targets. -// -// It replaces the iliad targets with provided. -// It replaces the geth targets with provided. -// It replaces the host label. -func ConfigForHost(bz []byte, newHost string, iliads []string, geths []string) []byte { - var iliadTargets []string - for _, iliad := range iliads { - iliadTargets = append(iliadTargets, fmt.Sprintf(`"%s:%d"`, iliad, promPort)) - } - replace := fmt.Sprintf(`[%s] # iliad targets`, strings.Join(iliadTargets, ",")) - bz = regexp.MustCompile(`(?m)\[.*\] # iliad targets$`). - ReplaceAll(bz, []byte(replace)) - - var gethTargets []string - for _, geth := range geths { - gethTargets = append(gethTargets, fmt.Sprintf(`"%s:%d"`, geth, gethPromPort)) - } - replace = fmt.Sprintf(`[%s] # geth targets`, strings.Join(gethTargets, ",")) - bz = regexp.MustCompile(`(?m)\[.*\] # geth targets$`). - ReplaceAll(bz, []byte(replace)) - - bz = regexp.MustCompile(`(?m)host: '.*'$`). - ReplaceAll(bz, []byte(fmt.Sprintf(`host: '%s'`, newHost))) - - return bz -} diff --git a/e2e/app/agent/prometheus.yml.tmpl b/e2e/app/agent/prometheus.yml.tmpl deleted file mode 100644 index d545d7af..00000000 --- a/e2e/app/agent/prometheus.yml.tmpl +++ /dev/null @@ -1,28 +0,0 @@ -global: - scrape_interval: 30s # Set the scrape interval to every 30 seconds. - evaluation_interval: 30s # Evaluate rules every 30 seconds. - -{{- if .RemoteURL }} -remote_write: - - url: {{ .RemoteURL }} - basic_auth: - username: {{ .RemoteUsername }} - password: {{ .RemotePassword }} - write_relabel_configs: - # Add 'container' label using 'instance without port' - - source_labels: [instance] - regex: '(.+):(\d+)' - target_label: container - replacement: '${1}' -{{ end }} - -scrape_configs: -{{- range .ScrapeConfigs }} - - job_name: "{{ .JobName }}" - metrics_path: "{{ .MetricsPath }}" - static_configs: - - targets: [{{ .Targets }}] # {{ .JobName }} targets - labels: - network: '{{ $.Network }}' - host: '{{ $.Host }}' -{{ end }} diff --git a/e2e/app/agent/prometheus_internal_test.go b/e2e/app/agent/prometheus_internal_test.go deleted file mode 100644 index 19220ea6..00000000 --- a/e2e/app/agent/prometheus_internal_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package agent - -import ( - "context" - "testing" - - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/lib/netconf" - "github.com/piplabs/story/lib/tutil" -) - -//go:generate go test . -golden -clean - -func TestPromGen(t *testing.T) { - t.Parallel() - tests := []struct { - name string - network netconf.ID - nodes []string - newNodes []string - geths []string - newGeths []string - hostname string - agentSecrets bool - }{ - { - name: "manifest1", - network: netconf.Devnet, - nodes: []string{"validator01", "validator02"}, - hostname: "localhost", - newNodes: []string{"validator01"}, - geths: []string{"iliad_evm"}, - newGeths: []string{"iliad_evm"}, - agentSecrets: false, - }, - { - name: "manifest2", - network: netconf.Staging, - nodes: []string{"validator01", "validator02", "fullnode03"}, - hostname: "vm", - newNodes: []string{"fullnode04"}, - geths: []string{"validator01_evm", "validator02_evm", "validator03_evm"}, - newGeths: []string{"fullnode04_evm"}, - agentSecrets: true, - }, - { - name: "manifest3", - network: netconf.Devnet, - nodes: []string{"validator01", "validator02"}, - hostname: "localhost", - newNodes: []string{"validator01"}, - agentSecrets: false, - }, - { - name: "manifest4", - network: netconf.Staging, - nodes: []string{"validator01", "validator02", "fullnode03"}, - hostname: "vm", - newNodes: []string{"fullnode04"}, - agentSecrets: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - ctx := context.Background() - - var nodes []*e2e.Node - for _, name := range test.nodes { - nodes = append(nodes, &e2e.Node{Name: name}) - } - - var geths []types.IliadEVM - for _, name := range test.geths { - geths = append(geths, types.IliadEVM{InstanceName: name}) - } - - testnet := types.Testnet{ - Network: test.network, - Testnet: &e2e.Testnet{ - Name: test.name, - Nodes: nodes, - }, - IliadEVMs: geths, - } - - var agentSecrets Secrets - if test.agentSecrets { - agentSecrets = Secrets{ - URL: "https://grafana.com", - User: "admin", - Pass: "password", - } - } - - cfg1, err := genPromConfig(ctx, testnet, agentSecrets, test.hostname) - require.NoError(t, err) - - cfg2 := ConfigForHost(cfg1, test.hostname+"-2", test.newNodes, test.newGeths) - - t.Run("gen", func(t *testing.T) { - t.Parallel() - tutil.RequireGoldenBytes(t, cfg1) - }) - - t.Run("update", func(t *testing.T) { - t.Parallel() - tutil.RequireGoldenBytes(t, cfg2) - }) - }) - } -} diff --git a/e2e/app/agent/testdata/TestPromGen_manifest1_gen.golden b/e2e/app/agent/testdata/TestPromGen_manifest1_gen.golden deleted file mode 100644 index 19bd91c2..00000000 --- a/e2e/app/agent/testdata/TestPromGen_manifest1_gen.golden +++ /dev/null @@ -1,21 +0,0 @@ -global: - scrape_interval: 30s # Set the scrape interval to every 30 seconds. - evaluation_interval: 30s # Evaluate rules every 30 seconds. - -scrape_configs: - - job_name: "iliad" - metrics_path: "/metrics" - static_configs: - - targets: [validator01:26660,validator02:26660] # iliad targets - labels: - network: 'manifest1-localhost' - host: 'localhost' - - - job_name: "geth" - metrics_path: "/debug/metrics/prometheus" - static_configs: - - targets: [iliad_evm:6060] # geth targets - labels: - network: 'manifest1-localhost' - host: 'localhost' - diff --git a/e2e/app/agent/testdata/TestPromGen_manifest1_update.golden b/e2e/app/agent/testdata/TestPromGen_manifest1_update.golden deleted file mode 100644 index 1dba1d91..00000000 --- a/e2e/app/agent/testdata/TestPromGen_manifest1_update.golden +++ /dev/null @@ -1,21 +0,0 @@ -global: - scrape_interval: 30s # Set the scrape interval to every 30 seconds. - evaluation_interval: 30s # Evaluate rules every 30 seconds. - -scrape_configs: - - job_name: "iliad" - metrics_path: "/metrics" - static_configs: - - targets: ["validator01:26660"] # iliad targets - labels: - network: 'manifest1-localhost' - host: 'localhost-2' - - - job_name: "geth" - metrics_path: "/debug/metrics/prometheus" - static_configs: - - targets: ["iliad_evm:6060"] # geth targets - labels: - network: 'manifest1-localhost' - host: 'localhost-2' - diff --git a/e2e/app/agent/testdata/TestPromGen_manifest2_gen.golden b/e2e/app/agent/testdata/TestPromGen_manifest2_gen.golden deleted file mode 100644 index 5b3357c6..00000000 --- a/e2e/app/agent/testdata/TestPromGen_manifest2_gen.golden +++ /dev/null @@ -1,33 +0,0 @@ -global: - scrape_interval: 30s # Set the scrape interval to every 30 seconds. - evaluation_interval: 30s # Evaluate rules every 30 seconds. -remote_write: - - url: https://grafana.com - basic_auth: - username: admin - password: password - write_relabel_configs: - # Add 'container' label using 'instance without port' - - source_labels: [instance] - regex: '(.+):(\d+)' - target_label: container - replacement: '${1}' - - -scrape_configs: - - job_name: "iliad" - metrics_path: "/metrics" - static_configs: - - targets: [validator01:26660,validator02:26660,fullnode03:26660] # iliad targets - labels: - network: 'staging' - host: 'vm' - - - job_name: "geth" - metrics_path: "/debug/metrics/prometheus" - static_configs: - - targets: [validator01_evm:6060,validator02_evm:6060,validator03_evm:6060] # geth targets - labels: - network: 'staging' - host: 'vm' - diff --git a/e2e/app/agent/testdata/TestPromGen_manifest2_update.golden b/e2e/app/agent/testdata/TestPromGen_manifest2_update.golden deleted file mode 100644 index ce7471a4..00000000 --- a/e2e/app/agent/testdata/TestPromGen_manifest2_update.golden +++ /dev/null @@ -1,33 +0,0 @@ -global: - scrape_interval: 30s # Set the scrape interval to every 30 seconds. - evaluation_interval: 30s # Evaluate rules every 30 seconds. -remote_write: - - url: https://grafana.com - basic_auth: - username: admin - password: password - write_relabel_configs: - # Add 'container' label using 'instance without port' - - source_labels: [instance] - regex: '(.+):(\d+)' - target_label: container - replacement: '${1}' - - -scrape_configs: - - job_name: "iliad" - metrics_path: "/metrics" - static_configs: - - targets: ["fullnode04:26660"] # iliad targets - labels: - network: 'staging' - host: 'vm-2' - - - job_name: "geth" - metrics_path: "/debug/metrics/prometheus" - static_configs: - - targets: ["fullnode04_evm:6060"] # geth targets - labels: - network: 'staging' - host: 'vm-2' - diff --git a/e2e/app/agent/testdata/TestPromGen_manifest3_gen.golden b/e2e/app/agent/testdata/TestPromGen_manifest3_gen.golden deleted file mode 100644 index 0087189c..00000000 --- a/e2e/app/agent/testdata/TestPromGen_manifest3_gen.golden +++ /dev/null @@ -1,21 +0,0 @@ -global: - scrape_interval: 30s # Set the scrape interval to every 30 seconds. - evaluation_interval: 30s # Evaluate rules every 30 seconds. - -scrape_configs: - - job_name: "iliad" - metrics_path: "/metrics" - static_configs: - - targets: [validator01:26660,validator02:26660] # iliad targets - labels: - network: 'manifest3-localhost' - host: 'localhost' - - - job_name: "geth" - metrics_path: "/debug/metrics/prometheus" - static_configs: - - targets: [] # geth targets - labels: - network: 'manifest3-localhost' - host: 'localhost' - diff --git a/e2e/app/agent/testdata/TestPromGen_manifest3_update.golden b/e2e/app/agent/testdata/TestPromGen_manifest3_update.golden deleted file mode 100644 index 1eed30b9..00000000 --- a/e2e/app/agent/testdata/TestPromGen_manifest3_update.golden +++ /dev/null @@ -1,21 +0,0 @@ -global: - scrape_interval: 30s # Set the scrape interval to every 30 seconds. - evaluation_interval: 30s # Evaluate rules every 30 seconds. - -scrape_configs: - - job_name: "iliad" - metrics_path: "/metrics" - static_configs: - - targets: ["validator01:26660"] # iliad targets - labels: - network: 'manifest3-localhost' - host: 'localhost-2' - - - job_name: "geth" - metrics_path: "/debug/metrics/prometheus" - static_configs: - - targets: [] # geth targets - labels: - network: 'manifest3-localhost' - host: 'localhost-2' - diff --git a/e2e/app/agent/testdata/TestPromGen_manifest4_gen.golden b/e2e/app/agent/testdata/TestPromGen_manifest4_gen.golden deleted file mode 100644 index bd5024d2..00000000 --- a/e2e/app/agent/testdata/TestPromGen_manifest4_gen.golden +++ /dev/null @@ -1,33 +0,0 @@ -global: - scrape_interval: 30s # Set the scrape interval to every 30 seconds. - evaluation_interval: 30s # Evaluate rules every 30 seconds. -remote_write: - - url: https://grafana.com - basic_auth: - username: admin - password: password - write_relabel_configs: - # Add 'container' label using 'instance without port' - - source_labels: [instance] - regex: '(.+):(\d+)' - target_label: container - replacement: '${1}' - - -scrape_configs: - - job_name: "iliad" - metrics_path: "/metrics" - static_configs: - - targets: [validator01:26660,validator02:26660,fullnode03:26660] # iliad targets - labels: - network: 'staging' - host: 'vm' - - - job_name: "geth" - metrics_path: "/debug/metrics/prometheus" - static_configs: - - targets: [] # geth targets - labels: - network: 'staging' - host: 'vm' - diff --git a/e2e/app/agent/testdata/TestPromGen_manifest4_update.golden b/e2e/app/agent/testdata/TestPromGen_manifest4_update.golden deleted file mode 100644 index f9e31b85..00000000 --- a/e2e/app/agent/testdata/TestPromGen_manifest4_update.golden +++ /dev/null @@ -1,33 +0,0 @@ -global: - scrape_interval: 30s # Set the scrape interval to every 30 seconds. - evaluation_interval: 30s # Evaluate rules every 30 seconds. -remote_write: - - url: https://grafana.com - basic_auth: - username: admin - password: password - write_relabel_configs: - # Add 'container' label using 'instance without port' - - source_labels: [instance] - regex: '(.+):(\d+)' - target_label: container - replacement: '${1}' - - -scrape_configs: - - job_name: "iliad" - metrics_path: "/metrics" - static_configs: - - targets: ["fullnode04:26660"] # iliad targets - labels: - network: 'staging' - host: 'vm-2' - - - job_name: "geth" - metrics_path: "/debug/metrics/prometheus" - static_configs: - - targets: [] # geth targets - labels: - network: 'staging' - host: 'vm-2' - diff --git a/e2e/app/cleanup.go b/e2e/app/cleanup.go deleted file mode 100644 index 50305370..00000000 --- a/e2e/app/cleanup.go +++ /dev/null @@ -1,65 +0,0 @@ -package app - -import ( - "context" - "fmt" - "os" - "path/filepath" - "runtime" - - "github.com/cometbft/cometbft/test/e2e/pkg/infra/docker" - - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/log" -) - -// CleanInfra stops and removes the infra containers. -func CleanInfra(ctx context.Context, def Definition) error { - if err := def.Infra.Clean(ctx); err != nil { - return errors.Wrap(err, "cleaning infrastructure") - } - - return nil -} - -// CleanupDir cleans up a testnet directory. -func CleanupDir(ctx context.Context, dir string) error { - if dir == "" { - return errors.New("no directory set") - } - - _, err := os.Stat(dir) - if os.IsNotExist(err) { - return nil - } else if err != nil { - return errors.Wrap(err, "stat") - } - - log.Info(ctx, "Cleanup dir", "dir", dir) - - // On Linux, some local files in the volume will be owned by root since CometBFT - // runs as root inside the container, so we need to clean them up from within a - // container running as root too. - if runtime.GOOS == "linux" { - absDir, err := filepath.Abs(dir) - if err != nil { - return errors.Wrap(err, "abs dir") - } - err = docker.Exec(ctx, "run", - "--rm", // Remove the container after it exits - "--entrypoint", "", // Clear the entrypoint so we can run a shell command - "-v", fmt.Sprintf("%v:/mount", absDir), // Mount the testnet dir into the container - "ethereum/client-go:latest", // Use the latest geth image (which runs as root) - "sh", "-c", "rm -rf /mount/*/") // Remove all files in the mounted testnet dir - if err != nil { - return errors.Wrap(err, "exec rm dir") - } - } - - err = os.RemoveAll(dir) - if err != nil { - return errors.Wrap(err, "remove dir") - } - - return nil -} diff --git a/e2e/app/create3.go b/e2e/app/create3.go deleted file mode 100644 index cb4c5b99..00000000 --- a/e2e/app/create3.go +++ /dev/null @@ -1,81 +0,0 @@ -package app - -import ( - "context" - - "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" - - "github.com/piplabs/story/lib/contracts/create3" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/log" -) - -type Create3DeployConfig struct { - ChainID uint64 // chain id of the chain to deploy to -} - -// Validate validates the Create3DeployConfig. -func (cfg Create3DeployConfig) Validate() error { - if cfg.ChainID == 0 { - return errors.New("chain id is zero") - } - - return nil -} - -// Create3Deploy deploys the Iliad Create3 contracts. -func Create3Deploy(ctx context.Context, def Definition, cfg Create3DeployConfig) error { - if err := cfg.Validate(); err != nil { - return errors.Wrap(err, "validate") - } - - addr, receipt, err := deployCreate3(ctx, def, cfg.ChainID) - if err != nil { - return errors.Wrap(err, "deploy create3") - } - - log.Info(ctx, "Create3 factory deployed", "chain", cfg.ChainID, "addr", addr.Hex(), "block", receipt.BlockNumber) - - return nil -} - -func deployPrivateCreate3(ctx context.Context, def Definition) error { - for _, c := range def.Testnet.AnvilChains { - _, _, err := deployCreate3(ctx, def, c.Chain.ChainID) - if err != nil { - return errors.Wrap(err, "deploy create3") - } - } - - // only deploy to iliad evm once - if len(def.Testnet.IliadEVMs) > 0 { - c := def.Testnet.IliadEVMs[0] - _, _, err := deployCreate3(ctx, def, c.Chain.ChainID) - if err != nil { - return errors.Wrap(err, "deploy create3") - } - } - - return nil -} - -func deployPublicCreate3(ctx context.Context, def Definition) error { - for _, c := range def.Testnet.PublicChains { - _, _, err := deployCreate3(ctx, def, c.Chain().ChainID) - if err != nil { - return errors.Wrap(err, "deploy create3") - } - } - - return nil -} - -func deployCreate3(ctx context.Context, def Definition, chainID uint64) (common.Address, *ethtypes.Receipt, error) { - backend, err := def.Backends().Backend(chainID) - if err != nil { - return common.Address{}, nil, err - } - - return create3.DeployIfNeeded(ctx, def.Testnet.Network, backend) -} diff --git a/e2e/app/definition.go b/e2e/app/definition.go deleted file mode 100644 index 5205785f..00000000 --- a/e2e/app/definition.go +++ /dev/null @@ -1,561 +0,0 @@ -package app - -import ( - "context" - "fmt" - "net" - "path/filepath" - "strings" - "sync" - "time" - - "github.com/BurntSushi/toml" - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - "github.com/cometbft/cometbft/test/e2e/pkg/exec" - "github.com/ethereum/go-ethereum/p2p/enode" - - "github.com/piplabs/story/e2e/app/agent" - "github.com/piplabs/story/e2e/app/key" - "github.com/piplabs/story/e2e/docker" - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/e2e/vmcompose" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/ethclient/ethbackend" - "github.com/piplabs/story/lib/fireblocks" - "github.com/piplabs/story/lib/netconf" - "github.com/piplabs/story/lib/tutil" -) - -const iliadConsensus = "iliad_consensus" - -// DefinitionConfig is the configuration required to create a full Definition. -type DefinitionConfig struct { - AgentSecrets agent.Secrets - - ManifestFile string - InfraProvider string - - // Secrets (not required for devnet) - DeployKeyFile string - FireAPIKey string - FireKeyPath string - RPCOverrides map[string]string // map[chainName]rpcURL1,rpcURL2,... - - InfraDataFile string // Not required for docker provider - IliadImgTag string // IliadImgTag is the docker image tag used for iliad. - - TracingEndpoint string - TracingHeaders string -} - -// DefaultDefinitionConfig returns a default configuration for a Definition. -func DefaultDefinitionConfig(ctx context.Context) DefinitionConfig { - defaultTag := "main" - if out, err := exec.CommandOutput(ctx, "git", "rev-parse", "--short=7", "HEAD"); err == nil { - defaultTag = strings.TrimSpace(string(out)) - } - - return DefinitionConfig{ - AgentSecrets: agent.Secrets{}, // empty agent.Secrets by default - InfraProvider: docker.ProviderName, - IliadImgTag: defaultTag, - } -} - -// Definition defines a e2e network. All (sub)commands of the e2e cli requires a definition operate. -// Armed with a definition, a e2e network can be deployed, started, tested, stopped, etc. -type Definition struct { - Manifest types.Manifest - Testnet types.Testnet // Note that testnet is the cometBFT term. - Infra types.InfraProvider - Cfg DefinitionConfig // Original config used to construct the Definition. - lazyNetwork *lazyNetwork // lazyNetwork does lazy setup of backends (only if required). -} - -// InitLazyNetwork initializes the lazy network, which is the backends. -func (d Definition) InitLazyNetwork() error { - return d.lazyNetwork.Init() -} - -// Backends returns the backends. -func (d Definition) Backends() ethbackend.Backends { - return d.lazyNetwork.MustBackends() -} - -func MakeDefinition(ctx context.Context, cfg DefinitionConfig, commandName string) (Definition, error) { - if strings.TrimSpace(cfg.ManifestFile) == "" { - return Definition{}, errors.New("manifest not specified, use --manifest-file or -f") - } - - manifest, err := LoadManifest(cfg.ManifestFile) - if err != nil { - return Definition{}, errors.Wrap(err, "loading manifest") - } - - var infd types.InfrastructureData - switch cfg.InfraProvider { - case docker.ProviderName: - infd, err = docker.NewInfraData(manifest) - case vmcompose.ProviderName: - infd, err = vmcompose.LoadData(cfg.InfraDataFile) - default: - return Definition{}, errors.New("unknown infra provider", "provider", cfg.InfraProvider) - } - if err != nil { - return Definition{}, errors.Wrap(err, "loading infrastructure data") - } - - testnet, err := TestnetFromManifest(ctx, manifest, infd, cfg) - if err != nil { - return Definition{}, errors.Wrap(err, "loading testnet") - } - - // Setup lazy network, this is only executed by command that require networking. - lazy := func() (ethbackend.Backends, error) { - backends, err := newBackends(ctx, cfg, testnet, commandName) - if err != nil { - return ethbackend.Backends{}, errors.Wrap(err, "new backends") - } - - return backends, nil - } - - var infp types.InfraProvider - switch cfg.InfraProvider { - case docker.ProviderName: - infp = docker.NewProvider(testnet, infd, cfg.IliadImgTag) - case vmcompose.ProviderName: - infp = vmcompose.NewProvider(testnet, infd, cfg.IliadImgTag) - default: - return Definition{}, errors.New("unknown infra provider", "provider", cfg.InfraProvider) - } - - return Definition{ - Manifest: manifest, - Testnet: testnet, - Infra: infp, - lazyNetwork: &lazyNetwork{initFunc: lazy}, - Cfg: cfg, - }, nil -} - -func newBackends(ctx context.Context, cfg DefinitionConfig, testnet types.Testnet, commandName string) (ethbackend.Backends, error) { - // If no fireblocks API key, use in-memory keys. - if cfg.FireAPIKey == "" { - return ethbackend.NewBackends(testnet, cfg.DeployKeyFile) - } - - key, err := fireblocks.LoadKey(cfg.FireKeyPath) - if err != nil { - return ethbackend.Backends{}, errors.Wrap(err, "load fireblocks key") - } - - fireCl, err := fireblocks.New(testnet.Network, cfg.FireAPIKey, key, - fireblocks.WithSignNote(fmt.Sprintf("iliad e2e %s %s", commandName, testnet.Network)), - ) - if err != nil { - return ethbackend.Backends{}, errors.Wrap(err, "new fireblocks") - } - - // TODO: Fireblocks keys need to be funded on private/internal chains we deploy. - - return ethbackend.NewFireBackends(ctx, testnet, fireCl) -} - -// adaptCometTestnet adapts the default comet testnet for iliad specific changes and custom config. -func adaptCometTestnet(ctx context.Context, manifest types.Manifest, testnet *e2e.Testnet, imgTag string) (*e2e.Testnet, error) { - testnet.Dir = runsDir(testnet.File) - testnet.VoteExtensionsEnableHeight = 1 - testnet.UpgradeVersion = "iliadops/iliad:" + imgTag - - for i := range testnet.Nodes { - var err error - testnet.Nodes[i], err = adaptNode(ctx, manifest, testnet, testnet.Nodes[i], imgTag) - if err != nil { - return nil, err - } - } - - return testnet, nil -} - -// adaptNode adapts the default comet node for iliad specific changes and custom config. -func adaptNode(ctx context.Context, manifest types.Manifest, testnet *e2e.Testnet, node *e2e.Node, tag string) (*e2e.Node, error) { - valKey, err := getOrGenKey(ctx, manifest, node.Name, key.Validator) - if err != nil { - return nil, err - } - nodeKey, err := getOrGenKey(ctx, manifest, node.Name, key.P2PConsensus) - if err != nil { - return nil, err - } - - node.Version = "iliadops/iliad:" + tag - node.PrivvalKey = valKey.PrivKey - node.NodeKey = nodeKey.PrivKey - - // Add seeds (cometBFT only adds seeds defined explicitly per node, we auto-add all seeds). - seeds := manifest.Seeds() - for seed := range seeds { - if seed == node.Name { - continue // Skip self - } - node.Seeds = append(node.Seeds, testnet.LookupNode(seed)) - } - // Remove seeds from persisted peers (cometBFT adds all nodes as peers by default). - var persisted []*e2e.Node - for _, peer := range node.PersistentPeers { - if seeds[peer.Name] { - continue - } - persisted = append(persisted, peer) - } - node.PersistentPeers = persisted - - return node, nil -} - -// runsDir returns the runs directory for a given manifest file. -// E.g. /path/to/manifests/manifest.toml > /path/to/runs/manifest. -func runsDir(manifestFile string) string { - resp := strings.TrimSuffix(manifestFile, filepath.Ext(manifestFile)) - return strings.Replace(resp, "manifests", "runs", 1) -} - -// LoadManifest loads a manifest from disk. -func LoadManifest(path string) (types.Manifest, error) { - manifest := types.Manifest{} - _, err := toml.DecodeFile(path, &manifest) //nolint:musttag // toml tags annotated in struct - if err != nil { - return manifest, errors.Wrap(err, "decode manifest") - } - - return manifest, nil -} - -func NoNodesTestnet(manifest types.Manifest, infd types.InfrastructureData, cfg DefinitionConfig) (types.Testnet, error) { - publics, err := publicChains(manifest, cfg) - if err != nil { - return types.Testnet{}, err - } - - cmtTestnet, err := noNodesTestnet(manifest.Manifest, cfg.ManifestFile, infd.InfrastructureData) - if err != nil { - return types.Testnet{}, errors.Wrap(err, "testnet from manifest") - } - - return types.Testnet{ - Network: manifest.Network, - Testnet: cmtTestnet, - PublicChains: publics, - }, nil -} - -// noNodesTestnet returns a bare minimum instance of *e2e.Testnet. It doesn't have any nodes or chain details setup. -func noNodesTestnet(manifest e2e.Manifest, file string, ifd e2e.InfrastructureData) (*e2e.Testnet, error) { - dir := strings.TrimSuffix(file, filepath.Ext(file)) - - _, ipNet, err := net.ParseCIDR(ifd.Network) - if err != nil { - return nil, errors.Wrap(err, "parse network ip", "network", ifd.Network) - } - - testnet := &e2e.Testnet{ - Name: filepath.Base(dir), - File: file, - Dir: runsDir(file), - IP: ipNet, - InitialState: manifest.InitialState, - Prometheus: manifest.Prometheus, - } - - return testnet, nil -} - -//nolint:nosprintfhostport // Not an issue for non-critical e2e test code. -func TestnetFromManifest(ctx context.Context, manifest types.Manifest, infd types.InfrastructureData, cfg DefinitionConfig) (types.Testnet, error) { - cmtTestnet, err := e2e.NewTestnetFromManifest(manifest.Manifest, cfg.ManifestFile, infd.InfrastructureData) - if err != nil { - return types.Testnet{}, errors.Wrap(err, "testnet from manifest") - } - cmtTestnet, err = adaptCometTestnet(ctx, manifest, cmtTestnet, cfg.IliadImgTag) - if err != nil { - return types.Testnet{}, errors.Wrap(err, "adapt comet testnet") - } - - var iliadEVMS []types.IliadEVM - for name, isArchive := range manifest.IliadEVMs() { - inst, ok := infd.Instances[name] - if !ok { - return types.Testnet{}, errors.New("iliad evm instance not found in infrastructure data") - } - - pk, err := getOrGenKey(ctx, manifest, name, key.P2PExecution) - if err != nil { - return types.Testnet{}, errors.Wrap(err, "execution node key") - } - nodeKey, err := pk.ECDSA() - if err != nil { - return types.Testnet{}, err - } - - en := enode.NewV4(&nodeKey.PublicKey, inst.IPAddress, 30303, 30303) - - internalIP := inst.IPAddress.String() - advertisedIP := inst.ExtIPAddress // EVM P2P NAT advertised address. - if infd.Provider == docker.ProviderName { - internalIP = name // For docker, we use container names - advertisedIP = inst.IPAddress // For docker, we use container IPs for evm p2p networking, not localhost. - } - - iliadEVMS = append(iliadEVMS, types.IliadEVM{ - Chain: types.IliadEVMByNetwork(manifest.Network), - InstanceName: name, - AdvertisedIP: advertisedIP, - ProxyPort: inst.Port, - InternalRPC: fmt.Sprintf("http://%s:8545", internalIP), - ExternalRPC: fmt.Sprintf("http://%s:%d", inst.ExtIPAddress.String(), inst.Port), - NodeKey: nodeKey, - Enode: en, - IsArchive: isArchive, - JWTSecret: tutil.RandomHash().Hex(), - }) - } - - // Second pass to mesh the bootnodes - for i := range iliadEVMS { - var bootnodes []*enode.Node - for j, bootEVM := range iliadEVMS { - if i == j { - continue // Skip self - } - bootnodes = append(bootnodes, bootEVM.Enode) - } - iliadEVMS[i].Peers = bootnodes - } - - anvilEVMs, err := types.AnvilChainsByNames(manifest.AnvilChains) - if err != nil { - return types.Testnet{}, err - } - - var anvils []types.AnvilChain - for _, chain := range anvilEVMs { - inst, ok := infd.Instances[chain.Name] - if !ok { - return types.Testnet{}, errors.New("anvil chain instance not found in infrastructure data") - } - - internalIP := inst.IPAddress.String() - if infd.Provider == docker.ProviderName { - internalIP = chain.Name // For docker, we use container names - } - - anvils = append(anvils, types.AnvilChain{ - Chain: chain, - InternalIP: inst.IPAddress, - ProxyPort: inst.Port, - LoadState: "./anvil/state.json", - InternalRPC: fmt.Sprintf("http://%s:8545", internalIP), - ExternalRPC: fmt.Sprintf("http://%s:%d", inst.ExtIPAddress.String(), inst.Port), - }) - } - - publics, err := publicChains(manifest, cfg) - if err != nil { - return types.Testnet{}, err - } - - return types.Testnet{ - Network: manifest.Network, - Testnet: cmtTestnet, - IliadEVMs: iliadEVMS, - AnvilChains: anvils, - PublicChains: publics, - Perturb: manifest.Perturb, - }, nil -} - -// getOrGenKey gets (based on manifest) or creates a private key for the given node and type. -func getOrGenKey(ctx context.Context, manifest types.Manifest, nodeName string, typ key.Type) (key.Key, error) { - addr, ok := manifest.Keys[nodeName][typ] - if !ok { // No key in manifest - // Generate an insecure deterministic key for devnet - if manifest.Network == netconf.Devnet { - return key.GenerateInsecureDeterministic(manifest.Network, typ, nodeName), nil - } - - // Otherwise generate a proper key - return key.Generate(typ), nil - } - - // Address configured in manifest, download from GCP - return key.Download(ctx, manifest.Network, nodeName, typ, addr) -} - -func publicChains(manifest types.Manifest, cfg DefinitionConfig) ([]types.PublicChain, error) { - var publics []types.PublicChain - for _, name := range manifest.PublicChains { - chain, err := types.PublicChainByName(name) - if err != nil { - return nil, errors.Wrap(err, "get public chain") - } - - addr, ok := cfg.RPCOverrides[name] - if !ok { - addr = types.PublicRPCByName(name) - } - - publics = append(publics, types.NewPublicChain(chain, strings.Split(addr, ","))) - } - - return publics, nil -} - -// externalEndpoints returns the evm rpc endpoints for access from outside the -// docker network. -func externalEndpoints(def Definition) (endpoints map[string]string) { - endpoints = make(map[string]string) - - // Add all public chains - for _, public := range def.Testnet.PublicChains { - endpoints[public.Chain().Name] = public.NextRPCAddress() - } - - // Connect to a proper iliad_evm that isn't unavailable - iliadEVM := def.Testnet.BroadcastIliadEVM() - endpoints[iliadEVM.Chain.Name] = iliadEVM.ExternalRPC - - // Add story consensus chain - endpoints[def.Testnet.Network.Static().IliadConsensusChain().Name] = def.Testnet.BroadcastNode().AddressRPC() - - // Add all anvil chains - for _, anvil := range def.Testnet.AnvilChains { - endpoints[anvil.Chain.Name] = anvil.ExternalRPC - } - - return endpoints -} - -// networkFromDef returns the network configuration from the definition. -func networkFromDef(def Definition) netconf.Network { - var chains []netconf.Chain - - // Add all public chains - for _, public := range def.Testnet.PublicChains { - chains = append(chains, netconf.Chain{ - ID: public.Chain().ChainID, - Name: public.Chain().Name, - BlockPeriod: public.Chain().BlockPeriod, - }) - } - - // Connect to a proper iliad_evm that isn't unavailable - iliadEVM := def.Testnet.BroadcastIliadEVM() - chains = append(chains, netconf.Chain{ - ID: iliadEVM.Chain.ChainID, - Name: iliadEVM.Chain.Name, - BlockPeriod: iliadEVM.Chain.BlockPeriod, - }) - - // Add iliad consensus chain - chains = append(chains, netconf.Chain{ - ID: def.Testnet.Network.Static().IliadConsensusChainIDUint64(), - Name: iliadConsensus, - // No RPC URLs, since we are going to remove it from netconf in any case. - BlockPeriod: iliadEVM.Chain.BlockPeriod, // Same block period as iliadEVM - }) - - // Add all anvil chains - for _, anvil := range def.Testnet.AnvilChains { - chains = append(chains, netconf.Chain{ - ID: anvil.Chain.ChainID, - Name: anvil.Chain.Name, - BlockPeriod: anvil.Chain.BlockPeriod, - }) - } - - for _, chain := range chains { - if netconf.IsIliadConsensus(def.Testnet.Network, chain.ID) { - continue - } - } - - return netconf.Network{ - ID: def.Testnet.Network, - Chains: chains, - } -} - -// iliadEVMByPrefix returns a iliadEVM from the testnet with the given prefix. -// Or a random iliadEVM if prefix is empty. -// Or the only iliadEVM if there is only one. -func iliadEVMByPrefix(testnet types.Testnet, prefix string) types.IliadEVM { - if prefix == "" { - return random(testnet.IliadEVMs) - } else if len(testnet.IliadEVMs) == 1 { - return testnet.IliadEVMs[0] - } - - for _, evm := range testnet.IliadEVMs { - if strings.HasPrefix(evm.InstanceName, prefix) { - return evm - } - } - - panic("evm not found") -} - -// nodeByPrefix returns a iliad node from the testnet with the given prefix. -// Or a random node if prefix is empty. -// Or the only node if there is only one. -// -//nolint:unused // Disable unused lint temporarily. -func nodeByPrefix(testnet types.Testnet, prefix string) *e2e.Node { - if prefix == "" { - return random(testnet.Nodes) - } else if len(testnet.Nodes) == 1 { - return testnet.Nodes[0] - } - - for _, node := range testnet.Nodes { - if strings.HasPrefix(node.Name, prefix) { - return node - } - } - - panic("node not found") -} - -// random returns a random item from a slice. -func random[T any](items []T) T { - return items[int(time.Now().UnixNano())%len(items)] -} - -// lazyNetwork is a lazy network setup that initializes the backends only if required. -// Some e2e commands do not require networking, so this mitigates the need for special networking flags in that case. -type lazyNetwork struct { - once sync.Once - initFunc func() (ethbackend.Backends, error) - backends ethbackend.Backends -} - -func (l *lazyNetwork) Init() error { - var err error - l.once.Do(func() { - l.backends, err = l.initFunc() - }) - - return err -} - -func (l *lazyNetwork) mustInit() { - if err := l.Init(); err != nil { - panic(err) - } -} - -func (l *lazyNetwork) MustBackends() ethbackend.Backends { - l.mustInit() - return l.backends -} diff --git a/e2e/app/eoa/eoa.go b/e2e/app/eoa/eoa.go deleted file mode 100644 index ea804ac1..00000000 --- a/e2e/app/eoa/eoa.go +++ /dev/null @@ -1,147 +0,0 @@ -// Package eoa defines well-known (non-fireblocks) eoa private keys used in an iliad network. -package eoa - -import ( - "context" - "crypto/ecdsa" - - "github.com/ethereum/go-ethereum/common" - - "github.com/piplabs/story/e2e/app/key" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/netconf" -) - -type Role string - -const ( - // RoleCreate3Deployer is used to deploy our create3 factories on all chains. This MUST only be done once with nonce 0. - RoleCreate3Deployer Role = "create3-deployer" - // RoleDeployer is used to deploy official iliad contracts on all chains. - RoleDeployer Role = "deployer" - // RoleAdmin is used to manage the iliad contracts on all chains. It has admin privileges on official iliad contracts. - RoleAdmin Role = "admin" - // RoleTester is used for general tasks and testing in non-mainnet networks. - RoleTester Role = "tester" -) - -func AllRoles() []Role { - return []Role{ - RoleCreate3Deployer, - RoleDeployer, - RoleAdmin, - RoleTester, - } -} - -func (r Role) Verify() error { - for _, role := range AllRoles() { - if r == role { - return nil - } - } - - return errors.New("invalid role", "role", r) -} - -type Type string - -const ( - TypeRemote Type = "remote" // stored in (fireblocks) accessible via API to sign - TypeSecret Type = "secret" // stored in GCP can be downloaded to disk - TypeWellKnown Type = "well-known" // well-known eoa private keys in the repo -) - -// Account defines a EOA account used within the iliad network. -type Account struct { - Type Type - Role Role - Address common.Address - privateKey *ecdsa.PrivateKey // only for devnet (well-known type) -} - -// privKey returns the private key for the account. -func (a Account) privKey() *ecdsa.PrivateKey { - return a.privateKey -} - -// MustAddress returns the address for the EOA identified by the network and role. -func MustAddress(network netconf.ID, role Role) common.Address { - resp, ok := Address(network, role) - if !ok { - panic(errors.New("eoa address not defined", "network", network, "role", role)) - } - - return resp -} - -// Address returns the address for the EOA identified by the network and role. -func Address(network netconf.ID, role Role) (common.Address, bool) { - accounts, ok := statics[network] - if !ok { - return common.Address{}, false - } - - for _, account := range accounts { - if account.Role == role { - return account.Address, true - } - } - - return common.Address{}, false -} - -// PrivateKey returns the private key for the EOA identified by the network and role. -func PrivateKey(ctx context.Context, network netconf.ID, role Role) (*ecdsa.PrivateKey, error) { - acc, ok := AccountForRole(network, role) - if !ok { - return nil, errors.New("eoa key not defined", "network", network, "role", role) - } - if acc.Type == TypeWellKnown { - return acc.privKey(), nil - } else if acc.Type == TypeRemote { - return nil, errors.New("private key not available for remote keys", "network", network, "role", role) - } - - k, err := key.Download(ctx, network, string(role), key.EOA, acc.Address.Hex()) - if err != nil { - return nil, errors.Wrap(err, "download key") - } - - return k.ECDSA() -} - -// AccountForRole returns the account for the network and role. -func AccountForRole(network netconf.ID, role Role) (Account, bool) { - accounts, ok := statics[network] - if !ok { - return Account{}, false - } - for _, account := range accounts { - if account.Role == role { - return account, true - } - } - - return Account{}, false -} - -// MustAddresses returns the addresses for the network and roles. -func MustAddresses(network netconf.ID, roles ...Role) []common.Address { - accounts := statics[network] - var addresses []common.Address - for _, role := range roles { - for _, account := range accounts { - if account.Role == role { - addresses = append(addresses, account.Address) - } - } - } - - return addresses -} - -// AllAccounts returns all accounts for the network. -func AllAccounts(network netconf.ID) []Account { - return statics[network] -} diff --git a/e2e/app/eoa/fund.go b/e2e/app/eoa/fund.go deleted file mode 100644 index 181bd9fb..00000000 --- a/e2e/app/eoa/fund.go +++ /dev/null @@ -1,87 +0,0 @@ -//nolint:mnd,unused // suppress linter for this file -package eoa - -import ( - "math/big" - - "cosmossdk.io/math" - - "github.com/ethereum/go-ethereum/params" - - "github.com/piplabs/story/lib/netconf" -) - -var ( - // thresholdTiny is used for EOAs which are rarely used, mostly to deploy a handful of contracts per network. - thresholdTiny = FundThresholds{ - minEther: 0.001, - targetEther: 0.01, - } - - // thresholdSmall is used by EOAs that deploy contracts or perform actions a couple times per week/month. - //nolint:unused // Might be used in the future. - thresholdSmall = FundThresholds{ - minEther: 0.1, - targetEther: 1.0, - } - - // thresholdMedium is used by EOAs that regularly perform actions and need enough balance - // to last a weekend without topping up even if fees are spiking. - thresholdMedium = FundThresholds{ - minEther: 0.5, - targetEther: 5, - } - - // thresholdLarge is used by EOAs that constantly perform actions and need enough balance - // to last a weekend without topping up even if fees are spiking. - thresholdLarge = FundThresholds{ - minEther: 5, - targetEther: 20, // TODO: Increase along with e2e/app#saneMaxEther - } - - defaultThresholdsByRole = map[Role]FundThresholds{ - RoleCreate3Deployer: thresholdTiny, // Only 1 contract per chain - RoleAdmin: thresholdTiny, // Rarely used - RoleDeployer: thresholdTiny, // Protected chains are only deployed once - RoleTester: thresholdLarge, // Tester funds pingpongs, validator updates, etc. - } - - ephemeralOverrides = map[Role]FundThresholds{ - RoleDeployer: thresholdMedium, // Ephemeral chains are deployed often and fees can spike by a lot - } -) - -func GetFundThresholds(network netconf.ID, role Role) (FundThresholds, bool) { - if network.IsEphemeral() { - if resp, ok := ephemeralOverrides[role]; ok { - return resp, true - } - } - - resp, ok := defaultThresholdsByRole[role] - - return resp, ok -} - -type FundThresholds struct { - minEther float64 - targetEther float64 -} - -func (t FundThresholds) MinBalance() *big.Int { - gwei := t.minEther * params.GWei - if gwei < 1 { - panic("ether float64 must be greater than 1 Gwei") - } - - return math.NewInt(params.GWei).MulRaw(int64(gwei)).BigInt() -} - -func (t FundThresholds) TargetBalance() *big.Int { - gwei := t.targetEther * params.GWei - if gwei < 1 { - panic("ether float64 must be greater than 1 Gwei") - } - - return math.NewInt(params.GWei).MulRaw(int64(gwei)).BigInt() -} diff --git a/e2e/app/eoa/helpers.go b/e2e/app/eoa/helpers.go deleted file mode 100644 index acdf534d..00000000 --- a/e2e/app/eoa/helpers.go +++ /dev/null @@ -1,76 +0,0 @@ -package eoa - -import ( - "crypto/ecdsa" - - "github.com/ethereum/go-ethereum/common" - ethcrypto "github.com/ethereum/go-ethereum/crypto" -) - -// Funder returns the address of the funder account. -func Funder() common.Address { - return common.HexToAddress(fbFunder) -} - -func dummy(roles ...Role) []Account { - var resp []Account - for _, role := range roles { - resp = append(resp, Account{ - Type: TypeWellKnown, - Role: role, - Address: common.HexToAddress(ZeroXDead), - }) - } - - return resp -} - -func remote(hex string, roles ...Role) []Account { - var resp []Account - for _, role := range roles { - resp = append(resp, Account{ - Type: TypeRemote, - Role: role, - Address: common.HexToAddress(hex), - }) - } - - return resp -} - -func wellKnown(pk *ecdsa.PrivateKey, roles ...Role) []Account { - var resp []Account - for _, role := range roles { - resp = append(resp, Account{ - Type: TypeWellKnown, - Role: role, - Address: ethcrypto.PubkeyToAddress(pk.PublicKey), - privateKey: pk, - }) - } - - return resp -} - -//nolint:unused // Will be used later -func secret(hex string, roles ...Role) []Account { - var resp []Account - for _, role := range roles { - resp = append(resp, Account{ - Type: TypeSecret, - Role: role, - Address: common.HexToAddress(hex), - }) - } - - return resp -} - -func flatten[T any](slices ...[]T) []T { - var resp []T - for _, slice := range slices { - resp = append(resp, slice...) - } - - return resp -} diff --git a/e2e/app/eoa/static.go b/e2e/app/eoa/static.go deleted file mode 100644 index 9440fe2b..00000000 --- a/e2e/app/eoa/static.go +++ /dev/null @@ -1,31 +0,0 @@ -package eoa - -import ( - "github.com/piplabs/story/lib/anvil" - "github.com/piplabs/story/lib/netconf" -) - -const ( - // fbFunder is the address of the fireblocks "funder" account. - fbFunder = "0xf63316AA39fEc9D2109AB0D9c7B1eE3a6F60AEA4" - fbDev = "0x7a6cF389082dc698285474976d7C75CAdE08ab7e" - ZeroXDead = "0x000000000000000000000000000000000000dead" -) - -//nolint:gochecknoglobals // Static mappings. -var statics = map[netconf.ID][]Account{ - netconf.Devnet: flatten( - wellKnown(anvil.DevPrivateKey0(), RoleTester, RoleCreate3Deployer, RoleDeployer), - ), - netconf.Staging: flatten( - remote("0xC8103859Ac7CB547d70307EdeF1A2319FC305fdC", RoleCreate3Deployer), - remote("0x274c4B3e5d27A65196d63964532366872F81D261", RoleDeployer), - ), - netconf.Testnet: flatten( - remote("0xeC5134556da0797A5C5cD51DD622b689Cac97Fe9", RoleCreate3Deployer), - remote("0x0CdCc644158b7D03f40197f55454dc7a11Bd92c1", RoleDeployer), - ), - netconf.Mainnet: flatten( - dummy(RoleAdmin, RoleCreate3Deployer, RoleDeployer, RoleTester), - ), -} diff --git a/e2e/app/fund.go b/e2e/app/fund.go deleted file mode 100644 index a236c1b8..00000000 --- a/e2e/app/fund.go +++ /dev/null @@ -1,186 +0,0 @@ -package app - -import ( - "context" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/params" - - "github.com/piplabs/story/e2e/app/eoa" - "github.com/piplabs/story/lib/anvil" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/log" - "github.com/piplabs/story/lib/netconf" - "github.com/piplabs/story/lib/txmgr" -) - -const saneMaxEther = 20 // Maximum amount to fund in ether. // TODO(corver): Increase this. - -// noAnvilDev returns a list of accounts that are not dev anvil accounts. -func noAnvilDev(accounts []common.Address) []common.Address { - var nonDevAccounts []common.Address - for _, account := range accounts { - if !anvil.IsDevAccount(account) { - nonDevAccounts = append(nonDevAccounts, account) - } - } - - return nonDevAccounts -} - -// accountsToFund returns a list of accounts to fund on anvil chains, based on the network. -func accountsToFund(network netconf.ID) []common.Address { - switch network { - case netconf.Staging: - return eoa.MustAddresses(netconf.Staging, eoa.AllRoles()...) - case netconf.Devnet: - return eoa.MustAddresses(netconf.Devnet, eoa.AllRoles()...) - default: - return []common.Address{} - } -} - -// fundAccounts funds the EOAs that need funding (just on anvil chains, for now). -func fundAccounts(ctx context.Context, def Definition) error { - accounts := accountsToFund(def.Testnet.Network) - eth100 := new(big.Int).Mul(big.NewInt(params.Ether), big.NewInt(100)) - for _, chain := range def.Testnet.AnvilChains { - if err := anvil.FundAccounts(ctx, chain.ExternalRPC, eth100, noAnvilDev(accounts)...); err != nil { - return errors.Wrap(err, "fund anvil account") - } - } - - return nil -} - -// FundEOAAccounts funds the EOAs that need funding to their target balance. -func FundEOAAccounts(ctx context.Context, def Definition, dryRun bool) error { - if def.Testnet.Network == netconf.Mainnet { - return errors.New("mainnet funding not supported yet") - } - - network := networkFromDef(def) - accounts := eoa.AllAccounts(network.ID) - - log.Info(ctx, "Checking accounts to fund", "network", network.ID, "count", len(accounts)) - - for _, chain := range network.EVMChains() { - backend, err := def.Backends().Backend(chain.ID) - if err != nil { - return errors.Wrap(err, "backend") - } - - funder := eoa.Funder() - funderBal, err := backend.BalanceAt(ctx, funder, nil) - if err != nil { - return err - } - - log.Info(ctx, "Funder balance", - "chain", chain.Name, - "funder", funder, - "balance", etherStr(funderBal), - ) - - for _, account := range accounts { - accCtx := log.WithCtx(ctx, - "chain", chain.Name, - "role", account.Role, - "address", account.Address, - "type", account.Type, - ) - - if account.Address == common.HexToAddress(eoa.ZeroXDead) { - log.Info(accCtx, "Skipping 0xdead account") - continue - } else if account.Type == eoa.TypeWellKnown { - log.Info(accCtx, "Skipping well-known anvil account") - continue - } - - thresholds, ok := eoa.GetFundThresholds(network.ID, account.Role) - if !ok { - log.Warn(accCtx, "Skipping account without fund thresholds", nil) - continue - } - - balance, err := backend.BalanceAt(accCtx, account.Address, nil) - if err != nil { - log.Warn(accCtx, "Failed fetching balance, skipping", err) - continue - } else if thresholds.MinBalance().Cmp(balance) < 0 { - log.Info(accCtx, - "Not funding account, balance sufficient", - "balance", etherStr(balance), - "min_balance", etherStr(thresholds.MinBalance()), - ) - - continue - } - - saneMax := new(big.Int).Mul(big.NewInt(saneMaxEther), big.NewInt(params.Ether)) - - amount := new(big.Int).Sub(thresholds.TargetBalance(), balance) - if amount.Cmp(big.NewInt(0)) <= 0 { - return errors.New("unexpected negative amount [BUG]") // Target balance below minimum balance - } else if amount.Cmp(saneMax) > 0 { - log.Warn(accCtx, "Funding amount exceeds sane max, skipping", nil, - "amount", etherStr(amount), - "max", etherStr(saneMax), - ) - - continue - } else if amount.Cmp(funderBal) >= 0 { - return errors.New("funder balance too low", - "amount", etherStr(amount), - "funder", etherStr(funderBal), - ) - } - - log.Info(accCtx, "Funding account", - "amount", etherStr(amount), - "balance", etherStr(balance), - "target_balance", etherStr(thresholds.TargetBalance()), - ) - - if dryRun { - log.Warn(accCtx, "Skipping actual funding tx due to dry-run", nil) - continue - } - - tx, rec, err := backend.Send(accCtx, eoa.Funder(), txmgr.TxCandidate{ - To: &account.Address, - GasLimit: 0, - Value: amount, - }) - if err != nil { - return errors.Wrap(err, "send tx") - } else if rec.Status != types.ReceiptStatusSuccessful { - return errors.New("funding tx failed", "tx", tx.Hash()) - } - - b, err := backend.BalanceAt(accCtx, account.Address, nil) - if err != nil { - return errors.Wrap(err, "get balance") - } - - log.Info(accCtx, "Account funded 🎉", - "amount_funded", etherStr(amount), - "resulting_balance", etherStr(b), - "tx", tx.Hash().Hex(), - ) - } - } - - return nil -} - -func etherStr(amount *big.Int) string { - b, _ := amount.Float64() - b /= params.Ether - - return fmt.Sprintf("%.4f", b) -} diff --git a/e2e/app/geth/config.go b/e2e/app/geth/config.go deleted file mode 100644 index e91a641d..00000000 --- a/e2e/app/geth/config.go +++ /dev/null @@ -1,127 +0,0 @@ -package geth - -import ( - "encoding/hex" - "encoding/json" - "os" - "path/filepath" - "time" - - "github.com/ethereum/go-ethereum/core" - ethcrypto "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/downloader" - - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/lib/errors" -) - -// WriteAllConfig writes all the geth config files for all iliadEVMs. -func WriteAllConfig(testnet types.Testnet, genesis core.Genesis) error { - gethGenesisBz, err := json.MarshalIndent(genesis, "", " ") - if err != nil { - return errors.Wrap(err, "marshal genesis") - } - - gethConfigFiles := func(evm types.IliadEVM) map[string][]byte { - return map[string][]byte{ - "genesis.json": gethGenesisBz, - "geth/nodekey": []byte(hex.EncodeToString(ethcrypto.FromECDSA(evm.NodeKey))), // Nodekey is hex encoded - "geth/jwtsecret": []byte(evm.JWTSecret), - } - } - - for _, evm := range testnet.IliadEVMs { - for file, data := range gethConfigFiles(evm) { - path := filepath.Join(testnet.Dir, evm.InstanceName, file) - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - return errors.Wrap(err, "mkdir", "path", path) - } - if err := os.WriteFile(path, data, 0o644); err != nil { - return errors.Wrap(err, "write geth config") - } - } - - conf := Config{ - Moniker: evm.InstanceName, - IsArchive: evm.IsArchive, - ChainID: evm.Chain.ChainID, - BootNodes: evm.Peers, // TODO: Use seed nodes once available. - TrustedNodes: evm.Peers, - } - if err := WriteConfigTOML(conf, filepath.Join(testnet.Dir, evm.InstanceName, "config.toml")); err != nil { - return errors.Wrap(err, "write geth config") - } - } - - return nil -} - -// WriteConfigTOML writes the geth config to a toml file. -func WriteConfigTOML(conf Config, path string) error { - bz, err := tomlSettings.Marshal(MakeGethConfig(conf)) - if err != nil { - return errors.Wrap(err, "marshal toml") - } - - if err := os.WriteFile(path, bz, 0o644); err != nil { - return errors.Wrap(err, "write toml") - } - - return nil -} - -// MakeGethConfig returns the full iliad geth config for the provided custom config. -func MakeGethConfig(conf Config) FullConfig { - cfg := defaultGethConfig() - cfg.Eth.NetworkId = conf.ChainID - cfg.Node.DataDir = "/geth" // Mount inside docker container - cfg.Node.IPCPath = "/geth/geth.ipc" - - // Use syncmode=full. Since default "snap" sync has race condition on startup. Where engineAPI newPayload fails - // if snapsync has not completed. Should probably wait for snapsync to complete before starting engineAPI? - cfg.Eth.SyncMode = downloader.FullSync - - // Disable pruning for archive nodes. - // Note that runtime flags are also required for archive nodes, specifically: - // --gcmode==archive - // --state.scheme=hash - // This will be deprecated once new state.scheme=path support archive nodes. - // See https://blog.ethereum.org/2023/09/12/geth-v1-13-0. - cfg.Eth.NoPruning = conf.IsArchive - cfg.Eth.Preimages = conf.IsArchive // Geth auto-enables this when NoPruning is set. - - // Ethereum has slow block building times (2~4s), but we need fast times (<1s). - // Use 500ms so blocks are built in less than 1s. - cfg.Eth.Miner.Recommit = 500 * time.Millisecond - - // Set the bootnodes and trusted nodes. - cfg.Node.P2P.Name = conf.Moniker - cfg.Node.P2P.DiscoveryV4 = true // TODO: Switch to v5. - cfg.Node.P2P.BootstrapNodesV5 = conf.BootNodes - cfg.Node.P2P.BootstrapNodes = conf.BootNodes - cfg.Node.P2P.TrustedNodes = conf.TrustedNodes - - // Bind listen addresses to all interfaces inside the container. - const allInterfaces = "0.0.0.0" - cfg.Node.AuthAddr = allInterfaces - cfg.Node.HTTPHost = allInterfaces - cfg.Node.WSHost = allInterfaces - cfg.Node.P2P.ListenAddr = allInterfaces + ":30303" - - // Add eth module - cfg.Node.HTTPModules = append(cfg.Node.HTTPModules, "eth") - cfg.Node.WSModules = append(cfg.Node.WSModules, "eth") - - if conf.IsArchive { - cfg.Node.HTTPModules = append(cfg.Node.HTTPModules, "debug") - cfg.Node.WSModules = append(cfg.Node.WSModules, "debug") - } - - // Allow all incoming connections. - cfg.Node.HTTPVirtualHosts = []string{"*"} - cfg.Node.AuthVirtualHosts = []string{"*"} - cfg.Node.WSOrigins = []string{"*"} - cfg.Node.HTTPCors = []string{"*"} - - return cfg -} diff --git a/e2e/app/geth/config_internal_test.go b/e2e/app/geth/config_internal_test.go deleted file mode 100644 index 293493c0..00000000 --- a/e2e/app/geth/config_internal_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package geth - -import ( - "encoding/json" - "net" - "os" - "os/exec" - "path/filepath" - "testing" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/lib/tutil" -) - -//go:generate go test . -golden -clean - -func TestWriteConfigTOML(t *testing.T) { - t.Parallel() - - testKey, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8") - node1 := enode.NewV4(&testKey.PublicKey, net.IP{127, 0, 0, 1}, 1, 1) - node2 := enode.NewV4(&testKey.PublicKey, net.IP{127, 0, 0, 2}, 2, 2) - - tests := map[string]bool{ - "archive": true, - "full": false, - } - for name, isArchive := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - - data := Config{ - Moniker: name, - BootNodes: []*enode.Node{node1}, - TrustedNodes: []*enode.Node{node1, node2}, - ChainID: 15651, - IsArchive: isArchive, - } - - tempFile := filepath.Join(t.TempDir(), name+".toml") - - err := WriteConfigTOML(data, tempFile) - require.NoError(t, err) - - bz, err := os.ReadFile(tempFile) - require.NoError(t, err) - - tutil.RequireGoldenBytes(t, bz) - - // Compare our generated config against the output of `geth dumpconfig` with this as the base config. - // Geth does some custom config parsing/sanitizing/updating of the config, so we ensure our config doesn't - // get silently updated by geth. - // See https://github.com/ethereum/go-ethereum/blob/master/cmd/utils/flags.go#L1640 - // result := gethDumpConfigToml(t, MakeGethConfig(data)) - // require.Equal(t, string(bz), string(result)) - }) - } -} - -// TestGethVersion checks if the geth version is up to date. -func TestGethVersion(t *testing.T) { - t.Parallel() - - out, err := exec.Command("go", "list", "-json", "github.com/ethereum/go-ethereum").CombinedOutput() - require.NoError(t, err) - - resp := struct { - Module struct { - Version string `json:"version"` - } `json:"module"` - }{} - err = json.Unmarshal(out, &resp) - require.NoError(t, err) - - require.Equal(t, Version, resp.Module.Version, "A different geth has been released, update `geth.Version`") -} - -// gethDumpConfigToml executes `geth dumpconfig` using the provided base config and -// returns the resulting toml config file content. -// func gethDumpConfigToml(t *testing.T, baseCfg FullConfig) []byte { -// t.Helper() - -// bz, err := tomlSettings.Marshal(baseCfg) -// require.NoError(t, err) - -// baseFile := filepath.Join(t.TempDir(), "base.toml") -// err = os.WriteFile(baseFile, bz, 0o644) -// require.NoError(t, err) - -// var stdout, stderr bytes.Buffer -// cmd := exec.Command("docker", "run", -// fmt.Sprintf("--volume=%s:/tmp/config.toml", baseFile), -// fmt.Sprintf("ethereum/client-go:%s", Version), -// "dumpconfig", -// "--config=/tmp/config.toml") -// cmd.Stdout = &stdout -// cmd.Stderr = &stderr - -// t.Logf("geth dumpconfig logs:\n%s", stderr.String()) - -// return stdout.Bytes() -// } diff --git a/e2e/app/geth/testdata/TestWriteConfigTOML_archive.golden b/e2e/app/geth/testdata/TestWriteConfigTOML_archive.golden deleted file mode 100644 index 95d50e06..00000000 --- a/e2e/app/geth/testdata/TestWriteConfigTOML_archive.golden +++ /dev/null @@ -1,105 +0,0 @@ -[Eth] -NetworkId = 15651 -SyncMode = "full" -EthDiscoveryURLs = [] -SnapDiscoveryURLs = [] -NoPruning = true -NoPrefetch = false -TxLookupLimit = 2350000 -TransactionHistory = 2350000 -StateHistory = 90000 -LightPeers = 100 -DatabaseCache = 512 -DatabaseFreezer = "" -TrieCleanCache = 154 -TrieDirtyCache = 256 -TrieTimeout = 3600000000000 -SnapshotCache = 102 -Preimages = true -FilterLogCacheSize = 32 -EnablePreimageRecording = false -VMTrace = "" -VMTraceJsonConfig = "" -RPCGasCap = 50000000 -RPCEVMTimeout = 5000000000 -RPCTxFeeCap = 1e+00 - -[Eth.Miner] -GasCeil = 30000000 -GasPrice = 1000000 -Recommit = 500000000 - -[Eth.TxPool] -Locals = [] -NoLocals = false -Journal = "transactions.rlp" -Rejournal = 3600000000000 -PriceLimit = 1 -PriceBump = 10 -AccountSlots = 16 -GlobalSlots = 5120 -AccountQueue = 64 -GlobalQueue = 1024 -Lifetime = 10800000000000 - -[Eth.BlobPool] -Datadir = "blobpool" -Datacap = 2684354560 -PriceBump = 100 - -[Eth.GPO] -Blocks = 20 -Percentile = 60 -MaxHeaderHistory = 1024 -MaxBlockHistory = 1024 -MaxPrice = 500000000000 -IgnorePrice = 2 - -[Node] -DataDir = "/geth" -IPCPath = "/geth/geth.ipc" -HTTPHost = "0.0.0.0" -HTTPPort = 8545 -HTTPCors = ["*"] -HTTPVirtualHosts = ["*"] -HTTPModules = ["net", "web3", "eth", "debug"] -AuthAddr = "0.0.0.0" -AuthPort = 8551 -AuthVirtualHosts = ["*"] -WSHost = "0.0.0.0" -WSPort = 8546 -WSOrigins = ["*"] -WSModules = ["net", "web3", "eth", "debug"] -GraphQLVirtualHosts = ["localhost"] -BatchRequestLimit = 1000 -BatchResponseMaxSize = 25000000 - -[Node.P2P] -MaxPeers = 50 -NoDiscovery = false -DiscoveryV4 = true -BootstrapNodes = ["enode://3a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d8072e77939dc03ba44790779b7a1025baf3003f6732430e20cd9b76d953391b3@127.0.0.1:1"] -BootstrapNodesV5 = ["enode://3a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d8072e77939dc03ba44790779b7a1025baf3003f6732430e20cd9b76d953391b3@127.0.0.1:1"] -StaticNodes = [] -TrustedNodes = ["enode://3a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d8072e77939dc03ba44790779b7a1025baf3003f6732430e20cd9b76d953391b3@127.0.0.1:1", "enode://3a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d8072e77939dc03ba44790779b7a1025baf3003f6732430e20cd9b76d953391b3@127.0.0.2:2"] -ListenAddr = "0.0.0.0:30303" -DiscAddr = "" -EnableMsgEvents = false - -[Node.HTTPTimeouts] -ReadTimeout = 30000000000 -ReadHeaderTimeout = 30000000000 -WriteTimeout = 30000000000 -IdleTimeout = 120000000000 - -[Metrics] -HTTP = "127.0.0.1" -Port = 6060 -InfluxDBEndpoint = "http://localhost:8086" -InfluxDBDatabase = "geth" -InfluxDBUsername = "test" -InfluxDBPassword = "test" -InfluxDBTags = "host=localhost" -InfluxDBToken = "test" -InfluxDBBucket = "geth" -InfluxDBOrganization = "geth" diff --git a/e2e/app/geth/testdata/TestWriteConfigTOML_full.golden b/e2e/app/geth/testdata/TestWriteConfigTOML_full.golden deleted file mode 100644 index 9f30dc73..00000000 --- a/e2e/app/geth/testdata/TestWriteConfigTOML_full.golden +++ /dev/null @@ -1,105 +0,0 @@ -[Eth] -NetworkId = 15651 -SyncMode = "full" -EthDiscoveryURLs = [] -SnapDiscoveryURLs = [] -NoPruning = false -NoPrefetch = false -TxLookupLimit = 2350000 -TransactionHistory = 2350000 -StateHistory = 90000 -LightPeers = 100 -DatabaseCache = 512 -DatabaseFreezer = "" -TrieCleanCache = 154 -TrieDirtyCache = 256 -TrieTimeout = 3600000000000 -SnapshotCache = 102 -Preimages = false -FilterLogCacheSize = 32 -EnablePreimageRecording = false -VMTrace = "" -VMTraceJsonConfig = "" -RPCGasCap = 50000000 -RPCEVMTimeout = 5000000000 -RPCTxFeeCap = 1e+00 - -[Eth.Miner] -GasCeil = 30000000 -GasPrice = 1000000 -Recommit = 500000000 - -[Eth.TxPool] -Locals = [] -NoLocals = false -Journal = "transactions.rlp" -Rejournal = 3600000000000 -PriceLimit = 1 -PriceBump = 10 -AccountSlots = 16 -GlobalSlots = 5120 -AccountQueue = 64 -GlobalQueue = 1024 -Lifetime = 10800000000000 - -[Eth.BlobPool] -Datadir = "blobpool" -Datacap = 2684354560 -PriceBump = 100 - -[Eth.GPO] -Blocks = 20 -Percentile = 60 -MaxHeaderHistory = 1024 -MaxBlockHistory = 1024 -MaxPrice = 500000000000 -IgnorePrice = 2 - -[Node] -DataDir = "/geth" -IPCPath = "/geth/geth.ipc" -HTTPHost = "0.0.0.0" -HTTPPort = 8545 -HTTPCors = ["*"] -HTTPVirtualHosts = ["*"] -HTTPModules = ["net", "web3", "eth"] -AuthAddr = "0.0.0.0" -AuthPort = 8551 -AuthVirtualHosts = ["*"] -WSHost = "0.0.0.0" -WSPort = 8546 -WSOrigins = ["*"] -WSModules = ["net", "web3", "eth"] -GraphQLVirtualHosts = ["localhost"] -BatchRequestLimit = 1000 -BatchResponseMaxSize = 25000000 - -[Node.P2P] -MaxPeers = 50 -NoDiscovery = false -DiscoveryV4 = true -BootstrapNodes = ["enode://3a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d8072e77939dc03ba44790779b7a1025baf3003f6732430e20cd9b76d953391b3@127.0.0.1:1"] -BootstrapNodesV5 = ["enode://3a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d8072e77939dc03ba44790779b7a1025baf3003f6732430e20cd9b76d953391b3@127.0.0.1:1"] -StaticNodes = [] -TrustedNodes = ["enode://3a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d8072e77939dc03ba44790779b7a1025baf3003f6732430e20cd9b76d953391b3@127.0.0.1:1", "enode://3a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d8072e77939dc03ba44790779b7a1025baf3003f6732430e20cd9b76d953391b3@127.0.0.2:2"] -ListenAddr = "0.0.0.0:30303" -DiscAddr = "" -EnableMsgEvents = false - -[Node.HTTPTimeouts] -ReadTimeout = 30000000000 -ReadHeaderTimeout = 30000000000 -WriteTimeout = 30000000000 -IdleTimeout = 120000000000 - -[Metrics] -HTTP = "127.0.0.1" -Port = 6060 -InfluxDBEndpoint = "http://localhost:8086" -InfluxDBDatabase = "geth" -InfluxDBUsername = "test" -InfluxDBPassword = "test" -InfluxDBTags = "host=localhost" -InfluxDBToken = "test" -InfluxDBBucket = "geth" -InfluxDBOrganization = "geth" diff --git a/e2e/app/geth/types.go b/e2e/app/geth/types.go deleted file mode 100644 index 5d7228ef..00000000 --- a/e2e/app/geth/types.go +++ /dev/null @@ -1,61 +0,0 @@ -package geth - -import ( - "reflect" - - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/naoina/toml" - - "github.com/piplabs/story/lib/errors" -) - -// Version defines the geth version deployed to all networks. -const Version = "v1.14.5" - -// Config is the configurable options for the standard iliad geth config. -type Config struct { - // Moniker is the p2p node name. - Moniker string - // ChainID is the chain ID of the network. - ChainID uint64 - // IsArchive defines whether the node should run in archive mode. - IsArchive bool - // BootNodes are the enode URLs of the P2P bootstrap nodes. - BootNodes []*enode.Node - // TrustedNodes are the enode URLs of the P2P trusted nodes. - TrustedNodes []*enode.Node -} - -// defaultGethConfig returns the default geth config. -func defaultGethConfig() FullConfig { - return FullConfig{ - Eth: ethconfig.Defaults, - Node: node.DefaultConfig, - Metrics: metrics.DefaultConfig, // Enable prometheus metrics via command line flags --metrics --pprof --pprof.addr=0.0.0.0 - } -} - -// FullConfig is the go struct representation of the geth.toml config file. -// Copied from https://github.com/ethereum/go-ethereum/blob/master/cmd/geth/config.go#L95 -type FullConfig struct { - Eth ethconfig.Config - Node node.Config - Metrics metrics.Config -} - -// tomlSettings is the toml settings used to parse/format the geth.toml config file. -// Copied from https://github.com/ethereum/go-ethereum/blob/master/cmd/geth/config.go#L70. -var tomlSettings = toml.Config{ - NormFieldName: func(_ reflect.Type, key string) string { - return key - }, - FieldToKey: func(_ reflect.Type, field string) string { - return field - }, - MissingField: func(rt reflect.Type, field string) error { - return errors.New("field not defined", "field", field, "type", rt.String()) - }, -} diff --git a/e2e/app/key/gcp.go b/e2e/app/key/gcp.go deleted file mode 100644 index 4d74d241..00000000 --- a/e2e/app/key/gcp.go +++ /dev/null @@ -1,71 +0,0 @@ -package key - -import ( - "bytes" - "context" - "encoding/base64" - "encoding/json" - "os/exec" - - "github.com/piplabs/story/lib/errors" -) - -// createGCPSecret creates a new GCP Secret Manager secret. -func createGCPSecret(ctx context.Context, name string, value []byte, labels map[string]string) error { - // Init the gcloud command to create a secret - cmd := exec.CommandContext(ctx, "gcloud", "secrets", "create", name, "--data-file=-") - - // Add labels to the command - for k, v := range labels { - cmd.Args = append(cmd.Args, "--labels="+k+"="+v) - } - - // Set the secret value as the input for the command - cmd.Stdin = bytes.NewReader(value) - - // Execute the command - out, err := cmd.CombinedOutput() - if err != nil { - return errors.Wrap(err, "gcloud create secret", "out", string(out)) - } - - return nil -} - -// getGCPSecret fetches the latest version of the specified secret. -func getGCPSecret(ctx context.Context, name string) ([]byte, error) { - // Init the gcloud command to access the secret - cmd := exec.CommandContext(ctx, "gcloud", "secrets", "versions", "access", "latest", "--secret", name, "--format=json") - - var stdOut, stdErr bytes.Buffer - cmd.Stderr = &stdErr - cmd.Stdout = &stdOut - - // Execute the command and capture the output - if err := cmd.Run(); err != nil { - return nil, errors.Wrap(err, "gcloud fetch secret", "out", stdErr.String()) - } - - // Unmarshal the json response - var resp response - if err := json.Unmarshal(stdOut.Bytes(), &resp); err != nil { - return nil, errors.Wrap(err, "unmarshal secret response") - } - - // Decode the base64 encoded secret data - bz, err := base64.URLEncoding.DecodeString(resp.Payload.DataBase64) - if err != nil { - return nil, errors.Wrap(err, "decode secret data") - } - - return bz, nil -} - -// response from gloud secret manager --format=json. -type response struct { - Name string `json:"name"` - Payload struct { - DataBase64 string `json:"data"` - DataCRC32c string `json:"dataCrc32c"` - } `json:"payload"` -} diff --git a/e2e/app/key/key.go b/e2e/app/key/key.go deleted file mode 100644 index 2a5e21d4..00000000 --- a/e2e/app/key/key.go +++ /dev/null @@ -1,144 +0,0 @@ -package key - -import ( - "crypto/ecdsa" - - "github.com/cometbft/cometbft/crypto" - ed "github.com/cometbft/cometbft/crypto/ed25519" - k1 "github.com/cometbft/cometbft/crypto/secp256k1" - ethcrypto "github.com/ethereum/go-ethereum/crypto" - - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/k1util" - "github.com/piplabs/story/lib/netconf" -) - -// Type represents the type of cryptographic key. -type Type string - -const ( - Validator Type = "validator" - P2PConsensus Type = "p2p_consensus" - P2PExecution Type = "p2p_execution" - EOA Type = "eoa" -) - -func (t Type) Verify() error { - if t == "" { - return errors.New("empty key type") - } - switch t { - case Validator, P2PConsensus, P2PExecution, EOA: - return nil - default: - return errors.New("invalid key type") - } -} - -func (t Type) String() string { - return string(t) -} - -// Key wraps a cometBFT private key adding custom a address function. -type Key struct { - crypto.PrivKey -} - -// Addr returns the address of the key. -// K1 keys have ethereum 0x addresses, -// ED keys have the default SHA256-20 of the raw pubkey bytes. -func (k Key) Addr() (string, error) { - switch t := k.PrivKey.(type) { - case k1.PrivKey: - addr, err := k1util.PubKeyToAddress(t.PubKey()) - if err != nil { - return "", err - } - - return addr.Hex(), nil - case ed.PrivKey: - return t.PubKey().Address().String(), nil - default: - return "", errors.New("unknown key type") - } -} - -// ECDSA returns the ECDSA representation of the k1 key. -// This returns an error for ed25519 keys. -func (k Key) ECDSA() (*ecdsa.PrivateKey, error) { - switch t := k.PrivKey.(type) { - case k1.PrivKey: - resp, err := ethcrypto.ToECDSA(t.Bytes()) - if err != nil { - return nil, errors.Wrap(err, "converting to ECDSA") - } - - return resp, nil - default: - return nil, errors.New("ed25519 keys do not have ECDSA representation") - } -} - -// Generate generates a cryptographic key of the specified type. -// It panics since it assumes that the type is valid. -func Generate(typ Type) Key { - switch typ { - case Validator, P2PExecution, EOA: - return Key{ - PrivKey: k1.GenPrivKey(), - } - - case P2PConsensus: - return Key{ - PrivKey: ed.GenPrivKey(), - } - default: - panic("invalid key type:" + typ) - } -} - -// GenerateInsecureDeterministic generates an insecure deterministic key of the specified type -// from the provided seed. -// NOTE THIS MUST ONLY BE USED FOR TESTING: It panics if network is not ephemeral. -func GenerateInsecureDeterministic(network netconf.ID, typ Type, seed string) Key { - if !network.IsEphemeral() { - panic("only ephemeral keys are supported") - } - - secret := []byte(string(typ) + "|" + seed) // Deterministic secret from typ+seed. - - switch typ { - case Validator, P2PExecution, EOA: - return Key{ - PrivKey: k1.GenPrivKeySecp256k1(secret), - } - - case P2PConsensus: - return Key{ - PrivKey: ed.GenPrivKeyFromSecret(secret), - } - default: - panic("invalid key type:" + typ) - } -} - -// FromBytes parses the given bytes into th eprovided key type. -func FromBytes(typ Type, b []byte) (Key, error) { - switch typ { - case Validator, P2PExecution, EOA: - if len(b) != k1.PrivKeySize { - return Key{}, errors.New("invalid key size") - } - - return Key{PrivKey: k1.PrivKey(b)}, nil - case P2PConsensus: - - if len(b) != ed.PrivateKeySize { - return Key{}, errors.New("invalid key size") - } - - return Key{PrivKey: ed.PrivKey(b)}, nil - default: - return Key{}, errors.New("invalid key type") - } -} diff --git a/e2e/app/key/key_internal_test.go b/e2e/app/key/key_internal_test.go deleted file mode 100644 index 1f1bc711..00000000 --- a/e2e/app/key/key_internal_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package key - -import ( - "context" - "os/exec" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/lib/netconf" -) - -func DeleteSecretForT(ctx context.Context, t *testing.T, network netconf.ID, name string, typ Type, addr string) { - t.Helper() - secret := secretName(network, name, typ, addr) - - out, err := exec.CommandContext(ctx, "gcloud", "secrets", "delete", secret, "--quiet").CombinedOutput() - require.NoError(t, err, string(out)) -} diff --git a/e2e/app/key/key_test.go b/e2e/app/key/key_test.go deleted file mode 100644 index 02c838d9..00000000 --- a/e2e/app/key/key_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package key_test - -import ( - "context" - "flag" - "fmt" - "testing" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/e2e/app/key" - "github.com/piplabs/story/lib/netconf" - "github.com/piplabs/story/lib/tutil" -) - -func TestKeys(t *testing.T) { - t.Parallel() - for _, typ := range []key.Type{key.Validator, key.P2PConsensus, key.P2PExecution, key.EOA} { - t.Run(typ.String(), func(t *testing.T) { - t.Parallel() - - key1 := key.Generate(typ) - - addrA, err := key1.Addr() - require.NoError(t, err) - - key2, err := key.FromBytes(typ, key1.Bytes()) - require.NoError(t, err) - - addrB, err := key2.Addr() - require.NoError(t, err) - require.Equal(t, addrA, addrB) - - require.True(t, key1.Equals(key2.PrivKey)) - - ecdsaKey, err := key1.ECDSA() - switch typ { - case key.Validator, key.P2PExecution, key.EOA: - require.NoError(t, err) - addrC := crypto.PubkeyToAddress(ecdsaKey.PublicKey) - require.Equal(t, addrA, addrC.Hex()) - case key.P2PConsensus: - require.Error(t, err) - } - }) - } -} - -var integration = flag.Bool("integration", false, "run integration tests") - -//go:generate go test . -integration -run=TestIntegration -v - -func TestIntegration(t *testing.T) { - t.Parallel() - if !*integration { - t.Skip("skipping integration tests") - } - - for _, typ := range []key.Type{key.Validator, key.P2PConsensus, key.P2PExecution} { - t.Run(typ.String(), func(t *testing.T) { - t.Parallel() - - ctx := context.Background() - network := netconf.Simnet - name := "deleteme" - - k, err := key.UploadNew(ctx, key.UploadConfig{ - Network: network, - Name: name, - Type: typ, - }) - require.NoError(t, err) - - addr, err := k.Addr() - require.NoError(t, err) - - k2, err := key.Download(ctx, network, name, typ, addr) - tutil.RequireNoError(t, err) - - require.True(t, k.Equals(k2.PrivKey)) - - key.DeleteSecretForT(ctx, t, network, name, typ, addr) - }) - } -} - -func TestGenInsecure(t *testing.T) { - t.Parallel() - - require.Panics(t, func() { - key.GenerateInsecureDeterministic(netconf.Testnet, key.EOA, "") - }) - - k1 := key.GenerateInsecureDeterministic(netconf.Devnet, key.EOA, "test1") - require.Equal(t, "d4c14667192a5966002361dd08cdd30619ac553928c423593d744dbb50ff232a", fmt.Sprintf("%x", k1.Bytes())) - - k2 := key.GenerateInsecureDeterministic(netconf.Devnet, key.P2PConsensus, "test2") - require.Equal(t, "4cf65aca4b199f5084b217b0728355b5ee93355c3f18324436b856337d60c07300e8fb91b5af3bc124e1b0f382a88377cb126b0989c5282959631596e4f8bc0b", fmt.Sprintf("%x", k2.Bytes())) -} diff --git a/e2e/app/key/upload.go b/e2e/app/key/upload.go deleted file mode 100644 index bd4086b4..00000000 --- a/e2e/app/key/upload.go +++ /dev/null @@ -1,71 +0,0 @@ -package key - -import ( - "context" - - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/log" - "github.com/piplabs/story/lib/netconf" -) - -// UploadConfig is the configuration for uploading a key. -type UploadConfig struct { - Network netconf.ID - Name string - Type Type -} - -// UploadNew generates a new key and uploads it to the gcp secret manager. -func UploadNew(ctx context.Context, cfg UploadConfig) (Key, error) { - if err := cfg.Network.Verify(); err != nil { - return Key{}, err - } - if err := cfg.Type.Verify(); err != nil { - return Key{}, err - } - - k := Generate(cfg.Type) - - addr, err := k.Addr() - if err != nil { - return Key{}, err - } - - // TODO: Delete or overwrite existing key with matching labels. - - secret := secretName(cfg.Network, cfg.Name, cfg.Type, addr) - if err := createGCPSecret(ctx, secret, k.Bytes(), nil); err != nil { - return Key{}, errors.Wrap(err, "upload key") - } - - log.Info(ctx, "🔐 Key uploaded: "+secret, "address", addr, "type", cfg.Type, "network", cfg.Network, "secret", cfg.Name) - - return k, nil -} - -// Download retrieves a key from the gcp secret manager. -func Download(ctx context.Context, network netconf.ID, name string, typ Type, addr string) (Key, error) { - bz, err := getGCPSecret(ctx, secretName(network, name, typ, addr)) - if err != nil { - return Key{}, errors.Wrap(err, "download key", "network", network, "name", name, "type", typ, "addr", addr) - } - - k, err := FromBytes(typ, bz) - if err != nil { - return Key{}, err - } - - actualAddr, err := k.Addr() - if err != nil { - return Key{}, err - } else if actualAddr != addr { - return Key{}, errors.New("unexpected key address") - } - - return k, nil -} - -// secretName returns the name of the secret in the gcp secret manager. -func secretName(network netconf.ID, name string, typ Type, addr string) string { - return network.String() + "-" + name + "-" + typ.String() + "-" + addr -} diff --git a/e2e/app/perturb.go b/e2e/app/perturb.go deleted file mode 100644 index 23a26d07..00000000 --- a/e2e/app/perturb.go +++ /dev/null @@ -1,126 +0,0 @@ -package app - -import ( - "context" - "time" - - rpctypes "github.com/cometbft/cometbft/rpc/core/types" - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - "github.com/cometbft/cometbft/test/e2e/pkg/infra/docker" - - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/log" -) - -// perturb the running testnet. -func perturb(ctx context.Context, testnet types.Testnet) error { - for _, node := range testnet.Nodes { - for _, perturbation := range node.Perturbations { - _, err := perturbNode(ctx, node, perturbation) - if err != nil { - return err - } - time.Sleep(3 * time.Second) // Give network some time to recover between each - } - } - - for service, purturbs := range testnet.Perturb { - for _, p := range purturbs { - if err := perturbService(ctx, service, testnet.Dir, p); err != nil { - return errors.Wrap(err, "purturb service", "service", service) - } - time.Sleep(3 * time.Second) // Give network some time to recover between each - } - } - - return nil -} - -// perturbService perturbs a docker service with a given perturbation. -func perturbService(ctx context.Context, service string, testnetDir string, perturb types.Perturb) error { - ctx = log.WithCtx(ctx, "service", service) - - log.Info(ctx, "Perturbing service", "perturb", perturb) - switch perturb { - case types.PerturbRestart: - if err := docker.ExecCompose(ctx, testnetDir, "restart", service); err != nil { - return errors.Wrap(err, "restart service") - } - case types.PerturbStopStart: - if err := docker.ExecCompose(ctx, testnetDir, "stop", service); err != nil { - return errors.Wrap(err, "stop service") - } - time.Sleep(5 * time.Second) - if err := docker.ExecCompose(ctx, testnetDir, "start", service); err != nil { - return errors.Wrap(err, "start service") - } - default: - return errors.New("unknown service perturbation") - } - - log.Info(ctx, "Perturbed service", "perturb", perturb) - - return nil -} - -// perturbNode perturbs a node with a given perturbation, returning its status -// after recovering. -func perturbNode(ctx context.Context, node *e2e.Node, perturbation e2e.Perturbation) (*rpctypes.ResultStatus, error) { - testnet := node.Testnet - name := node.Name - ctx = log.WithCtx(ctx, "name", name) - - switch perturbation { - case e2e.PerturbationDisconnect: - networkName := testnet.Name + "_" + testnet.Name - log.Info(ctx, "Perturb node: disconnect") - if err := docker.Exec(ctx, "network", "disconnect", networkName, name); err != nil { - return nil, errors.Wrap(err, "disconnect node from network") - } - time.Sleep(10 * time.Second) - if err := docker.Exec(ctx, "network", "connect", networkName, name); err != nil { - return nil, errors.Wrap(err, "connect node tp network") - } - - case e2e.PerturbationKill: - log.Info(ctx, "Perturb node: kill") - if err := docker.ExecCompose(ctx, testnet.Dir, "kill", "-s", "SIGKILL", name); err != nil { - return nil, errors.Wrap(err, "kill node") - } - if err := docker.ExecCompose(ctx, testnet.Dir, "start", name); err != nil { - return nil, errors.Wrap(err, "start node") - } - - case e2e.PerturbationPause: - log.Info(ctx, "Perturb node: pause") - if err := docker.ExecCompose(ctx, testnet.Dir, "pause", name); err != nil { - return nil, errors.Wrap(err, "pause node") - } - time.Sleep(10 * time.Second) - if err := docker.ExecCompose(ctx, testnet.Dir, "unpause", name); err != nil { - return nil, errors.Wrap(err, "unpause node") - } - - case e2e.PerturbationRestart: - log.Info(ctx, "Perturb node: restart") - if err := docker.ExecCompose(ctx, testnet.Dir, "restart", name); err != nil { - return nil, errors.Wrap(err, "restart node") - } - - case e2e.PerturbationUpgrade: - return nil, errors.New("upgrade perturbation not supported") - - default: - return nil, errors.New("unexpected perturbation type", "type", perturbation) - } - - status, err := waitForNode(ctx, node, 0, 20*time.Second) - if err != nil { - return nil, err - } - - log.Info(ctx, "Node recovered from perturbation", "height", status.SyncInfo.LatestBlockHeight) - - return status, nil -} diff --git a/e2e/app/rpc.go b/e2e/app/rpc.go deleted file mode 100644 index 4a2c08b4..00000000 --- a/e2e/app/rpc.go +++ /dev/null @@ -1,154 +0,0 @@ -package app - -import ( - "context" - "fmt" - "time" - - rpchttp "github.com/cometbft/cometbft/rpc/client/http" - rpctypes "github.com/cometbft/cometbft/rpc/core/types" - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - "github.com/cometbft/cometbft/types" - - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/log" -) - -// waitForHeight waits for the network to reach a certain height (or above), -// returning the highest height seen. Errors if the network is not making -// progress at all. -func waitForHeight(ctx context.Context, testnet *e2e.Testnet, height int64) (*types.Block, *types.BlockID, error) { - var ( - err error - maxResult *rpctypes.ResultBlock - clients = map[string]*rpchttp.HTTP{} - lastIncrease = time.Now() - ) - - currentBlock := func(ctx context.Context, client *rpchttp.HTTP) (*rpctypes.ResultBlock, error) { - ctx, cancel := context.WithTimeout(ctx, 1*time.Second) - defer cancel() - resp, err := client.Block(ctx, nil) - if err != nil { - return nil, errors.Wrap(err, "get block") - } - - return resp, nil - } - - queryTicker := time.NewTicker(time.Second) - defer queryTicker.Stop() - logTicker := time.NewTicker(5 * time.Second) - defer logTicker.Stop() - - for { - select { - case <-ctx.Done(): - return nil, nil, errors.Wrap(ctx.Err(), "context canceled") - case <-logTicker.C: - current := "unknown" - if maxResult != nil { - current = fmt.Sprint(maxResult.Block.Height) - } - log.Debug(ctx, "Still waiting for height", "current_height", current, "wait_for_height", height) - case <-queryTicker.C: - for _, node := range testnet.Nodes { - if node.Stateless() { - continue - } - client, ok := clients[node.Name] - if !ok { - client, err = node.Client() - if err != nil { - continue - } - clients[node.Name] = client - } - - result, err := currentBlock(ctx, client) - if errors.Is(err, context.DeadlineExceeded) { - return nil, nil, errors.Wrap(err, "timeout") - } else if err != nil { - continue - } - - if result.Block != nil && (maxResult == nil || result.Block.Height > maxResult.Block.Height) { - maxResult = result - lastIncrease = time.Now() - } - if maxResult != nil && maxResult.Block.Height >= height { - return maxResult.Block, &maxResult.BlockID, nil - } - } - - if len(clients) == 0 { - return nil, nil, errors.New("unable to connect to any network nodes") - } - if time.Since(lastIncrease) >= 20*time.Second { - if maxResult == nil { - return nil, nil, errors.New("chain stalled at unknown height") - } - - return nil, nil, errors.New("chain stalled", "height", maxResult.Block.Height) - } - } - } -} - -// waitForNode waits for a node to become available and catch up to the given block height. -func waitForNode(ctx context.Context, node *e2e.Node, height int64, timeout time.Duration, -) (*rpctypes.ResultStatus, error) { - client, err := node.Client() - if err != nil { - return nil, errors.Wrap(err, "getting client") - } - - timer := time.NewTimer(0) - defer timer.Stop() - var curHeight int64 - lastChanged := time.Now() - for { - select { - case <-ctx.Done(): - return nil, errors.Wrap(ctx.Err(), "context canceled") - case <-timer.C: - status, err := client.Status(ctx) - switch { - case time.Since(lastChanged) > timeout: - return nil, errors.New("timed out waiting for height", "name", node.Name, "height", height) - case err != nil: - case status.SyncInfo.LatestBlockHeight >= height && (height == 0 || !status.SyncInfo.CatchingUp): - return status, nil - case curHeight < status.SyncInfo.LatestBlockHeight: - curHeight = status.SyncInfo.LatestBlockHeight - lastChanged = time.Now() - } - - timer.Reset(300 * time.Millisecond) - } - } -} - -// waitForAllNodes waits for all nodes to become available and catch up to the given block height. -func waitForAllNodes(ctx context.Context, testnet *e2e.Testnet, height int64, timeout time.Duration) (int64, error) { - var lastHeight int64 - - deadline := time.Now().Add(timeout) - - for _, node := range testnet.Nodes { - if node.Mode == e2e.ModeSeed { - continue - } - - status, err := waitForNode(ctx, node, height, time.Until(deadline)) - if err != nil { - return 0, err - } - - if status.SyncInfo.LatestBlockHeight > lastHeight { - lastHeight = status.SyncInfo.LatestBlockHeight - } - } - - return lastHeight, nil -} diff --git a/e2e/app/run.go b/e2e/app/run.go deleted file mode 100644 index d9438821..00000000 --- a/e2e/app/run.go +++ /dev/null @@ -1,118 +0,0 @@ -package app - -import ( - "context" - - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/log" -) - -func DefaultDeployConfig() DeployConfig { - return DeployConfig{} -} - -type DeployConfig struct { - // Internal use parameters (no command line flags). - testConfig bool -} - -// Deploy a new e2e network. -func Deploy(ctx context.Context, def Definition, cfg DeployConfig) error { - if def.Testnet.Network.IsProtected() { - // If a protected network needs to be deployed temporarily comment out this check. - return errors.New("cannot deploy protected network", "network", def.Testnet.Network) - } - - if err := deployPublicCreate3(ctx, def); err != nil { - return err - } - - if err := Setup(ctx, def, cfg); err != nil { - return err - } - - // Only stop and delete existing network right before actually starting new ones. - if err := CleanInfra(ctx, def); err != nil { - return err - } - - if err := StartInitial(ctx, def.Testnet.Testnet, def.Infra); err != nil { - return err - } - - if err := fundAccounts(ctx, def); err != nil { - return err - } - - if err := deployPrivateCreate3(ctx, def); err != nil { - return err - } - - //nolint:revive // Will add more logic after this if check - if err := FundValidatorsForTesting(ctx, def); err != nil { - return err - } - - return nil -} - -// E2ETestConfig is the configuration required to run a full e2e test. -type E2ETestConfig struct { - Preserve bool -} - -// DefaultE2ETestConfig returns a default configuration for a e2e test. -func DefaultE2ETestConfig() E2ETestConfig { - return E2ETestConfig{} -} - -// E2ETest runs a full e2e test. -func E2ETest(ctx context.Context, def Definition, cfg E2ETestConfig) error { - stopValidatorUpdates := StartValidatorUpdates(ctx, def) - - if err := StartRemaining(ctx, def.Testnet.Testnet, def.Infra); err != nil { - return err - } - - if err := Wait(ctx, def.Testnet.Testnet, 5); err != nil { // allow some txs to go through - return err - } - - if def.Testnet.HasPerturbations() { - if err := perturb(ctx, def.Testnet); err != nil { - return err - } - } - - if def.Testnet.Evidence > 0 { - return errors.New("evidence injection not supported yet") - } - - if err := stopValidatorUpdates(); err != nil { - return errors.Wrap(err, "stop validator updates") - } - - // Start unit tests. - if err := Test(ctx, def, false); err != nil { - return err - } - - if cfg.Preserve { - log.Warn(ctx, "Docker containers not stopped, --preserve=true", nil) - } else if err := CleanInfra(ctx, def); err != nil { - return err - } - - return nil -} - -// Upgrade generates all local artifacts, but only copies the docker-compose file to the VMs. -// It them calls docker-compose up. -func Upgrade(ctx context.Context, def Definition, cfg DeployConfig, upgradeCfg types.UpgradeConfig) error { - if err := Setup(ctx, def, cfg); err != nil { - return err - } - - return def.Infra.Upgrade(ctx, upgradeCfg) -} diff --git a/e2e/app/setup.go b/e2e/app/setup.go deleted file mode 100644 index b85e7bd7..00000000 --- a/e2e/app/setup.go +++ /dev/null @@ -1,345 +0,0 @@ -package app - -import ( - "context" - "fmt" - "log/slog" - "os" - "path/filepath" - "regexp" - "time" - - "github.com/cometbft/cometbft/config" - "github.com/cometbft/cometbft/crypto" - "github.com/cometbft/cometbft/p2p" - "github.com/cometbft/cometbft/privval" - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - - iliadcmd "github.com/piplabs/story/client/cmd" - iliadcfg "github.com/piplabs/story/client/config" - "github.com/piplabs/story/client/genutil" - evmgenutil "github.com/piplabs/story/client/genutil/evm" - "github.com/piplabs/story/e2e/app/agent" - "github.com/piplabs/story/e2e/app/geth" - "github.com/piplabs/story/e2e/app/static" - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/e2e/vmcompose" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/log" - "github.com/piplabs/story/lib/netconf" - - _ "embed" // Embed requires blank import -) - -const ( - AppAddressTCP = "tcp://127.0.0.1:30000" - AppAddressUNIX = "unix:///var/run/app.sock" - - PrivvalKeyFile = "config/priv_validator_key.json" - PrivvalStateFile = "data/priv_validator_state.json" -) - -// Setup sets up the testnet configuration. -func Setup(ctx context.Context, def Definition, depCfg DeployConfig) error { - log.Info(ctx, "Setup testnet", "dir", def.Testnet.Dir) - - if err := CleanupDir(ctx, def.Testnet.Dir); err != nil { - return err - } - - if err := os.MkdirAll(def.Testnet.Dir, os.ModePerm); err != nil { - return errors.Wrap(err, "mkdir") - } - - // Setup geth execution genesis - gethGenesis, err := evmgenutil.MakeGenesis(def.Manifest.Network) - if err != nil { - return errors.Wrap(err, "make genesis") - } - if err := geth.WriteAllConfig(def.Testnet, gethGenesis); err != nil { - return err - } - - var vals []crypto.PubKey - // var valPrivKeys []crypto.PrivKey - for val := range def.Testnet.Validators { - vals = append(vals, val.PrivvalKey.PubKey()) - // valPrivKeys = append(valPrivKeys, val.PrivvalKey) - } - - cosmosGenesis, err := genutil.MakeGenesis( - def.Manifest.Network, - time.Now(), - gethGenesis.ToBlock().Hash(), - vals...) - if err != nil { - return errors.Wrap(err, "make genesis") - } - cmtGenesis, err := cosmosGenesis.ToGenesisDoc() - if err != nil { - return errors.Wrap(err, "convert genesis") - } - - logCfg := logConfig(def) - - if err := writeAnvilState(def.Testnet); err != nil { - return err - } - - for _, node := range def.Testnet.Nodes { - nodeDir := filepath.Join(def.Testnet.Dir, node.Name) - - dirs := []string{ - filepath.Join(nodeDir, "config"), - filepath.Join(nodeDir, "data"), - } - for _, dir := range dirs { - err := os.MkdirAll(dir, 0o755) - if err != nil { - return errors.Wrap(err, "make dir") - } - } - - cfg, err := MakeConfig(node, nodeDir) - if err != nil { - return err - } - config.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), cfg) // panics - - iliadEVM := iliadEVMByPrefix(def.Testnet, node.Name) - - if err := writeIliadConfig( - def.Testnet.Network, - nodeDir, - def.Cfg, - logCfg, - depCfg.testConfig, - node.Mode, - iliadEVM.InstanceName, - ); err != nil { - return err - } - - if err := os.WriteFile(filepath.Join(nodeDir, "config", "jwtsecret"), []byte(iliadEVM.JWTSecret), 0o600); err != nil { - return errors.Wrap(err, "write jwtsecret") - } - - err = cmtGenesis.SaveAs(filepath.Join(nodeDir, "config", "genesis.json")) - if err != nil { - return errors.Wrap(err, "write genesis") - } - - err = (&p2p.NodeKey{PrivKey: node.NodeKey}).SaveAs(filepath.Join(nodeDir, "config", "node_key.json")) - if err != nil { - return errors.Wrap(err, "write node key") - } - - (privval.NewFilePV(node.PrivvalKey, - filepath.Join(nodeDir, PrivvalKeyFile), - filepath.Join(nodeDir, PrivvalStateFile), - )).Save() - - // Initialize the node's data directory (with noop logger since it is noisy). - initCfg := iliadcmd.InitConfig{ - HomeDir: nodeDir, - Network: def.Testnet.Network, - } - if err := iliadcmd.InitFiles(log.WithNoopLogger(ctx), initCfg); err != nil { - return errors.Wrap(err, "init files") - } - } - - if def.Testnet.Prometheus { - if err := agent.WriteConfig(ctx, def.Testnet, def.Cfg.AgentSecrets); err != nil { - return errors.Wrap(err, "write prom config") - } - } - - if err := def.Infra.Setup(); err != nil { - return errors.Wrap(err, "setup provider") - } - - return nil -} - -// writeAnvilState writes the embedded /static/el-anvil-state.json -// to /anvil/state.json for use by all anvil chains. -func writeAnvilState(testnet types.Testnet) error { - anvilStateFile := filepath.Join(testnet.Dir, "anvil", "state.json") - if err := os.MkdirAll(filepath.Dir(anvilStateFile), 0o755); err != nil { - return errors.Wrap(err, "mkdir") - } - if err := os.WriteFile(anvilStateFile, static.GetDevnetAnvilState(), 0o644); err != nil { - return errors.Wrap(err, "write anvil state") - } - - return nil -} - -// MakeConfig generates a CometBFT config for a node. -// -//nolint:lll // CometBFT super long names :( -func MakeConfig(node *e2e.Node, nodeDir string) (*config.Config, error) { - cfg := iliadcmd.DefaultCometConfig(nodeDir) - cfg.Moniker = node.Name - cfg.ProxyApp = AppAddressTCP - cfg.RPC.ListenAddress = "tcp://0.0.0.0:26657" - cfg.RPC.PprofListenAddress = ":6060" - cfg.P2P.ExternalAddress = fmt.Sprintf("tcp://%v", node.AddressP2P(false)) - cfg.P2P.AddrBookStrict = false - cfg.DBBackend = node.Database - cfg.StateSync.DiscoveryTime = 5 * time.Second - cfg.BlockSync.Version = node.BlockSyncVersion - cfg.Mempool.ExperimentalMaxGossipConnectionsToNonPersistentPeers = int(node.Testnet.ExperimentalMaxGossipConnectionsToNonPersistentPeers) - cfg.Mempool.ExperimentalMaxGossipConnectionsToPersistentPeers = int(node.Testnet.ExperimentalMaxGossipConnectionsToPersistentPeers) - - switch node.ABCIProtocol { - case e2e.ProtocolUNIX: - cfg.ProxyApp = AppAddressUNIX - case e2e.ProtocolTCP: - cfg.ProxyApp = AppAddressTCP - case e2e.ProtocolGRPC: - cfg.ProxyApp = AppAddressTCP - cfg.ABCI = "grpc" - case e2e.ProtocolBuiltin, e2e.ProtocolBuiltinConnSync: - cfg.ProxyApp = "" - cfg.ABCI = "" - default: - return nil, errors.New("unexpected ABCI protocol") - } - - // CometBFT errors if it does not have a privval key set up, regardless of whether - // it's actually needed (e.g. for remote KMS or non-validators). We set up a dummy - // key here by default, and use the real key for actual validators that should use - // the file privval. - cfg.PrivValidatorListenAddr = "" - cfg.PrivValidatorKey = PrivvalKeyFile - cfg.PrivValidatorState = PrivvalStateFile - - if node.PrivvalProtocol != e2e.ProtocolFile { - return nil, errors.New("only PrivvalKeyFile is supported") - } - - if node.Mode == types.ModeSeed { - cfg.P2P.SeedMode = true - cfg.P2P.PexReactor = true - } - - if node.StateSync { - cfg.StateSync.Enable = true - cfg.StateSync.RPCServers = []string{} - for _, peer := range node.Testnet.ArchiveNodes() { - if peer.Name == node.Name { - continue - } - cfg.StateSync.RPCServers = append(cfg.StateSync.RPCServers, peer.AddressRPC()) - } - if len(cfg.StateSync.RPCServers) < 2 { - return nil, errors.New("unable to find 2 suitable state sync RPC servers") - } - } - - cfg.P2P.Seeds = "" - for _, seed := range node.Seeds { - if len(cfg.P2P.Seeds) > 0 { - cfg.P2P.Seeds += "," - } - cfg.P2P.Seeds += seed.AddressP2P(true) - } - cfg.P2P.PersistentPeers = "" - for _, peer := range node.PersistentPeers { - if len(cfg.P2P.PersistentPeers) > 0 { - cfg.P2P.PersistentPeers += "," - } - cfg.P2P.PersistentPeers += peer.AddressP2P(true) - } - - if node.Prometheus { - cfg.Instrumentation.Prometheus = true - } - - return &cfg, nil -} - -// writeIliadConfig generates an iliad application config for a node and writes it to disk. -func writeIliadConfig( - network netconf.ID, - nodeDir string, - defCfg DefinitionConfig, - logCfg log.Config, - testCfg bool, - mode e2e.Mode, - evmInstance string, -) error { - cfg := iliadcfg.DefaultConfig() - - switch mode { - case e2e.ModeValidator, e2e.ModeFull: - cfg.PruningOption = "nothing" - cfg.MinRetainBlocks = 0 - case e2e.ModeSeed, e2e.ModeLight: - cfg.PruningOption = "everything" - cfg.MinRetainBlocks = 1 - default: - cfg.PruningOption = "default" - cfg.MinRetainBlocks = 0 - } - - cfg.Network = network - cfg.HomeDir = nodeDir - cfg.EngineEndpoint = fmt.Sprintf("http://%s:8551", evmInstance) //nolint:nosprintfhostport // net.JoinHostPort doesn't prefix http. - cfg.EngineJWTFile = "/client/config/jwtsecret" // Absolute path inside docker container - cfg.Tracer.Endpoint = defCfg.TracingEndpoint - cfg.Tracer.Headers = defCfg.TracingHeaders - - if testCfg { - cfg.SnapshotInterval = 1 // Write snapshots each block in e2e tests - cfg.SnapshotKeepRecent = 0 // Keep all snapshots in e2e tests - } - - return iliadcfg.WriteConfigTOML(cfg, logCfg) -} - -// updateConfigStateSync updates the state sync config for a node. -func updateConfigStateSync(nodeDir string, height int64, hash []byte) error { - cfgPath := filepath.Join(nodeDir, "config", "config.toml") - - // FIXME Apparently there's no function to simply load a config file without - // involving the entire Viper apparatus, so we'll just resort to regexps. - bz, err := os.ReadFile(cfgPath) - if err != nil { - return errors.Wrap(err, "read config") - } - - before := string(bz) - - bz = regexp.MustCompile(`(?m)^trust_height =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust_height = %v`, height))) - bz = regexp.MustCompile(`(?m)^trust_hash =.*`).ReplaceAll(bz, []byte(fmt.Sprintf(`trust_hash = "%X"`, hash))) - bz = regexp.MustCompile(`(?m)^log_level =.*`).ReplaceAll(bz, []byte(`log_level = "info"`)) // Increase log level. - - after := string(bz) - if before == after { - return errors.New("no changes to config") - } - - if err := os.WriteFile(cfgPath, bz, 0o644); err != nil { - return errors.Wrap(err, "write config") - } - - return nil -} - -// logConfig returns a default e2e log config. -// Default format is console (local dev), but vmcompose uses logfmt. -func logConfig(def Definition) log.Config { - format := log.FormatConsole - if def.Infra.GetInfrastructureData().Provider == vmcompose.ProviderName { - format = log.FormatLogfmt - } - - return log.Config{ - Format: format, - Level: slog.LevelDebug.String(), - Color: log.ColorForce, - } -} diff --git a/e2e/app/setup_internal_test.go b/e2e/app/setup_internal_test.go deleted file mode 100644 index b414567d..00000000 --- a/e2e/app/setup_internal_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package app - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/lib/tutil" -) - -//go:generate go test . -golden -clean - -func TestUpdateConfigStateSync(t *testing.T) { - t.Parallel() - config := ` -# Database directory -db_dir = "data" - -# Output level for logging, including package level options -log_level = "error" - -# Output format: 'plain' (colored text) or 'json' -log_format = "plain" - -# For Cosmos SDK-based chains, trust_period should usually be about 2/3 of the unbonding time (~2 -# weeks) during which they can be financially punished (slashed) for misbehavior. -rpc_servers = "" -trust_height = 0 -trust_hash = "" -trust_period = "168h0m0s" -` - - dir := os.TempDir() - configFile := filepath.Join(dir, "config", "config.toml") - require.NoError(t, os.MkdirAll(filepath.Dir(configFile), 0o755)) - err := os.WriteFile(configFile, []byte(config), 0o644) - require.NoError(t, err) - - err = updateConfigStateSync(dir, 1, []byte("test")) - require.NoError(t, err) - - bz, err := os.ReadFile(configFile) - require.NoError(t, err) - - tutil.RequireGoldenBytes(t, bz) -} diff --git a/e2e/app/start.go b/e2e/app/start.go deleted file mode 100644 index 982fc29f..00000000 --- a/e2e/app/start.go +++ /dev/null @@ -1,170 +0,0 @@ -package app - -import ( - "context" - "path/filepath" - "slices" - "sort" - "time" - - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - "github.com/cometbft/cometbft/test/e2e/pkg/infra" - - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/log" -) - -// StartInitial starts the initial nodes (start_at==0). -func StartInitial(ctx context.Context, testnet *e2e.Testnet, p infra.Provider) error { - allNodes, err := getSortedNodes(testnet) - if err != nil { - return err - } - - // Start initial nodes (StartAt: 0) - initialNodes := make([]*e2e.Node, 0) - for _, node := range allNodes { - if node.StartAt > 0 { - continue - } - initialNodes = append(initialNodes, node) - } - if len(initialNodes) == 0 { - return errors.New("no initial nodes in testnet") - } - - log.Info(ctx, "Starting initial network nodes...", "count", len(initialNodes)) - - if err = p.StartNodes(ctx, initialNodes...); err != nil { - return errors.Wrap(err, "starting initial nodes") - } - - for _, node := range initialNodes { - log.Info(ctx, "Starting node", - "name", node.Name, - "external_ip", node.ExternalIP, - "proxy_port", node.ProxyPort, - "prom", node.PrometheusProxyPort, - ) - if _, err := waitForNode(ctx, node, 0, 15*time.Second); err != nil { - return err - } - } - - networkHeight := testnet.InitialHeight - - // Wait for initial height - log.Info(ctx, "Waiting for initial height", - "height", networkHeight, - "initial", len(initialNodes), - "pending", len(allNodes)-len(initialNodes)) - - _, _, err = waitForHeight(ctx, testnet, networkHeight) - if err != nil { - return err - } - - return nil -} - -// StartRemaining starts the remaining nodes (start_at>0). -func StartRemaining(ctx context.Context, testnet *e2e.Testnet, p infra.Provider) error { - nodeQueue, err := getSortedNodes(testnet) - if err != nil { - return err - } - - var remaining []*e2e.Node - for _, node := range nodeQueue { - if node.StartAt > 0 { - remaining = append(remaining, node) - } - } - - if len(remaining) == 0 { - return nil - } - - block, blockID, err := waitForHeight(ctx, testnet, testnet.InitialHeight) - if err != nil { - return err - } - networkHeight := block.Height - - log.Debug(ctx, "Setting catchup node state sync", "height", block.Height, "catchup_nodes", len(remaining)) - - // Update any state sync nodes with a trusted height and hash - for _, node := range remaining { - if node.StateSync || node.Mode == e2e.ModeLight { - nodeDir := filepath.Join(testnet.Dir, node.Name) - err = updateConfigStateSync(nodeDir, block.Height, blockID.Hash.Bytes()) - if err != nil { - return err - } - } - } - - for _, node := range remaining { - if node.StartAt > networkHeight { - // if we're starting a node that's ahead of - // the last known height of the network, then - // we should make sure that the rest of the - // network has reached at least the height - // that this node will start at before we - // start the node. - - log.Info(ctx, "Waiting for network to advance before starting catchup node", - "node", node.Name, - "current_height", networkHeight, - "wait_for_height", node.StartAt) - - networkHeight = node.StartAt - - if _, _, err := waitForHeight(ctx, testnet, networkHeight); err != nil { - return err - } - } - - log.Info(ctx, "Starting catchup node", "node", node.Name, "height", node.StartAt) - - err := p.StartNodes(ctx, node) - if err != nil { - return errors.Wrap(err, "starting catchup node") - } - status, err := waitForNode(ctx, node, node.StartAt, 3*time.Minute) - if err != nil { - return err - } - log.Info(ctx, "Started catchup node", "name", node.Name, "height", status.SyncInfo.LatestBlockHeight) - } - - return nil -} - -// getSortedNodes returns a copy of the testnet nodes by startAt, then mode, then name. -func getSortedNodes(testnet *e2e.Testnet) ([]*e2e.Node, error) { - if len(testnet.Nodes) == 0 { - return nil, errors.New("no nodes in testnet") - } - - nodeQueue := slices.Clone(testnet.Nodes) - sort.SliceStable(nodeQueue, func(i, j int) bool { - a, b := nodeQueue[i], nodeQueue[j] - switch { - case a.Mode == b.Mode: - return false - case a.Mode == e2e.ModeSeed: - return true - case a.Mode == e2e.ModeValidator && b.Mode == e2e.ModeFull: - return true - } - - return false - }) - - sort.SliceStable(nodeQueue, func(i, j int) bool { - return nodeQueue[i].StartAt < nodeQueue[j].StartAt - }) - - return nodeQueue, nil -} diff --git a/e2e/app/static/anvil-state.json b/e2e/app/static/anvil-state.json deleted file mode 100644 index 87dd9786..00000000 --- a/e2e/app/static/anvil-state.json +++ /dev/null @@ -1,292 +0,0 @@ -{ - "block": { - "number": "0x0", - "coinbase": "0x0000000000000000000000000000000000000000", - "timestamp": "0x65fb7439", - "gas_limit": "0x1c9c380", - "basefee": "0x41ee1a7", - "difficulty": "0x0", - "prevrandao": "0x0000000000000000000000000000000000000000000000000000000000000000", - "blob_excess_gas_and_price": { - "excess_blob_gas": 0, - "blob_gasprice": 1 - } - }, - "accounts": { - "0x0000000000000000000000000000000000000000": { - "nonce": 0, - "balance": "0x135e801bef60c00", - "code": "0x", - "storage": {} - }, - "0x05b4cb126885fb10464fdd12666feb25e2563b76": { - "nonce": 1, - "balance": "0x0", - "code": "0x608060405234801561001057600080fd5b50600436106101425760003560e01c80638da5cb5b116100b8578063d79aceab1161007c578063d79aceab146102f8578063df5cf7231461031f578063ec76f44214610346578063f2fde38b14610359578063f698da251461036c578063fabc1cbc1461037457600080fd5b80638da5cb5b1461029b5780639926ee7d146102ac578063a1060c88146102bf578063a364f4da146102d2578063a98fb355146102e557600080fd5b806349075da31161010a57806349075da3146101fa578063595c6a67146102355780635ac86ab71461023d5780635c975abb14610260578063715018a614610268578063886f11951461027057600080fd5b806310d67a2f14610147578063136439dd1461015c5780631794bb3c1461016f57806320606b7014610182578063374823b5146101bc575b600080fd5b61015a6101553660046117b2565b610387565b005b61015a61016a3660046117d6565b610443565b61015a61017d3660046117ef565b610582565b6101a97f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86681565b6040519081526020015b60405180910390f35b6101ea6101ca366004611830565b609960209081526000928352604080842090915290825290205460ff1681565b60405190151581526020016101b3565b61022861020836600461185c565b609860209081526000928352604080842090915290825290205460ff1681565b6040516101b391906118ab565b61015a6106ac565b6101ea61024b3660046118d3565b606654600160ff9092169190911b9081161490565b6066546101a9565b61015a610773565b606554610283906001600160a01b031681565b6040516001600160a01b0390911681526020016101b3565b6033546001600160a01b0316610283565b61015a6102ba366004611966565b610787565b6101a96102cd366004611a4d565b610b1a565b61015a6102e03660046117b2565b610bd3565b61015a6102f3366004611a93565b610d3c565b6101a97fda2c89bafdd34776a2b8bb9c83c82f419e20cc8c67207f70edd58249b92661bd81565b6102837f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af81565b61015a6103543660046117d6565b610d83565b61015a6103673660046117b2565b610e2e565b6101a9610ea4565b61015a6103823660046117d6565b610ee2565b606560009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103fe9190611b05565b6001600160a01b0316336001600160a01b0316146104375760405162461bcd60e51b815260040161042e90611b22565b60405180910390fd5b6104408161103e565b50565b60655460405163237dfb4760e11b81523360048201526001600160a01b03909116906346fbf68e90602401602060405180830381865afa15801561048b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104af9190611b6c565b6104cb5760405162461bcd60e51b815260040161042e90611b8e565b606654818116146105445760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e70617573653a20696e76616c696420617474656d70742060448201527f746f20756e70617573652066756e6374696f6e616c6974790000000000000000606482015260840161042e565b606681905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d906020015b60405180910390a250565b600054610100900460ff16158080156105a25750600054600160ff909116105b806105bc5750303b1580156105bc575060005460ff166001145b61061f5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b606482015260840161042e565b6000805460ff191660011790558015610642576000805461ff0019166101001790555b61064c8383611135565b61065461121f565b609755610660846112b6565b80156106a6576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b50505050565b60655460405163237dfb4760e11b81523360048201526001600160a01b03909116906346fbf68e90602401602060405180830381865afa1580156106f4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107189190611b6c565b6107345760405162461bcd60e51b815260040161042e90611b8e565b600019606681905560405190815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a2565b61077b611308565b61078560006112b6565b565b606654600090600190811614156107dc5760405162461bcd60e51b815260206004820152601960248201527814185d5cd8589b194e881a5b99195e081a5cc81c185d5cd959603a1b604482015260640161042e565b42826040015110156108445760405162461bcd60e51b815260206004820152603e6024820152600080516020611c8d83398151915260448201527f56533a206f70657261746f72207369676e617475726520657870697265640000606482015260840161042e565b60013360009081526098602090815260408083206001600160a01b038816845290915290205460ff16600181111561087e5761087e611895565b14156108e05760405162461bcd60e51b815260206004820152603f6024820152600080516020611c8d83398151915260448201527f56533a206f70657261746f7220616c7265616479207265676973746572656400606482015260840161042e565b6001600160a01b038316600090815260996020908152604080832085830151845290915290205460ff16156109645760405162461bcd60e51b81526020600482015260366024820152600080516020611c8d8339815191526044820152751594ce881cd85b1d08185b1c9958591e481cdc195b9d60521b606482015260840161042e565b6040516336b87bd760e11b81526001600160a01b0384811660048301527f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af1690636d70f7ae90602401602060405180830381865afa1580156109ca573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109ee9190611b6c565b610a645760405162461bcd60e51b815260206004820152604d6024820152600080516020611c8d83398151915260448201527f56533a206f70657261746f72206e6f74207265676973746572656420746f204560648201526c1a59d95b93185e595c881e595d609a1b608482015260a40161042e565b6000610a7a843385602001518660400151610b1a565b9050610a8b84828560000151611362565b3360008181526098602090815260408083206001600160a01b0389168085529083528184208054600160ff199182168117909255609985528386208a860151875290945293829020805490931684179092555190917ff0952b1c65271d819d39983d2abb044b9cace59bcc4d4dd389f586ebdcb15b4191610b0c91906118ab565b60405180910390a350505050565b604080517fda2c89bafdd34776a2b8bb9c83c82f419e20cc8c67207f70edd58249b92661bd6020808301919091526001600160a01b0387811683850152861660608301526080820185905260a08083018590528351808403909101815260c0909201909252805191012060009081610b90610ea4565b60405161190160f01b602082015260228101919091526042810183905260620160408051808303601f190181529190528051602090910120979650505050505050565b60665460009060019081161415610c285760405162461bcd60e51b815260206004820152601960248201527814185d5cd8589b194e881a5b99195e081a5cc81c185d5cd959603a1b604482015260640161042e565b60013360009081526098602090815260408083206001600160a01b038716845290915290205460ff166001811115610c6257610c62611895565b14610cd55760405162461bcd60e51b815260206004820152603f60248201527f4156534469726563746f72792e646572656769737465724f70657261746f724660448201527f726f6d4156533a206f70657261746f72206e6f74207265676973746572656400606482015260840161042e565b3360008181526098602090815260408083206001600160a01b0387168085529252808320805460ff191690555190917ff0952b1c65271d819d39983d2abb044b9cace59bcc4d4dd389f586ebdcb15b4191610d3091906118ab565b60405180910390a35050565b336001600160a01b03167fa89c1dc243d8908a96dd84944bcc97d6bc6ac00dd78e20621576be6a3c9437138383604051610d77929190611bd6565b60405180910390a25050565b33600090815260996020908152604080832084845290915290205460ff1615610e085760405162461bcd60e51b815260206004820152603160248201527f4156534469726563746f72792e63616e63656c53616c743a2063616e6e6f742060448201527018d85b98d95b081cdc195b9d081cd85b1d607a1b606482015260840161042e565b33600090815260996020908152604080832093835292905220805460ff19166001179055565b610e36611308565b6001600160a01b038116610e9b5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b606482015260840161042e565b610440816112b6565b60007f0000000000000000000000000000000000000000000000000000000000007a69461415610ed5575060975490565b610edd61121f565b905090565b606560009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610f35573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f599190611b05565b6001600160a01b0316336001600160a01b031614610f895760405162461bcd60e51b815260040161042e90611b22565b6066541981196066541916146110075760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e756e70617573653a20696e76616c696420617474656d7060448201527f7420746f2070617573652066756e6374696f6e616c6974790000000000000000606482015260840161042e565b606681905560405181815233907f3582d1828e26bf56bd801502bc021ac0bc8afb57c826e4986b45593c8fad389c90602001610577565b6001600160a01b0381166110cc5760405162461bcd60e51b815260206004820152604960248201527f5061757361626c652e5f73657450617573657252656769737472793a206e657760448201527f50617573657252656769737472792063616e6e6f7420626520746865207a65726064820152686f206164647265737360b81b608482015260a40161042e565b606554604080516001600160a01b03928316815291831660208301527f6e9fcd539896fca60e8b0f01dd580233e48a6b0f7df013b89ba7f565869acdb6910160405180910390a1606580546001600160a01b0319166001600160a01b0392909216919091179055565b6065546001600160a01b031615801561115657506001600160a01b03821615155b6111d85760405162461bcd60e51b815260206004820152604760248201527f5061757361626c652e5f696e697469616c697a655061757365723a205f696e6960448201527f7469616c697a6550617573657228292063616e206f6e6c792062652063616c6c6064820152666564206f6e636560c81b608482015260a40161042e565b606681905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a261121b8261103e565b5050565b604080518082018252600a81526922b4b3b2b72630bcb2b960b11b60209182015281517f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a866818301527f71b625cfad44bac63b13dba07f2e1d6084ee04b6f8752101ece6126d584ee6ea81840152466060820152306080808301919091528351808303909101815260a0909101909252815191012090565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6033546001600160a01b031633146107855760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161042e565b6001600160a01b0383163b1561148157604051630b135d3f60e11b808252906001600160a01b03851690631626ba7e906113a29086908690600401611c05565b602060405180830381865afa1580156113bf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113e39190611c62565b6001600160e01b0319161461147c5760405162461bcd60e51b815260206004820152605360248201527f454950313237315369676e61747572655574696c732e636865636b5369676e6160448201527f747572655f454950313237313a2045524331323731207369676e6174757265206064820152721d995c9a599a58d85d1a5bdb8819985a5b1959606a1b608482015260a40161042e565b505050565b826001600160a01b03166114958383611521565b6001600160a01b03161461147c5760405162461bcd60e51b815260206004820152604760248201527f454950313237315369676e61747572655574696c732e636865636b5369676e6160448201527f747572655f454950313237313a207369676e6174757265206e6f742066726f6d6064820152661039b4b3b732b960c91b608482015260a40161042e565b60008060006115308585611545565b9150915061153d8161158b565b509392505050565b60008082516041141561157c5760208301516040840151606085015160001a611570878285856116d9565b94509450505050611584565b506000905060025b9250929050565b600081600481111561159f5761159f611895565b14156115a85750565b60018160048111156115bc576115bc611895565b141561160a5760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e61747572650000000000000000604482015260640161042e565b600281600481111561161e5761161e611895565b141561166c5760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e67746800604482015260640161042e565b600381600481111561168057611680611895565b14156104405760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b606482015260840161042e565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08311156117105750600090506003611794565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015611764573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811661178d57600060019250925050611794565b9150600090505b94509492505050565b6001600160a01b038116811461044057600080fd5b6000602082840312156117c457600080fd5b81356117cf8161179d565b9392505050565b6000602082840312156117e857600080fd5b5035919050565b60008060006060848603121561180457600080fd5b833561180f8161179d565b9250602084013561181f8161179d565b929592945050506040919091013590565b6000806040838503121561184357600080fd5b823561184e8161179d565b946020939093013593505050565b6000806040838503121561186f57600080fd5b823561187a8161179d565b9150602083013561188a8161179d565b809150509250929050565b634e487b7160e01b600052602160045260246000fd5b60208101600283106118cd57634e487b7160e01b600052602160045260246000fd5b91905290565b6000602082840312156118e557600080fd5b813560ff811681146117cf57600080fd5b634e487b7160e01b600052604160045260246000fd5b6040516060810167ffffffffffffffff8111828210171561192f5761192f6118f6565b60405290565b604051601f8201601f1916810167ffffffffffffffff8111828210171561195e5761195e6118f6565b604052919050565b6000806040838503121561197957600080fd5b82356119848161179d565b915060208381013567ffffffffffffffff808211156119a257600080fd5b90850190606082880312156119b657600080fd5b6119be61190c565b8235828111156119cd57600080fd5b8301601f810189136119de57600080fd5b8035838111156119f0576119f06118f6565b611a02601f8201601f19168701611935565b93508084528986828401011115611a1857600080fd5b808683018786013760008682860101525050818152838301358482015260408301356040820152809450505050509250929050565b60008060008060808587031215611a6357600080fd5b8435611a6e8161179d565b93506020850135611a7e8161179d565b93969395505050506040820135916060013590565b60008060208385031215611aa657600080fd5b823567ffffffffffffffff80821115611abe57600080fd5b818501915085601f830112611ad257600080fd5b813581811115611ae157600080fd5b866020828501011115611af357600080fd5b60209290920196919550909350505050565b600060208284031215611b1757600080fd5b81516117cf8161179d565b6020808252602a908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526939903ab73830bab9b2b960b11b606082015260800190565b600060208284031215611b7e57600080fd5b815180151581146117cf57600080fd5b60208082526028908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526739903830bab9b2b960c11b606082015260800190565b60208152816020820152818360408301376000818301604090810191909152601f909201601f19160101919050565b82815260006020604081840152835180604085015260005b81811015611c3957858101830151858201606001528201611c1d565b81811115611c4b576000606083870101525b50601f01601f191692909201606001949350505050565b600060208284031215611c7457600080fd5b81516001600160e01b0319811681146117cf57600080fdfe4156534469726563746f72792e72656769737465724f70657261746f72546f41a2646970667358221220357ae0ebbbd5cff467b8031d418d71991e1379a4836ab611d82a77f612fa836964736f6c634300080c0033", - "storage": { - "0x0": "0xff" - } - }, - "0x0c8e79f3534b00d9a3d4a856b665bf4ebc22f2ba": { - "nonce": 1, - "balance": "0x0", - "code": "0x60806040523661001357610011610017565b005b6100115b61001f6101b7565b6001600160a01b0316336001600160a01b0316141561016f5760606001600160e01b031960003516631b2ce7f360e11b8114156100655761005e6101ea565b9150610167565b6001600160e01b0319811663278f794360e11b14156100865761005e610241565b6001600160e01b031981166308f2839760e41b14156100a75761005e610287565b6001600160e01b031981166303e1469160e61b14156100c85761005e6102b8565b6001600160e01b03198116635c60da1b60e01b14156100e95761005e6102f8565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b61017761030c565b565b606061019e83836040518060600160405280602781526020016108576027913961031c565b9392505050565b90565b6001600160a01b03163b151590565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101f4610394565b600061020336600481846106a2565b81019061021091906106e8565b905061022d8160405180602001604052806000815250600061039f565b505060408051602081019091526000815290565b606060008061025336600481846106a2565b8101906102609190610719565b915091506102708282600161039f565b604051806020016040528060008152509250505090565b6060610291610394565b60006102a036600481846106a2565b8101906102ad91906106e8565b905061022d816103cb565b60606102c2610394565b60006102cc6101b7565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b6060610302610394565b60006102cc610422565b610177610317610422565b610431565b6060600080856001600160a01b0316856040516103399190610807565b600060405180830381855af49150503d8060008114610374576040519150601f19603f3d011682016040523d82523d6000602084013e610379565b606091505b509150915061038a86838387610455565b9695505050505050565b341561017757600080fd5b6103a8836104d3565b6000825111806103b55750805b156103c6576103c48383610179565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103f46101b7565b604080516001600160a01b03928316815291841660208301520160405180910390a161041f81610513565b50565b600061042c6105bc565b905090565b3660008037600080366000845af43d6000803e808015610450573d6000f35b3d6000fd5b606083156104c15782516104ba576001600160a01b0385163b6104ba5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161015e565b50816104cb565b6104cb83836105e4565b949350505050565b6104dc8161060e565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6001600160a01b0381166105785760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161015e565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6101db565b8151156105f45781518083602001fd5b8060405162461bcd60e51b815260040161015e9190610823565b6001600160a01b0381163b61067b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161015e565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61059b565b600080858511156106b257600080fd5b838611156106bf57600080fd5b5050820193919092039150565b80356001600160a01b03811681146106e357600080fd5b919050565b6000602082840312156106fa57600080fd5b61019e826106cc565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561072c57600080fd5b610735836106cc565b9150602083013567ffffffffffffffff8082111561075257600080fd5b818501915085601f83011261076657600080fd5b81358181111561077857610778610703565b604051601f8201601f19908116603f011681019083821181831017156107a0576107a0610703565b816040528281528860208487010111156107b957600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156107f65781810151838201526020016107de565b838111156103c45750506000910152565b600082516108198184602087016107db565b9190910192915050565b60208152600082518060208401526108428160408501602087016107db565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212205685c4ad655ee149c77141d53c938af08bb4457fb9509b6f1725a2ee99781c1a64736f6c634300080c0033", - "storage": { - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0xd04ff4a75edd737a73e92b2f2274cb887d96e110", - "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x700b6a60ce7eaaea56f065753d8dcb9653dbad35" - } - }, - "0x12975173b87f7595ee45dffb2ab812ece596bf84": { - "nonce": 1, - "balance": "0x0", - "code": "0x6080604052600436106100345760003560e01c80632289511814610039578063621fd13014610052578063c5f2892f14610077575b600080fd5b6100506100473660046100dc565b50505050505050565b005b34801561005e57600080fd5b50606060405161006e919061017f565b60405180910390f35b34801561008357600080fd5b506040516000815260200161006e565b60008083601f8401126100a557600080fd5b50813567ffffffffffffffff8111156100bd57600080fd5b6020830191508360208285010111156100d557600080fd5b9250929050565b60008060008060008060006080888a0312156100f757600080fd5b873567ffffffffffffffff8082111561010f57600080fd5b61011b8b838c01610093565b909950975060208a013591508082111561013457600080fd5b6101408b838c01610093565b909750955060408a013591508082111561015957600080fd5b506101668a828b01610093565b989b979a50959894979596606090950135949350505050565b600060208083528351808285015260005b818110156101ac57858101830151858201604001528201610190565b818111156101be576000604083870101525b50601f01601f191692909201604001939250505056fea2646970667358221220f9063df0d2ce20889bef5c74a36468da24316bb8892074a75b8b5b237ad0e1dc64736f6c634300080c0033", - "storage": {} - }, - "0x14dc79964da2c08b23698b3d3cc7ca32193d9955": { - "nonce": 0, - "balance": "0x21e19e0c9bab2400000", - "code": "0x", - "storage": {} - }, - "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": { - "nonce": 0, - "balance": "0x21e19e0c9bab2400000", - "code": "0x", - "storage": {} - }, - "0x196dbcbb54b8ec4958c959d8949ebfe87ac2aaaf": { - "nonce": 1, - "balance": "0x0", - "code": "0x608060405234801561001057600080fd5b50600436106100575760003560e01c80633659cfe61461005c5780635c60da1b14610071578063715018a61461009a5780638da5cb5b146100a2578063f2fde38b146100b3575b600080fd5b61006f61006a3660046102ee565b6100c6565b005b6001546001600160a01b03165b6040516001600160a01b03909116815260200160405180910390f35b61006f61010e565b6000546001600160a01b031661007e565b61006f6100c13660046102ee565b610122565b6100ce6101af565b6100d781610209565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6101166101af565b610120600061029e565b565b61012a6101af565b6001600160a01b0381166101945760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b61019d8161029e565b50565b6001600160a01b03163b151590565b6000546001600160a01b031633146101205760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161018b565b6001600160a01b0381163b61027c5760405162461bcd60e51b815260206004820152603360248201527f5570677261646561626c65426561636f6e3a20696d706c656d656e746174696f6044820152721b881a5cc81b9bdd08184818dbdb9d1c9858dd606a1b606482015260840161018b565b600180546001600160a01b0319166001600160a01b0392909216919091179055565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006020828403121561030057600080fd5b81356001600160a01b038116811461031757600080fd5b939250505056fea2646970667358221220ca5748d604d0e0b2e2d1f245764d61c1a68c535b585e13b3c2793b00a76d688964736f6c634300080c0033", - "storage": { - "0x0": "0xa0ee7a142d267c1f36714e4a8f75612f20a79720", - "0x1": "0x82dc47734901ee7d4f4232f398752cb9dd5daccc" - } - }, - "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { - "nonce": 0, - "balance": "0x21e19e0c9bab2400000", - "code": "0x", - "storage": {} - }, - "0x29a79095352a718b3d7fe84e1f14e9f34a35598e": { - "nonce": 1, - "balance": "0x0", - "code": "0x60806040526004361061014b5760003560e01c806385594e58116100b6578063e4f4f8871161006f578063e4f4f887146103cc578063e5db06c014610405578063eb990c5914610425578063ecb7cb1b14610445578063f2fde38b14610472578063fabc1cbc1461049257600080fd5b806385594e5814610317578063886f1195146103445780638da5cb5b14610364578063c0db354c14610382578063ca661c0414610395578063d44e1b76146103ac57600080fd5b806350f73e7c1161010857806350f73e7c14610254578063595c6a67146102785780635ac86ab71461028d5780635c975abb146102cd578063715018a6146102e257806375608896146102f757600080fd5b806310d67a2f14610150578063136439dd146101725780631f39d87f146101925780633e1de008146101c85780634665bcda146101e85780634d50f9a414610234575b600080fd5b34801561015c57600080fd5b5061017061016b36600461192a565b6104b2565b005b34801561017e57600080fd5b5061017061018d36600461194e565b61056e565b34801561019e57600080fd5b506101b26101ad36600461192a565b6106ad565b6040516101bf9190611985565b60405180910390f35b3480156101d457600080fd5b506101b26101e336600461192a565b6108a8565b3480156101f457600080fd5b5061021c7f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e81565b6040516001600160a01b0390911681526020016101bf565b34801561024057600080fd5b5061017061024f36600461194e565b6109ee565b34801561026057600080fd5b5061026a60c95481565b6040519081526020016101bf565b34801561028457600080fd5b506101706109ff565b34801561029957600080fd5b506102bd6102a83660046119d2565b609854600160ff9092169190911b9081161490565b60405190151581526020016101bf565b3480156102d957600080fd5b5060985461026a565b3480156102ee57600080fd5b50610170610ac6565b34801561030357600080fd5b506102bd6103123660046119f5565b610ada565b34801561032357600080fd5b506103376103323660046119f5565b610b5d565b6040516101bf9190611a21565b34801561035057600080fd5b5060975461021c906001600160a01b031681565b34801561037057600080fd5b506033546001600160a01b031661021c565b610170610390366004611a2f565b610bdd565b3480156103a157600080fd5b5061026a62034bc081565b3480156103b857600080fd5b506101706103c736600461194e565b610e9d565b3480156103d857600080fd5b5061026a6103e736600461192a565b6001600160a01b0316600090815260ca602052604090206001015490565b34801561041157600080fd5b506101706104203660046119f5565b610ee3565b34801561043157600080fd5b50610170610440366004611a68565b610f2d565b34801561045157600080fd5b5061046561046036600461192a565b611055565b6040516101bf9190611aae565b34801561047e57600080fd5b5061017061048d36600461192a565b61110f565b34801561049e57600080fd5b506101706104ad36600461194e565b611185565b609760009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610505573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105299190611b04565b6001600160a01b0316336001600160a01b0316146105625760405162461bcd60e51b815260040161055990611b21565b60405180910390fd5b61056b816112e1565b50565b60975460405163237dfb4760e11b81523360048201526001600160a01b03909116906346fbf68e90602401602060405180830381865afa1580156105b6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105da9190611b6b565b6105f65760405162461bcd60e51b815260040161055990611b8d565b6098548181161461066f5760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e70617573653a20696e76616c696420617474656d70742060448201527f746f20756e70617573652066756e6374696f6e616c69747900000000000000006064820152608401610559565b609881905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d906020015b60405180910390a250565b6001600160a01b038116600090815260ca6020526040812080546001909101546060926106da8383611beb565b90508060005b82811015610786576001600160a01b038716600090815260ca6020526040812060010161070d8388611c02565b8154811061071d5761071d611c1a565b6000918252602091829020604080518082019091529101546001600160e01b0381168252600160e01b900463ffffffff1691810182905260c95490925061076391611c02565b4310156107735781925050610786565b508061077e81611c30565b9150506106e0565b508060008167ffffffffffffffff8111156107a3576107a3611c4b565b6040519080825280602002602001820160405280156107e857816020015b60408051808201909152600080825260208201528152602001906001900390816107c15790505b509050811561089d5760005b8281101561089b576001600160a01b038916600090815260ca602052604090206001016108218289611c02565b8154811061083157610831611c1a565b6000918252602091829020604080518082019091529101546001600160e01b0381168252600160e01b900463ffffffff1691810191909152825183908390811061087d5761087d611c1a565b6020026020010181905250808061089390611c30565b9150506107f4565b505b979650505050505050565b6001600160a01b038116600090815260ca6020526040812080546001909101546060926108d58383611beb565b905060008167ffffffffffffffff8111156108f2576108f2611c4b565b60405190808252806020026020018201604052801561093757816020015b60408051808201909152600080825260208201528152602001906001900390816109105790505b50905060005b828110156109e4576001600160a01b038716600090815260ca6020526040902060010161096a8287611c02565b8154811061097a5761097a611c1a565b6000918252602091829020604080518082019091529101546001600160e01b0381168252600160e01b900463ffffffff169181019190915282518390839081106109c6576109c6611c1a565b602002602001018190525080806109dc90611c30565b91505061093d565b5095945050505050565b6109f66113d8565b61056b81611432565b60975460405163237dfb4760e11b81523360048201526001600160a01b03909116906346fbf68e90602401602060405180830381865afa158015610a47573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a6b9190611b6b565b610a875760405162461bcd60e51b815260040161055990611b8d565b600019609881905560405190815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a2565b610ace6113d8565b610ad860006114fa565b565b6001600160a01b038216600090815260ca60205260408120548210801590610b54575060c9546001600160a01b038416600090815260ca60205260409020600101805484908110610b2d57610b2d611c1a565b600091825260209091200154610b509190600160e01b900463ffffffff16611c02565b4310155b90505b92915050565b60408051808201909152600080825260208201526001600160a01b038316600090815260ca60205260409020600101805483908110610b9e57610b9e611c1a565b6000918252602091829020604080518082019091529101546001600160e01b0381168252600160e01b900463ffffffff16918101919091529392505050565b60405163a38406a360e01b81526001600160a01b038084166004830152839133917f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e169063a38406a390602401602060405180830381865afa158015610c47573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c6b9190611b04565b6001600160a01b031614610ce75760405162461bcd60e51b815260206004820152603d60248201527f44656c617965645769746864726177616c526f757465722e6f6e6c794569676560448201527f6e506f643a206e6f7420706f644f776e6572277320456967656e506f640000006064820152608401610559565b60985460009060019081161415610d105760405162461bcd60e51b815260040161055990611c61565b6001600160a01b038316610da65760405162461bcd60e51b815260206004820152605160248201527f44656c617965645769746864726177616c526f757465722e637265617465446560448201527f6c617965645769746864726177616c3a20726563697069656e742063616e6e6f60648201527074206265207a65726f206164647265737360781b608482015260a401610559565b346001600160e01b03811615610e96576040805180820182526001600160e01b03808416825263ffffffff43811660208085019182526001600160a01b038a16600081815260ca8352968720600190810180548083018255818a5293892088519551909616600160e01b029490961693909317939091019290925593525490917fb8f1b14c7caf74150801dcc9bc18d575cbeaf5b421943497e409df92c92e0f5991889188918691610e5791611beb565b604080516001600160a01b0395861681529490931660208501526001600160e01b039091169183019190915260608201526080015b60405180910390a1505b5050505050565b610ea561154c565b60985460009060019081161415610ece5760405162461bcd60e51b815260040161055990611c61565b610ed833836115a6565b5061056b6001606555565b610eeb61154c565b60985460009060019081161415610f145760405162461bcd60e51b815260040161055990611c61565b610f1e83836115a6565b50610f296001606555565b5050565b600054610100900460ff1615808015610f4d5750600054600160ff909116105b80610f675750303b158015610f67575060005460ff166001145b610fca5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610559565b6000805460ff191660011790558015610fed576000805461ff0019166101001790555b610ff6856114fa565b6110008484611711565b61100982611432565b8015610e96576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15050505050565b6040805180820190915260008152606060208201526001600160a01b038216600090815260ca6020908152604080832081518083018352815481526001820180548451818702810187019095528085529195929486810194939192919084015b8282101561110157600084815260209081902060408051808201909152908401546001600160e01b0381168252600160e01b900463ffffffff16818301528252600190920191016110b5565b505050915250909392505050565b6111176113d8565b6001600160a01b03811661117c5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610559565b61056b816114fa565b609760009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156111d8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111fc9190611b04565b6001600160a01b0316336001600160a01b03161461122c5760405162461bcd60e51b815260040161055990611b21565b6098541981196098541916146112aa5760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e756e70617573653a20696e76616c696420617474656d7060448201527f7420746f2070617573652066756e6374696f6e616c69747900000000000000006064820152608401610559565b609881905560405181815233907f3582d1828e26bf56bd801502bc021ac0bc8afb57c826e4986b45593c8fad389c906020016106a2565b6001600160a01b03811661136f5760405162461bcd60e51b815260206004820152604960248201527f5061757361626c652e5f73657450617573657252656769737472793a206e657760448201527f50617573657252656769737472792063616e6e6f7420626520746865207a65726064820152686f206164647265737360b81b608482015260a401610559565b609754604080516001600160a01b03928316815291831660208301527f6e9fcd539896fca60e8b0f01dd580233e48a6b0f7df013b89ba7f565869acdb6910160405180910390a1609780546001600160a01b0319166001600160a01b0392909216919091179055565b6033546001600160a01b03163314610ad85760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610559565b62034bc08111156114b95760405162461bcd60e51b815260206004820152604560248201527f44656c617965645769746864726177616c526f757465722e5f7365745769746860448201527f64726177616c44656c6179426c6f636b733a206e657756616c756520746f6f206064820152646c6172676560d81b608482015260a401610559565b60c95460408051918252602082018390527f4ffb00400574147429ee377a5633386321e66d45d8b14676014b5fa393e61e9e910160405180910390a160c955565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6002606554141561159f5760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610559565b6002606555565b6001600160a01b038216600090815260ca602052604081208054600190910154825b84811080156115df5750816115dd8285611c02565b105b1561168c576001600160a01b038616600090815260ca602052604081206001016116098386611c02565b8154811061161957611619611c1a565b6000918252602091829020604080518082019091529101546001600160e01b0381168252600160e01b900463ffffffff1691810182905260c95490925061165f91611c02565b43101561166c575061168c565b8051611681906001600160e01b031686611c02565b9450506001016115c8565b6116968184611c02565b6001600160a01b038716600090815260ca602052604090205583156116bf576116bf86856117f7565b7f6b7151500bd0b5cc211bcc47b3029831b769004df4549e8e1c9a69da05bb094386856116ec8487611c02565b604080516001600160a01b039094168452602084019290925290820152606001610e8c565b6097546001600160a01b031615801561173257506001600160a01b03821615155b6117b45760405162461bcd60e51b815260206004820152604760248201527f5061757361626c652e5f696e697469616c697a655061757365723a205f696e6960448201527f7469616c697a6550617573657228292063616e206f6e6c792062652063616c6c6064820152666564206f6e636560c81b608482015260a401610559565b609881905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a2610f29826112e1565b804710156118475760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e63650000006044820152606401610559565b6000826001600160a01b03168260405160006040518083038185875af1925050503d8060008114611894576040519150601f19603f3d011682016040523d82523d6000602084013e611899565b606091505b50509050806119105760405162461bcd60e51b815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d617920686176652072657665727465640000000000006064820152608401610559565b505050565b6001600160a01b038116811461056b57600080fd5b60006020828403121561193c57600080fd5b813561194781611915565b9392505050565b60006020828403121561196057600080fd5b5035919050565b80516001600160e01b0316825260209081015163ffffffff16910152565b602080825282518282018190526000919060409081850190868401855b828110156119c5576119b5848351611967565b92840192908501906001016119a2565b5091979650505050505050565b6000602082840312156119e457600080fd5b813560ff8116811461194757600080fd5b60008060408385031215611a0857600080fd5b8235611a1381611915565b946020939093013593505050565b60408101610b578284611967565b60008060408385031215611a4257600080fd5b8235611a4d81611915565b91506020830135611a5d81611915565b809150509250929050565b60008060008060808587031215611a7e57600080fd5b8435611a8981611915565b93506020850135611a9981611915565b93969395505050506040820135916060013590565b602080825282518282015282810151604080840181905281516060850181905260009392830191849160808701905b8084101561089b57611af0828651611967565b938501936001939093019290820190611add565b600060208284031215611b1657600080fd5b815161194781611915565b6020808252602a908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526939903ab73830bab9b2b960b11b606082015260800190565b600060208284031215611b7d57600080fd5b8151801515811461194757600080fd5b60208082526028908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526739903830bab9b2b960c11b606082015260800190565b634e487b7160e01b600052601160045260246000fd5b600082821015611bfd57611bfd611bd5565b500390565b60008219821115611c1557611c15611bd5565b500190565b634e487b7160e01b600052603260045260246000fd5b6000600019821415611c4457611c44611bd5565b5060010190565b634e487b7160e01b600052604160045260246000fd5b60208082526019908201527f5061757361626c653a20696e646578206973207061757365640000000000000060408201526060019056fea2646970667358221220ce2fc4b5cd610b8fde4bd0087332394fd991351e261b38fd93bbb9c91135e97264736f6c634300080c0033", - "storage": { - "0x0": "0xff" - } - }, - "0x2a264f26859166c5bf3868a54593ee716aebc848": { - "nonce": 1, - "balance": "0x0", - "code": "0x608060405234801561001057600080fd5b50600436106102275760003560e01c806394f649dd11610130578063c6656702116100b8578063df5cf7231161007c578063df5cf72314610596578063e7a050aa146105bd578063f2fde38b146105d0578063f698da25146105e3578063fabc1cbc146105eb57600080fd5b8063c665670214610520578063cbc2bd6214610533578063cd293f6f14610546578063cf756fdf14610570578063df5b35471461058357600080fd5b8063b43b514b116100ff578063b43b514b146104b1578063b5d8b5b8146104c4578063c3c6b3a9146104d7578063c4623ea1146104fa578063c608c7f31461050d57600080fd5b806394f649dd14610433578063967fc0d2146104545780639b4da03d14610467578063b13442711461048a57600080fd5b80635c975abb116101b35780637ecebe00116101825780637ecebe00146103b3578063886f1195146103d35780638b8aac3c146103e65780638c80d4e51461040f5780638da5cb5b1461042257600080fd5b80635c975abb14610355578063663c1de41461035d578063715018a6146103805780637a7e0d921461038857600080fd5b80634665bcda116101fa5780634665bcda146102a157806348825e94146102e05780634e5a426314610307578063595c6a671461031a5780635ac86ab71461032257600080fd5b806310d67a2f1461022c578063136439dd1461024157806320606b701461025457806332e89ace1461028e575b600080fd5b61023f61023a366004612a1e565b6105fe565b005b61023f61024f366004612a3b565b6106ba565b61027b7f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86681565b6040519081526020015b60405180910390f35b61027b61029c366004612ad4565b6107f9565b6102c87f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e81565b6040516001600160a01b039091168152602001610285565b61027b7f4337f82d142e41f2a8c10547cd8c859bddb92262a61058e77842e24d9dea922481565b61023f610315366004612bc1565b610a9d565b61023f610ad5565b610345610330366004612bfa565b609854600160ff9092169190911b9081161490565b6040519015158152602001610285565b60985461027b565b61034561036b366004612a1e565b60d16020526000908152604090205460ff1681565b61023f610b9c565b61027b610396366004612c1d565b60cd60209081526000928352604080842090915290825290205481565b61027b6103c1366004612a1e565b60ca6020526000908152604090205481565b6097546102c8906001600160a01b031681565b61027b6103f4366004612a1e565b6001600160a01b0316600090815260ce602052604090205490565b61023f61041d366004612c4b565b610bb0565b6033546001600160a01b03166102c8565b610446610441366004612a1e565b610c09565b604051610285929190612d00565b60cb546102c8906001600160a01b031681565b610345610475366004612a1e565b60d36020526000908152604090205460ff1681565b6102c87f0000000000000000000000000c8e79f3534b00d9a3d4a856b665bf4ebc22f2ba81565b61027b6104bf366004612e9e565b610d89565b61023f6104d2366004612fb9565b610dd6565b6103456104e5366004612a3b565b60cf6020526000908152604090205460ff1681565b61023f610508366004612ffb565b610f4a565b61023f61051b36600461304c565b610f9e565b61023f61052e366004612a1e565b611056565b6102c861054136600461309f565b611067565b610559610554366004612e9e565b61109f565b604080519215158352602083019190915201610285565b61023f61057e366004612ffb565b611133565b61023f6105913660046130cb565b611267565b6102c87f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af81565b61027b6105cb366004612c4b565b611490565b61023f6105de366004612a1e565b61150f565b61027b611585565b61023f6105f9366004612a3b565b6115c3565b609760009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610651573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106759190613137565b6001600160a01b0316336001600160a01b0316146106ae5760405162461bcd60e51b81526004016106a590613154565b60405180910390fd5b6106b78161171f565b50565b60975460405163237dfb4760e11b81523360048201526001600160a01b03909116906346fbf68e90602401602060405180830381865afa158015610702573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610726919061319e565b6107425760405162461bcd60e51b81526004016106a5906131bb565b609854818116146107bb5760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e70617573653a20696e76616c696420617474656d70742060448201527f746f20756e70617573652066756e6374696f6e616c697479000000000000000060648201526084016106a5565b609881905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d906020015b60405180910390a250565b6098546000908190600190811614156108505760405162461bcd60e51b815260206004820152601960248201527814185d5cd8589b194e881a5b99195e081a5cc81c185d5cd959603a1b60448201526064016106a5565b610858611816565b6001600160a01b038816600090815260d3602052604090205460ff16156108fa5760405162461bcd60e51b815260206004820152604a60248201527f53747261746567794d616e616765722e6465706f736974496e746f537472617460448201527f656779576974685369676e61747572653a207468697264207472616e736665726064820152691cc8191a5cd8589b195960b21b608482015260a4016106a5565b4284101561097c5760405162461bcd60e51b815260206004820152604360248201527f53747261746567794d616e616765722e6465706f736974496e746f537472617460448201527f656779576974685369676e61747572653a207369676e617475726520657870696064820152621c995960ea1b608482015260a4016106a5565b6001600160a01b03858116600081815260ca602090815260408083205481517f4337f82d142e41f2a8c10547cd8c859bddb92262a61058e77842e24d9dea922493810193909352908201939093528b84166060820152928a16608084015260a0830189905260c0830182905260e0830187905290916101000160408051601f1981840301815291815281516020928301206001600160a01b038a16600090815260ca9093529082206001850190559150610a34611585565b60405161190160f01b6020820152602281019190915260428101839052606201604051602081830303815290604052805190602001209050610a77888288611870565b610a83888c8c8c611a2f565b9450505050610a926001606555565b509695505050505050565b60cb546001600160a01b03163314610ac75760405162461bcd60e51b81526004016106a590613203565b610ad18282611bfe565b5050565b60975460405163237dfb4760e11b81523360048201526001600160a01b03909116906346fbf68e90602401602060405180830381865afa158015610b1d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b41919061319e565b610b5d5760405162461bcd60e51b81526004016106a5906131bb565b600019609881905560405190815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a2565b610ba4611c6c565b610bae6000611cc6565b565b336001600160a01b037f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af1614610bf85760405162461bcd60e51b81526004016106a59061326d565b610c03838383611d18565b50505050565b6001600160a01b038116600090815260ce60205260408120546060918291908167ffffffffffffffff811115610c4157610c41612a64565b604051908082528060200260200182016040528015610c6a578160200160208202803683370190505b50905060005b82811015610cfb576001600160a01b038616600090815260cd6020908152604080832060ce9092528220805491929184908110610caf57610caf6132cb565b60009182526020808320909101546001600160a01b031683528201929092526040019020548251839083908110610ce857610ce86132cb565b6020908102919091010152600101610c70565b5060ce6000866001600160a01b03166001600160a01b031681526020019081526020016000208181805480602002602001604051908101604052809291908181526020018280548015610d7757602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610d59575b50505050509150935093505050915091565b80516020808301516040808501516060860151608087015160a08801519351600097610db99790969591016132e1565b604051602081830303815290604052805190602001209050919050565b60cb546001600160a01b03163314610e005760405162461bcd60e51b81526004016106a590613203565b8060005b81811015610c035760d16000858584818110610e2257610e226132cb565b9050602002016020810190610e379190612a1e565b6001600160a01b0316815260208101919091526040016000205460ff1615610f4257600060d16000868685818110610e7157610e716132cb565b9050602002016020810190610e869190612a1e565b6001600160a01b031681526020810191909152604001600020805460ff19169115159190911790557f4074413b4b443e4e58019f2855a8765113358c7c72e39509c6af45fc0f5ba030848483818110610ee157610ee16132cb565b9050602002016020810190610ef69190612a1e565b6040516001600160a01b03909116815260200160405180910390a1610f42848483818110610f2657610f266132cb565b9050602002016020810190610f3b9190612a1e565b6000611bfe565b600101610e04565b336001600160a01b037f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af1614610f925760405162461bcd60e51b81526004016106a59061326d565b610c0384848484611e74565b336001600160a01b037f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af1614610fe65760405162461bcd60e51b81526004016106a59061326d565b604051636ce5768960e11b81526001600160a01b03858116600483015282811660248301526044820184905284169063d9caed1290606401600060405180830381600087803b15801561103857600080fd5b505af115801561104c573d6000803e3d6000fd5b5050505050505050565b61105e611c6c565b6106b781612101565b60ce602052816000526040600020818154811061108357600080fd5b6000918252602090912001546001600160a01b03169150829050565b600080336001600160a01b037f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af16146110ea5760405162461bcd60e51b81526004016106a59061326d565b60006110f584610d89565b600081815260cf60205260408120549192509060ff161561112a5750600081815260cf60205260409020805460ff1916905560015b92509050915091565b600054610100900460ff16158080156111535750600054600160ff909116105b8061116d5750303b15801561116d575060005460ff166001145b6111d05760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016106a5565b6000805460ff1916600117905580156111f3576000805461ff0019166101001790555b6111fb61216a565b60c9556112088383612201565b61121185611cc6565b61121a84612101565b8015611260576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050565b60cb546001600160a01b031633146112915760405162461bcd60e51b81526004016106a590613203565b82811461131a5760405162461bcd60e51b815260206004820152604b60248201527f53747261746567794d616e616765722e61646453747261746567696573546f4460448201527f65706f73697457686974656c6973743a206172726179206c656e67746873206460648201526a0de40dcdee840dac2e8c6d60ab1b608482015260a4016106a5565b8260005b818110156114885760d1600087878481811061133c5761133c6132cb565b90506020020160208101906113519190612a1e565b6001600160a01b0316815260208101919091526040016000205460ff1661148057600160d1600088888581811061138a5761138a6132cb565b905060200201602081019061139f9190612a1e565b6001600160a01b031681526020810191909152604001600020805460ff19169115159190911790557f0c35b17d91c96eb2751cd456e1252f42a386e524ef9ff26ecc9950859fdc04fe8686838181106113fa576113fa6132cb565b905060200201602081019061140f9190612a1e565b6040516001600160a01b03909116815260200160405180910390a161148086868381811061143f5761143f6132cb565b90506020020160208101906114549190612a1e565b858584818110611466576114666132cb565b905060200201602081019061147b919061335a565b611bfe565b60010161131e565b505050505050565b6098546000908190600190811614156114e75760405162461bcd60e51b815260206004820152601960248201527814185d5cd8589b194e881a5b99195e081a5cc81c185d5cd959603a1b60448201526064016106a5565b6114ef611816565b6114fb33868686611a2f565b91506115076001606555565b509392505050565b611517611c6c565b6001600160a01b03811661157c5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016106a5565b6106b781611cc6565b60007f0000000000000000000000000000000000000000000000000000000000007a694614156115b6575060c95490565b6115be61216a565b905090565b609760009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611616573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061163a9190613137565b6001600160a01b0316336001600160a01b03161461166a5760405162461bcd60e51b81526004016106a590613154565b6098541981196098541916146116e85760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e756e70617573653a20696e76616c696420617474656d7060448201527f7420746f2070617573652066756e6374696f6e616c697479000000000000000060648201526084016106a5565b609881905560405181815233907f3582d1828e26bf56bd801502bc021ac0bc8afb57c826e4986b45593c8fad389c906020016107ee565b6001600160a01b0381166117ad5760405162461bcd60e51b815260206004820152604960248201527f5061757361626c652e5f73657450617573657252656769737472793a206e657760448201527f50617573657252656769737472792063616e6e6f7420626520746865207a65726064820152686f206164647265737360b81b608482015260a4016106a5565b609754604080516001600160a01b03928316815291831660208301527f6e9fcd539896fca60e8b0f01dd580233e48a6b0f7df013b89ba7f565869acdb6910160405180910390a1609780546001600160a01b0319166001600160a01b0392909216919091179055565b600260655414156118695760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064016106a5565b6002606555565b6001600160a01b0383163b1561198f57604051630b135d3f60e11b808252906001600160a01b03851690631626ba7e906118b090869086906004016133cf565b602060405180830381865afa1580156118cd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118f191906133e8565b6001600160e01b0319161461198a5760405162461bcd60e51b815260206004820152605360248201527f454950313237315369676e61747572655574696c732e636865636b5369676e6160448201527f747572655f454950313237313a2045524331323731207369676e6174757265206064820152721d995c9a599a58d85d1a5bdb8819985a5b1959606a1b608482015260a4016106a5565b505050565b826001600160a01b03166119a383836122e7565b6001600160a01b03161461198a5760405162461bcd60e51b815260206004820152604760248201527f454950313237315369676e61747572655574696c732e636865636b5369676e6160448201527f747572655f454950313237313a207369676e6174757265206e6f742066726f6d6064820152661039b4b3b732b960c91b608482015260a4016106a5565b6001600160a01b038316600090815260d16020526040812054849060ff16611ad55760405162461bcd60e51b815260206004820152604d60248201527f53747261746567794d616e616765722e6f6e6c7953747261746567696573576860448201527f6974656c6973746564466f724465706f7369743a207374726174656779206e6f60648201526c1d081dda1a5d195b1a5cdd1959609a1b608482015260a4016106a5565b611aea6001600160a01b038516338786612303565b6040516311f9fbc960e21b81526001600160a01b038581166004830152602482018590528616906347e7ef24906044016020604051808303816000875af1158015611b39573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b5d9190613412565b9150611b6b86858785611e74565b604051631452b9d760e11b81526001600160a01b0387811660048301528681166024830152604482018490527f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af16906328a573ae90606401600060405180830381600087803b158015611bdd57600080fd5b505af1158015611bf1573d6000803e3d6000fd5b5050505050949350505050565b604080516001600160a01b038416815282151560208201527f77d930df4937793473a95024d87a98fd2ccb9e92d3c2463b3dacd65d3e6a5786910160405180910390a16001600160a01b0391909116600090815260d360205260409020805460ff1916911515919091179055565b6033546001600160a01b03163314610bae5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016106a5565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600081611d8d5760405162461bcd60e51b815260206004820152603e60248201527f53747261746567794d616e616765722e5f72656d6f76655368617265733a207360448201527f68617265416d6f756e742073686f756c64206e6f74206265207a65726f21000060648201526084016106a5565b6001600160a01b03808516600090815260cd602090815260408083209387168352929052205480831115611e1f5760405162461bcd60e51b815260206004820152603360248201527f53747261746567794d616e616765722e5f72656d6f76655368617265733a20736044820152720d0c2e4ca82dadeeadce840e8dede40d0d2ced606b1b60648201526084016106a5565b6001600160a01b03808616600090815260cd602090815260408083209388168352929052208382039081905590831415611e6757611e5d858561235d565b6001915050611e6d565b60009150505b9392505050565b6001600160a01b038416611ef05760405162461bcd60e51b815260206004820152603960248201527f53747261746567794d616e616765722e5f6164645368617265733a207374616b60448201527f65722063616e6e6f74206265207a65726f20616464726573730000000000000060648201526084016106a5565b80611f5c5760405162461bcd60e51b815260206004820152603660248201527f53747261746567794d616e616765722e5f6164645368617265733a207368617260448201527565732073686f756c64206e6f74206265207a65726f2160501b60648201526084016106a5565b6001600160a01b03808516600090815260cd602090815260408083209386168352929052205461206d576001600160a01b038416600090815260ce60209081526040909120541061202e5760405162461bcd60e51b815260206004820152605060248201527f53747261746567794d616e616765722e5f6164645368617265733a206465706f60448201527f73697420776f756c6420657863656564204d41585f5354414b45525f5354524160648201526f0a88a8eb2be9892a6a8be988a9c8ea8960831b608482015260a4016106a5565b6001600160a01b03848116600090815260ce602090815260408220805460018101825590835291200180546001600160a01b0319169184169190911790555b6001600160a01b03808516600090815260cd60209081526040808320938616835292905290812080548392906120a4908490613441565b9091555050604080516001600160a01b03868116825285811660208301528416818301526060810183905290517f7cfff908a4b583f36430b25d75964c458d8ede8a99bd61be750e97ee1b2f3a969181900360800190a150505050565b60cb54604080516001600160a01b03928316815291831660208301527f4264275e593955ff9d6146a51a4525f6ddace2e81db9391abcc9d1ca48047d29910160405180910390a160cb80546001600160a01b0319166001600160a01b0392909216919091179055565b604080518082018252600a81526922b4b3b2b72630bcb2b960b11b60209182015281517f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a866818301527f71b625cfad44bac63b13dba07f2e1d6084ee04b6f8752101ece6126d584ee6ea81840152466060820152306080808301919091528351808303909101815260a0909101909252815191012090565b6097546001600160a01b031615801561222257506001600160a01b03821615155b6122a45760405162461bcd60e51b815260206004820152604760248201527f5061757361626c652e5f696e697469616c697a655061757365723a205f696e6960448201527f7469616c697a6550617573657228292063616e206f6e6c792062652063616c6c6064820152666564206f6e636560c81b608482015260a4016106a5565b609881905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a2610ad18261171f565b60008060006122f6858561254f565b9150915061150781612595565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180516001600160e01b03166323b872dd60e01b179052610c039085906126e3565b6001600160a01b038216600090815260ce6020526040812054905b81811015612478576001600160a01b03848116600090815260ce60205260409020805491851691839081106123af576123af6132cb565b6000918252602090912001546001600160a01b03161415612470576001600160a01b038416600090815260ce6020526040902080546123f090600190613459565b81548110612400576124006132cb565b60009182526020808320909101546001600160a01b03878116845260ce909252604090922080549190921691908390811061243d5761243d6132cb565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b03160217905550612478565b600101612378565b818114156125005760405162461bcd60e51b815260206004820152604960248201527f53747261746567794d616e616765722e5f72656d6f766553747261746567794660448201527f726f6d5374616b657253747261746567794c6973743a207374726174656779206064820152681b9bdd08199bdd5b9960ba1b608482015260a4016106a5565b6001600160a01b038416600090815260ce6020526040902080548061252757612527613470565b600082815260209020810160001990810180546001600160a01b031916905501905550505050565b6000808251604114156125865760208301516040840151606085015160001a61257a878285856127b8565b9450945050505061258e565b506000905060025b9250929050565b60008160048111156125a9576125a9613486565b14156125b25750565b60018160048111156125c6576125c6613486565b14156126145760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e6174757265000000000000000060448201526064016106a5565b600281600481111561262857612628613486565b14156126765760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e6774680060448201526064016106a5565b600381600481111561268a5761268a613486565b14156106b75760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b60648201526084016106a5565b6000612738826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031661287c9092919063ffffffff16565b9050805160001480612759575080806020019051810190612759919061319e565b61198a5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016106a5565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08311156127ef5750600090506003612873565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015612843573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811661286c57600060019250925050612873565b9150600090505b94509492505050565b606061288b8484600085612893565b949350505050565b6060824710156128f45760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016106a5565b600080866001600160a01b03168587604051612910919061349c565b60006040518083038185875af1925050503d806000811461294d576040519150601f19603f3d011682016040523d82523d6000602084013e612952565b606091505b50915091506129638783838761296e565b979650505050505050565b606083156129da5782516129d3576001600160a01b0385163b6129d35760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016106a5565b508161288b565b61288b83838151156129ef5781518083602001fd5b8060405162461bcd60e51b81526004016106a591906134b8565b6001600160a01b03811681146106b757600080fd5b600060208284031215612a3057600080fd5b8135611e6d81612a09565b600060208284031215612a4d57600080fd5b5035919050565b8035612a5f81612a09565b919050565b634e487b7160e01b600052604160045260246000fd5b60405160c0810167ffffffffffffffff81118282101715612a9d57612a9d612a64565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715612acc57612acc612a64565b604052919050565b60008060008060008060c08789031215612aed57600080fd5b8635612af881612a09565b9550602087810135612b0981612a09565b9550604088013594506060880135612b2081612a09565b93506080880135925060a088013567ffffffffffffffff80821115612b4457600080fd5b818a0191508a601f830112612b5857600080fd5b813581811115612b6a57612b6a612a64565b612b7c601f8201601f19168501612aa3565b91508082528b84828501011115612b9257600080fd5b80848401858401376000848284010152508093505050509295509295509295565b80151581146106b757600080fd5b60008060408385031215612bd457600080fd5b8235612bdf81612a09565b91506020830135612bef81612bb3565b809150509250929050565b600060208284031215612c0c57600080fd5b813560ff81168114611e6d57600080fd5b60008060408385031215612c3057600080fd5b8235612c3b81612a09565b91506020830135612bef81612a09565b600080600060608486031215612c6057600080fd5b8335612c6b81612a09565b92506020840135612c7b81612a09565b929592945050506040919091013590565b600081518084526020808501945080840160005b83811015612cc55781516001600160a01b031687529582019590820190600101612ca0565b509495945050505050565b600081518084526020808501945080840160005b83811015612cc557815187529582019590820190600101612ce4565b604081526000612d136040830185612c8c565b8281036020840152612d258185612cd0565b95945050505050565b600067ffffffffffffffff821115612d4857612d48612a64565b5060051b60200190565b600082601f830112612d6357600080fd5b81356020612d78612d7383612d2e565b612aa3565b82815260059290921b84018101918181019086841115612d9757600080fd5b8286015b84811015610a92578035612dae81612a09565b8352918301918301612d9b565b600082601f830112612dcc57600080fd5b81356020612ddc612d7383612d2e565b82815260059290921b84018101918181019086841115612dfb57600080fd5b8286015b84811015610a925780358352918301918301612dff565b600060408284031215612e2857600080fd5b6040516040810181811067ffffffffffffffff82111715612e4b57612e4b612a64565b6040529050808235612e5c81612a09565b815260208301356bffffffffffffffffffffffff81168114612e7d57600080fd5b6020919091015292915050565b803563ffffffff81168114612a5f57600080fd5b600060208284031215612eb057600080fd5b813567ffffffffffffffff80821115612ec857600080fd5b9083019060e08286031215612edc57600080fd5b612ee4612a7a565b823582811115612ef357600080fd5b612eff87828601612d52565b825250602083013582811115612f1457600080fd5b612f2087828601612dbb565b602083015250612f3260408401612a54565b6040820152612f448660608501612e16565b6060820152612f5560a08401612e8a565b6080820152612f6660c08401612a54565b60a082015295945050505050565b60008083601f840112612f8657600080fd5b50813567ffffffffffffffff811115612f9e57600080fd5b6020830191508360208260051b850101111561258e57600080fd5b60008060208385031215612fcc57600080fd5b823567ffffffffffffffff811115612fe357600080fd5b612fef85828601612f74565b90969095509350505050565b6000806000806080858703121561301157600080fd5b843561301c81612a09565b9350602085013561302c81612a09565b9250604085013561303c81612a09565b9396929550929360600135925050565b6000806000806080858703121561306257600080fd5b843561306d81612a09565b9350602085013561307d81612a09565b925060408501359150606085013561309481612a09565b939692955090935050565b600080604083850312156130b257600080fd5b82356130bd81612a09565b946020939093013593505050565b600080600080604085870312156130e157600080fd5b843567ffffffffffffffff808211156130f957600080fd5b61310588838901612f74565b9096509450602087013591508082111561311e57600080fd5b5061312b87828801612f74565b95989497509550505050565b60006020828403121561314957600080fd5b8151611e6d81612a09565b6020808252602a908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526939903ab73830bab9b2b960b11b606082015260800190565b6000602082840312156131b057600080fd5b8151611e6d81612bb3565b60208082526028908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526739903830bab9b2b960c11b606082015260800190565b60208082526044908201527f53747261746567794d616e616765722e6f6e6c7953747261746567795768697460408201527f656c69737465723a206e6f742074686520737472617465677957686974656c6960608201526339ba32b960e11b608082015260a00190565b602080825260409082018190527f53747261746567794d616e616765722e6f6e6c7944656c65676174696f6e4d61908201527f6e616765723a206e6f74207468652044656c65676174696f6e4d616e61676572606082015260800190565b634e487b7160e01b600052603260045260246000fd5b60e0815260006132f460e0830189612c8c565b82810360208401526133068189612cd0565b6001600160a01b0397881660408501528651881660608501526020909601516bffffffffffffffffffffffff166080840152505063ffffffff9290921660a083015290921660c09092019190915292915050565b60006020828403121561336c57600080fd5b8135611e6d81612bb3565b60005b8381101561339257818101518382015260200161337a565b83811115610c035750506000910152565b600081518084526133bb816020860160208601613377565b601f01601f19169290920160200192915050565b82815260406020820152600061288b60408301846133a3565b6000602082840312156133fa57600080fd5b81516001600160e01b031981168114611e6d57600080fd5b60006020828403121561342457600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b600082198211156134545761345461342b565b500190565b60008282101561346b5761346b61342b565b500390565b634e487b7160e01b600052603160045260246000fd5b634e487b7160e01b600052602160045260246000fd5b600082516134ae818460208701613377565b9190910192915050565b602081526000611e6d60208301846133a356fea2646970667358221220c4d14edc8209c4112dca230018f1f879727d443599b024a8f5a55e488a4e312564736f6c634300080c0033", - "storage": { - "0x0": "0xff" - } - }, - "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": { - "nonce": 0, - "balance": "0x21e19e0c9bab2400000", - "code": "0x", - "storage": {} - }, - "0x45009dd3abbe29db54fc5d893ceaa98a624882df": { - "nonce": 1, - "balance": "0x0", - "code": "0x608060405234801561001057600080fd5b50600436106100b45760003560e01c806340c10f191161007157806340c10f191461014157806370a082311461015657806395d89b411461017f578063a457c2d714610187578063a9059cbb1461019a578063dd62ed3e146101ad57600080fd5b806306fdde03146100b9578063095ea7b3146100d757806318160ddd146100fa57806323b872dd1461010c578063313ce5671461011f578063395093511461012e575b600080fd5b6100c16101c0565b6040516100ce9190610787565b60405180910390f35b6100ea6100e53660046107f8565b610252565b60405190151581526020016100ce565b6002545b6040519081526020016100ce565b6100ea61011a366004610822565b61026a565b604051601281526020016100ce565b6100ea61013c3660046107f8565b61028e565b61015461014f3660046107f8565b6102b0565b005b6100fe61016436600461085e565b6001600160a01b031660009081526020819052604090205490565b6100c16102be565b6100ea6101953660046107f8565b6102cd565b6100ea6101a83660046107f8565b61034d565b6100fe6101bb366004610880565b61035b565b6060600380546101cf906108b3565b80601f01602080910402602001604051908101604052809291908181526020018280546101fb906108b3565b80156102485780601f1061021d57610100808354040283529160200191610248565b820191906000526020600020905b81548152906001019060200180831161022b57829003601f168201915b5050505050905090565b600033610260818585610386565b5060019392505050565b6000336102788582856104aa565b610283858585610524565b506001949350505050565b6000336102608185856102a1838361035b565b6102ab91906108ee565b610386565b6102ba82826106c8565b5050565b6060600480546101cf906108b3565b600033816102db828661035b565b9050838110156103405760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b6102838286868403610386565b600033610260818585610524565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103e85760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610337565b6001600160a01b0382166104495760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610337565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006104b6848461035b565b9050600019811461051e57818110156105115760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610337565b61051e8484848403610386565b50505050565b6001600160a01b0383166105885760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610337565b6001600160a01b0382166105ea5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610337565b6001600160a01b038316600090815260208190526040902054818110156106625760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610337565b6001600160a01b03848116600081815260208181526040808320878703905593871680835291849020805487019055925185815290927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a361051e565b6001600160a01b03821661071e5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610337565b806002600082825461073091906108ee565b90915550506001600160a01b038216600081815260208181526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b818110156107b457858101830151858201604001528201610798565b818111156107c6576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b03811681146107f357600080fd5b919050565b6000806040838503121561080b57600080fd5b610814836107dc565b946020939093013593505050565b60008060006060848603121561083757600080fd5b610840846107dc565b925061084e602085016107dc565b9150604084013590509250925092565b60006020828403121561087057600080fd5b610879826107dc565b9392505050565b6000806040838503121561089357600080fd5b61089c836107dc565b91506108aa602084016107dc565b90509250929050565b600181811c908216806108c757607f821691505b602082108114156108e857634e487b7160e01b600052602260045260246000fd5b50919050565b6000821982111561090f57634e487b7160e01b600052601160045260246000fd5b50019056fea2646970667358221220ad178920ddad0e9ccdede99a0ebf77eb0e2987daf014917631edf4b9b145843b64736f6c634300080c0033", - "storage": { - "0x3": "0x7765746800000000000000000000000000000000000000000000000000000008", - "0x4": "0x5745544800000000000000000000000000000000000000000000000000000008" - } - }, - "0x4e59b44847b379578588920ca78fbf26c0b4956c": { - "nonce": 0, - "balance": "0x0", - "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", - "storage": {} - }, - "0x700b6a60ce7eaaea56f065753d8dcb9653dbad35": { - "nonce": 1, - "balance": "0x0", - "code": "0x60806040526004361061007b5760003560e01c80639623609d1161004e5780639623609d1461011157806399a88ec414610124578063f2fde38b14610144578063f3b7dead1461016457600080fd5b8063204e1c7a14610080578063715018a6146100bc5780637eff275e146100d35780638da5cb5b146100f3575b600080fd5b34801561008c57600080fd5b506100a061009b366004610499565b610184565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c857600080fd5b506100d1610215565b005b3480156100df57600080fd5b506100d16100ee3660046104bd565b610229565b3480156100ff57600080fd5b506000546001600160a01b03166100a0565b6100d161011f36600461050c565b610291565b34801561013057600080fd5b506100d161013f3660046104bd565b610300565b34801561015057600080fd5b506100d161015f366004610499565b610336565b34801561017057600080fd5b506100a061017f366004610499565b6103b4565b6000806000836001600160a01b03166040516101aa90635c60da1b60e01b815260040190565b600060405180830381855afa9150503d80600081146101e5576040519150601f19603f3d011682016040523d82523d6000602084013e6101ea565b606091505b5091509150816101f957600080fd5b8080602001905181019061020d91906105e2565b949350505050565b61021d6103da565b6102276000610434565b565b6102316103da565b6040516308f2839760e41b81526001600160a01b038281166004830152831690638f283970906024015b600060405180830381600087803b15801561027557600080fd5b505af1158015610289573d6000803e3d6000fd5b505050505050565b6102996103da565b60405163278f794360e11b81526001600160a01b03841690634f1ef2869034906102c990869086906004016105ff565b6000604051808303818588803b1580156102e257600080fd5b505af11580156102f6573d6000803e3d6000fd5b5050505050505050565b6103086103da565b604051631b2ce7f360e11b81526001600160a01b038281166004830152831690633659cfe69060240161025b565b61033e6103da565b6001600160a01b0381166103a85760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b6103b181610434565b50565b6000806000836001600160a01b03166040516101aa906303e1469160e61b815260040190565b6000546001600160a01b031633146102275760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161039f565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146103b157600080fd5b6000602082840312156104ab57600080fd5b81356104b681610484565b9392505050565b600080604083850312156104d057600080fd5b82356104db81610484565b915060208301356104eb81610484565b809150509250929050565b634e487b7160e01b600052604160045260246000fd5b60008060006060848603121561052157600080fd5b833561052c81610484565b9250602084013561053c81610484565b9150604084013567ffffffffffffffff8082111561055957600080fd5b818601915086601f83011261056d57600080fd5b81358181111561057f5761057f6104f6565b604051601f8201601f19908116603f011681019083821181831017156105a7576105a76104f6565b816040528281528960208487010111156105c057600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b6000602082840312156105f457600080fd5b81516104b681610484565b60018060a01b038316815260006020604081840152835180604085015260005b8181101561063b5785810183015185820160600152820161061f565b8181111561064d576000606083870101525b50601f01601f19169290920160600194935050505056fea2646970667358221220c05fd01e9cb5a6d57ff48f40dd8e3c410a74fcc7c0c567cb80c11c5959dda6a164736f6c634300080c0033", - "storage": { - "0x0": "0xa0ee7a142d267c1f36714e4a8f75612f20a79720" - } - }, - "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { - "nonce": 0, - "balance": "0x21e19e0c9bab2400000", - "code": "0x", - "storage": {} - }, - "0x82c6d3ed4cd33d8ec1e51d0b5cc1d822eaa0c3dc": { - "nonce": 1, - "balance": "0x0", - "code": "0x608060405234801561001057600080fd5b50600436106103785760003560e01c806360d7faed116101d3578063b7f06ebe11610104578063cf80873e116100a2578063f16172b01161007c578063f16172b01461097d578063f2fde38b14610990578063f698da25146109a3578063fabc1cbc146109ab57600080fd5b8063cf80873e14610936578063da8be86414610957578063eea9064b1461096a57600080fd5b8063c488375a116100de578063c488375a14610853578063c5e480db14610873578063c94b511114610919578063ca661c041461092c57600080fd5b8063b7f06ebe146107f9578063bb45fef21461081c578063c448feb81461084a57600080fd5b8063886f1195116101715780639104c3191161014b5780639104c3191461078457806399be81c81461079f578063a1788484146107b2578063b1344271146107d257600080fd5b8063886f1195146107405780638da5cb5b14610753578063900413471461076457600080fd5b80636d70f7ae116101ad5780636d70f7ae146106e7578063715018a6146106fa578063778e55f3146107025780637f5480711461072d57600080fd5b806360d7faed14610698578063635bbd10146106ab57806365da1264146106be57600080fd5b806329c77d4f116102ad5780634fc40b611161024b5780635ac86ab7116102255780635ac86ab71461062e5780635c975abb146106515780635cfe8d2c146106595780635f966f141461066c57600080fd5b80634fc40b6114610609578063595c6a6714610613578063597b36da1461061b57600080fd5b80633cdeb5e0116102875780633cdeb5e0146105695780633e28391d1461059857806343377382146105bb5780634665bcda146105e257600080fd5b806329c77d4f146104f7578063334043961461051757806339b70e381461052a57600080fd5b8063136439dd1161031a5780631bbce091116102f45780631bbce0911461049757806320606b70146104aa57806322bf40e4146104d157806328a573ae146104e457600080fd5b8063136439dd146104385780631522bf021461044b578063169283651461045e57600080fd5b80630dd8dd02116103565780630dd8dd02146103dd5780630f589e59146103fd57806310d67a2f14610412578063132d49671461042557600080fd5b80630449ca391461037d57806304a4f979146103a35780630b9f487a146103ca575b600080fd5b61039061038b366004614993565b6109be565b6040519081526020015b60405180910390f35b6103907f14bde674c9f64b2ad00eaaee4a8bed1fabef35c7507e3c5b9cfc9436909a2dad81565b6103906103d83660046149f9565b610a43565b6103f06103eb366004614993565b610b05565b60405161039a9190614a54565b61041061040b366004614af1565b610e6e565b005b610410610420366004614b44565b610fbe565b610410610433366004614b68565b611071565b610410610446366004614ba9565b611128565b610410610459366004614bc2565b611267565b61039061046c366004614b44565b6001600160a01b0316600090815260996020526040902060010154600160a01b900463ffffffff1690565b6103906104a5366004614b68565b61127b565b6103907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86681565b6104106104df366004614c2d565b6112a9565b6104106104f2366004614b68565b6113ed565b610390610505366004614b44565b609b6020526000908152604090205481565b610410610525366004614cd4565b61149d565b6105517f000000000000000000000000e1da8919f262ee86f9be05059c9280142cf23f4881565b6040516001600160a01b03909116815260200161039a565b610551610577366004614b44565b6001600160a01b039081166000908152609960205260409020600101541690565b6105ab6105a6366004614b44565b611584565b604051901515815260200161039a565b6103907f39111bc4a4d688e1f685123d7497d4615370152a8ee4a0593e647bd06ad8bb0b81565b6105517f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e81565b6103906213c68081565b6104106115a4565b610390610629366004614ff3565b61166b565b6105ab61063c36600461502f565b606654600160ff9092169190911b9081161490565b606654610390565b6104106106673660046150a5565b61169b565b61055161067a366004614b44565b6001600160a01b039081166000908152609960205260409020541690565b6104106106a6366004615205565b611946565b6104106106b9366004614ba9565b611996565b6105516106cc366004614b44565b609a602052600090815260409020546001600160a01b031681565b6105ab6106f5366004614b44565b6119a7565b6104106119c7565b610390610710366004615294565b609860209081526000928352604080842090915290825290205481565b61041061073b366004615375565b6119db565b606554610551906001600160a01b031681565b6033546001600160a01b0316610551565b610777610772366004615405565b611ae0565b60405161039a919061548f565b61055173beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac081565b6104106107ad3660046154a2565b611bba565b6103906107c0366004614b44565b609f6020526000908152604090205481565b6105517f0000000000000000000000000c8e79f3534b00d9a3d4a856b665bf4ebc22f2ba81565b6105ab610807366004614ba9565b609e6020526000908152604090205460ff1681565b6105ab61082a3660046154d7565b609c60209081526000928352604080842090915290825290205460ff1681565b610390609d5481565b610390610861366004614b44565b60a16020526000908152604090205481565b6108e3610881366004614b44565b6040805160608082018352600080835260208084018290529284018190526001600160a01b03948516815260998352839020835191820184528054851682526001015493841691810191909152600160a01b90920463ffffffff169082015290565b6040805182516001600160a01b039081168252602080850151909116908201529181015163ffffffff169082015260600161039a565b610390610927366004615503565b611c8c565b61039062034bc081565b610949610944366004614b44565b611d45565b60405161039a929190615584565b6103f0610965366004614b44565b6120fd565b6104106109783660046155a9565b6125c1565b61041061098b366004615601565b6125cd565b61041061099e366004614b44565b61265e565b6103906126d4565b6104106109b9366004614ba9565b612712565b609d54600090815b83811015610a3b57600060a160008787858181106109e6576109e661561d565b90506020020160208101906109fb9190614b44565b6001600160a01b03166001600160a01b0316815260200190815260200160002054905082811115610a2a578092505b50610a3481615649565b90506109c6565b509392505050565b604080517f14bde674c9f64b2ad00eaaee4a8bed1fabef35c7507e3c5b9cfc9436909a2dad6020808301919091526001600160a01b038681168385015288811660608401528716608083015260a0820185905260c08083018590528351808403909101815260e0909201909252805191012060009081610ac16126d4565b60405161190160f01b602082015260228101919091526042810183905260620160408051808303601f19018152919052805160209091012098975050505050505050565b60665460609060019060029081161415610b3a5760405162461bcd60e51b8152600401610b3190615664565b60405180910390fd5b6000836001600160401b03811115610b5457610b54614d76565b604051908082528060200260200182016040528015610b7d578160200160208202803683370190505b50336000908152609a60205260408120549192506001600160a01b03909116905b85811015610e6357868682818110610bb857610bb861561d565b9050602002810190610bca919061569b565b610bd89060208101906156bb565b9050878783818110610bec57610bec61561d565b9050602002810190610bfe919061569b565b610c0890806156bb565b905014610c7d5760405162461bcd60e51b815260206004820152603860248201527f44656c65676174696f6e4d616e616765722e717565756557697468647261776160448201527f6c3a20696e707574206c656e677468206d69736d6174636800000000000000006064820152608401610b31565b33878783818110610c9057610c9061561d565b9050602002810190610ca2919061569b565b610cb3906060810190604001614b44565b6001600160a01b031614610d2f5760405162461bcd60e51b815260206004820152603c60248201527f44656c65676174696f6e4d616e616765722e717565756557697468647261776160448201527f6c3a2077697468647261776572206d757374206265207374616b6572000000006064820152608401610b31565b610e343383898985818110610d4657610d4661561d565b9050602002810190610d58919061569b565b610d69906060810190604001614b44565b8a8a86818110610d7b57610d7b61561d565b9050602002810190610d8d919061569b565b610d9790806156bb565b808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508e92508d9150889050818110610ddd57610ddd61561d565b9050602002810190610def919061569b565b610dfd9060208101906156bb565b8080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061286e92505050565b838281518110610e4657610e4661561d565b602090810291909101015280610e5b81615649565b915050610b9e565b509095945050505050565b336000908152609960205260409020546001600160a01b031615610f085760405162461bcd60e51b815260206004820152604560248201527f44656c65676174696f6e4d616e616765722e726567697374657241734f70657260448201527f61746f723a206f70657261746f722068617320616c72656164792072656769736064820152641d195c995960da1b608482015260a401610b31565b610f123384612e2e565b604080518082019091526060815260006020820152610f3433808360006130ca565b336001600160a01b03167f8e8485583a2310d41f7c82b9427d0bd49bad74bb9cff9d3402a29d8f9b28a0e285604051610f6d9190615704565b60405180910390a2336001600160a01b03167f02a919ed0e2acad1dd90f17ef2fa4ae5462ee1339170034a8531cca4b67080908484604051610fb0929190615756565b60405180910390a250505050565b606560009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611011573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110359190615785565b6001600160a01b0316336001600160a01b0316146110655760405162461bcd60e51b8152600401610b31906157a2565b61106e8161346f565b50565b336001600160a01b037f000000000000000000000000e1da8919f262ee86f9be05059c9280142cf23f481614806110d05750336001600160a01b037f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e16145b6110ec5760405162461bcd60e51b8152600401610b31906157ec565b6110f583611584565b15611123576001600160a01b038084166000908152609a60205260409020541661112181858585613566565b505b505050565b60655460405163237dfb4760e11b81523360048201526001600160a01b03909116906346fbf68e90602401602060405180830381865afa158015611170573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111949190615849565b6111b05760405162461bcd60e51b8152600401610b3190615866565b606654818116146112295760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e70617573653a20696e76616c696420617474656d70742060448201527f746f20756e70617573652066756e6374696f6e616c69747900000000000000006064820152608401610b31565b606681905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d906020015b60405180910390a250565b61126f6135e1565b6111218484848461363b565b6001600160a01b0383166000908152609b60205260408120546112a085828686611c8c565b95945050505050565b600054610100900460ff16158080156112c95750600054600160ff909116105b806112e35750303b1580156112e3575060005460ff166001145b6113465760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610b31565b6000805460ff191660011790558015611369576000805461ff0019166101001790555b6113738888613859565b61137b61393f565b609755611387896139d6565b61139086613a28565b61139c8585858561363b565b80156113e2576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050505050565b336001600160a01b037f000000000000000000000000e1da8919f262ee86f9be05059c9280142cf23f4816148061144c5750336001600160a01b037f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e16145b6114685760405162461bcd60e51b8152600401610b31906157ec565b61147183611584565b15611123576001600160a01b038084166000908152609a60205260409020541661112181858585613b22565b606654600290600490811614156114c65760405162461bcd60e51b8152600401610b3190615664565b6114ce613b9d565b60005b88811015611579576115698a8a838181106114ee576114ee61561d565b905060200281019061150091906158ae565b8989848181106115125761151261561d565b905060200281019061152491906156bb565b8989868181106115365761153661561d565b9050602002013588888781811061154f5761154f61561d565b905060200201602081019061156491906158c4565b613bf7565b61157281615649565b90506114d1565b506113e2600160c955565b6001600160a01b039081166000908152609a602052604090205416151590565b60655460405163237dfb4760e11b81523360048201526001600160a01b03909116906346fbf68e90602401602060405180830381865afa1580156115ec573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116109190615849565b61162c5760405162461bcd60e51b8152600401610b3190615866565b600019606681905560405190815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a2565b60008160405160200161167e9190615955565b604051602081830303815290604052805190602001209050919050565b60005b81518110156119425760008282815181106116bb576116bb61561d565b602002602001015190506000807f000000000000000000000000e1da8919f262ee86f9be05059c9280142cf23f486001600160a01b031663cd293f6f846040518263ffffffff1660e01b81526004016117149190615968565b60408051808303816000875af1158015611732573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117569190615a14565b915091508115611934576040808401516001600160a01b0381166000908152609f6020529182208054919282919061178d83615649565b919050555060006040518060e00160405280846001600160a01b031681526020018760a001516001600160a01b031681526020018760600151600001516001600160a01b03168152602001838152602001876080015163ffffffff1681526020018760000151815260200187602001518152509050600061180d8261166b565b6000818152609e602052604090205490915060ff16156118a35760405162461bcd60e51b815260206004820152604560248201527f44656c65676174696f6e4d616e616765722e6d6967726174655175657565645760448201527f69746864726177616c733a207769746864726177616c20616c72656164792065606482015264786973747360d81b608482015260a401610b31565b6000818152609e602052604090819020805460ff19166001179055517f9009ab153e8014fbfb02f2217f5cde7aa7f9ad734ae85ca3ee3f4ca2fdd499f9906118ee9083908590615a42565b60405180910390a160408051868152602081018390527fdc00758b65eef71dc3780c04ebe36cab6bdb266c3a698187e29e0f0dca012630910160405180910390a1505050505b83600101935050505061169e565b5050565b6066546002906004908116141561196f5760405162461bcd60e51b8152600401610b3190615664565b611977613b9d565b6119848686868686613bf7565b61198e600160c955565b505050505050565b61199e6135e1565b61106e81613a28565b6001600160a01b0390811660009081526099602052604090205416151590565b6119cf6135e1565b6119d960006139d6565b565b4283602001511015611a5f5760405162461bcd60e51b815260206004820152604160248201527f44656c65676174696f6e4d616e616765722e64656c6567617465546f4279536960448201527f676e61747572653a207374616b6572207369676e6174757265206578706972656064820152601960fa1b608482015260a401610b31565b6000609b6000876001600160a01b03166001600160a01b031681526020019081526020016000205490506000611a9b8783888860200151611c8c565b6001600160a01b0388166000908152609b602052604090206001840190558551909150611acb90889083906143e1565b611ad7878786866130ca565b50505050505050565b6060600082516001600160401b03811115611afd57611afd614d76565b604051908082528060200260200182016040528015611b26578160200160208202803683370190505b50905060005b8351811015610a3b576001600160a01b03851660009081526098602052604081208551909190869084908110611b6457611b6461561d565b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002054828281518110611b9f57611b9f61561d565b6020908102919091010152611bb381615649565b9050611b2c565b611bc3336119a7565b611c455760405162461bcd60e51b815260206004820152604760248201527f44656c65676174696f6e4d616e616765722e7570646174654f70657261746f7260448201527f4d657461646174615552493a2063616c6c6572206d75737420626520616e206f6064820152663832b930ba37b960c91b608482015260a401610b31565b336001600160a01b03167f02a919ed0e2acad1dd90f17ef2fa4ae5462ee1339170034a8531cca4b67080908383604051611c80929190615756565b60405180910390a25050565b604080517f39111bc4a4d688e1f685123d7497d4615370152a8ee4a0593e647bd06ad8bb0b6020808301919091526001600160a01b0387811683850152851660608301526080820186905260a08083018590528351808403909101815260c0909201909252805191012060009081611d026126d4565b60405161190160f01b602082015260228101919091526042810183905260620160408051808303601f190181529190528051602090910120979650505050505050565b6040516360f4062b60e01b81526001600160a01b03828116600483015260609182916000917f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e909116906360f4062b90602401602060405180830381865afa158015611db5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dd99190615a5b565b6040516394f649dd60e01b81526001600160a01b03868116600483015291925060009182917f000000000000000000000000e1da8919f262ee86f9be05059c9280142cf23f48909116906394f649dd90602401600060405180830381865afa158015611e49573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611e719190810190615acf565b9150915060008313611e8857909590945092505050565b606080835160001415611f42576040805160018082528183019092529060208083019080368337505060408051600180825281830190925292945090506020808301908036833701905050905073beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac082600081518110611efd57611efd61561d565b60200260200101906001600160a01b031690816001600160a01b0316815250508481600081518110611f3157611f3161561d565b6020026020010181815250506120f0565b8351611f4f906001615b89565b6001600160401b03811115611f6657611f66614d76565b604051908082528060200260200182016040528015611f8f578160200160208202803683370190505b50915081516001600160401b03811115611fab57611fab614d76565b604051908082528060200260200182016040528015611fd4578160200160208202803683370190505b50905060005b845181101561206e57848181518110611ff557611ff561561d565b602002602001015183828151811061200f5761200f61561d565b60200260200101906001600160a01b031690816001600160a01b0316815250508381815181106120415761204161561d565b602002602001015182828151811061205b5761205b61561d565b6020908102919091010152600101611fda565b5073beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac082600184516120939190615ba1565b815181106120a3576120a361561d565b60200260200101906001600160a01b031690816001600160a01b0316815250508481600184516120d39190615ba1565b815181106120e3576120e361561d565b6020026020010181815250505b9097909650945050505050565b606654606090600190600290811614156121295760405162461bcd60e51b8152600401610b3190615664565b61213283611584565b6121b25760405162461bcd60e51b8152602060048201526044602482018190527f44656c65676174696f6e4d616e616765722e756e64656c65676174653a207374908201527f616b6572206d7573742062652064656c65676174656420746f20756e64656c656064820152636761746560e01b608482015260a401610b31565b6121bb836119a7565b1561222e5760405162461bcd60e51b815260206004820152603d60248201527f44656c65676174696f6e4d616e616765722e756e64656c65676174653a206f7060448201527f657261746f72732063616e6e6f7420626520756e64656c6567617465640000006064820152608401610b31565b6001600160a01b0383166122aa5760405162461bcd60e51b815260206004820152603c60248201527f44656c65676174696f6e4d616e616765722e756e64656c65676174653a20636160448201527f6e6e6f7420756e64656c6567617465207a65726f2061646472657373000000006064820152608401610b31565b6001600160a01b038084166000818152609a6020526040902054909116903314806122dd5750336001600160a01b038216145b8061230457506001600160a01b038181166000908152609960205260409020600101541633145b6123765760405162461bcd60e51b815260206004820152603d60248201527f44656c65676174696f6e4d616e616765722e756e64656c65676174653a20636160448201527f6c6c65722063616e6e6f7420756e64656c6567617465207374616b65720000006064820152608401610b31565b60008061238286611d45565b9092509050336001600160a01b038716146123d857826001600160a01b0316866001600160a01b03167ff0eddf07e6ea14f388b47e1e94a0f464ecbd9eed4171130e0fc0e99fb4030a8a60405160405180910390a35b826001600160a01b0316866001600160a01b03167ffee30966a256b71e14bc0ebfc94315e28ef4a97a7131a9e2b7a310a73af4467660405160405180910390a36001600160a01b0386166000908152609a6020526040902080546001600160a01b0319169055815161245a5760408051600081526020810190915294506125b8565b81516001600160401b0381111561247357612473614d76565b60405190808252806020026020018201604052801561249c578160200160208202803683370190505b50945060005b82518110156125b6576040805160018082528183019092526000916020808301908036833750506040805160018082528183019092529293506000929150602080830190803683370190505090508483815181106125025761250261561d565b60200260200101518260008151811061251d5761251d61561d565b60200260200101906001600160a01b031690816001600160a01b03168152505083838151811061254f5761254f61561d565b60200260200101518160008151811061256a5761256a61561d565b60200260200101818152505061258389878b858561286e565b8884815181106125955761259561561d565b602002602001018181525050505080806125ae90615649565b9150506124a2565b505b50505050919050565b611123338484846130ca565b6125d6336119a7565b6126545760405162461bcd60e51b815260206004820152604360248201527f44656c65676174696f6e4d616e616765722e6d6f646966794f70657261746f7260448201527f44657461696c733a2063616c6c6572206d75737420626520616e206f706572616064820152623a37b960e91b608482015260a401610b31565b61106e3382612e2e565b6126666135e1565b6001600160a01b0381166126cb5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610b31565b61106e816139d6565b60007f0000000000000000000000000000000000000000000000000000000000007a69461415612705575060975490565b61270d61393f565b905090565b606560009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612765573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127899190615785565b6001600160a01b0316336001600160a01b0316146127b95760405162461bcd60e51b8152600401610b31906157a2565b6066541981196066541916146128375760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e756e70617573653a20696e76616c696420617474656d7060448201527f7420746f2070617573652066756e6374696f6e616c69747900000000000000006064820152608401610b31565b606681905560405181815233907f3582d1828e26bf56bd801502bc021ac0bc8afb57c826e4986b45593c8fad389c9060200161125c565b60006001600160a01b0386166129055760405162461bcd60e51b815260206004820152605060248201527f44656c65676174696f6e4d616e616765722e5f72656d6f76655368617265734160448201527f6e6451756575655769746864726177616c3a207374616b65722063616e6e6f7460648201526f206265207a65726f206164647265737360801b608482015260a401610b31565b825161298f5760405162461bcd60e51b815260206004820152604d60248201527f44656c65676174696f6e4d616e616765722e5f72656d6f76655368617265734160448201527f6e6451756575655769746864726177616c3a207374726174656769657320636160648201526c6e6e6f7420626520656d70747960981b608482015260a401610b31565b60005b8351811015612d3c576001600160a01b038616156129e8576129e886888684815181106129c1576129c161561d565b60200260200101518685815181106129db576129db61561d565b6020026020010151613566565b73beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac06001600160a01b0316848281518110612a1857612a1861561d565b60200260200101516001600160a01b03161415612ae1577f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e6001600160a01b031663beffbb8988858481518110612a7157612a7161561d565b60200260200101516040518363ffffffff1660e01b8152600401612aaa9291906001600160a01b03929092168252602082015260400190565b600060405180830381600087803b158015612ac457600080fd5b505af1158015612ad8573d6000803e3d6000fd5b50505050612d34565b846001600160a01b0316876001600160a01b03161480612bb357507f000000000000000000000000e1da8919f262ee86f9be05059c9280142cf23f486001600160a01b0316639b4da03d858381518110612b3d57612b3d61561d565b60200260200101516040518263ffffffff1660e01b8152600401612b7091906001600160a01b0391909116815260200190565b602060405180830381865afa158015612b8d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612bb19190615849565b155b612c7f5760405162461bcd60e51b8152602060048201526084602482018190527f44656c65676174696f6e4d616e616765722e5f72656d6f76655368617265734160448301527f6e6451756575655769746864726177616c3a2077697468647261776572206d7560648301527f73742062652073616d652061646472657373206173207374616b657220696620908201527f746869726450617274795472616e7366657273466f7262696464656e2061726560a482015263081cd95d60e21b60c482015260e401610b31565b7f000000000000000000000000e1da8919f262ee86f9be05059c9280142cf23f486001600160a01b0316638c80d4e588868481518110612cc157612cc161561d565b6020026020010151868581518110612cdb57612cdb61561d565b60200260200101516040518463ffffffff1660e01b8152600401612d0193929190615bb8565b600060405180830381600087803b158015612d1b57600080fd5b505af1158015612d2f573d6000803e3d6000fd5b505050505b600101612992565b506001600160a01b0386166000908152609f60205260408120805491829190612d6483615649565b919050555060006040518060e00160405280896001600160a01b03168152602001886001600160a01b03168152602001876001600160a01b031681526020018381526020014363ffffffff1681526020018681526020018581525090506000612dcc8261166b565b6000818152609e602052604090819020805460ff19166001179055519091507f9009ab153e8014fbfb02f2217f5cde7aa7f9ad734ae85ca3ee3f4ca2fdd499f990612e1a9083908590615a42565b60405180910390a198975050505050505050565b6000612e3d6020830183614b44565b6001600160a01b03161415612ed75760405162461bcd60e51b815260206004820152605460248201527f44656c65676174696f6e4d616e616765722e5f7365744f70657261746f72446560448201527f7461696c733a2063616e6e6f742073657420606561726e696e677352656365696064820152737665726020746f207a65726f206164647265737360601b608482015260a401610b31565b6213c680612eeb6060830160408401615bdc565b63ffffffff161115612fa05760405162461bcd60e51b815260206004820152606c60248201527f44656c65676174696f6e4d616e616765722e5f7365744f70657261746f72446560448201527f7461696c733a207374616b65724f70744f757457696e646f77426c6f636b732060648201527f63616e6e6f74206265203e204d41585f5354414b45525f4f50545f4f55545f5760848201526b494e444f575f424c4f434b5360a01b60a482015260c401610b31565b6001600160a01b0382166000908152609960205260409081902060010154600160a01b900463ffffffff1690612fdc9060608401908401615bdc565b63ffffffff1610156130725760405162461bcd60e51b815260206004820152605360248201527f44656c65676174696f6e4d616e616765722e5f7365744f70657261746f72446560448201527f7461696c733a207374616b65724f70744f757457696e646f77426c6f636b732060648201527218d85b9b9bdd08189948191958dc99585cd959606a1b608482015260a401610b31565b6001600160a01b038216600090815260996020526040902081906130968282615c19565b505060405133907ffebe5cd24b2cbc7b065b9d0fdeb904461e4afcff57dd57acda1e7832031ba7ac90611c80908490615704565b606654600090600190811614156130f35760405162461bcd60e51b8152600401610b3190615664565b6130fc85611584565b156131795760405162461bcd60e51b815260206004820152604160248201527f44656c65676174696f6e4d616e616765722e5f64656c65676174653a2073746160448201527f6b657220697320616c7265616479206163746976656c792064656c65676174656064820152601960fa1b608482015260a401610b31565b613182846119a7565b6132025760405162461bcd60e51b815260206004820152604560248201527f44656c65676174696f6e4d616e616765722e5f64656c65676174653a206f706560448201527f7261746f72206973206e6f74207265676973746572656420696e20456967656e6064820152642630bcb2b960d91b608482015260a401610b31565b6001600160a01b038085166000908152609960205260409020600101541680158015906132385750336001600160a01b03821614155b801561324d5750336001600160a01b03861614155b156133ba5742846020015110156132cc5760405162461bcd60e51b815260206004820152603760248201527f44656c65676174696f6e4d616e616765722e5f64656c65676174653a2061707060448201527f726f766572207369676e617475726520657870697265640000000000000000006064820152608401610b31565b6001600160a01b0381166000908152609c6020908152604080832086845290915290205460ff16156133665760405162461bcd60e51b815260206004820152603760248201527f44656c65676174696f6e4d616e616765722e5f64656c65676174653a2061707060448201527f726f76657253616c7420616c7265616479207370656e740000000000000000006064820152608401610b31565b6001600160a01b0381166000908152609c6020908152604080832086845282528220805460ff191660011790558501516133a7908890889085908890610a43565b90506133b8828287600001516143e1565b505b6001600160a01b038681166000818152609a602052604080822080546001600160a01b031916948a169485179055517fc3ee9f2e5fda98e8066a1f745b2df9285f416fe98cf2559cd21484b3d87433049190a360008061341988611d45565b9150915060005b82518110156113e257613467888a8584815181106134405761344061561d565b602002602001015185858151811061345a5761345a61561d565b6020026020010151613b22565b600101613420565b6001600160a01b0381166134fd5760405162461bcd60e51b815260206004820152604960248201527f5061757361626c652e5f73657450617573657252656769737472793a206e657760448201527f50617573657252656769737472792063616e6e6f7420626520746865207a65726064820152686f206164647265737360b81b608482015260a401610b31565b606554604080516001600160a01b03928316815291831660208301527f6e9fcd539896fca60e8b0f01dd580233e48a6b0f7df013b89ba7f565869acdb6910160405180910390a1606580546001600160a01b0319166001600160a01b0392909216919091179055565b6001600160a01b0380851660009081526098602090815260408083209386168352929052908120805483929061359d908490615ba1565b92505081905550836001600160a01b03167f6909600037b75d7b4733aedd815442b5ec018a827751c832aaff64eba5d6d2dd848484604051610fb093929190615bb8565b6033546001600160a01b031633146119d95760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610b31565b8281146136c35760405162461bcd60e51b815260206004820152604a60248201527f44656c65676174696f6e4d616e616765722e5f7365745374726174656779576960448201527f746864726177616c44656c6179426c6f636b733a20696e707574206c656e67746064820152690d040dad2e6dac2e8c6d60b31b608482015260a401610b31565b8260005b8181101561198e5760008686838181106136e3576136e361561d565b90506020020160208101906136f89190614b44565b6001600160a01b038116600090815260a160205260408120549192508686858181106137265761372661561d565b90506020020135905062034bc08111156137ea5760405162461bcd60e51b815260206004820152607360248201527f44656c65676174696f6e4d616e616765722e5f7365745374726174656779576960448201527f746864726177616c44656c6179426c6f636b733a205f7769746864726177616c60648201527f44656c6179426c6f636b732063616e6e6f74206265203e204d41585f5749544860848201527244524157414c5f44454c41595f424c4f434b5360681b60a482015260c401610b31565b6001600160a01b038316600081815260a160209081526040918290208490558151928352820184905281018290527f0e7efa738e8b0ce6376a0c1af471655540d2e9a81647d7b09ed823018426576d9060600160405180910390a15050508061385290615649565b90506136c7565b6065546001600160a01b031615801561387a57506001600160a01b03821615155b6138fc5760405162461bcd60e51b815260206004820152604760248201527f5061757361626c652e5f696e697469616c697a655061757365723a205f696e6960448201527f7469616c697a6550617573657228292063616e206f6e6c792062652063616c6c6064820152666564206f6e636560c81b608482015260a401610b31565b606681905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a26119428261346f565b604080518082018252600a81526922b4b3b2b72630bcb2b960b11b60209182015281517f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a866818301527f71b625cfad44bac63b13dba07f2e1d6084ee04b6f8752101ece6126d584ee6ea81840152466060820152306080808301919091528351808303909101815260a0909101909252815191012090565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b62034bc0811115613ae15760405162461bcd60e51b815260206004820152607160248201527f44656c65676174696f6e4d616e616765722e5f7365744d696e5769746864726160448201527f77616c44656c6179426c6f636b733a205f6d696e5769746864726177616c446560648201527f6c6179426c6f636b732063616e6e6f74206265203e204d41585f5749544844526084820152704157414c5f44454c41595f424c4f434b5360781b60a482015260c401610b31565b609d5460408051918252602082018390527fafa003cd76f87ff9d62b35beea889920f33c0c42b8d45b74954d61d50f4b6b69910160405180910390a1609d55565b6001600160a01b03808516600090815260986020908152604080832093861683529290529081208054839290613b59908490615b89565b92505081905550836001600160a01b03167f1ec042c965e2edd7107b51188ee0f383e22e76179041ab3a9d18ff151405166c848484604051610fb093929190615bb8565b600260c9541415613bf05760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610b31565b600260c955565b6000613c0561062987615c7c565b6000818152609e602052604090205490915060ff16613c865760405162461bcd60e51b81526020600482015260436024820152600080516020615db483398151915260448201527f645769746864726177616c3a20616374696f6e206973206e6f7420696e20717560648201526265756560e81b608482015260a401610b31565b609d544390613c9b60a0890160808a01615bdc565b63ffffffff16613cab9190615b89565b1115613d335760405162461bcd60e51b815260206004820152605f6024820152600080516020615db483398151915260448201527f645769746864726177616c3a206d696e5769746864726177616c44656c61794260648201527f6c6f636b7320706572696f6420686173206e6f74207965742070617373656400608482015260a401610b31565b613d436060870160408801614b44565b6001600160a01b0316336001600160a01b031614613dd05760405162461bcd60e51b81526020600482015260506024820152600080516020615db483398151915260448201527f645769746864726177616c3a206f6e6c7920776974686472617765722063616e60648201526f1031b7b6b83632ba329030b1ba34b7b760811b608482015260a401610b31565b8115613e5257613de360a08701876156bb565b85149050613e525760405162461bcd60e51b81526020600482015260426024820152600080516020615db483398151915260448201527f645769746864726177616c3a20696e707574206c656e677468206d69736d61746064820152610c6d60f31b608482015260a401610b31565b6000818152609e60205260409020805460ff191690558115613fb75760005b613e7e60a08801886156bb565b9050811015613fb1574360a16000613e9960a08b018b6156bb565b85818110613ea957613ea961561d565b9050602002016020810190613ebe9190614b44565b6001600160a01b03168152602081019190915260400160002054613ee860a08a0160808b01615bdc565b63ffffffff16613ef89190615b89565b1115613f165760405162461bcd60e51b8152600401610b3190615c8e565b613fa9613f266020890189614b44565b33613f3460a08b018b6156bb565b85818110613f4457613f4461561d565b9050602002016020810190613f599190614b44565b613f6660c08c018c6156bb565b86818110613f7657613f7661561d565b905060200201358a8a87818110613f8f57613f8f61561d565b9050602002016020810190613fa49190614b44565b61459b565b600101613e71565b506143a6565b336000908152609a60205260408120546001600160a01b0316905b613fdf60a08901896156bb565b90508110156143a3574360a16000613ffa60a08c018c6156bb565b8581811061400a5761400a61561d565b905060200201602081019061401f9190614b44565b6001600160a01b0316815260208101919091526040016000205461404960a08b0160808c01615bdc565b63ffffffff166140599190615b89565b11156140775760405162461bcd60e51b8152600401610b3190615c8e565b73beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac061409960a08a018a6156bb565b838181106140a9576140a961561d565b90506020020160208101906140be9190614b44565b6001600160a01b0316141561420e5760006140dc60208a018a614b44565b905060006001600160a01b037f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e16630e81073c8361411d60c08e018e6156bb565b8781811061412d5761412d61561d565b6040516001600160e01b031960e087901b1681526001600160a01b03909416600485015260200291909101356024830152506044016020604051808303816000875af1158015614181573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141a59190615a5b565b6001600160a01b038084166000908152609a60205260409020549192501680156142065761420681846141db60a08f018f6156bb565b888181106141eb576141eb61561d565b90506020020160208101906142009190614b44565b85613b22565b50505061439b565b7f000000000000000000000000e1da8919f262ee86f9be05059c9280142cf23f486001600160a01b031663c4623ea1338989858181106142505761425061561d565b90506020020160208101906142659190614b44565b61427260a08d018d6156bb565b868181106142825761428261561d565b90506020020160208101906142979190614b44565b6142a460c08e018e6156bb565b878181106142b4576142b461561d565b60405160e088901b6001600160e01b03191681526001600160a01b03968716600482015294861660248601529290941660448401526020909102013560648201526084019050600060405180830381600087803b15801561431457600080fd5b505af1158015614328573d6000803e3d6000fd5b505050506001600160a01b0382161561439b5761439b823361434d60a08c018c6156bb565b8581811061435d5761435d61561d565b90506020020160208101906143729190614b44565b61437f60c08d018d6156bb565b8681811061438f5761438f61561d565b90506020020135613b22565b600101613fd2565b50505b6040518181527fc97098c2f658800b4df29001527f7324bcdffcf6e8751a699ab920a1eced5b1d9060200160405180910390a1505050505050565b6001600160a01b0383163b156144fb57604051630b135d3f60e11b808252906001600160a01b03851690631626ba7e906144219086908690600401615d16565b602060405180830381865afa15801561443e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906144629190615d73565b6001600160e01b031916146111235760405162461bcd60e51b815260206004820152605360248201527f454950313237315369676e61747572655574696c732e636865636b5369676e6160448201527f747572655f454950313237313a2045524331323731207369676e6174757265206064820152721d995c9a599a58d85d1a5bdb8819985a5b1959606a1b608482015260a401610b31565b826001600160a01b031661450f83836146db565b6001600160a01b0316146111235760405162461bcd60e51b815260206004820152604760248201527f454950313237315369676e61747572655574696c732e636865636b5369676e6160448201527f747572655f454950313237313a207369676e6174757265206e6f742066726f6d6064820152661039b4b3b732b960c91b608482015260a401610b31565b6001600160a01b03831673beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac014156146465760405162387b1360e81b81526001600160a01b037f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e169063387b13009061460f90889088908790600401615bb8565b600060405180830381600087803b15801561462957600080fd5b505af115801561463d573d6000803e3d6000fd5b505050506146d4565b60405163c608c7f360e01b81526001600160a01b03858116600483015284811660248301526044820184905282811660648301527f000000000000000000000000e1da8919f262ee86f9be05059c9280142cf23f48169063c608c7f390608401600060405180830381600087803b1580156146c057600080fd5b505af11580156113e2573d6000803e3d6000fd5b5050505050565b60008060006146ea85856146f7565b91509150610a3b8161473d565b60008082516041141561472e5760208301516040840151606085015160001a6147228782858561488b565b94509450505050614736565b506000905060025b9250929050565b600081600481111561475157614751615d9d565b141561475a5750565b600181600481111561476e5761476e615d9d565b14156147bc5760405162461bcd60e51b815260206004820152601860248201527f45434453413a20696e76616c6964207369676e617475726500000000000000006044820152606401610b31565b60028160048111156147d0576147d0615d9d565b141561481e5760405162461bcd60e51b815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e677468006044820152606401610b31565b600381600481111561483257614832615d9d565b141561106e5760405162461bcd60e51b815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c604482015261756560f01b6064820152608401610b31565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08311156148c25750600090506003614946565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015614916573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811661493f57600060019250925050614946565b9150600090505b94509492505050565b60008083601f84011261496157600080fd5b5081356001600160401b0381111561497857600080fd5b6020830191508360208260051b850101111561473657600080fd5b600080602083850312156149a657600080fd5b82356001600160401b038111156149bc57600080fd5b6149c88582860161494f565b90969095509350505050565b6001600160a01b038116811461106e57600080fd5b80356149f4816149d4565b919050565b600080600080600060a08688031215614a1157600080fd5b8535614a1c816149d4565b94506020860135614a2c816149d4565b93506040860135614a3c816149d4565b94979396509394606081013594506080013592915050565b6020808252825182820181905260009190848201906040850190845b81811015614a8c57835183529284019291840191600101614a70565b50909695505050505050565b600060608284031215614aaa57600080fd5b50919050565b60008083601f840112614ac257600080fd5b5081356001600160401b03811115614ad957600080fd5b60208301915083602082850101111561473657600080fd5b600080600060808486031215614b0657600080fd5b614b108585614a98565b925060608401356001600160401b03811115614b2b57600080fd5b614b3786828701614ab0565b9497909650939450505050565b600060208284031215614b5657600080fd5b8135614b61816149d4565b9392505050565b600080600060608486031215614b7d57600080fd5b8335614b88816149d4565b92506020840135614b98816149d4565b929592945050506040919091013590565b600060208284031215614bbb57600080fd5b5035919050565b60008060008060408587031215614bd857600080fd5b84356001600160401b0380821115614bef57600080fd5b614bfb8883890161494f565b90965094506020870135915080821115614c1457600080fd5b50614c218782880161494f565b95989497509550505050565b60008060008060008060008060c0898b031215614c4957600080fd5b8835614c54816149d4565b97506020890135614c64816149d4565b9650604089013595506060890135945060808901356001600160401b0380821115614c8e57600080fd5b614c9a8c838d0161494f565b909650945060a08b0135915080821115614cb357600080fd5b50614cc08b828c0161494f565b999c989b5096995094979396929594505050565b6000806000806000806000806080898b031215614cf057600080fd5b88356001600160401b0380821115614d0757600080fd5b614d138c838d0161494f565b909a50985060208b0135915080821115614d2c57600080fd5b614d388c838d0161494f565b909850965060408b0135915080821115614d5157600080fd5b614d5d8c838d0161494f565b909650945060608b0135915080821115614cb357600080fd5b634e487b7160e01b600052604160045260246000fd5b60405160e081016001600160401b0381118282101715614dae57614dae614d76565b60405290565b604080519081016001600160401b0381118282101715614dae57614dae614d76565b60405160c081016001600160401b0381118282101715614dae57614dae614d76565b604051601f8201601f191681016001600160401b0381118282101715614e2057614e20614d76565b604052919050565b63ffffffff8116811461106e57600080fd5b80356149f481614e28565b60006001600160401b03821115614e5e57614e5e614d76565b5060051b60200190565b600082601f830112614e7957600080fd5b81356020614e8e614e8983614e45565b614df8565b82815260059290921b84018101918181019086841115614ead57600080fd5b8286015b84811015614ed1578035614ec4816149d4565b8352918301918301614eb1565b509695505050505050565b600082601f830112614eed57600080fd5b81356020614efd614e8983614e45565b82815260059290921b84018101918181019086841115614f1c57600080fd5b8286015b84811015614ed15780358352918301918301614f20565b600060e08284031215614f4957600080fd5b614f51614d8c565b9050614f5c826149e9565b8152614f6a602083016149e9565b6020820152614f7b604083016149e9565b604082015260608201356060820152614f9660808301614e3a565b608082015260a08201356001600160401b0380821115614fb557600080fd5b614fc185838601614e68565b60a084015260c0840135915080821115614fda57600080fd5b50614fe784828501614edc565b60c08301525092915050565b60006020828403121561500557600080fd5b81356001600160401b0381111561501b57600080fd5b61502784828501614f37565b949350505050565b60006020828403121561504157600080fd5b813560ff81168114614b6157600080fd5b60006040828403121561506457600080fd5b61506c614db4565b90508135615079816149d4565b815260208201356bffffffffffffffffffffffff8116811461509a57600080fd5b602082015292915050565b600060208083850312156150b857600080fd5b82356001600160401b03808211156150cf57600080fd5b818501915085601f8301126150e357600080fd5b81356150f1614e8982614e45565b81815260059190911b8301840190848101908883111561511057600080fd5b8585015b838110156151ea5780358581111561512c5760008081fd5b860160e0818c03601f19018113156151445760008081fd5b61514c614dd6565b898301358881111561515e5760008081fd5b61516c8e8c83870101614e68565b825250604080840135898111156151835760008081fd5b6151918f8d83880101614edc565b8c8401525060606151a38186016149e9565b82840152608091506151b78f838701615052565b908301526151c760c08501614e3a565b908201526151d68383016149e9565b60a082015285525050918601918601615114565b5098975050505050505050565b801515811461106e57600080fd5b60008060008060006080868803121561521d57600080fd5b85356001600160401b038082111561523457600080fd5b9087019060e0828a03121561524857600080fd5b9095506020870135908082111561525e57600080fd5b5061526b8882890161494f565b909550935050604086013591506060860135615286816151f7565b809150509295509295909350565b600080604083850312156152a757600080fd5b82356152b2816149d4565b915060208301356152c2816149d4565b809150509250929050565b6000604082840312156152df57600080fd5b6152e7614db4565b905081356001600160401b038082111561530057600080fd5b818401915084601f83011261531457600080fd5b813560208282111561532857615328614d76565b61533a601f8301601f19168201614df8565b9250818352868183860101111561535057600080fd5b8181850182850137600081838501015282855280860135818601525050505092915050565b600080600080600060a0868803121561538d57600080fd5b8535615398816149d4565b945060208601356153a8816149d4565b935060408601356001600160401b03808211156153c457600080fd5b6153d089838a016152cd565b945060608801359150808211156153e657600080fd5b506153f3888289016152cd565b95989497509295608001359392505050565b6000806040838503121561541857600080fd5b8235615423816149d4565b915060208301356001600160401b0381111561543e57600080fd5b61544a85828601614e68565b9150509250929050565b600081518084526020808501945080840160005b8381101561548457815187529582019590820190600101615468565b509495945050505050565b602081526000614b616020830184615454565b600080602083850312156154b557600080fd5b82356001600160401b038111156154cb57600080fd5b6149c885828601614ab0565b600080604083850312156154ea57600080fd5b82356154f5816149d4565b946020939093013593505050565b6000806000806080858703121561551957600080fd5b8435615524816149d4565b935060208501359250604085013561553b816149d4565b9396929550929360600135925050565b600081518084526020808501945080840160005b838110156154845781516001600160a01b03168752958201959082019060010161555f565b604081526000615597604083018561554b565b82810360208401526112a08185615454565b6000806000606084860312156155be57600080fd5b83356155c9816149d4565b925060208401356001600160401b038111156155e457600080fd5b6155f0868287016152cd565b925050604084013590509250925092565b60006060828403121561561357600080fd5b614b618383614a98565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b600060001982141561565d5761565d615633565b5060010190565b60208082526019908201527f5061757361626c653a20696e6465782069732070617573656400000000000000604082015260600190565b60008235605e198336030181126156b157600080fd5b9190910192915050565b6000808335601e198436030181126156d257600080fd5b8301803591506001600160401b038211156156ec57600080fd5b6020019150600581901b360382131561473657600080fd5b606081018235615713816149d4565b6001600160a01b03908116835260208401359061572f826149d4565b166020830152604083013561574381614e28565b63ffffffff811660408401525092915050565b60208152816020820152818360408301376000818301604090810191909152601f909201601f19160101919050565b60006020828403121561579757600080fd5b8151614b61816149d4565b6020808252602a908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526939903ab73830bab9b2b960b11b606082015260800190565b60208082526037908201527f44656c65676174696f6e4d616e616765723a206f6e6c7953747261746567794d60408201527f616e616765724f72456967656e506f644d616e61676572000000000000000000606082015260800190565b60006020828403121561585b57600080fd5b8151614b61816151f7565b60208082526028908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526739903830bab9b2b960c11b606082015260800190565b6000823560de198336030181126156b157600080fd5b6000602082840312156158d657600080fd5b8135614b61816151f7565b600060018060a01b03808351168452806020840151166020850152806040840151166040850152506060820151606084015263ffffffff608083015116608084015260a082015160e060a085015261593c60e085018261554b565b905060c083015184820360c08601526112a08282615454565b602081526000614b6160208301846158e1565b602081526000825160e0602084015261598561010084018261554b565b90506020840151601f198483030160408501526159a28282615454565b915050604084015160018060a01b03808216606086015260608601519150808251166080860152506bffffffffffffffffffffffff60208201511660a08501525060808401516159fa60c085018263ffffffff169052565b5060a08401516001600160a01b03811660e0850152610a3b565b60008060408385031215615a2757600080fd5b8251615a32816151f7565b6020939093015192949293505050565b82815260406020820152600061502760408301846158e1565b600060208284031215615a6d57600080fd5b5051919050565b600082601f830112615a8557600080fd5b81516020615a95614e8983614e45565b82815260059290921b84018101918181019086841115615ab457600080fd5b8286015b84811015614ed15780518352918301918301615ab8565b60008060408385031215615ae257600080fd5b82516001600160401b0380821115615af957600080fd5b818501915085601f830112615b0d57600080fd5b81516020615b1d614e8983614e45565b82815260059290921b84018101918181019089841115615b3c57600080fd5b948201945b83861015615b63578551615b54816149d4565b82529482019490820190615b41565b91880151919650909350505080821115615b7c57600080fd5b5061544a85828601615a74565b60008219821115615b9c57615b9c615633565b500190565b600082821015615bb357615bb3615633565b500390565b6001600160a01b039384168152919092166020820152604081019190915260600190565b600060208284031215615bee57600080fd5b8135614b6181614e28565b80546001600160a01b0319166001600160a01b0392909216919091179055565b8135615c24816149d4565b615c2e8183615bf9565b50600181016020830135615c41816149d4565b615c4b8183615bf9565b506040830135615c5a81614e28565b815463ffffffff60a01b191660a09190911b63ffffffff60a01b161790555050565b6000615c883683614f37565b92915050565b6020808252606e90820152600080516020615db483398151915260408201527f645769746864726177616c3a207769746864726177616c44656c6179426c6f6360608201527f6b7320706572696f6420686173206e6f74207965742070617373656420666f7260808201526d207468697320737472617465677960901b60a082015260c00190565b82815260006020604081840152835180604085015260005b81811015615d4a57858101830151858201606001528201615d2e565b81811115615d5c576000606083870101525b50601f01601f191692909201606001949350505050565b600060208284031215615d8557600080fd5b81516001600160e01b031981168114614b6157600080fd5b634e487b7160e01b600052602160045260246000fdfe44656c65676174696f6e4d616e616765722e5f636f6d706c6574655175657565a264697066735822122065bef68dcce15bc7412c9185c1d02811cb4839b5e272883a936d7cdaab2c67fd64736f6c634300080c0033", - "storage": { - "0x0": "0xff" - } - }, - "0x82dc47734901ee7d4f4232f398752cb9dd5daccc": { - "nonce": 1, - "balance": "0x0", - "code": "0x6080604052600436106101855760003560e01c806374cdd798116100d1578063c49074421161008a578063e251ef5211610064578063e251ef5214610563578063e2c8344514610583578063f2882461146105a3578063fe80b087146105d757600080fd5b8063c490744214610503578063c4d66de814610523578063dda3346c1461054357600080fd5b806374cdd7981461044057806387e0d289146104745780639b4e46341461049b578063a50600f4146104ae578063b522538a146104ce578063baa7145a146104ee57600080fd5b806334bea20a1161013e57806358eaee791161011857806358eaee791461038f5780635d3f65b6146103bc5780636fcd0e53146103dc5780637439841f1461040957600080fd5b806334bea20a146103005780633f65cf191461033b5780634665bcda1461035b57600080fd5b80630b18ff66146101db5780630cd4649e146102185780631a5057be1461022f5780631d905d5c146102635780633106ab53146102af5780633474aa16146102e057600080fd5b366101d657346037600082825461019c9190614ab6565b90915550506040513481527f6fdd3dbdb173299608c0aa9f368735857c8842b581f8389238bf05bd04b3bf499060200160405180910390a1005b600080fd5b3480156101e757600080fd5b506033546101fb906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561022457600080fd5b5061022d6105fb565b005b34801561023b57600080fd5b506101fb7f000000000000000000000000f7cd8fa9b94db2aa972023b379c7f72c65e4de9d81565b34801561026f57600080fd5b506102977f000000000000000000000000000000000000000000000000000000077359400081565b6040516001600160401b03909116815260200161020f565b3480156102bb57600080fd5b506034546102d090600160401b900460ff1681565b604051901515815260200161020f565b3480156102ec57600080fd5b50603454610297906001600160401b031681565b34801561030c57600080fd5b506102d061031b366004614af3565b603560209081526000928352604080842090915290825290205460ff1681565b34801561034757600080fd5b5061022d610356366004614b86565b610764565b34801561036757600080fd5b506101fb7f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e81565b34801561039b57600080fd5b506103af6103aa366004614c97565b610c05565b60405161020f9190614d10565b3480156103c857600080fd5b50603854610297906001600160401b031681565b3480156103e857600080fd5b506103fc6103f7366004614d1e565b610c6a565b60405161020f9190614d37565b34801561041557600080fd5b506103af610424366004614d1e565b600090815260366020526040902054600160c01b900460ff1690565b34801561044c57600080fd5b506101fb7f00000000000000000000000012975173b87f7595ee45dffb2ab812ece596bf8481565b34801561048057600080fd5b5060335461029790600160a01b90046001600160401b031681565b61022d6104a9366004614d7f565b610d17565b3480156104ba57600080fd5b5061022d6104c9366004614df2565b610ec4565b3480156104da57600080fd5b506103fc6104e9366004614c97565b611293565b3480156104fa57600080fd5b5061022d611386565b34801561050f57600080fd5b5061022d61051e366004614e9c565b6113f1565b34801561052f57600080fd5b5061022d61053e366004614ec8565b61162e565b34801561054f57600080fd5b5061022d61055e366004614fe2565b611806565b34801561056f57600080fd5b5061022d61057e3660046150b3565b6119d9565b34801561058f57600080fd5b5061022d61059e366004614e9c565b611da4565b3480156105af57600080fd5b506102977f000000000000000000000000000000000000000000000000000000006059f46081565b3480156105e357600080fd5b506105ed60375481565b60405190815260200161020f565b604051635ac86ab760e01b8152600260048201819052907f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e6001600160a01b031690635ac86ab790602401602060405180830381865afa158015610663573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061068791906151ae565b156106ad5760405162461bcd60e51b81526004016106a4906151d0565b60405180910390fd5b6033546001600160a01b031633146106d75760405162461bcd60e51b81526004016106a49061522d565b603454600160401b900460ff16156107015760405162461bcd60e51b81526004016106a490615275565b6034805460ff60401b1916600160401b179055603354610729906001600160a01b0316611f87565b6033546040516001600160a01b03909116907fca8dfc8c5e0a67a74501c072a3325f685259bebbae7cfd230ab85198a78b70cd90600090a250565b6033546001600160a01b0316331461078e5760405162461bcd60e51b81526004016106a49061522d565b604051635ac86ab760e01b8152600260048201819052907f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e6001600160a01b031690635ac86ab790602401602060405180830381865afa1580156107f6573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061081a91906151ae565b156108375760405162461bcd60e51b81526004016106a4906151d0565b60335489906001600160401b03600160a01b90910481169082161161086e5760405162461bcd60e51b81526004016106a4906152c4565b603454600160401b900460ff166108e65760405162461bcd60e51b815260206004820152603660248201527f456967656e506f642e686173456e61626c656452657374616b696e673a2072656044820152751cdd185ada5b99c81a5cc81b9bdd08195b98589b195960521b60648201526084016106a4565b86851480156108f457508483145b6109845760405162461bcd60e51b815260206004820152605560248201527f456967656e506f642e7665726966795769746864726177616c43726564656e7460448201527f69616c733a2076616c696461746f72496e646963657320616e642070726f6f666064820152740e640daeae6e840c4ca40e6c2daca40d8cadccee8d605b1b608482015260a4016106a4565b4261099a613f486001600160401b038d16614ab6565b1015610a235760405162461bcd60e51b815260206004820152604c60248201527f456967656e506f642e7665726966795769746864726177616c43726564656e7460448201527f69616c733a207370656369666965642074696d657374616d7020697320746f6f60648201526b0819985c881a5b881c185cdd60a21b608482015260a4016106a4565b60405163d1c64cc960e01b81526001600160401b038b166004820152610acc907f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e6001600160a01b03169063d1c64cc990602401602060405180830381865afa158015610a94573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ab8919061535f565b8a35610ac760208d018d615378565b611fbb565b6000805b88811015610b7057610b528c8c358c8c85818110610af057610af06153be565b9050602002016020810190610b0591906153d4565b8b8b86818110610b1757610b176153be565b9050602002810190610b299190615378565b8b8b88818110610b3b57610b3b6153be565b9050602002810190610b4d91906153fb565b612149565b610b5c9083614ab6565b915080610b6881615444565b915050610ad0565b5060335460405163030b147160e61b81526001600160a01b039182166004820152602481018390527f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e9091169063c2c51c4090604401600060405180830381600087803b158015610be057600080fd5b505af1158015610bf4573d6000803e3d6000fd5b505050505050505050505050505050565b600080610c4784848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506125ee92505050565b600090815260366020526040902054600160c01b900460ff169150505b92915050565b610c926040805160808101825260008082526020820181905291810182905290606082015290565b600082815260366020908152604091829020825160808101845281546001600160401b038082168352600160401b8204811694830194909452600160801b810490931693810193909352906060830190600160c01b900460ff166002811115610cfd57610cfd614cd8565b6002811115610d0e57610d0e614cd8565b90525092915050565b336001600160a01b037f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e1614610d5f5760405162461bcd60e51b81526004016106a49061545f565b346801bc16d674ec80000014610deb5760405162461bcd60e51b8152602060048201526044602482018190527f456967656e506f642e7374616b653a206d75737420696e697469616c6c792073908201527f74616b6520666f7220616e792076616c696461746f72207769746820333220656064820152633a3432b960e11b608482015260a4016106a4565b7f00000000000000000000000012975173b87f7595ee45dffb2ab812ece596bf846001600160a01b031663228951186801bc16d674ec8000008787610e2e6126e8565b8888886040518863ffffffff1660e01b8152600401610e5296959493929190615531565b6000604051808303818588803b158015610e6b57600080fd5b505af1158015610e7f573d6000803e3d6000fd5b50505050507f606865b7934a25d4aed43f6cdb426403353fa4b3009c4d228407474581b01e238585604051610eb5929190615580565b60405180910390a15050505050565b604051635ac86ab760e01b8152600360048201819052907f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e6001600160a01b031690635ac86ab790602401602060405180830381865afa158015610f2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f5091906151ae565b15610f6d5760405162461bcd60e51b81526004016106a4906151d0565b8684148015610f7b57508382145b6110045760405162461bcd60e51b815260206004820152604e60248201527f456967656e506f642e76657269667942616c616e6365557064617465733a207660448201527f616c696461746f72496e646963657320616e642070726f6f6673206d7573742060648201526d0c4ca40e6c2daca40d8cadccee8d60931b608482015260a4016106a4565b4261101a613f486001600160401b038c16614ab6565b101561109c5760405162461bcd60e51b815260206004820152604560248201527f456967656e506f642e76657269667942616c616e6365557064617465733a207360448201527f70656369666965642074696d657374616d7020697320746f6f2066617220696e606482015264081c185cdd60da1b608482015260a4016106a4565b60405163d1c64cc960e01b81526001600160401b038a166004820152611140907f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e6001600160a01b03169063d1c64cc990602401602060405180830381865afa15801561110d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611131919061535f565b8735610ac760208a018a615378565b6000805b888110156111e4576111c68b8b8b84818110611162576111626153be565b905060200201602081019061117791906153d4565b8a358a8a8681811061118b5761118b6153be565b905060200281019061119d9190615378565b8a8a888181106111af576111af6153be565b90506020028101906111c191906153fb565b61272d565b6111d09083615594565b9150806111dc81615444565b915050611144565b506033546001600160a01b037f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e81169163c2c51c409116611229633b9aca00856155d5565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b15801561126f57600080fd5b505af1158015611283573d6000803e3d6000fd5b5050505050505050505050505050565b6112bb6040805160808101825260008082526020820181905291810182905290606082015290565b603660006112fe85858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506125ee92505050565b81526020808201929092526040908101600020815160808101835281546001600160401b038082168352600160401b8204811695830195909552600160801b81049094169281019290925290916060830190600160c01b900460ff16600281111561136b5761136b614cd8565b600281111561137c5761137c614cd8565b9052509392505050565b6033546001600160a01b031633146113b05760405162461bcd60e51b81526004016106a49061522d565b603454600160401b900460ff16156113da5760405162461bcd60e51b81526004016106a490615275565b6033546113ef906001600160a01b0316611f87565b565b336001600160a01b037f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e16146114395760405162461bcd60e51b81526004016106a49061545f565b611447633b9aca0082615670565b156114d15760405162461bcd60e51b815260206004820152604e60248201527f456967656e506f642e776974686472617752657374616b6564426561636f6e4360448201527f6861696e4554483a20616d6f756e74576569206d75737420626520612077686f60648201526d1b194811ddd95a48185b5bdd5b9d60921b608482015260a4016106a4565b60006114e1633b9aca0083615684565b6034549091506001600160401b03908116908216111561159a5760405162461bcd60e51b815260206004820152606260248201527f456967656e506f642e776974686472617752657374616b6564426561636f6e4360448201527f6861696e4554483a20616d6f756e74477765692065786365656473207769746860648201527f6472617761626c6552657374616b6564457865637574696f6e4c617965724777608482015261656960f01b60a482015260c4016106a4565b603480548291906000906115b89084906001600160401b0316615698565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550826001600160a01b03167f8947fd2ce07ef9cc302c4e8f0461015615d91ce851564839e91cc804c2f49d8e8360405161161791815260200190565b60405180910390a26116298383612c0b565b505050565b600054610100900460ff161580801561164e5750600054600160ff909116105b806116685750303b158015611668575060005460ff166001145b6116cb5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016106a4565b6000805460ff1916600117905580156116ee576000805461ff0019166101001790555b6001600160a01b0382166117615760405162461bcd60e51b815260206004820152603460248201527f456967656e506f642e696e697469616c697a653a20706f644f776e65722063616044820152736e6e6f74206265207a65726f206164647265737360601b60648201526084016106a4565b603380546001600160a01b0384166001600160a01b031990911681179091556034805460ff60401b1916600160401b1790556040517fca8dfc8c5e0a67a74501c072a3325f685259bebbae7cfd230ab85198a78b70cd90600090a28015611802576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6033546001600160a01b031633146118305760405162461bcd60e51b81526004016106a49061522d565b604051635ac86ab760e01b8152600560048201819052907f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e6001600160a01b031690635ac86ab790602401602060405180830381865afa158015611898573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118bc91906151ae565b156118d95760405162461bcd60e51b81526004016106a4906151d0565b82518451146119645760405162461bcd60e51b815260206004820152604b60248201527f456967656e506f642e7265636f766572546f6b656e733a20746f6b656e4c697360448201527f7420616e6420616d6f756e7473546f5769746864726177206d7573742062652060648201526a0e6c2daca40d8cadccee8d60ab1b608482015260a4016106a4565b60005b84518110156119d2576119c083858381518110611986576119866153be565b60200260200101518784815181106119a0576119a06153be565b60200260200101516001600160a01b0316612c159092919063ffffffff16565b806119ca81615444565b915050611967565b5050505050565b604051635ac86ab760e01b81526004808201819052907f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e6001600160a01b031690635ac86ab790602401602060405180830381865afa158015611a40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a6491906151ae565b15611a815760405162461bcd60e51b81526004016106a4906151d0565b8386148015611a8f57508588145b8015611a9a57508782145b611b0e576040805162461bcd60e51b81526020600482015260248101919091527f456967656e506f642e766572696679416e6450726f636573735769746864726160448201527f77616c733a20696e70757473206d7573742062652073616d65206c656e67746860648201526084016106a4565b60405163d1c64cc960e01b81526001600160401b038c166004820152611bb2907f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e6001600160a01b03169063d1c64cc990602401602060405180830381865afa158015611b7f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ba3919061535f565b8b35610ac760208e018e615378565b604080518082019091526000808252602082015260005b83811015611cb2576000611c6d8d358d8d85818110611bea57611bea6153be565b9050602002810190611bfc91906156c0565b8c8c86818110611c0e57611c0e6153be565b9050602002810190611c209190615378565b8c8c88818110611c3257611c326153be565b9050602002810190611c4491906153fb565b8c8c8a818110611c5657611c566153be565b9050602002810190611c6891906153fb565b612c67565b80518451919250908490611c82908390614ab6565b9052506020808201519084018051611c9b908390615594565b905250819050611caa81615444565b915050611bc9565b50805115611ce1576033548151611ce1916001600160a01b031690611cdc90633b9aca00906156e1565b613152565b602081015115611d965760335460208201516001600160a01b037f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e81169263c2c51c4092911690611d3790633b9aca00906155d5565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b158015611d7d57600080fd5b505af1158015611d91573d6000803e3d6000fd5b505050505b505050505050505050505050565b6033546001600160a01b03163314611dce5760405162461bcd60e51b81526004016106a49061522d565b604051635ac86ab760e01b8152600560048201819052907f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e6001600160a01b031690635ac86ab790602401602060405180830381865afa158015611e36573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e5a91906151ae565b15611e775760405162461bcd60e51b81526004016106a4906151d0565b603754821115611f285760405162461bcd60e51b815260206004820152606a60248201527f456967656e506f642e77697468647261776e6f6e426561636f6e436861696e4560448201527f544842616c616e63655765693a20616d6f756e74546f5769746864726177206960648201527f732067726561746572207468616e206e6f6e426561636f6e436861696e45544860848201526942616c616e636557656960b01b60a482015260c4016106a4565b8160376000828254611f3a9190615700565b90915550506040518281526001600160a01b038416907f30420aacd028abb3c1fd03aba253ae725d6ddd52d16c9ac4cb5742cd43f530969060200160405180910390a26116298383613152565b6033805467ffffffffffffffff60a01b19164263ffffffff16600160a01b021790556000603755611fb88147613152565b50565b611fc7600360206156e1565b81146120575760405162461bcd60e51b815260206004820152605360248201527f426561636f6e436861696e50726f6f66732e7665726966795374617465526f6f60448201527f74416761696e73744c6174657374426c6f636b526f6f743a2050726f6f6620686064820152720c2e640d2dcc6dee4e4cac6e840d8cadccee8d606b1b608482015260a4016106a4565b61209c82828080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250889250879150600390506131e0565b6121435760405162461bcd60e51b815260206004820152606660248201527f426561636f6e436861696e50726f6f66732e7665726966795374617465526f6f60448201527f74416761696e73744c6174657374426c6f636b526f6f743a20496e76616c696460648201527f206c617465737420626c6f636b2068656164657220726f6f74206d65726b6c6560848201526510383937b7b360d11b60a482015260c4016106a4565b50505050565b6000806121888484808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506131fa92505050565b6000818152603660209081526040808320815160808101835281546001600160401b038082168352600160401b8204811695830195909552600160801b8104909416928101929092529394509192906060830190600160c01b900460ff1660028111156121f7576121f7614cd8565b600281111561220857612208614cd8565b905250905060008160600151600281111561222557612225614cd8565b146122ce5760405162461bcd60e51b815260206004820152606760248201527f456967656e506f642e766572696679436f72726563745769746864726177616c60448201527f43726564656e7469616c733a2056616c696461746f72206d757374206265206960648201527f6e61637469766520746f2070726f7665207769746864726177616c2063726564608482015266656e7469616c7360c81b60a482015260c4016106a4565b6122d66126e8565b6122df90615717565b61231b86868080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061321e92505050565b146123a25760405162461bcd60e51b815260206004820152604b60248201527f456967656e506f642e766572696679436f72726563745769746864726177616c60448201527f43726564656e7469616c733a2050726f6f66206973206e6f7420666f7220746860648201526a1a5cc8115a59d95b941bd960aa1b608482015260a4016106a4565b60006123e086868080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061323392505050565b90506123f08a87878b8b8e613258565b6001606083015264ffffffffff891682526001600160401b038b811660408401527f000000000000000000000000000000000000000000000000000000077359400081169082161115612471576001600160401b037f0000000000000000000000000000000000000000000000000000000773594000166020830152612481565b6001600160401b03811660208301525b6000838152603660209081526040918290208451815492860151938601516001600160401b03908116600160801b0267ffffffffffffffff60801b19958216600160401b026001600160801b0319909516919092161792909217928316821781556060850151859391929091839160ff60c01b191668ffffffffffffffffff60801b1990911617600160c01b83600281111561251f5761251f614cd8565b02179055505060405164ffffffffff8b1681527f2d0800bbc377ea54a08c5db6a87aafff5e3e9c8fead0eda110e40e0c10441449915060200160405180910390a17f0e5fac175b83177cc047381e030d8fb3b42b37bd1c025e22c280facad62c32df898c84602001516040516125ba9392919064ffffffffff9390931683526001600160401b03918216602084015216604082015260600190565b60405180910390a1633b9aca0082602001516001600160401b03166125df91906156e1565b9b9a5050505050505050505050565b600081516030146126775760405162461bcd60e51b815260206004820152604760248201527f456967656e506f642e5f63616c63756c61746556616c696461746f725075626b60448201527f657948617368206d75737420626520612034382d6279746520424c53207075626064820152666c6963206b657960c81b608482015260a4016106a4565b60405160029061268e90849060009060200161573b565b60408051601f19818403018152908290526126a89161576a565b602060405180830381855afa1580156126c5573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190610c64919061535f565b60408051600160f81b60208201526000602182015230606090811b6bffffffffffffffffffffffff1916602c8301529101604051602081830303815290604052905090565b60008061276c84848080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061323392505050565b905060006127ac8585808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506131fa92505050565b6000818152603660209081526040808320815160808101835281546001600160401b038082168352600160401b8204811695830195909552600160801b8104909416928101929092529394509192906060830190600160c01b900460ff16600281111561281b5761281b614cd8565b600281111561282c5761282c614cd8565b8152505090508a6001600160401b031681604001516001600160401b0316106128e35760405162461bcd60e51b815260206004820152605c60248201527f456967656e506f642e76657269667942616c616e63655570646174653a20566160448201527f6c696461746f72732062616c616e63652068617320616c72656164792062656560648201527f6e207570646174656420666f7220746869732074696d657374616d7000000000608482015260a4016106a4565b6001816060015160028111156128fb576128fb614cd8565b146129635760405162461bcd60e51b815260206004820152603260248201527f456967656e506f642e76657269667942616c616e63655570646174653a2056616044820152716c696461746f72206e6f742061637469766560701b60648201526084016106a4565b61296c8b6134af565b6001600160401b03166129b187878080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061359992505050565b6001600160401b031611612a54576000836001600160401b031611612a545760405162461bcd60e51b815260206004820152604d60248201527f456967656e506f642e76657269667942616c616e63655570646174653a20766160448201527f6c696461746f7220697320776974686472617761626c6520627574206861732060648201526c3737ba103bb4ba34323930bbb760991b608482015260a4016106a4565b612a628987878b8b8f613258565b602081015160006001600160401b037f000000000000000000000000000000000000000000000000000000077359400081169086161115612ac457507f0000000000000000000000000000000000000000000000000000000773594000612ac7565b50835b6001600160401b0380821660208086019182528f831660408088019182526000898152603690935290912086518154935192518516600160801b0267ffffffffffffffff60801b19938616600160401b026001600160801b031990951691909516179290921790811683178255606086015186939091839160ff60c01b191668ffffffffffffffffff60801b1990911617600160c01b836002811115612b6f57612b6f614cd8565b0217905550905050816001600160401b0316816001600160401b031614612bfb577f0e5fac175b83177cc047381e030d8fb3b42b37bd1c025e22c280facad62c32df8c8e83604051612be69392919064ffffffffff9390931683526001600160401b03918216602084015216604082015260600190565b60405180910390a1612bf881836135b1565b95505b5050505050979650505050505050565b61180282826135d0565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526116299084906136e9565b6040805180820190915260008082526020820152612c8c612c87896157eb565b6137be565b6033546001600160401b03600160a01b909104811690821611612cc15760405162461bcd60e51b81526004016106a4906152c4565b6000612ccf612c878b6157eb565b90506000612d0f8888808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506131fa92505050565b905060008082815260366020526040902054600160c01b900460ff166002811115612d3c57612d3c614cd8565b1415612df35760405162461bcd60e51b815260206004820152607460248201527f456967656e506f642e5f766572696679416e6450726f6365737357697468647260448201527f6177616c3a2056616c696461746f72206e657665722070726f76656e20746f2060648201527f68617665207769746864726177616c2063726564656e7469616c7320706f696e6084820152731d1959081d1bc81d1a1a5cc818dbdb9d1c9858dd60621b60a482015260c4016106a4565b60008181526035602090815260408083206001600160401b038616845290915290205460ff1615612eb25760405162461bcd60e51b815260206004820152605b60248201527f456967656e506f642e5f766572696679416e6450726f6365737357697468647260448201527f6177616c3a207769746864726177616c2068617320616c72656164792062656560648201527f6e2070726f76656e20666f7220746869732074696d657374616d700000000000608482015260a4016106a4565b6001603560008381526020019081526020016000206000846001600160401b03166001600160401b0316815260200190815260200160002060006101000a81548160ff021916908315150217905550612f8f8c87878e7f000000000000000000000000ed1db453c3156ff3155a97ad217b3087d5dc5f6e6001600160a01b03166344e71c806040518163ffffffff1660e01b8152600401602060405180830381865afa158015612f66573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f8a9190615927565b6137ce565b6000612fcd8787808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506141ef92505050565b9050612fdd8d8a8a8e8e86613258565b600061301b88888080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061420792505050565b90506130598a8a8080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525061359992505050565b6001600160401b031661307361306e8f6157eb565b61421f565b6001600160401b03161061312b57603354600084815260366020908152604091829020825160808101845281546001600160401b038082168352600160401b8204811694830194909452600160801b81049093169381019390935261312093869388938a936001600160a01b03909316928892916060830190600160c01b900460ff16600281111561310757613107614cd8565b600281111561311857613118614cd8565b905250614231565b955050505050613145565b60335461312090839086906001600160a01b031684614442565b5098975050505050505050565b603354604051633036cd5360e21b81526001600160a01b03918216600482015283821660248201527f000000000000000000000000f7cd8fa9b94db2aa972023b379c7f72c65e4de9d9091169063c0db354c9083906044016000604051808303818588803b1580156131c357600080fd5b505af11580156131d7573d6000803e3d6000fd5b50505050505050565b6000836131ee868585614520565b1490505b949350505050565b60008160008151811061320f5761320f6153be565b60200260200101519050919050565b60008160018151811061320f5761320f6153be565b6000610c648260028151811061324b5761324b6153be565b602002602001015161466c565b61326460036002615a28565b84146132ef5760405162461bcd60e51b815260206004820152604e60248201527f426561636f6e436861696e50726f6f66732e76657269667956616c696461746f60448201527f724669656c64733a2056616c696461746f72206669656c64732068617320696e60648201526d0c6dee4e4cac6e840d8cadccee8d60931b608482015260a4016106a4565b60056132fd60286001614ab6565b6133079190614ab6565b6133129060206156e1565b82146133925760405162461bcd60e51b815260206004820152604360248201527f426561636f6e436861696e50726f6f66732e76657269667956616c696461746f60448201527f724669656c64733a2050726f6f662068617320696e636f7272656374206c656e6064820152620cee8d60eb1b608482015260a4016106a4565b600064ffffffffff82166133a860286001614ab6565b600b901b17905060006133ed8787808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506146d392505050565b905061343385858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508c92508591508690506131e0565b6134a55760405162461bcd60e51b815260206004820152603d60248201527f426561636f6e436861696e50726f6f66732e76657269667956616c696461746f60448201527f724669656c64733a20496e76616c6964206d65726b6c652070726f6f6600000060648201526084016106a4565b5050505050505050565b60007f000000000000000000000000000000000000000000000000000000006059f4606001600160401b0316826001600160401b031610156135595760405162461bcd60e51b815260206004820152603760248201527f456967656e506f642e5f74696d657374616d70546f45706f63683a2074696d6560448201527f7374616d70206973206265666f72652067656e6573697300000000000000000060648201526084016106a4565b613565600c6020615a34565b61358f7f000000000000000000000000000000000000000000000000000000006059f46084615698565b610c649190615a63565b6000610c648260078151811061324b5761324b6153be565b60006135c96001600160401b03808416908516615a89565b9392505050565b804710156136205760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e636500000060448201526064016106a4565b6000826001600160a01b03168260405160006040518083038185875af1925050503d806000811461366d576040519150601f19603f3d011682016040523d82523d6000602084013e613672565b606091505b50509050806116295760405162461bcd60e51b815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d6179206861766520726576657274656400000000000060648201526084016106a4565b600061373e826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166149809092919063ffffffff16565b905080516000148061375f57508080602001905181019061375f91906151ae565b6116295760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016106a4565b6000610c6482610140015161466c565b6137d9600280615a28565b831461384d5760405162461bcd60e51b81526020600482015260496024820152600080516020615b2483398151915260448201527f616c3a207769746864726177616c4669656c64732068617320696e636f7272656064820152680c6e840d8cadccee8d60bb1b608482015260a4016106a4565b613859600d6002615a28565b61386960c0840160a08501615ac8565b6001600160401b0316106138d35760405162461bcd60e51b815260206004820152603f6024820152600080516020615b2483398151915260448201527f616c3a20626c6f636b526f6f74496e64657820697320746f6f206c617267650060648201526084016106a4565b6138df60046002615a28565b6138f0610100840160e08501615ac8565b6001600160401b03161061395c576040805162461bcd60e51b8152602060048201526024810191909152600080516020615b2483398151915260448201527f616c3a207769746864726177616c496e64657820697320746f6f206c6172676560648201526084016106a4565b61396860186002615a28565b61397860e0840160c08501615ac8565b6001600160401b0316106139f25760405162461bcd60e51b81526020600482015260476024820152600080516020615b2483398151915260448201527f616c3a20686973746f726963616c53756d6d617279496e64657820697320746f6064820152666f206c6172676560c81b608482015260a4016106a4565b60006001600160401b038216613a0a612c87856157eb565b6001600160401b031610613a1f576005613a22565b60045b9050613a2f600482614ab6565b613a3a906001614ab6565b613a459060206156e1565b613a4f8480615378565b905014613ac35760405162461bcd60e51b81526020600482015260486024820152600080516020615b2483398151915260448201527f616c3a207769746864726177616c50726f6f662068617320696e636f727265636064820152670e840d8cadccee8d60c31b608482015260a4016106a4565b613acf60046003614ab6565b613ada9060206156e1565b613ae76040850185615378565b905014613b615760405162461bcd60e51b815260206004820152604e6024820152600080516020615b2483398151915260448201527f616c3a20657865637574696f6e5061796c6f616450726f6f662068617320696e60648201526d0c6dee4e4cac6e840d8cadccee8d60931b608482015260a4016106a4565b613b6d600360206156e1565b613b7a6020850185615378565b905014613be85760405162461bcd60e51b81526020600482015260426024820152600080516020615b2483398151915260448201527f616c3a20736c6f7450726f6f662068617320696e636f7272656374206c656e676064820152610e8d60f31b608482015260a4016106a4565b613bf38160206156e1565b613c006060850185615378565b905014613c735760405162461bcd60e51b81526020600482015260476024820152600080516020615b2483398151915260448201527f616c3a2074696d657374616d7050726f6f662068617320696e636f7272656374606482015266040d8cadccee8d60cb1b608482015260a4016106a4565b600d613c8160186001614ab6565b613c8c906005614ab6565b613c97906001614ab6565b613ca19190614ab6565b613cac9060206156e1565b613cb96080850185615378565b905014613d425760405162461bcd60e51b81526020600482015260586024820152600080516020615b2483398151915260448201527f616c3a20686973746f726963616c53756d6d617279426c6f636b526f6f74507260648201527f6f6f662068617320696e636f7272656374206c656e6774680000000000000000608482015260a4016106a4565b6000613d5460c0850160a08601615ac8565b6001600160401b03166000613d6b600d6001614ab6565b613d7b60e0880160c08901615ac8565b6001600160401b0316901b600d613d9460186001614ab6565b613d9f906001614ab6565b613da99190614ab6565b601b901b1717179050613e04613dc26080860186615378565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508b92505050610100870135846131e0565b613e775760405162461bcd60e51b815260206004820152604a6024820152600080516020615b2483398151915260448201527f616c3a20496e76616c696420686973746f726963616c73756d6d617279206d656064820152693935b63290383937b7b360b11b608482015260a4016106a4565b613ece613e876020860186615378565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052506101008a013593506101208a0135925090506131e0565b613f2e5760405162461bcd60e51b815260206004820152603d6024820152600080516020615b2483398151915260448201527f616c3a20496e76616c696420736c6f74206d65726b6c652070726f6f6600000060648201526084016106a4565b6049613f86613f406040870187615378565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250505050610100870135610160880135846131e0565b613ff85760405162461bcd60e51b81526020600482015260496024820152600080516020615b2483398151915260448201527f616c3a20496e76616c696420657865637574696f6e5061796c6f6164206d657260648201526835b63290383937b7b360b91b608482015260a4016106a4565b506140506140096060860186615378565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050505061016086013561014087013560096131e0565b6140bb5760405162461bcd60e51b81526020600482015260426024820152600080516020615b2483398151915260448201527f616c3a20496e76616c69642074696d657374616d70206d65726b6c652070726f60648201526137b360f11b608482015260a4016106a4565b60006140ce610100860160e08701615ac8565b6001600160401b03166140e360046001614ab6565b600e901b17905060006141288888808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152506146d392505050565b90506141786141378780615378565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050505061016088013583856131e0565b6141e45760405162461bcd60e51b81526020600482015260436024820152600080516020615b2483398151915260448201527f616c3a20496e76616c6964207769746864726177616c206d65726b6c6520707260648201526237b7b360e91b608482015260a4016106a4565b505050505050505050565b6000610c648260018151811061324b5761324b6153be565b6000610c648260038151811061324b5761324b6153be565b6000602061358f83610120015161466c565b604080518082019091526000808252602082015260007f00000000000000000000000000000000000000000000000000000007735940006001600160401b0316846001600160401b031611156142a857507f00000000000000000000000000000000000000000000000000000007735940006142ab565b50825b60408051808201909152600080825260208201526142c98286615698565b6001600160401b0390811682526034805484926000916142eb91859116615ae5565b92506101000a8154816001600160401b0302191690836001600160401b0316021790555061431d8285602001516135b1565b602082810191909152600090850152600260608501819052506000888152603660209081526040918290208651815492880151938801516001600160401b03908116600160801b0267ffffffffffffffff60801b19958216600160401b026001600160801b0319909516919092161792909217928316821781556060870151879391929091839160ff60c01b191668ffffffffffffffffff60801b1990911617600160c01b8360028111156143d4576143d4614cd8565b0217905550506040805164ffffffffff8c1681526001600160401b038a8116602083015288168183015290516001600160a01b03891692507fb76a93bb649ece524688f1a01d184e0bbebcda58eae80c28a898bec3fb5a09639181900360600190a298975050505050505050565b60408051808201909152600080825260208201526040805164ffffffffff871681526001600160401b0380871660208301528416918101919091526001600160a01b038416907f8a7335714231dbd551aaba6314f4a97a14c201e53a3e25e1140325cdf67d7a4e9060600160405180910390a2603880548391906000906144d39084906001600160401b0316615ae5565b92506101000a8154816001600160401b0302191690836001600160401b031602179055506040518060400160405280836001600160401b0316815260200160008152509050949350505050565b6000835160001415801561453f57506020845161453d9190615670565b155b6145ce5760405162461bcd60e51b815260206004820152605460248201527f4d65726b6c652e70726f63657373496e636c7573696f6e50726f6f665368613260448201527f35363a2070726f6f66206c656e6774682073686f756c642062652061206e6f6e60648201527316bd32b9379036bab63a34b836329037b310199960611b608482015260a4016106a4565b604080516020808201909252848152905b85518111614662576145f2600285615670565b614625578151600052808601516020526020826040600060026107d05a03fa61461a57600080fd5b600284049350614650565b8086015160005281516020526020826040600060026107d05a03fa61464957600080fd5b6002840493505b61465b602082614ab6565b90506145df565b5051949350505050565b60f881901c60e882901c61ff00161760d882901c62ff0000161760c882901c63ff000000161764ff0000000060b883901c161765ff000000000060a883901c161766ff000000000000609883901c161767ff0000000000000060889290921c919091161790565b600080600283516146e49190615684565b90506000816001600160401b0381111561470057614700614ee5565b604051908082528060200260200182016040528015614729578160200160208202803683370190505b50905060005b828110156148305760028561474483836156e1565b81518110614754576147546153be565b60200260200101518683600261476a91906156e1565b614775906001614ab6565b81518110614785576147856153be565b60200260200101516040516020016147a7929190918252602082015260400190565b60408051601f19818403018152908290526147c19161576a565b602060405180830381855afa1580156147de573d6000803e3d6000fd5b5050506040513d601f19601f82011682018060405250810190614801919061535f565b828281518110614813576148136153be565b60209081029190910101528061482881615444565b91505061472f565b5061483c600283615684565b91505b811561495c5760005b828110156149495760028261485d83836156e1565b8151811061486d5761486d6153be565b60200260200101518383600261488391906156e1565b61488e906001614ab6565b8151811061489e5761489e6153be565b60200260200101516040516020016148c0929190918252602082015260400190565b60408051601f19818403018152908290526148da9161576a565b602060405180830381855afa1580156148f7573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061491a919061535f565b82828151811061492c5761492c6153be565b60209081029190910101528061494181615444565b915050614848565b50614955600283615684565b915061483f565b8060008151811061496f5761496f6153be565b602002602001015192505050919050565b60606131f2848460008585600080866001600160a01b031685876040516149a7919061576a565b60006040518083038185875af1925050503d80600081146149e4576040519150601f19603f3d011682016040523d82523d6000602084013e6149e9565b606091505b50915091506149fa87838387614a05565b979650505050505050565b60608315614a71578251614a6a576001600160a01b0385163b614a6a5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016106a4565b50816131f2565b6131f28383815115614a865781518083602001fd5b8060405162461bcd60e51b81526004016106a49190615b10565b634e487b7160e01b600052601160045260246000fd5b60008219821115614ac957614ac9614aa0565b500190565b6001600160401b0381168114611fb857600080fd5b8035614aee81614ace565b919050565b60008060408385031215614b0657600080fd5b823591506020830135614b1881614ace565b809150509250929050565b600060408284031215614b3557600080fd5b50919050565b60008083601f840112614b4d57600080fd5b5081356001600160401b03811115614b6457600080fd5b6020830191508360208260051b8501011115614b7f57600080fd5b9250929050565b60008060008060008060008060a0898b031215614ba257600080fd5b8835614bad81614ace565b975060208901356001600160401b0380821115614bc957600080fd5b614bd58c838d01614b23565b985060408b0135915080821115614beb57600080fd5b614bf78c838d01614b3b565b909850965060608b0135915080821115614c1057600080fd5b614c1c8c838d01614b3b565b909650945060808b0135915080821115614c3557600080fd5b50614c428b828c01614b3b565b999c989b5096995094979396929594505050565b60008083601f840112614c6857600080fd5b5081356001600160401b03811115614c7f57600080fd5b602083019150836020828501011115614b7f57600080fd5b60008060208385031215614caa57600080fd5b82356001600160401b03811115614cc057600080fd5b614ccc85828601614c56565b90969095509350505050565b634e487b7160e01b600052602160045260246000fd5b60038110614d0c57634e487b7160e01b600052602160045260246000fd5b9052565b60208101610c648284614cee565b600060208284031215614d3057600080fd5b5035919050565b60006080820190506001600160401b03808451168352806020850151166020840152806040850151166040840152506060830151614d786060840182614cee565b5092915050565b600080600080600060608688031215614d9757600080fd5b85356001600160401b0380821115614dae57600080fd5b614dba89838a01614c56565b90975095506020880135915080821115614dd357600080fd5b50614de088828901614c56565b96999598509660400135949350505050565b60008060008060008060008060a0898b031215614e0e57600080fd5b8835614e1981614ace565b975060208901356001600160401b0380821115614e3557600080fd5b614e418c838d01614b3b565b909950975060408b0135915080821115614e5a57600080fd5b614e668c838d01614b23565b965060608b0135915080821115614c1057600080fd5b6001600160a01b0381168114611fb857600080fd5b8035614aee81614e7c565b60008060408385031215614eaf57600080fd5b8235614eba81614e7c565b946020939093013593505050565b600060208284031215614eda57600080fd5b81356135c981614e7c565b634e487b7160e01b600052604160045260246000fd5b60405161018081016001600160401b0381118282101715614f1e57614f1e614ee5565b60405290565b604051601f8201601f191681016001600160401b0381118282101715614f4c57614f4c614ee5565b604052919050565b60006001600160401b03821115614f6d57614f6d614ee5565b5060051b60200190565b600082601f830112614f8857600080fd5b81356020614f9d614f9883614f54565b614f24565b82815260059290921b84018101918181019086841115614fbc57600080fd5b8286015b84811015614fd75780358352918301918301614fc0565b509695505050505050565b600080600060608486031215614ff757600080fd5b83356001600160401b038082111561500e57600080fd5b818601915086601f83011261502257600080fd5b81356020615032614f9883614f54565b82815260059290921b8401810191818101908a84111561505157600080fd5b948201945b8386101561507857853561506981614e7c565b82529482019490820190615056565b9750508701359250508082111561508e57600080fd5b5061509b86828701614f77565b9250506150aa60408501614e91565b90509250925092565b60008060008060008060008060008060c08b8d0312156150d257600080fd5b6150db8b614ae3565b995060208b01356001600160401b03808211156150f757600080fd5b6151038e838f01614b23565b9a5060408d013591508082111561511957600080fd5b6151258e838f01614b3b565b909a50985060608d013591508082111561513e57600080fd5b61514a8e838f01614b3b565b909850965060808d013591508082111561516357600080fd5b61516f8e838f01614b3b565b909650945060a08d013591508082111561518857600080fd5b506151958d828e01614b3b565b915080935050809150509295989b9194979a5092959850565b6000602082840312156151c057600080fd5b815180151581146135c957600080fd5b6020808252603e908201527f456967656e506f642e6f6e6c795768656e4e6f745061757365643a20696e646560408201527f782069732070617573656420696e20456967656e506f644d616e616765720000606082015260800190565b60208082526028908201527f456967656e506f642e6f6e6c79456967656e506f644f776e65723a206e6f74206040820152673837b227bbb732b960c11b606082015260800190565b6020808252602f908201527f456967656e506f642e6861734e6576657252657374616b65643a20726573746160408201526e1ada5b99c81a5cc8195b98589b1959608a1b606082015260800190565b6020808252606f908201527f456967656e506f642e70726f6f664973466f7256616c696454696d657374616d60408201527f703a20626561636f6e20636861696e2070726f6f66206d75737420626520666f60608201527f722074696d657374616d70206166746572206d6f7374526563656e745769746860808201526e064726177616c54696d657374616d7608c1b60a082015260c00190565b60006020828403121561537157600080fd5b5051919050565b6000808335601e1984360301811261538f57600080fd5b8301803591506001600160401b038211156153a957600080fd5b602001915036819003821315614b7f57600080fd5b634e487b7160e01b600052603260045260246000fd5b6000602082840312156153e657600080fd5b813564ffffffffff811681146135c957600080fd5b6000808335601e1984360301811261541257600080fd5b8301803591506001600160401b0382111561542c57600080fd5b6020019150600581901b3603821315614b7f57600080fd5b600060001982141561545857615458614aa0565b5060010190565b60208082526031908201527f456967656e506f642e6f6e6c79456967656e506f644d616e616765723a206e6f6040820152703a1032b4b3b2b72837b226b0b730b3b2b960791b606082015260800190565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60005b838110156154f45781810151838201526020016154dc565b838111156121435750506000910152565b6000815180845261551d8160208601602086016154d9565b601f01601f19169290920160200192915050565b60808152600061554560808301888a6154b0565b82810360208401526155578188615505565b9050828103604084015261556c8186886154b0565b915050826060830152979650505050505050565b6020815260006131f26020830184866154b0565b600080821280156001600160ff1b03849003851316156155b6576155b6614aa0565b600160ff1b83900384128116156155cf576155cf614aa0565b50500190565b60006001600160ff1b03818413828413808216868404861116156155fb576155fb614aa0565b600160ff1b600087128281168783058912161561561a5761561a614aa0565b6000871292508782058712848416161561563657615636614aa0565b8785058712818416161561564c5761564c614aa0565b505050929093029392505050565b634e487b7160e01b600052601260045260246000fd5b60008261567f5761567f61565a565b500690565b6000826156935761569361565a565b500490565b60006001600160401b03838116908316818110156156b8576156b8614aa0565b039392505050565b6000823561017e198336030181126156d757600080fd5b9190910192915050565b60008160001904831182151516156156fb576156fb614aa0565b500290565b60008282101561571257615712614aa0565b500390565b80516020808301519190811015614b355760001960209190910360031b1b16919050565b6000835161574d8184602088016154d9565b6001600160801b0319939093169190920190815260100192915050565b600082516156d78184602087016154d9565b600082601f83011261578d57600080fd5b81356001600160401b038111156157a6576157a6614ee5565b6157b9601f8201601f1916602001614f24565b8181528460208386010111156157ce57600080fd5b816020850160208301376000918101602001919091529392505050565b600061018082360312156157fe57600080fd5b615806614efb565b82356001600160401b038082111561581d57600080fd5b6158293683870161577c565b8352602085013591508082111561583f57600080fd5b61584b3683870161577c565b6020840152604085013591508082111561586457600080fd5b6158703683870161577c565b6040840152606085013591508082111561588957600080fd5b6158953683870161577c565b606084015260808501359150808211156158ae57600080fd5b506158bb3682860161577c565b6080830152506158cd60a08401614ae3565b60a08201526158de60c08401614ae3565b60c08201526158ef60e08401614ae3565b60e082015261010083810135908201526101208084013590820152610140808401359082015261016092830135928101929092525090565b60006020828403121561593957600080fd5b81516135c981614ace565b600181815b8085111561597f57816000190482111561596557615965614aa0565b8085161561597257918102915b93841c9390800290615949565b509250929050565b60008261599657506001610c64565b816159a357506000610c64565b81600181146159b957600281146159c3576159df565b6001915050610c64565b60ff8411156159d4576159d4614aa0565b50506001821b610c64565b5060208310610133831016604e8410600b8410161715615a02575081810a610c64565b615a0c8383615944565b8060001904821115615a2057615a20614aa0565b029392505050565b60006135c98383615987565b60006001600160401b0380831681851681830481118215151615615a5a57615a5a614aa0565b02949350505050565b60006001600160401b0380841680615a7d57615a7d61565a565b92169190910492915050565b60008083128015600160ff1b850184121615615aa757615aa7614aa0565b6001600160ff1b0384018313811615615ac257615ac2614aa0565b50500390565b600060208284031215615ada57600080fd5b81356135c981614ace565b60006001600160401b03808316818516808303821115615b0757615b07614aa0565b01949350505050565b6020815260006135c9602083018461550556fe426561636f6e436861696e50726f6f66732e7665726966795769746864726177a2646970667358221220ee4b79d40165064464d98297b8cbf7c82c24d69c3c69e735c529c0d6b2c496b464736f6c634300080c0033", - "storage": { - "0x0": "0xff" - } - }, - "0x8ce361602b935680e8dec218b820ff5056beb7af": { - "nonce": 1, - "balance": "0x0", - "code": "0x60806040523661001357610011610017565b005b6100115b61001f6101b7565b6001600160a01b0316336001600160a01b0316141561016f5760606001600160e01b031960003516631b2ce7f360e11b8114156100655761005e6101ea565b9150610167565b6001600160e01b0319811663278f794360e11b14156100865761005e610241565b6001600160e01b031981166308f2839760e41b14156100a75761005e610287565b6001600160e01b031981166303e1469160e61b14156100c85761005e6102b8565b6001600160e01b03198116635c60da1b60e01b14156100e95761005e6102f8565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b61017761030c565b565b606061019e83836040518060600160405280602781526020016108576027913961031c565b9392505050565b90565b6001600160a01b03163b151590565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101f4610394565b600061020336600481846106a2565b81019061021091906106e8565b905061022d8160405180602001604052806000815250600061039f565b505060408051602081019091526000815290565b606060008061025336600481846106a2565b8101906102609190610719565b915091506102708282600161039f565b604051806020016040528060008152509250505090565b6060610291610394565b60006102a036600481846106a2565b8101906102ad91906106e8565b905061022d816103cb565b60606102c2610394565b60006102cc6101b7565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b6060610302610394565b60006102cc610422565b610177610317610422565b610431565b6060600080856001600160a01b0316856040516103399190610807565b600060405180830381855af49150503d8060008114610374576040519150601f19603f3d011682016040523d82523d6000602084013e610379565b606091505b509150915061038a86838387610455565b9695505050505050565b341561017757600080fd5b6103a8836104d3565b6000825111806103b55750805b156103c6576103c48383610179565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103f46101b7565b604080516001600160a01b03928316815291841660208301520160405180910390a161041f81610513565b50565b600061042c6105bc565b905090565b3660008037600080366000845af43d6000803e808015610450573d6000f35b3d6000fd5b606083156104c15782516104ba576001600160a01b0385163b6104ba5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161015e565b50816104cb565b6104cb83836105e4565b949350505050565b6104dc8161060e565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6001600160a01b0381166105785760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161015e565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6101db565b8151156105f45781518083602001fd5b8060405162461bcd60e51b815260040161015e9190610823565b6001600160a01b0381163b61067b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161015e565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61059b565b600080858511156106b257600080fd5b838611156106bf57600080fd5b5050820193919092039150565b80356001600160a01b03811681146106e357600080fd5b919050565b6000602082840312156106fa57600080fd5b61019e826106cc565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561072c57600080fd5b610735836106cc565b9150602083013567ffffffffffffffff8082111561075257600080fd5b818501915085601f83011261076657600080fd5b81358181111561077857610778610703565b604051601f8201601f19908116603f011681019083821181831017156107a0576107a0610703565b816040528281528860208487010111156107b957600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156107f65781810151838201526020016107de565b838111156103c45750506000910152565b600082516108198184602087016107db565b9190910192915050565b60208152600082518060208401526108428160408501602087016107db565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212205685c4ad655ee149c77141d53c938af08bb4457fb9509b6f1725a2ee99781c1a64736f6c634300080c0033", - "storage": { - "0x0": "0x1", - "0x33": "0xa0ee7a142d267c1f36714e4a8f75612f20a79720", - "0x65": "0xa15bb66138824a1c7167f5e85b957d04dd34e468", - "0x66": "0x0", - "0x97": "0xdc385c976b9c09c9f3d60037ddb883ae6a226829befccc1535e616a4fea08a6", - "0x9d": "0x0", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x82c6d3ed4cd33d8ec1e51d0b5cc1d822eaa0c3dc", - "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x700b6a60ce7eaaea56f065753d8dcb9653dbad35" - } - }, - "0x90f79bf6eb2c4f870365e785982e1f101e93b906": { - "nonce": 0, - "balance": "0x21e19e0c9bab2400000", - "code": "0x", - "storage": {} - }, - "0x976ea74026e726554db657fa54763abd0c3a0aa9": { - "nonce": 0, - "balance": "0x21e19e0c9bab2400000", - "code": "0x", - "storage": {} - }, - "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": { - "nonce": 0, - "balance": "0x21e19e0c9bab2400000", - "code": "0x", - "storage": {} - }, - "0xa0ee7a142d267c1f36714e4a8f75612f20a79720": { - "nonce": 27, - "balance": "0x21e1888b34963d1a2bb", - "code": "0x", - "storage": {} - }, - "0xa15bb66138824a1c7167f5e85b957d04dd34e468": { - "nonce": 1, - "balance": "0x0", - "code": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c806346fbf68e146100515780638568520614610089578063ce5484281461009e578063eab66d7a146100b1575b600080fd5b61007461005f366004610313565b60006020819052908152604090205460ff1681565b60405190151581526020015b60405180910390f35b61009c610097366004610335565b6100dc565b005b61009c6100ac366004610313565b61011d565b6001546100c4906001600160a01b031681565b6040516001600160a01b039091168152602001610080565b6001546001600160a01b0316331461010f5760405162461bcd60e51b815260040161010690610371565b60405180910390fd5b6101198282610153565b5050565b6001546001600160a01b031633146101475760405162461bcd60e51b815260040161010690610371565b61015081610220565b50565b6001600160a01b0382166101bf5760405162461bcd60e51b815260206004820152602d60248201527f50617573657252656769737472792e5f7365745061757365723a207a65726f2060448201526c1859191c995cdcc81a5b9c1d5d609a1b6064820152608401610106565b6001600160a01b03821660008181526020818152604091829020805460ff19168515159081179091558251938452908301527f65d3a1fd4c13f05cba164f80d03ce90fb4b5e21946bfc3ab7dbd434c2d0b9152910160405180910390a15050565b6001600160a01b03811661028e5760405162461bcd60e51b815260206004820152602f60248201527f50617573657252656769737472792e5f736574556e7061757365723a207a657260448201526e1bc81859191c995cdcc81a5b9c1d5d608a1b6064820152608401610106565b600154604080516001600160a01b03928316815291831660208301527f06b4167a2528887a1e97a366eefe8549bfbf1ea3e6ac81cb2564a934d20e8892910160405180910390a1600180546001600160a01b0319166001600160a01b0392909216919091179055565b80356001600160a01b038116811461030e57600080fd5b919050565b60006020828403121561032557600080fd5b61032e826102f7565b9392505050565b6000806040838503121561034857600080fd5b610351836102f7565b91506020830135801515811461036657600080fd5b809150509250929050565b6020808252602a908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526939903ab73830bab9b2b960b11b60608201526080019056fea264697066735822122026d0da6bfc317f8d9c6ef8f95a0f798928296740e228ba50c1258f9c7fc9dbd464736f6c634300080c0033", - "storage": { - "0x1": "0x1e9", - "0x8279194eb4b29c3028809787a815b5fcda9c911d6a8c1dab7e4ef293adf594b9": "0x1" - } - }, - "0xb19b36b1456e65e3a6d514d3f715f204bd59f431": { - "nonce": 1, - "balance": "0x0", - "code": "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b600060405190815260200160405180910390f3fea2646970667358221220dd7e2a31432852d491862c4dbe404a1fc851ccb128c615f36b216c053090772d64736f6c634300080c0033", - "storage": {} - }, - "0xc6b8fbf96cf7bbe45576417ec2163acecfa88ecc": { - "nonce": 1, - "balance": "0x0", - "code": "0x60806040526004361061020f5760003560e01c80638ffb5a0d11610118578063c0ccbf10116100a0578063daf12cd41161006f578063daf12cd41461067b578063ea4d3c9b1461069b578063f2fde38b146106cf578063f6848d24146106ef578063fabc1cbc1461072a57600080fd5b8063c0ccbf1014610605578063c1de3aef1461061b578063c2c51c401461063b578063d1c64cc91461065b57600080fd5b8063a38406a3116100e7578063a38406a31461055b578063a6a509be1461057b578063b134427114610591578063beffbb89146105c5578063c052bd61146105e557600080fd5b80638ffb5a0d146104ca5780639104c319146104ea5780639b4e4634146105125780639ba062751461052557600080fd5b8063595c6a671161019b578063715018a61161016a578063715018a61461042e57806374cdd7981461044357806384d8106214610477578063886f11951461048c5780638da5cb5b146104ac57600080fd5b8063595c6a67146103975780635ac86ab7146103ac5780635c975abb146103ec57806360f4062b1461040157600080fd5b8063292b7b2b116101e2578063292b7b2b146102a9578063387b1300146102f557806339b70e381461031557806344e71c8014610349578063463db0381461037757600080fd5b80630cf2686d146102145780630e81073c1461023657806310d67a2f14610269578063136439dd14610289575b600080fd5b34801561022057600080fd5b5061023461022f3660046126aa565b61074a565b005b34801561024257600080fd5b506102566102513660046126d8565b610806565b6040519081526020015b60405180910390f35b34801561027557600080fd5b50610234610284366004612704565b610a3b565b34801561029557600080fd5b506102346102a43660046126aa565b610aeb565b3480156102b557600080fd5b506102dd7f000000000000000000000000196dbcbb54b8ec4958c959d8949ebfe87ac2aaaf81565b6040516001600160a01b039091168152602001610260565b34801561030157600080fd5b50610234610310366004612721565b610c2a565b34801561032157600080fd5b506102dd7f000000000000000000000000e1da8919f262ee86f9be05059c9280142cf23f4881565b34801561035557600080fd5b5061035e610fc9565b60405167ffffffffffffffff9091168152602001610260565b34801561038357600080fd5b50610234610392366004612762565b610ff2565b3480156103a357600080fd5b5061023461117e565b3480156103b857600080fd5b506103dc6103c736600461278c565b606654600160ff9092169190911b9081161490565b6040519015158152602001610260565b3480156103f857600080fd5b50606654610256565b34801561040d57600080fd5b5061025661041c366004612704565b609b6020526000908152604090205481565b34801561043a57600080fd5b50610234611245565b34801561044f57600080fd5b506102dd7f00000000000000000000000012975173b87f7595ee45dffb2ab812ece596bf8481565b34801561048357600080fd5b506102dd611259565b34801561049857600080fd5b506065546102dd906001600160a01b031681565b3480156104b857600080fd5b506033546001600160a01b03166102dd565b3480156104d657600080fd5b506102346104e53660046126d8565b611343565b3480156104f657600080fd5b506102dd73beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac081565b6102346105203660046127f8565b611437565b34801561053157600080fd5b506102dd610540366004612704565b6098602052600090815260409020546001600160a01b031681565b34801561056757600080fd5b506102dd610576366004612704565b611526565b34801561058757600080fd5b5061025660995481565b34801561059d57600080fd5b506102dd7f0000000000000000000000000c8e79f3534b00d9a3d4a856b665bf4ebc22f2ba81565b3480156105d157600080fd5b506102346105e03660046126d8565b6115f8565b3480156105f157600080fd5b506097546102dd906001600160a01b031681565b34801561061157600080fd5b50610256609a5481565b34801561062757600080fd5b50610234610636366004612704565b61180f565b34801561064757600080fd5b506102346106563660046126d8565b611820565b34801561066757600080fd5b50610256610676366004612762565b611bd3565b34801561068757600080fd5b5061023461069636600461286c565b611cde565b3480156106a757600080fd5b506102dd7f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af81565b3480156106db57600080fd5b506102346106ea366004612704565b611e11565b3480156106fb57600080fd5b506103dc61070a366004612704565b6001600160a01b0390811660009081526098602052604090205416151590565b34801561073657600080fd5b506102346107453660046126aa565b611e87565b606560009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561079d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107c191906128c7565b6001600160a01b0316336001600160a01b0316146107fa5760405162461bcd60e51b81526004016107f1906128e4565b60405180910390fd5b61080381611fe3565b50565b6000336001600160a01b037f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af16146108505760405162461bcd60e51b81526004016107f19061292e565b6001600160a01b0383166108cc5760405162461bcd60e51b815260206004820152603a60248201527f456967656e506f644d616e616765722e6164645368617265733a20706f644f7760448201527f6e65722063616e6e6f74206265207a65726f206164647265737300000000000060648201526084016107f1565b600082121561093a5760405162461bcd60e51b815260206004820152603460248201527f456967656e506f644d616e616765722e6164645368617265733a207368617265604482015273732063616e6e6f74206265206e6567617469766560601b60648201526084016107f1565b610948633b9aca00836129a2565b156109bb5760405162461bcd60e51b815260206004820152603d60248201527f456967656e506f644d616e616765722e6164645368617265733a20736861726560448201527f73206d75737420626520612077686f6c65204777656920616d6f756e7400000060648201526084016107f1565b6001600160a01b0383166000908152609b6020526040812054906109df84836129cc565b6001600160a01b0386166000818152609b602052604090819020839055519192509060008051602061351483398151915290610a1e9087815260200190565b60405180910390a2610a308282612024565b925050505b92915050565b606560009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a8e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ab291906128c7565b6001600160a01b0316336001600160a01b031614610ae25760405162461bcd60e51b81526004016107f1906128e4565b61080381612066565b60655460405163237dfb4760e11b81523360048201526001600160a01b03909116906346fbf68e90602401602060405180830381865afa158015610b33573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b579190612a0d565b610b735760405162461bcd60e51b81526004016107f190612a2f565b60665481811614610bec5760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e70617573653a20696e76616c696420617474656d70742060448201527f746f20756e70617573652066756e6374696f6e616c697479000000000000000060648201526084016107f1565b606681905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d906020015b60405180910390a250565b336001600160a01b037f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af1614610c725760405162461bcd60e51b81526004016107f19061292e565b6001600160a01b038316610cec5760405162461bcd60e51b8152602060048201526047602482015260008051602061353483398151915260448201527f546f6b656e733a20706f644f776e65722063616e6e6f74206265207a65726f206064820152666164647265737360c81b608482015260a4016107f1565b6001600160a01b038216610d695760405162461bcd60e51b815260206004820152604a602482015260008051602061353483398151915260448201527f546f6b656e733a2064657374696e6174696f6e2063616e6e6f74206265207a65606482015269726f206164647265737360b01b608482015260a4016107f1565b6000811215610dd85760405162461bcd60e51b8152602060048201526041602482015260008051602061353483398151915260448201527f546f6b656e733a207368617265732063616e6e6f74206265206e6567617469766064820152606560f81b608482015260a4016107f1565b610de6633b9aca00826129a2565b15610e5a5760405162461bcd60e51b815260206004820152604a602482015260008051602061353483398151915260448201527f546f6b656e733a20736861726573206d75737420626520612077686f6c6520476064820152691dd95a48185b5bdd5b9d60b21b608482015260a4016107f1565b6001600160a01b0383166000908152609b602052604081205490811215610f4d576000610e8682612a77565b905080831115610eeb576001600160a01b0385166000908152609b6020526040812055610eb38184612a94565b9250846001600160a01b031660008051602061351483398151915282604051610ede91815260200190565b60405180910390a2610f4b565b6001600160a01b0385166000908152609b602052604081208054859290610f139084906129cc565b90915550506040518381526001600160a01b038616906000805160206135148339815191529060200160405180910390a25050505050565b505b6001600160a01b03848116600090815260986020526040908190205490516362483a2160e11b815285831660048201526024810185905291169063c490744290604401600060405180830381600087803b158015610faa57600080fd5b505af1158015610fbe573d6000803e3d6000fd5b50505050505b505050565b609c5460009067ffffffffffffffff1680610fed5767ffffffffffffffff91505090565b919050565b610ffa61215d565b67ffffffffffffffff811661108c5760405162461bcd60e51b815260206004820152604c60248201527f456967656e506f644d616e616765722e73657444656e6562466f726b54696d6560448201527f7374616d703a2063616e6e6f7420736574206e657744656e6562466f726b546960648201526b06d657374616d7020746f20360a41b608482015260a4016107f1565b609c5467ffffffffffffffff16156111285760405162461bcd60e51b815260206004820152605360248201527f456967656e506f644d616e616765722e73657444656e6562466f726b54696d6560448201527f7374616d703a2063616e6e6f74207365742064656e6562466f726b54696d657360648201527274616d70206d6f7265207468616e206f6e636560681b608482015260a4016107f1565b609c805467ffffffffffffffff191667ffffffffffffffff83169081179091556040519081527f19200b6fdad58f91b2f496b0c444fc4be3eff74a7e24b07770e04a7137bfd9db9060200160405180910390a150565b60655460405163237dfb4760e11b81523360048201526001600160a01b03909116906346fbf68e90602401602060405180830381865afa1580156111c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111ea9190612a0d565b6112065760405162461bcd60e51b81526004016107f190612a2f565b600019606681905560405190815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a2565b61124d61215d565b61125760006121b7565b565b6066546000908190600190811614156112b05760405162461bcd60e51b815260206004820152601960248201527814185d5cd8589b194e881a5b99195e081a5cc81c185d5cd959603a1b60448201526064016107f1565b336000908152609860205260409020546001600160a01b0316156113325760405162461bcd60e51b815260206004820152603360248201527f456967656e506f644d616e616765722e637265617465506f643a2053656e64656044820152721c88185b1c9958591e481a185cc818481c1bd9606a1b60648201526084016107f1565b600061133c612209565b9250505090565b6001600160a01b0382166000908152609b60205260408120549061136783836129cc565b6001600160a01b0385166000908152609b6020526040812082905590915061138f8383612024565b604051631452b9d760e11b81526001600160a01b03878116600483015273beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac06024830152604482018390529192507f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af909116906328a573ae90606401600060405180830381600087803b15801561141857600080fd5b505af115801561142c573d6000803e3d6000fd5b505050505050505050565b6066546000906001908116141561148c5760405162461bcd60e51b815260206004820152601960248201527814185d5cd8589b194e881a5b99195e081a5cc81c185d5cd959603a1b60448201526064016107f1565b336000908152609860205260409020546001600160a01b0316806114b5576114b2612209565b90505b6040516326d3918d60e21b81526001600160a01b03821690639b4e46349034906114eb908b908b908b908b908b90600401612ad4565b6000604051808303818588803b15801561150457600080fd5b505af1158015611518573d6000803e3d6000fd5b505050505050505050505050565b6001600160a01b0380821660009081526098602052604081205490911680610a35576115f1836001600160a01b031660001b60405180610940016040528061090e8152602001612c0661090e9139604080516001600160a01b037f000000000000000000000000196dbcbb54b8ec4958c959d8949ebfe87ac2aaaf166020820152808201919091526000606082015260800160408051601f19818403018152908290526115d69291602001612b49565b604051602081830303815290604052805190602001206123e4565b9392505050565b336001600160a01b037f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af16146116405760405162461bcd60e51b81526004016107f19061292e565b60008112156116b75760405162461bcd60e51b815260206004820152603760248201527f456967656e506f644d616e616765722e72656d6f76655368617265733a20736860448201527f617265732063616e6e6f74206265206e6567617469766500000000000000000060648201526084016107f1565b6116c5633b9aca00826129a2565b1561173a576040805162461bcd60e51b81526020600482015260248101919091527f456967656e506f644d616e616765722e72656d6f76655368617265733a20736860448201527f61726573206d75737420626520612077686f6c65204777656920616d6f756e7460648201526084016107f1565b6001600160a01b0382166000908152609b602052604081205461175e908390612b66565b905060008112156117ef5760405162461bcd60e51b815260206004820152604f60248201527f456967656e506f644d616e616765722e72656d6f76655368617265733a20636160448201527f6e6e6f7420726573756c7420696e20706f64206f776e657220686176696e672060648201526e6e656761746976652073686172657360881b608482015260a4016107f1565b6001600160a01b039092166000908152609b602052604090209190915550565b61181761215d565b610803816123f1565b6001600160a01b03808316600090815260986020526040902054839116331461189b5760405162461bcd60e51b815260206004820152602760248201527f456967656e506f644d616e616765722e6f6e6c79456967656e506f643a206e6f6044820152661d0818481c1bd960ca1b60648201526084016107f1565b6118a361243b565b6001600160a01b03831661193a5760405162461bcd60e51b815260206004820152605260248201527f456967656e506f644d616e616765722e7265636f7264426561636f6e4368616960448201527f6e45544842616c616e63655570646174653a20706f644f776e65722063616e6e6064820152716f74206265207a65726f206164647265737360701b608482015260a4016107f1565b611948633b9aca0083612ba5565b156119e15760405162461bcd60e51b815260206004820152605a60248201527f456967656e506f644d616e616765722e7265636f7264426561636f6e4368616960448201527f6e45544842616c616e63655570646174653a2073686172657344656c7461206d60648201527f75737420626520612077686f6c65204777656920616d6f756e74000000000000608482015260a4016107f1565b6001600160a01b0383166000908152609b602052604081205490611a0584836129cc565b6001600160a01b0386166000908152609b60205260408120829055909150611a2d8383612024565b90508015611b95576000811215611af8576001600160a01b037f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af1663132d49678773beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac0611a8c85612a77565b6040516001600160e01b031960e086901b1681526001600160a01b0393841660048201529290911660248301526044820152606401600060405180830381600087803b158015611adb57600080fd5b505af1158015611aef573d6000803e3d6000fd5b50505050611b95565b604051631452b9d760e11b81526001600160a01b03878116600483015273beac0eeeeeeeeeeeeeeeeeeeeeeeeeeeeeebeac06024830152604482018390527f0000000000000000000000008ce361602b935680e8dec218b820ff5056beb7af16906328a573ae90606401600060405180830381600087803b158015611b7c57600080fd5b505af1158015611b90573d6000803e3d6000fd5b505050505b856001600160a01b031660008051602061351483398151915286604051611bbe91815260200190565b60405180910390a2505050610fc4600160c955565b60975460405163321accf960e11b815267ffffffffffffffff8316600482015260009182916001600160a01b039091169063643599f290602401602060405180830381865afa158015611c2a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c4e9190612bb9565b905080610a355760405162461bcd60e51b815260206004820152605260248201527f456967656e506f644d616e616765722e676574426c6f636b526f6f744174546960448201527f6d657374616d703a20737461746520726f6f742061742074696d657374616d70606482015271081b9bdd081e595d08199a5b985b1a5e995960721b608482015260a4016107f1565b600054610100900460ff1615808015611cfe5750600054600160ff909116105b80611d185750303b158015611d18575060005460ff166001145b611d7b5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016107f1565b6000805460ff191660011790558015611d9e576000805461ff0019166101001790555b611da786611fe3565b611db0856123f1565b611db9846121b7565b611dc38383612495565b8015611e09576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050505050565b611e1961215d565b6001600160a01b038116611e7e5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016107f1565b610803816121b7565b606560009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611eda573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611efe91906128c7565b6001600160a01b0316336001600160a01b031614611f2e5760405162461bcd60e51b81526004016107f1906128e4565b606654198119606654191614611fac5760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e756e70617573653a20696e76616c696420617474656d7060448201527f7420746f2070617573652066756e6374696f6e616c697479000000000000000060648201526084016107f1565b606681905560405181815233907f3582d1828e26bf56bd801502bc021ac0bc8afb57c826e4986b45593c8fad389c90602001610c1f565b609a5460408051918252602082018390527f4e65c41a3597bda732ca64980235cf51494171d5853998763fb05db45afaacb3910160405180910390a1609a55565b6000808313612044576000821361203d57506000610a35565b5080610a35565b6000821361205c5761205583612a77565b9050610a35565b6120558383612b66565b6001600160a01b0381166120f45760405162461bcd60e51b815260206004820152604960248201527f5061757361626c652e5f73657450617573657252656769737472793a206e657760448201527f50617573657252656769737472792063616e6e6f7420626520746865207a65726064820152686f206164647265737360b81b608482015260a4016107f1565b606554604080516001600160a01b03928316815291831660208301527f6e9fcd539896fca60e8b0f01dd580233e48a6b0f7df013b89ba7f565869acdb6910160405180910390a1606580546001600160a01b0319166001600160a01b0392909216919091179055565b6033546001600160a01b031633146112575760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016107f1565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6000609a54609954600161221d9190612bd2565b11156122815760405162461bcd60e51b815260206004820152602d60248201527f456967656e506f644d616e616765722e5f6465706c6f79506f643a20706f642060448201526c1b1a5b5a5d081c995858da1959609a1b60648201526084016107f1565b60996000815461229090612bea565b9091555060408051610940810190915261090e80825260009161232f9183913391612c066020830139604080516001600160a01b037f000000000000000000000000196dbcbb54b8ec4958c959d8949ebfe87ac2aaaf166020820152808201919091526000606082015260800160408051601f198184030181529082905261231b9291602001612b49565b60405160208183030381529060405261257f565b60405163189acdbd60e31b81523360048201529091506001600160a01b0382169063c4d66de890602401600060405180830381600087803b15801561237357600080fd5b505af1158015612387573d6000803e3d6000fd5b50503360008181526098602052604080822080546001600160a01b0319166001600160a01b038816908117909155905192945092507f21c99d0db02213c32fff5b05cf0a718ab5f858802b91498f80d82270289d856a91a3919050565b60006115f1838330612680565b609780546001600160a01b0319166001600160a01b0383169081179091556040517f08f0470754946ccfbb446ff7fd2d6ae6af1bbdae19f85794c0cc5ed5e8ceb4f690600090a250565b600260c954141561248e5760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064016107f1565b600260c955565b6065546001600160a01b03161580156124b657506001600160a01b03821615155b6125385760405162461bcd60e51b815260206004820152604760248201527f5061757361626c652e5f696e697469616c697a655061757365723a205f696e6960448201527f7469616c697a6550617573657228292063616e206f6e6c792062652063616c6c6064820152666564206f6e636560c81b608482015260a4016107f1565b606681905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a261257b82612066565b5050565b6000834710156125d15760405162461bcd60e51b815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e636500000060448201526064016107f1565b815161261f5760405162461bcd60e51b815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f60448201526064016107f1565b8282516020840186f590506001600160a01b0381166115f15760405162461bcd60e51b815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f790000000000000060448201526064016107f1565b6000604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b6000602082840312156126bc57600080fd5b5035919050565b6001600160a01b038116811461080357600080fd5b600080604083850312156126eb57600080fd5b82356126f6816126c3565b946020939093013593505050565b60006020828403121561271657600080fd5b81356115f1816126c3565b60008060006060848603121561273657600080fd5b8335612741816126c3565b92506020840135612751816126c3565b929592945050506040919091013590565b60006020828403121561277457600080fd5b813567ffffffffffffffff811681146115f157600080fd5b60006020828403121561279e57600080fd5b813560ff811681146115f157600080fd5b60008083601f8401126127c157600080fd5b50813567ffffffffffffffff8111156127d957600080fd5b6020830191508360208285010111156127f157600080fd5b9250929050565b60008060008060006060868803121561281057600080fd5b853567ffffffffffffffff8082111561282857600080fd5b61283489838a016127af565b9097509550602088013591508082111561284d57600080fd5b5061285a888289016127af565b96999598509660400135949350505050565b600080600080600060a0868803121561288457600080fd5b853594506020860135612896816126c3565b935060408601356128a6816126c3565b925060608601356128b6816126c3565b949793965091946080013592915050565b6000602082840312156128d957600080fd5b81516115f1816126c3565b6020808252602a908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526939903ab73830bab9b2b960b11b606082015260800190565b602080825260409082018190527f456967656e506f644d616e616765722e6f6e6c7944656c65676174696f6e4d61908201527f6e616765723a206e6f74207468652044656c65676174696f6e4d616e61676572606082015260800190565b634e487b7160e01b600052601260045260246000fd5b6000826129b1576129b161298c565b500690565b634e487b7160e01b600052601160045260246000fd5b600080821280156001600160ff1b03849003851316156129ee576129ee6129b6565b600160ff1b8390038412811615612a0757612a076129b6565b50500190565b600060208284031215612a1f57600080fd5b815180151581146115f157600080fd5b60208082526028908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526739903830bab9b2b960c11b606082015260800190565b6000600160ff1b821415612a8d57612a8d6129b6565b5060000390565b600082821015612aa657612aa66129b6565b500390565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b606081526000612ae8606083018789612aab565b8281036020840152612afb818688612aab565b9150508260408301529695505050505050565b6000815160005b81811015612b2f5760208185018101518683015201612b15565b81811115612b3e576000828601525b509290920192915050565b6000612b5e612b588386612b0e565b84612b0e565b949350505050565b60008083128015600160ff1b850184121615612b8457612b846129b6565b6001600160ff1b0384018313811615612b9f57612b9f6129b6565b50500390565b600082612bb457612bb461298c565b500790565b600060208284031215612bcb57600080fd5b5051919050565b60008219821115612be557612be56129b6565b500190565b6000600019821415612bfe57612bfe6129b6565b506001019056fe608060405260405161090e38038061090e83398101604081905261002291610460565b61002e82826000610035565b505061058a565b61003e83610100565b6040516001600160a01b038416907f1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e90600090a260008251118061007f5750805b156100fb576100f9836001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e99190610520565b836102a360201b6100291760201c565b505b505050565b610113816102cf60201b6100551760201c565b6101725760405162461bcd60e51b815260206004820152602560248201527f455243313936373a206e657720626561636f6e206973206e6f74206120636f6e6044820152641d1c9858dd60da1b60648201526084015b60405180910390fd5b6101e6816001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156101b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d79190610520565b6102cf60201b6100551760201c565b61024b5760405162461bcd60e51b815260206004820152603060248201527f455243313936373a20626561636f6e20696d706c656d656e746174696f6e206960448201526f1cc81b9bdd08184818dbdb9d1c9858dd60821b6064820152608401610169565b806102827fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5060001b6102de60201b6100641760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b60606102c883836040518060600160405280602781526020016108e7602791396102e1565b9392505050565b6001600160a01b03163b151590565b90565b6060600080856001600160a01b0316856040516102fe919061053b565b600060405180830381855af49150503d8060008114610339576040519150601f19603f3d011682016040523d82523d6000602084013e61033e565b606091505b5090925090506103508683838761035a565b9695505050505050565b606083156103c65782516103bf576001600160a01b0385163b6103bf5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610169565b50816103d0565b6103d083836103d8565b949350505050565b8151156103e85781518083602001fd5b8060405162461bcd60e51b81526004016101699190610557565b80516001600160a01b038116811461041957600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561044f578181015183820152602001610437565b838111156100f95750506000910152565b6000806040838503121561047357600080fd5b61047c83610402565b60208401519092506001600160401b038082111561049957600080fd5b818501915085601f8301126104ad57600080fd5b8151818111156104bf576104bf61041e565b604051601f8201601f19908116603f011681019083821181831017156104e7576104e761041e565b8160405282815288602084870101111561050057600080fd5b610511836020830160208801610434565b80955050505050509250929050565b60006020828403121561053257600080fd5b6102c882610402565b6000825161054d818460208701610434565b9190910192915050565b6020815260008251806020840152610576816040850160208701610434565b601f01601f19169190910160400192915050565b61034e806105996000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610067565b610100565b565b606061004e83836040518060600160405280602781526020016102f260279139610124565b9392505050565b6001600160a01b03163b151590565b90565b600061009a7fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50546001600160a01b031690565b6001600160a01b0316635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156100d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100fb9190610249565b905090565b3660008037600080366000845af43d6000803e80801561011f573d6000f35b3d6000fd5b6060600080856001600160a01b03168560405161014191906102a2565b600060405180830381855af49150503d806000811461017c576040519150601f19603f3d011682016040523d82523d6000602084013e610181565b606091505b50915091506101928683838761019c565b9695505050505050565b6060831561020d578251610206576001600160a01b0385163b6102065760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610217565b610217838361021f565b949350505050565b81511561022f5781518083602001fd5b8060405162461bcd60e51b81526004016101fd91906102be565b60006020828403121561025b57600080fd5b81516001600160a01b038116811461004e57600080fd5b60005b8381101561028d578181015183820152602001610275565b8381111561029c576000848401525b50505050565b600082516102b4818460208701610272565b9190910192915050565b60208152600082518060208401526102dd816040850160208701610272565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220d51e81d3bc5ed20a26aeb05dce7e825c503b2061aa78628027300c8d65b9d89a64736f6c634300080c0033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c65644e2b791dedccd9fb30141b088cabf5c14a8912b52f59375c95c010700b8c6193456967656e506f644d616e616765722e77697468647261775368617265734173a264697066735822122077cb0b3b215321776ad0fd7ba21d797178c740d4654ab7a48d0f1d47ac4abace64736f6c634300080c0033", - "storage": { - "0x0": "0xff" - } - }, - "0xd04ff4a75edd737a73e92b2f2274cb887d96e110": { - "nonce": 1, - "balance": "0x0", - "code": "0x608060405234801561001057600080fd5b50600436106101f05760003560e01c80637cf72bba1161010f578063d98128c0116100a2578063e921d4fa11610071578063e921d4fa146103c6578063f2fde38b1461044c578063f73b7519146102a9578063fabc1cbc1461045f57600080fd5b8063d98128c014610430578063da16e29b14610322578063df5cf723146102ba578063e58398361461043e57600080fd5b80638da5cb5b116100de5780638da5cb5b146103b5578063a49db732146103c6578063c747075b146103da578063d7b7fa13146103ee57600080fd5b80637cf72bba146103465780638105e04314610354578063855fcc4a1461036b578063886f1195146103a257600080fd5b806339b70e38116101875780636f0c2f74116101565780636f0c2f7414610322578063715018a614610330578063723e59c7146103385780637259a45c1461024257600080fd5b806339b70e38146102ba578063595c6a67146102d55780635ac86ab7146102dd5780635c975abb1461031057600080fd5b80631794bb3c116101c35780631794bb3c1461022f5780631874e5ae14610242578063282670fc1461027257806338c8ee64146102a957600080fd5b80630ffabbce146101f557806310d67a2f14610209578063136439dd1461021c578063175d3205146101f5575b600080fd5b610207610203366004610b25565b5050565b005b610207610217366004610b5a565b610472565b61020761022a366004610b7e565b61052b565b61020761023d366004610b97565b505050565b610258610250366004610b25565b600092915050565b60405163ffffffff90911681526020015b60405180910390f35b610285610280366004610bd8565b61066a565b60408051825163ffffffff9081168252602093840151169281019290925201610269565b6102076102b7366004610b5a565b50565b60005b6040516001600160a01b039091168152602001610269565b610207610685565b6103006102eb366004610c04565b606654600160ff9092169190911b9081161490565b6040519015158152602001610269565b6066545b604051908152602001610269565b610258610250366004610c27565b61020761074c565b610314610250366004610b25565b610207610203366004610c60565b610300610362366004610cd5565b60009392505050565b610385610379366004610c27565b60008060009250925092565b604080519315158452602084019290925290820152606001610269565b6065546102bd906001600160a01b031681565b6033546001600160a01b03166102bd565b6103146103d4366004610b5a565b50600090565b6102076103e8366004610d13565b50505050565b6104016103fc366004610c27565b610760565b60408051825163ffffffff90811682526020808501518216908301529282015190921690820152606001610269565b610300610250366004610c27565b6103006103d4366004610b5a565b61020761045a366004610b5a565b610782565b61020761046d366004610b7e565b6107f8565b606560009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104c5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104e99190610d60565b6001600160a01b0316336001600160a01b0316146105225760405162461bcd60e51b815260040161051990610d7d565b60405180910390fd5b6102b781610954565b60655460405163237dfb4760e11b81523360048201526001600160a01b03909116906346fbf68e90602401602060405180830381865afa158015610573573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105979190610dc7565b6105b35760405162461bcd60e51b815260040161051990610de9565b6066548181161461062c5760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e70617573653a20696e76616c696420617474656d70742060448201527f746f20756e70617573652066756e6374696f6e616c69747900000000000000006064820152608401610519565b606681905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d906020015b60405180910390a250565b60408051808201909152600080825260208201525b92915050565b60655460405163237dfb4760e11b81523360048201526001600160a01b03909116906346fbf68e90602401602060405180830381865afa1580156106cd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106f19190610dc7565b61070d5760405162461bcd60e51b815260040161051990610de9565b600019606681905560405190815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a2565b610754610a4b565b61075e6000610aa5565b565b604080516060810182526000808252602082018190529181019190915261067f565b61078a610a4b565b6001600160a01b0381166107ef5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610519565b6102b781610aa5565b606560009054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561084b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086f9190610d60565b6001600160a01b0316336001600160a01b03161461089f5760405162461bcd60e51b815260040161051990610d7d565b60665419811960665419161461091d5760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e756e70617573653a20696e76616c696420617474656d7060448201527f7420746f2070617573652066756e6374696f6e616c69747900000000000000006064820152608401610519565b606681905560405181815233907f3582d1828e26bf56bd801502bc021ac0bc8afb57c826e4986b45593c8fad389c9060200161065f565b6001600160a01b0381166109e25760405162461bcd60e51b815260206004820152604960248201527f5061757361626c652e5f73657450617573657252656769737472793a206e657760448201527f50617573657252656769737472792063616e6e6f7420626520746865207a65726064820152686f206164647265737360b81b608482015260a401610519565b606554604080516001600160a01b03928316815291831660208301527f6e9fcd539896fca60e8b0f01dd580233e48a6b0f7df013b89ba7f565869acdb6910160405180910390a1606580546001600160a01b0319166001600160a01b0392909216919091179055565b6033546001600160a01b0316331461075e5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610519565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6001600160a01b03811681146102b757600080fd5b803563ffffffff81168114610b2057600080fd5b919050565b60008060408385031215610b3857600080fd5b8235610b4381610af7565b9150610b5160208401610b0c565b90509250929050565b600060208284031215610b6c57600080fd5b8135610b7781610af7565b9392505050565b600060208284031215610b9057600080fd5b5035919050565b600080600060608486031215610bac57600080fd5b8335610bb781610af7565b92506020840135610bc781610af7565b929592945050506040919091013590565b60008060408385031215610beb57600080fd5b8235610bf681610af7565b946020939093013593505050565b600060208284031215610c1657600080fd5b813560ff81168114610b7757600080fd5b60008060408385031215610c3a57600080fd5b8235610c4581610af7565b91506020830135610c5581610af7565b809150509250929050565b60008060208385031215610c7357600080fd5b823567ffffffffffffffff80821115610c8b57600080fd5b818501915085601f830112610c9f57600080fd5b813581811115610cae57600080fd5b8660208260051b8501011115610cc357600080fd5b60209290920196919550909350505050565b600080600060608486031215610cea57600080fd5b8335610cf581610af7565b9250610d0360208501610b0c565b9150604084013590509250925092565b60008060008060808587031215610d2957600080fd5b8435610d3481610af7565b9350610d4260208601610b0c565b9250610d5060408601610b0c565b9396929550929360600135925050565b600060208284031215610d7257600080fd5b8151610b7781610af7565b6020808252602a908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526939903ab73830bab9b2b960b11b606082015260800190565b600060208284031215610dd957600080fd5b81518015158114610b7757600080fd5b60208082526028908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526739903830bab9b2b960c11b60608201526080019056fea2646970667358221220ba817d3c200ab51ada7e33d2c1b7e3ab9d28f58097be69b9312d60e3000623cd64736f6c634300080c0033", - "storage": {} - }, - "0xdbd296711ec8ef9aacb623ee3f1c0922dce0d7b2": { - "nonce": 1, - "balance": "0x0", - "code": "0x60806040523661001357610011610017565b005b6100115b61001f6101b7565b6001600160a01b0316336001600160a01b0316141561016f5760606001600160e01b031960003516631b2ce7f360e11b8114156100655761005e6101ea565b9150610167565b6001600160e01b0319811663278f794360e11b14156100865761005e610241565b6001600160e01b031981166308f2839760e41b14156100a75761005e610287565b6001600160e01b031981166303e1469160e61b14156100c85761005e6102b8565b6001600160e01b03198116635c60da1b60e01b14156100e95761005e6102f8565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b61017761030c565b565b606061019e83836040518060600160405280602781526020016108576027913961031c565b9392505050565b90565b6001600160a01b03163b151590565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101f4610394565b600061020336600481846106a2565b81019061021091906106e8565b905061022d8160405180602001604052806000815250600061039f565b505060408051602081019091526000815290565b606060008061025336600481846106a2565b8101906102609190610719565b915091506102708282600161039f565b604051806020016040528060008152509250505090565b6060610291610394565b60006102a036600481846106a2565b8101906102ad91906106e8565b905061022d816103cb565b60606102c2610394565b60006102cc6101b7565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b6060610302610394565b60006102cc610422565b610177610317610422565b610431565b6060600080856001600160a01b0316856040516103399190610807565b600060405180830381855af49150503d8060008114610374576040519150601f19603f3d011682016040523d82523d6000602084013e610379565b606091505b509150915061038a86838387610455565b9695505050505050565b341561017757600080fd5b6103a8836104d3565b6000825111806103b55750805b156103c6576103c48383610179565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103f46101b7565b604080516001600160a01b03928316815291841660208301520160405180910390a161041f81610513565b50565b600061042c6105bc565b905090565b3660008037600080366000845af43d6000803e808015610450573d6000f35b3d6000fd5b606083156104c15782516104ba576001600160a01b0385163b6104ba5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161015e565b50816104cb565b6104cb83836105e4565b949350505050565b6104dc8161060e565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6001600160a01b0381166105785760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161015e565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6101db565b8151156105f45781518083602001fd5b8060405162461bcd60e51b815260040161015e9190610823565b6001600160a01b0381163b61067b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161015e565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61059b565b600080858511156106b257600080fd5b838611156106bf57600080fd5b5050820193919092039150565b80356001600160a01b03811681146106e357600080fd5b919050565b6000602082840312156106fa57600080fd5b61019e826106cc565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561072c57600080fd5b610735836106cc565b9150602083013567ffffffffffffffff8082111561075257600080fd5b818501915085601f83011261076657600080fd5b81358181111561077857610778610703565b604051601f8201601f19908116603f011681019083821181831017156107a0576107a0610703565b816040528281528860208487010111156107b957600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156107f65781810151838201526020016107de565b838111156103c45750506000910152565b600082516108198184602087016107db565b9190910192915050565b60208152600082518060208401526108428160408501602087016107db565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212205685c4ad655ee149c77141d53c938af08bb4457fb9509b6f1725a2ee99781c1a64736f6c634300080c0033", - "storage": { - "0x0": "0xa15bb66138824a1c7167f5e85b957d04dd34e4680001", - "0x1": "0x0", - "0x32": "0x45009dd3abbe29db54fc5d893ceaa98a624882df", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0xf56aa3aceddf88ab12e494d0b96da3c09a5d264e", - "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x700b6a60ce7eaaea56f065753d8dcb9653dbad35" - } - }, - "0xe1aa25618fa0c7a1cfdab5d6b456af611873b629": { - "nonce": 1, - "balance": "0x0", - "code": "0x60806040523661001357610011610017565b005b6100115b61001f6101b7565b6001600160a01b0316336001600160a01b0316141561016f5760606001600160e01b031960003516631b2ce7f360e11b8114156100655761005e6101ea565b9150610167565b6001600160e01b0319811663278f794360e11b14156100865761005e610241565b6001600160e01b031981166308f2839760e41b14156100a75761005e610287565b6001600160e01b031981166303e1469160e61b14156100c85761005e6102b8565b6001600160e01b03198116635c60da1b60e01b14156100e95761005e6102f8565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b61017761030c565b565b606061019e83836040518060600160405280602781526020016108576027913961031c565b9392505050565b90565b6001600160a01b03163b151590565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101f4610394565b600061020336600481846106a2565b81019061021091906106e8565b905061022d8160405180602001604052806000815250600061039f565b505060408051602081019091526000815290565b606060008061025336600481846106a2565b8101906102609190610719565b915091506102708282600161039f565b604051806020016040528060008152509250505090565b6060610291610394565b60006102a036600481846106a2565b8101906102ad91906106e8565b905061022d816103cb565b60606102c2610394565b60006102cc6101b7565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b6060610302610394565b60006102cc610422565b610177610317610422565b610431565b6060600080856001600160a01b0316856040516103399190610807565b600060405180830381855af49150503d8060008114610374576040519150601f19603f3d011682016040523d82523d6000602084013e610379565b606091505b509150915061038a86838387610455565b9695505050505050565b341561017757600080fd5b6103a8836104d3565b6000825111806103b55750805b156103c6576103c48383610179565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103f46101b7565b604080516001600160a01b03928316815291841660208301520160405180910390a161041f81610513565b50565b600061042c6105bc565b905090565b3660008037600080366000845af43d6000803e808015610450573d6000f35b3d6000fd5b606083156104c15782516104ba576001600160a01b0385163b6104ba5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161015e565b50816104cb565b6104cb83836105e4565b949350505050565b6104dc8161060e565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6001600160a01b0381166105785760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161015e565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6101db565b8151156105f45781518083602001fd5b8060405162461bcd60e51b815260040161015e9190610823565b6001600160a01b0381163b61067b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161015e565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61059b565b600080858511156106b257600080fd5b838611156106bf57600080fd5b5050820193919092039150565b80356001600160a01b03811681146106e357600080fd5b919050565b6000602082840312156106fa57600080fd5b61019e826106cc565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561072c57600080fd5b610735836106cc565b9150602083013567ffffffffffffffff8082111561075257600080fd5b818501915085601f83011261076657600080fd5b81358181111561077857610778610703565b604051601f8201601f19908116603f011681019083821181831017156107a0576107a0610703565b816040528281528860208487010111156107b957600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156107f65781810151838201526020016107de565b838111156103c45750506000910152565b600082516108198184602087016107db565b9190910192915050565b60208152600082518060208401526108428160408501602087016107db565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212205685c4ad655ee149c77141d53c938af08bb4457fb9509b6f1725a2ee99781c1a64736f6c634300080c0033", - "storage": { - "0x0": "0x1", - "0x33": "0xa0ee7a142d267c1f36714e4a8f75612f20a79720", - "0x65": "0xa15bb66138824a1c7167f5e85b957d04dd34e468", - "0x66": "0x0", - "0x97": "0xfb1da1faeb15d2e5bcaa2c1b8c84e5bad2599d652a22be70ffa667ddd680b27", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x5b4cb126885fb10464fdd12666feb25e2563b76", - "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x700b6a60ce7eaaea56f065753d8dcb9653dbad35" - } - }, - "0xe1da8919f262ee86f9be05059c9280142cf23f48": { - "nonce": 1, - "balance": "0x0", - "code": "0x60806040523661001357610011610017565b005b6100115b61001f6101b7565b6001600160a01b0316336001600160a01b0316141561016f5760606001600160e01b031960003516631b2ce7f360e11b8114156100655761005e6101ea565b9150610167565b6001600160e01b0319811663278f794360e11b14156100865761005e610241565b6001600160e01b031981166308f2839760e41b14156100a75761005e610287565b6001600160e01b031981166303e1469160e61b14156100c85761005e6102b8565b6001600160e01b03198116635c60da1b60e01b14156100e95761005e6102f8565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b61017761030c565b565b606061019e83836040518060600160405280602781526020016108576027913961031c565b9392505050565b90565b6001600160a01b03163b151590565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101f4610394565b600061020336600481846106a2565b81019061021091906106e8565b905061022d8160405180602001604052806000815250600061039f565b505060408051602081019091526000815290565b606060008061025336600481846106a2565b8101906102609190610719565b915091506102708282600161039f565b604051806020016040528060008152509250505090565b6060610291610394565b60006102a036600481846106a2565b8101906102ad91906106e8565b905061022d816103cb565b60606102c2610394565b60006102cc6101b7565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b6060610302610394565b60006102cc610422565b610177610317610422565b610431565b6060600080856001600160a01b0316856040516103399190610807565b600060405180830381855af49150503d8060008114610374576040519150601f19603f3d011682016040523d82523d6000602084013e610379565b606091505b509150915061038a86838387610455565b9695505050505050565b341561017757600080fd5b6103a8836104d3565b6000825111806103b55750805b156103c6576103c48383610179565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103f46101b7565b604080516001600160a01b03928316815291841660208301520160405180910390a161041f81610513565b50565b600061042c6105bc565b905090565b3660008037600080366000845af43d6000803e808015610450573d6000f35b3d6000fd5b606083156104c15782516104ba576001600160a01b0385163b6104ba5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161015e565b50816104cb565b6104cb83836105e4565b949350505050565b6104dc8161060e565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6001600160a01b0381166105785760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161015e565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6101db565b8151156105f45781518083602001fd5b8060405162461bcd60e51b815260040161015e9190610823565b6001600160a01b0381163b61067b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161015e565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61059b565b600080858511156106b257600080fd5b838611156106bf57600080fd5b5050820193919092039150565b80356001600160a01b03811681146106e357600080fd5b919050565b6000602082840312156106fa57600080fd5b61019e826106cc565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561072c57600080fd5b610735836106cc565b9150602083013567ffffffffffffffff8082111561075257600080fd5b818501915085601f83011261076657600080fd5b81358181111561077857610778610703565b604051601f8201601f19908116603f011681019083821181831017156107a0576107a0610703565b816040528281528860208487010111156107b957600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156107f65781810151838201526020016107de565b838111156103c45750506000910152565b600082516108198184602087016107db565b9190910192915050565b60208152600082518060208401526108428160408501602087016107db565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212205685c4ad655ee149c77141d53c938af08bb4457fb9509b6f1725a2ee99781c1a64736f6c634300080c0033", - "storage": { - "0x0": "0x1", - "0x33": "0xa0ee7a142d267c1f36714e4a8f75612f20a79720", - "0x97": "0xa15bb66138824a1c7167f5e85b957d04dd34e468", - "0x98": "0x0", - "0xc9": "0x32c1f929f27df9d818b4cf4f764cb3eff5216bcff20e0adc6477816696325b0f", - "0xcb": "0xa0ee7a142d267c1f36714e4a8f75612f20a79720", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x2a264f26859166c5bf3868a54593ee716aebc848", - "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x700b6a60ce7eaaea56f065753d8dcb9653dbad35" - } - }, - "0xed1db453c3156ff3155a97ad217b3087d5dc5f6e": { - "nonce": 1, - "balance": "0x0", - "code": "0x60806040523661001357610011610017565b005b6100115b61001f6101b7565b6001600160a01b0316336001600160a01b0316141561016f5760606001600160e01b031960003516631b2ce7f360e11b8114156100655761005e6101ea565b9150610167565b6001600160e01b0319811663278f794360e11b14156100865761005e610241565b6001600160e01b031981166308f2839760e41b14156100a75761005e610287565b6001600160e01b031981166303e1469160e61b14156100c85761005e6102b8565b6001600160e01b03198116635c60da1b60e01b14156100e95761005e6102f8565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b61017761030c565b565b606061019e83836040518060600160405280602781526020016108576027913961031c565b9392505050565b90565b6001600160a01b03163b151590565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101f4610394565b600061020336600481846106a2565b81019061021091906106e8565b905061022d8160405180602001604052806000815250600061039f565b505060408051602081019091526000815290565b606060008061025336600481846106a2565b8101906102609190610719565b915091506102708282600161039f565b604051806020016040528060008152509250505090565b6060610291610394565b60006102a036600481846106a2565b8101906102ad91906106e8565b905061022d816103cb565b60606102c2610394565b60006102cc6101b7565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b6060610302610394565b60006102cc610422565b610177610317610422565b610431565b6060600080856001600160a01b0316856040516103399190610807565b600060405180830381855af49150503d8060008114610374576040519150601f19603f3d011682016040523d82523d6000602084013e610379565b606091505b509150915061038a86838387610455565b9695505050505050565b341561017757600080fd5b6103a8836104d3565b6000825111806103b55750805b156103c6576103c48383610179565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103f46101b7565b604080516001600160a01b03928316815291841660208301520160405180910390a161041f81610513565b50565b600061042c6105bc565b905090565b3660008037600080366000845af43d6000803e808015610450573d6000f35b3d6000fd5b606083156104c15782516104ba576001600160a01b0385163b6104ba5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161015e565b50816104cb565b6104cb83836105e4565b949350505050565b6104dc8161060e565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6001600160a01b0381166105785760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161015e565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6101db565b8151156105f45781518083602001fd5b8060405162461bcd60e51b815260040161015e9190610823565b6001600160a01b0381163b61067b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161015e565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61059b565b600080858511156106b257600080fd5b838611156106bf57600080fd5b5050820193919092039150565b80356001600160a01b03811681146106e357600080fd5b919050565b6000602082840312156106fa57600080fd5b61019e826106cc565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561072c57600080fd5b610735836106cc565b9150602083013567ffffffffffffffff8082111561075257600080fd5b818501915085601f83011261076657600080fd5b81358181111561077857610778610703565b604051601f8201601f19908116603f011681019083821181831017156107a0576107a0610703565b816040528281528860208487010111156107b957600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156107f65781810151838201526020016107de565b838111156103c45750506000910152565b600082516108198184602087016107db565b9190910192915050565b60208152600082518060208401526108428160408501602087016107db565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212205685c4ad655ee149c77141d53c938af08bb4457fb9509b6f1725a2ee99781c1a64736f6c634300080c0033", - "storage": { - "0x0": "0x1", - "0x33": "0xa0ee7a142d267c1f36714e4a8f75612f20a79720", - "0x65": "0xa15bb66138824a1c7167f5e85b957d04dd34e468", - "0x66": "0x0", - "0x97": "0x0", - "0x9a": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0xc6b8fbf96cf7bbe45576417ec2163acecfa88ecc", - "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x700b6a60ce7eaaea56f065753d8dcb9653dbad35" - } - }, - "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { - "nonce": 0, - "balance": "0x21e19e0c9bab2400000", - "code": "0x", - "storage": {} - }, - "0xf56aa3aceddf88ab12e494d0b96da3c09a5d264e": { - "nonce": 1, - "balance": "0x0", - "code": "0x608060405234801561001057600080fd5b50600436106101375760003560e01c80635c975abb116100b8578063ab5921e11161007c578063ab5921e11461029c578063ce7c2ac2146102b1578063d9caed12146102c4578063e3dae51c146102d7578063f3e73875146102ea578063fabc1cbc146102fd57600080fd5b80635c975abb146102425780637a8b26371461024a578063886f11951461025d5780638c871019146102765780638f6a62401461028957600080fd5b806347e7ef24116100ff57806347e7ef24146101d2578063485cc955146101e5578063553ca5f8146101f8578063595c6a671461020b5780635ac86ab71461021357600080fd5b806310d67a2f1461013c578063136439dd146101515780632495a5991461016457806339b70e38146101945780633a98ef39146101bb575b600080fd5b61014f61014a36600461137b565b610310565b005b61014f61015f36600461139f565b6103cc565b603254610177906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101777f000000000000000000000000e1da8919f262ee86f9be05059c9280142cf23f4881565b6101c460335481565b60405190815260200161018b565b6101c46101e03660046113b8565b610510565b61014f6101f33660046113e4565b6106b4565b6101c461020636600461137b565b6107c9565b61014f6107dd565b61023261022136600461141d565b6001805460ff9092161b9081161490565b604051901515815260200161018b565b6001546101c4565b6101c461025836600461139f565b6108a9565b600054610177906201000090046001600160a01b031681565b6101c461028436600461139f565b6108f4565b6101c461029736600461137b565b6108ff565b6102a461090d565b60405161018b9190611470565b6101c46102bf36600461137b565b61092d565b61014f6102d23660046114a3565b6109c2565b6101c46102e536600461139f565b610b8b565b6101c46102f836600461139f565b610bc4565b61014f61030b36600461139f565b610bcf565b600060029054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610363573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061038791906114e4565b6001600160a01b0316336001600160a01b0316146103c05760405162461bcd60e51b81526004016103b790611501565b60405180910390fd5b6103c981610d2b565b50565b60005460405163237dfb4760e11b8152336004820152620100009091046001600160a01b0316906346fbf68e90602401602060405180830381865afa158015610419573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061043d919061154b565b6104595760405162461bcd60e51b81526004016103b79061156d565b600154818116146104d25760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e70617573653a20696e76616c696420617474656d70742060448201527f746f20756e70617573652066756e6374696f6e616c697479000000000000000060648201526084016103b7565b600181905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d906020015b60405180910390a250565b600180546000918291811614156105655760405162461bcd60e51b815260206004820152601960248201527814185d5cd8589b194e881a5b99195e081a5cc81c185d5cd959603a1b60448201526064016103b7565b336001600160a01b037f000000000000000000000000e1da8919f262ee86f9be05059c9280142cf23f4816146105dd5760405162461bcd60e51b815260206004820181905260248201527f5374726174656779426173652e6f6e6c7953747261746567794d616e6167657260448201526064016103b7565b6105e78484610e30565b60335460006105f86103e8836115cb565b905060006103e8610607610eb0565b61061191906115cb565b9050600061061f87836115e3565b90508061062c84896115fa565b6106369190611619565b95508561069c5760405162461bcd60e51b815260206004820152602e60248201527f5374726174656779426173652e6465706f7369743a206e65775368617265732060448201526d63616e6e6f74206265207a65726f60901b60648201526084016103b7565b6106a686856115cb565b603355505050505092915050565b600054610100900460ff16158080156106d45750600054600160ff909116105b806106ee5750303b1580156106ee575060005460ff166001145b6107515760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016103b7565b6000805460ff191660011790558015610774576000805461ff0019166101001790555b61077e8383610f22565b80156107c4576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b505050565b60006107d76102588361092d565b92915050565b60005460405163237dfb4760e11b8152336004820152620100009091046001600160a01b0316906346fbf68e90602401602060405180830381865afa15801561082a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061084e919061154b565b61086a5760405162461bcd60e51b81526004016103b79061156d565b600019600181905560405190815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a2565b6000806103e86033546108bc91906115cb565b905060006103e86108cb610eb0565b6108d591906115cb565b9050816108e285836115fa565b6108ec9190611619565b949350505050565b60006107d782610b8b565b60006107d76102f88361092d565b60606040518060800160405280604d8152602001611671604d9139905090565b604051633d3f06c960e11b81526001600160a01b0382811660048301523060248301526000917f000000000000000000000000e1da8919f262ee86f9be05059c9280142cf23f4890911690637a7e0d9290604401602060405180830381865afa15801561099e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107d7919061163b565b6001805460029081161415610a155760405162461bcd60e51b815260206004820152601960248201527814185d5cd8589b194e881a5b99195e081a5cc81c185d5cd959603a1b60448201526064016103b7565b336001600160a01b037f000000000000000000000000e1da8919f262ee86f9be05059c9280142cf23f481614610a8d5760405162461bcd60e51b815260206004820181905260248201527f5374726174656779426173652e6f6e6c7953747261746567794d616e6167657260448201526064016103b7565b610a98848484610fb3565b60335480831115610b275760405162461bcd60e51b815260206004820152604d60248201527f5374726174656779426173652e77697468647261773a20616d6f756e7453686160448201527f726573206d757374206265206c657373207468616e206f7220657175616c207460648201526c6f20746f74616c53686172657360981b608482015260a4016103b7565b6000610b356103e8836115cb565b905060006103e8610b44610eb0565b610b4e91906115cb565b9050600082610b5d87846115fa565b610b679190611619565b9050610b7386856115e3565b603355610b81888883611036565b5050505050505050565b6000806103e8603354610b9e91906115cb565b905060006103e8610bad610eb0565b610bb791906115cb565b9050806108e283866115fa565b60006107d7826108a9565b600060029054906101000a90046001600160a01b03166001600160a01b031663eab66d7a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c22573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c4691906114e4565b6001600160a01b0316336001600160a01b031614610c765760405162461bcd60e51b81526004016103b790611501565b600154198119600154191614610cf45760405162461bcd60e51b815260206004820152603860248201527f5061757361626c652e756e70617573653a20696e76616c696420617474656d7060448201527f7420746f2070617573652066756e6374696f6e616c697479000000000000000060648201526084016103b7565b600181905560405181815233907f3582d1828e26bf56bd801502bc021ac0bc8afb57c826e4986b45593c8fad389c90602001610505565b6001600160a01b038116610db95760405162461bcd60e51b815260206004820152604960248201527f5061757361626c652e5f73657450617573657252656769737472793a206e657760448201527f50617573657252656769737472792063616e6e6f7420626520746865207a65726064820152686f206164647265737360b81b608482015260a4016103b7565b600054604080516001600160a01b03620100009093048316815291831660208301527f6e9fcd539896fca60e8b0f01dd580233e48a6b0f7df013b89ba7f565869acdb6910160405180910390a1600080546001600160a01b03909216620100000262010000600160b01b0319909216919091179055565b6032546001600160a01b03838116911614610eac5760405162461bcd60e51b815260206004820152603660248201527f5374726174656779426173652e6465706f7369743a2043616e206f6e6c79206460448201527532b837b9b4ba103ab73232b9363cb4b733aa37b5b2b760511b60648201526084016103b7565b5050565b6032546040516370a0823160e01b81523060048201526000916001600160a01b0316906370a0823190602401602060405180830381865afa158015610ef9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f1d919061163b565b905090565b600054610100900460ff16610f8d5760405162461bcd60e51b815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201526a6e697469616c697a696e6760a81b60648201526084016103b7565b603280546001600160a01b0319166001600160a01b038416179055610eac81600061104a565b6032546001600160a01b038381169116146107c45760405162461bcd60e51b815260206004820152603b60248201527f5374726174656779426173652e77697468647261773a2043616e206f6e6c792060448201527f77697468647261772074686520737472617465677920746f6b656e000000000060648201526084016103b7565b6107c46001600160a01b0383168483611136565b6000546201000090046001600160a01b031615801561107157506001600160a01b03821615155b6110f35760405162461bcd60e51b815260206004820152604760248201527f5061757361626c652e5f696e697469616c697a655061757365723a205f696e6960448201527f7469616c697a6550617573657228292063616e206f6e6c792062652063616c6c6064820152666564206f6e636560c81b608482015260a4016103b7565b600181905560405181815233907fab40a374bc51de372200a8bc981af8c9ecdc08dfdaef0bb6e09f88f3c616ef3d9060200160405180910390a2610eac82610d2b565b604080516001600160a01b03848116602483015260448083018590528351808403909101815260649092018352602080830180516001600160e01b031663a9059cbb60e01b17905283518085019094528084527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564908401526107c4928692916000916111c6918516908490611246565b90508051600014806111e75750808060200190518101906111e7919061154b565b6107c45760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016103b7565b60606108ec848460008585600080866001600160a01b0316858760405161126d9190611654565b60006040518083038185875af1925050503d80600081146112aa576040519150601f19603f3d011682016040523d82523d6000602084013e6112af565b606091505b50915091506112c0878383876112cb565b979650505050505050565b60608315611337578251611330576001600160a01b0385163b6113305760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016103b7565b50816108ec565b6108ec838381511561134c5781518083602001fd5b8060405162461bcd60e51b81526004016103b79190611470565b6001600160a01b03811681146103c957600080fd5b60006020828403121561138d57600080fd5b813561139881611366565b9392505050565b6000602082840312156113b157600080fd5b5035919050565b600080604083850312156113cb57600080fd5b82356113d681611366565b946020939093013593505050565b600080604083850312156113f757600080fd5b823561140281611366565b9150602083013561141281611366565b809150509250929050565b60006020828403121561142f57600080fd5b813560ff8116811461139857600080fd5b60005b8381101561145b578181015183820152602001611443565b8381111561146a576000848401525b50505050565b602081526000825180602084015261148f816040850160208701611440565b601f01601f19169190910160400192915050565b6000806000606084860312156114b857600080fd5b83356114c381611366565b925060208401356114d381611366565b929592945050506040919091013590565b6000602082840312156114f657600080fd5b815161139881611366565b6020808252602a908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526939903ab73830bab9b2b960b11b606082015260800190565b60006020828403121561155d57600080fd5b8151801515811461139857600080fd5b60208082526028908201527f6d73672e73656e646572206973206e6f74207065726d697373696f6e6564206160408201526739903830bab9b2b960c11b606082015260800190565b634e487b7160e01b600052601160045260246000fd5b600082198211156115de576115de6115b5565b500190565b6000828210156115f5576115f56115b5565b500390565b6000816000190483118215151615611614576116146115b5565b500290565b60008261163657634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561164d57600080fd5b5051919050565b60008251611666818460208701611440565b919091019291505056fe4261736520537472617465677920696d706c656d656e746174696f6e20746f20696e68657269742066726f6d20666f72206d6f726520636f6d706c657820696d706c656d656e746174696f6e73a26469706673582212207cdcf9338121603e52694ba2996bd478aa76c1d212140028e6f3354193e88e3764736f6c634300080c0033", - "storage": { - "0x0": "0xff" - } - }, - "0xf7cd8fa9b94db2aa972023b379c7f72c65e4de9d": { - "nonce": 1, - "balance": "0x0", - "code": "0x60806040523661001357610011610017565b005b6100115b61001f6101b7565b6001600160a01b0316336001600160a01b0316141561016f5760606001600160e01b031960003516631b2ce7f360e11b8114156100655761005e6101ea565b9150610167565b6001600160e01b0319811663278f794360e11b14156100865761005e610241565b6001600160e01b031981166308f2839760e41b14156100a75761005e610287565b6001600160e01b031981166303e1469160e61b14156100c85761005e6102b8565b6001600160e01b03198116635c60da1b60e01b14156100e95761005e6102f8565b60405162461bcd60e51b815260206004820152604260248201527f5472616e73706172656e745570677261646561626c6550726f78793a2061646d60448201527f696e2063616e6e6f742066616c6c6261636b20746f2070726f78792074617267606482015261195d60f21b608482015260a4015b60405180910390fd5b815160208301f35b61017761030c565b565b606061019e83836040518060600160405280602781526020016108576027913961031c565b9392505050565b90565b6001600160a01b03163b151590565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60606101f4610394565b600061020336600481846106a2565b81019061021091906106e8565b905061022d8160405180602001604052806000815250600061039f565b505060408051602081019091526000815290565b606060008061025336600481846106a2565b8101906102609190610719565b915091506102708282600161039f565b604051806020016040528060008152509250505090565b6060610291610394565b60006102a036600481846106a2565b8101906102ad91906106e8565b905061022d816103cb565b60606102c2610394565b60006102cc6101b7565b604080516001600160a01b03831660208201529192500160405160208183030381529060405291505090565b6060610302610394565b60006102cc610422565b610177610317610422565b610431565b6060600080856001600160a01b0316856040516103399190610807565b600060405180830381855af49150503d8060008114610374576040519150601f19603f3d011682016040523d82523d6000602084013e610379565b606091505b509150915061038a86838387610455565b9695505050505050565b341561017757600080fd5b6103a8836104d3565b6000825111806103b55750805b156103c6576103c48383610179565b505b505050565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103f46101b7565b604080516001600160a01b03928316815291841660208301520160405180910390a161041f81610513565b50565b600061042c6105bc565b905090565b3660008037600080366000845af43d6000803e808015610450573d6000f35b3d6000fd5b606083156104c15782516104ba576001600160a01b0385163b6104ba5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161015e565b50816104cb565b6104cb83836105e4565b949350505050565b6104dc8161060e565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6001600160a01b0381166105785760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b606482015260840161015e565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80546001600160a01b0319166001600160a01b039290921691909117905550565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6101db565b8151156105f45781518083602001fd5b8060405162461bcd60e51b815260040161015e9190610823565b6001600160a01b0381163b61067b5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840161015e565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61059b565b600080858511156106b257600080fd5b838611156106bf57600080fd5b5050820193919092039150565b80356001600160a01b03811681146106e357600080fd5b919050565b6000602082840312156106fa57600080fd5b61019e826106cc565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561072c57600080fd5b610735836106cc565b9150602083013567ffffffffffffffff8082111561075257600080fd5b818501915085601f83011261076657600080fd5b81358181111561077857610778610703565b604051601f8201601f19908116603f011681019083821181831017156107a0576107a0610703565b816040528281528860208487010111156107b957600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b60005b838110156107f65781810151838201526020016107de565b838111156103c45750506000910152565b600082516108198184602087016107db565b9190910192915050565b60208152600082518060208401526108428160408501602087016107db565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212205685c4ad655ee149c77141d53c938af08bb4457fb9509b6f1725a2ee99781c1a64736f6c634300080c0033", - "storage": { - "0x0": "0x1", - "0x33": "0xa0ee7a142d267c1f36714e4a8f75612f20a79720", - "0x97": "0xa15bb66138824a1c7167f5e85b957d04dd34e468", - "0x98": "0x0", - "0xc9": "0xc4e0", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x29a79095352a718b3d7fe84e1f14e9f34a35598e", - "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x700b6a60ce7eaaea56f065753d8dcb9653dbad35" - } - } - } -} diff --git a/e2e/app/static/static.go b/e2e/app/static/static.go deleted file mode 100644 index 5f6aaf52..00000000 --- a/e2e/app/static/static.go +++ /dev/null @@ -1,12 +0,0 @@ -package static - -import _ "embed" - -// original: anvil-state.json -// -//go:embed anvil-state.json -var anvilState []byte - -func GetDevnetAnvilState() []byte { - return anvilState -} diff --git a/e2e/app/test.go b/e2e/app/test.go deleted file mode 100644 index 58234db0..00000000 --- a/e2e/app/test.go +++ /dev/null @@ -1,87 +0,0 @@ -package app - -import ( - "context" - "encoding/json" - "os" - "path/filepath" - - "github.com/cometbft/cometbft/test/e2e/pkg/exec" - - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/log" -) - -const ( - EnvInfraType = "INFRASTRUCTURE_TYPE" - EnvInfraFile = "INFRASTRUCTURE_FILE" - EnvE2EManifest = "E2E_MANIFEST" - EnvE2ENode = "E2E_NODE" - EnvE2ERPCEndpoints = "E2E_RPC_ENDPOINTS" -) - -// Test runs test cases under tests/. -func Test(ctx context.Context, def Definition, verbose bool) error { - log.Info(ctx, "Running tests in ./test/...") - endpoints := externalEndpoints(def) - - networkDir, err := os.MkdirTemp("", "iliad-e2e") - if err != nil { - return errors.Wrap(err, "creating temp dir") - } - - endpointsFile := filepath.Join(networkDir, "endpoints.json") - if endopintsBytes, err := json.Marshal(endpoints); err != nil { - return errors.Wrap(err, "marshaling endpoints") - } else if err := os.WriteFile(endpointsFile, endopintsBytes, 0644); err != nil { - return errors.Wrap(err, "writing endpoints") - } else if err = os.Setenv(EnvE2ERPCEndpoints, endpointsFile); err != nil { - return errors.Wrap(err, "setting env ar") - } - - manifestFile, err := filepath.Abs(def.Testnet.File) - if err != nil { - return errors.Wrap(err, "absolute manifest path") - } - - if err = os.Setenv(EnvE2EManifest, manifestFile); err != nil { - return errors.Wrap(err, "setting env var") - } - - infd := def.Infra.GetInfrastructureData() - if infd.Path != "" { - infdPath, err := filepath.Abs(infd.Path) - if err != nil { - return errors.Wrap(err, "absolute infrastructure path") - } - err = os.Setenv(EnvInfraFile, infdPath) - if err != nil { - return errors.Wrap(err, "setting env var") - } - } - - if err = os.Setenv(EnvInfraType, infd.Provider); err != nil { - return errors.Wrap(err, "setting env var") - } - - log.Debug(ctx, "Env files", - EnvE2EManifest, manifestFile, - EnvInfraType, infd.Provider, - EnvInfraFile, infd.Path, - EnvE2ERPCEndpoints, endpointsFile, - ) - - args := []string{"go", "test", "-timeout", "60s", "-count", "1"} - if verbose { - args = append(args, "-v") - } - args = append(args, "github.com/piplabs/story/e2e/test") - log.Debug(ctx, "Test command", "args", args) - - err = exec.CommandVerbose(ctx, args...) - if err != nil { - return errors.Wrap(err, "go tests failed") - } - - return nil -} diff --git a/e2e/app/testdata/TestUpdateConfigStateSync.golden b/e2e/app/testdata/TestUpdateConfigStateSync.golden deleted file mode 100644 index d74eb312..00000000 --- a/e2e/app/testdata/TestUpdateConfigStateSync.golden +++ /dev/null @@ -1,16 +0,0 @@ - -# Database directory -db_dir = "data" - -# Output level for logging, including package level options -log_level = "info" - -# Output format: 'plain' (colored text) or 'json' -log_format = "plain" - -# For Cosmos SDK-based chains, trust_period should usually be about 2/3 of the unbonding time (~2 -# weeks) during which they can be financially punished (slashed) for misbehavior. -rpc_servers = "" -trust_height = 1 -trust_hash = "74657374" -trust_period = "168h0m0s" diff --git a/e2e/app/valupdates.go b/e2e/app/valupdates.go deleted file mode 100644 index 642c7ce7..00000000 --- a/e2e/app/valupdates.go +++ /dev/null @@ -1,238 +0,0 @@ -package app - -import ( - "context" - "crypto/ecdsa" - "sort" - "time" - - "golang.org/x/sync/errgroup" - - "cosmossdk.io/math" - - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/params" - - "github.com/piplabs/story/client/genutil/evm/predeploys" - "github.com/piplabs/story/contracts/bindings" - "github.com/piplabs/story/e2e/app/eoa" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/ethclient" - "github.com/piplabs/story/lib/ethclient/ethbackend" - "github.com/piplabs/story/lib/k1util" - "github.com/piplabs/story/lib/log" - "github.com/piplabs/story/lib/txmgr" -) - -// FundValidatorsForTesting funds validators in ephemeral networks: devnet and staging. -// This is required by load generation for periodic validator self-delegation. -func FundValidatorsForTesting(ctx context.Context, def Definition) error { - if !def.Testnet.Network.IsEphemeral() { - // Only fund validators in ephemeral networks, devnet and staging. - return nil - } - - log.Info(ctx, "Funding validators for testing", "count", len(def.Testnet.Nodes)) - - network := networkFromDef(def) - omniEVM, _ := network.IliadEVMChain() - funder := eoa.MustAddress(network.ID, eoa.RoleTester) // Fund validators using tester eoa - _, fundBackend, err := def.Backends().BindOpts(ctx, omniEVM.ID, funder) - if err != nil { - return errors.Wrap(err, "bind opts") - } - - // Iterate over all nodes, since all maybe become validators. - var eg errgroup.Group - for _, node := range def.Testnet.Nodes { - eg.Go(func() error { - addr, _ := k1util.PubKeyToAddress(node.PrivvalKey.PubKey()) - tx, _, err := fundBackend.Send(ctx, funder, txmgr.TxCandidate{ - To: &addr, - GasLimit: 100_000, - Value: math.NewInt(1000).MulRaw(params.Ether).BigInt(), - }) - if err != nil { - return errors.Wrap(err, "send") - } - recp, err := fundBackend.WaitMined(ctx, tx) - if err != nil { - return errors.Wrap(err, "wait mined") - } - - bal, err := fundBackend.EtherBalanceAt(ctx, addr) - if err != nil { - return err - } - - log.Debug(ctx, "Funded validator address", - "node", node.Name, "addr", addr, - "balance", bal, "height", recp.BlockNumber.Uint64()) - - return nil - }) - } - - if err := eg.Wait(); err != nil { - return errors.Wrap(err, "wait fund") - } - - return nil -} - -type valUpdate struct { - Height int64 - Powers map[*e2e.Node]int64 -} - -func StartValidatorUpdates(ctx context.Context, def Definition) func() error { - errChan := make(chan error, 1) - returnErr := func(err error) { - if err != nil { - log.Error(ctx, "Validator updates failed", err) - } - select { - case errChan <- err: - default: - log.Error(ctx, "Error channel full, dropping error", err) - } - } - - go func() { - // Get all private keys - var privkeys []*ecdsa.PrivateKey - for _, node := range def.Testnet.Nodes { - pk, err := k1util.StdPrivKeyFromComet(node.PrivvalKey) - if err != nil { - returnErr(err) - return - } - - privkeys = append(privkeys, pk) - } - - // Get a sorted list of validator updates - var updates []valUpdate - for height, powers := range def.Testnet.ValidatorUpdates { - updates = append(updates, valUpdate{ - Height: height, - Powers: powers, - }) - } - sort.Slice(updates, func(i, j int) bool { - return updates[i].Height < updates[j].Height - }) - - // Create a backend to trigger deposits from - network := networkFromDef(def) - endpoints := externalEndpoints(def) - iliadEVM, _ := network.IliadEVMChain() - rpc, found := endpoints[iliadEVM.Name] - if !found { - returnErr(errors.New("get rpc")) - return - } - ethCl, err := ethclient.Dial(iliadEVM.Name, rpc) - if err != nil { - returnErr(errors.Wrap(err, "dial")) - return - } - valBackend, err := ethbackend.NewBackend(iliadEVM.Name, iliadEVM.ID, iliadEVM.BlockPeriod, ethCl, privkeys...) - if err != nil { - returnErr(errors.Wrap(err, "new backend")) - return - } - - // Create the IPTokenStaking contract - ipTokenStaking, err := bindings.NewIPTokenStaking(common.HexToAddress(predeploys.IPTokenStaking), valBackend) - if err != nil { - returnErr(errors.Wrap(err, "new iliad stake")) - return - } - - // Wait for each update, then submit self-delegations - for _, update := range updates { - log.Debug(ctx, "Waiting for next validator update", "wait_for_height", update.Height) - _, _, err := waitForHeight(ctx, def.Testnet.Testnet, update.Height) - if err != nil { - returnErr(errors.Wrap(err, "wait for height")) - return - } - - for node, power := range update.Powers { - pubkey := node.PrivvalKey.PubKey() - addr, err := k1util.PubKeyToAddress(pubkey) - if err != nil { - returnErr(errors.Wrap(err, "pubkey to addr")) - return - } - - // Wait until we have enough balance. - // FundValidatorsForTesting should ensure this, but this sometimes fails...? - for range 10 { - height, err := valBackend.BlockNumber(ctx) - if err != nil { - returnErr(errors.Wrap(err, "block height")) - return - } - - balance, err := valBackend.EtherBalanceAt(ctx, addr) - if err != nil { - returnErr(errors.Wrap(err, "balance at")) - return - } - - if balance > float64(power) { - break // We have enough balance - } - - log.Warn(ctx, "Cannot self-delegate, balance to low (will retry)", nil, - "height", height, "balance", balance, "require", power, - "node", node.Name, "addr", addr.Hex()) - time.Sleep(time.Second) - } - - txOpts, err := valBackend.BindOpts(ctx, addr) - if err != nil { - returnErr(errors.Wrap(err, "bind opts")) - return - } - txOpts.Value = math.NewInt(power).MulRaw(params.Ether).BigInt() - - // NOTE: We can use CreateValidator here, rather than Delegate (self-delegation) - // because current e2e manifest validator_udpates are only used to create a new validator, - // and not to self-delegate an existing one. - // TODO: Use CreateValidator - tx, err := ipTokenStaking.CreateValidatorOnBehalf(txOpts, pubkey.Bytes()) - if err != nil { - returnErr(errors.Wrap(err, "deposit", "node", node.Name, "addr", addr.Hex())) - return - } - rec, err := valBackend.WaitMined(ctx, tx) - if err != nil { - returnErr(errors.Wrap(err, "wait minded", "node", node.Name, "addr", addr.Hex())) - return - } - - log.Info(ctx, "Deposited stake", - "validator", node.Name, - "address", addr.Hex(), - "power", power, - "height", rec.BlockNumber.Uint64(), - ) - } - } - - returnErr(nil) - }() - - return func() error { - select { - case err := <-errChan: - return err - case <-ctx.Done(): - return errors.Wrap(ctx.Err(), "timeout") - } - } -} diff --git a/e2e/app/wait.go b/e2e/app/wait.go deleted file mode 100644 index eb5c85d2..00000000 --- a/e2e/app/wait.go +++ /dev/null @@ -1,38 +0,0 @@ -package app - -import ( - "context" - "time" - - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - - "github.com/piplabs/story/lib/log" -) - -// Wait waits for a number of blocks to be produced, and for all nodes to catch -// up with it. -func Wait(ctx context.Context, testnet *e2e.Testnet, blocks int64) error { - block, _, err := waitForHeight(ctx, testnet, 0) - if err != nil { - return err - } - - return WaitUntil(ctx, testnet, block.Height+blocks) -} - -// WaitUntil waits until a given height has been reached. -func WaitUntil(ctx context.Context, testnet *e2e.Testnet, height int64) error { - log.Info(ctx, "Waiting for nodes to reach height", "height", height) - _, err := waitForAllNodes(ctx, testnet, height, waitingTime(len(testnet.Nodes), height)) - if err != nil { - return err - } - - return nil -} - -// waitingTime estimates how long it should take for a node to reach the height. -// More nodes in a network implies we may expect a slower network and may have to wait longer. -func waitingTime(nodes int, height int64) time.Duration { - return time.Duration(20+(int64(nodes)*height)) * time.Second -} diff --git a/e2e/cmd/cmd.go b/e2e/cmd/cmd.go deleted file mode 100644 index 349c0c26..00000000 --- a/e2e/cmd/cmd.go +++ /dev/null @@ -1,273 +0,0 @@ -package cmd - -import ( - "context" - "log/slog" - "regexp" - - cmtdocker "github.com/cometbft/cometbft/test/e2e/pkg/infra/docker" - "github.com/ethereum/go-ethereum/common" - "github.com/spf13/cobra" - - "github.com/piplabs/story/e2e/app" - "github.com/piplabs/story/e2e/app/eoa" - "github.com/piplabs/story/e2e/app/key" - "github.com/piplabs/story/e2e/types" - libcmd "github.com/piplabs/story/lib/cmd" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/log" - "github.com/piplabs/story/lib/netconf" -) - -func New() *cobra.Command { - // E2E app is aimed at devs and CI, so debug level and force colors by default. - logCfg := log.DefaultConfig() - logCfg.Level = slog.LevelDebug.String() - logCfg.Color = log.ColorForce - - defCfg := app.DefaultDefinitionConfig(context.Background()) - - var def app.Definition - - cmd := libcmd.NewRootCmd("e2e", "e2e network generator and test runner") - cmd.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - if _, err := log.Init(ctx, logCfg); err != nil { - return err - } - - if err := libcmd.LogFlags(ctx, cmd.Flags()); err != nil { - return err - } - - var err error - def, err = app.MakeDefinition(ctx, defCfg, cmd.Use) - if err != nil { - return errors.Wrap(err, "make definition") - } - - // Some commands require networking, this ensures proper errors instead of panics. - if matchAny(cmd.Use, ".*deploy.*", ".*update.*", "e2e") { - if err := def.InitLazyNetwork(); err != nil { - return errors.Wrap(err, "init network") - } - } - - return err - } - - bindDefFlags(cmd.PersistentFlags(), &defCfg) - log.BindFlags(cmd.PersistentFlags(), &logCfg) - - // Root command runs the full E2E test. - e2eTestCfg := app.DefaultE2ETestConfig() - bindE2EFlags(cmd.Flags(), &e2eTestCfg) - cmd.RunE = func(cmd *cobra.Command, _ []string) error { - return app.E2ETest(cmd.Context(), def, e2eTestCfg) - } - - // Add subcommands - cmd.AddCommand( - newCreate3DeployCmd(&def), - newDeployCmd(&def), - newLogsCmd(&def), - newCleanCmd(&def), - newTestCmd(&def), - newUpgradeCmd(&def), - newKeyCreate(&def), - fundAccounts(&def), - ) - - return cmd -} - -func matchAny(str string, patterns ...string) bool { - for _, pattern := range patterns { - if ok, _ := regexp.MatchString(pattern, str); ok { - return true - } - } - - return false -} - -func newDeployCmd(def *app.Definition) *cobra.Command { - return &cobra.Command{ - Use: "deploy", - Short: "Deploys the e2e network", - RunE: func(cmd *cobra.Command, _ []string) error { - err := app.Deploy(cmd.Context(), *def, app.DefaultDeployConfig()) - return err - }, - } -} - -func newLogsCmd(def *app.Definition) *cobra.Command { - return &cobra.Command{ - Use: "logs", - Short: "Prints the infrastructure logs (of a previously preserved network)", - RunE: func(cmd *cobra.Command, _ []string) error { - err := cmtdocker.ExecComposeVerbose(cmd.Context(), def.Testnet.Dir, "logs") - if err != nil { - return errors.Wrap(err, "executing docker-compose logs") - } - - return nil - }, - } -} - -func newCleanCmd(def *app.Definition) *cobra.Command { - return &cobra.Command{ - Use: "clean", - Short: "Cleans (deletes) previously preserved network infrastructure", - RunE: func(cmd *cobra.Command, _ []string) error { - if err := app.CleanInfra(cmd.Context(), *def); err != nil { - return err - } - - return app.CleanupDir(cmd.Context(), def.Testnet.Dir) - }, - } -} - -func newTestCmd(def *app.Definition) *cobra.Command { - return &cobra.Command{ - Use: "test", - Short: "Runs go tests against the a previously preserved network", - RunE: func(cmd *cobra.Command, _ []string) error { - return app.Test(cmd.Context(), *def, true) - }, - } -} - -func newUpgradeCmd(def *app.Definition) *cobra.Command { - cfg := app.DefaultDeployConfig() - upgradeCfg := types.DefaultUpgradeConfig() - - cmd := &cobra.Command{ - Use: "upgrade", - Short: "Upgrades docker containers of a previously preserved network", - RunE: func(cmd *cobra.Command, _ []string) error { - return app.Upgrade(cmd.Context(), *def, cfg, upgradeCfg) - }, - } - - bindUpgradeFlags(cmd.Flags(), &upgradeCfg) - - return cmd -} - -func newCreate3DeployCmd(def *app.Definition) *cobra.Command { - cfg := app.Create3DeployConfig{} - - cmd := &cobra.Command{ - Use: "create3-deploy", - Short: "Deploys the Create3 factory", - RunE: func(cmd *cobra.Command, _ []string) error { - return app.Create3Deploy(cmd.Context(), *def, cfg) - }, - } - - bindCreate3DeployFlags(cmd.Flags(), &cfg) - - return cmd -} - -func newKeyCreate(def *app.Definition) *cobra.Command { - cfg := key.UploadConfig{} - - cmd := &cobra.Command{ - Use: "key-create", - Short: "Creates a private key in GCP secret manager for a node in a manifest", - RunE: func(cmd *cobra.Command, _ []string) error { - if def.Testnet.Network == netconf.Simnet || def.Testnet.Network == netconf.Devnet { - return errors.New("cannot create keys for simnet or devnet") - } - - cfg.Network = def.Testnet.Network - - if err := verifyKeyNodeType(*def, cfg); err != nil { - return err - } - - _, err := key.UploadNew(cmd.Context(), cfg) - - return err - }, - } - - bindKeyCreateFlags(cmd, &cfg) - - return cmd -} - -func fundAccounts(def *app.Definition) *cobra.Command { - cmd := &cobra.Command{ - Use: "fund", - Short: "Funds accounts to their target balance, network based on the manifest", - RunE: func(cmd *cobra.Command, _ []string) error { - if def.Testnet.Network == netconf.Simnet || def.Testnet.Network == netconf.Devnet { - return errors.New("cannot fund accounts on simnet or devnet") - } - if err := def.InitLazyNetwork(); err != nil { - return errors.Wrap(err, "init network") - } - - return app.FundEOAAccounts(cmd.Context(), *def, false) - }, - } - - return cmd -} - -// verifyKeyNodeType checks if the node exists in the manifest and if the key type is allowed for the node. -func verifyKeyNodeType(def app.Definition, cfg key.UploadConfig) error { - if err := cfg.Type.Verify(); err != nil { - return err - } - - if cfg.Type == key.EOA { - eoaRole := eoa.Role(cfg.Name) - if err := eoaRole.Verify(); err != nil { - return errors.Wrap(err, "verifying name as eoa type") - } - - account, ok := eoa.AccountForRole(def.Testnet.Network, eoaRole) - if !ok { - return errors.New("eoa account not found", "role", eoaRole) - } - - if account.Type != eoa.TypeSecret { - return errors.New("cannot create eoa key for non secret account") - } - - if account.Address != (common.Address{}) { - return errors.New("cannot create eoa key already defined", "addr", account.Address.Hex()) - } - - return nil - } - - for _, node := range def.Testnet.Nodes { - if node.Name == cfg.Name { - if cfg.Type == key.P2PExecution { - return errors.New("cannot create execution key for iliad node") - } - - return nil - } - } - - for _, evm := range def.Testnet.IliadEVMs { - if evm.InstanceName == cfg.Name { - if cfg.Type != key.P2PExecution { - return errors.New("only execution keys allowed for evm nodes") - } - - return nil - } - } - - return errors.New("node not found", "name", cfg.Name) -} diff --git a/e2e/cmd/flags.go b/e2e/cmd/flags.go deleted file mode 100644 index 928b4b7a..00000000 --- a/e2e/cmd/flags.go +++ /dev/null @@ -1,52 +0,0 @@ -//nolint:lll // Long lines are easier to read for flag descriptions. -package cmd - -import ( - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "github.com/piplabs/story/e2e/app" - "github.com/piplabs/story/e2e/app/agent" - "github.com/piplabs/story/e2e/app/key" - "github.com/piplabs/story/e2e/types" -) - -func bindDefFlags(flags *pflag.FlagSet, cfg *app.DefinitionConfig) { - bindPromFlags(flags, &cfg.AgentSecrets) - flags.StringVarP(&cfg.ManifestFile, "manifest-file", "f", cfg.ManifestFile, "path to manifest file") - flags.StringVar(&cfg.InfraProvider, "infra", cfg.InfraProvider, "infrastructure provider: docker, vmcompose") - flags.StringVar(&cfg.InfraDataFile, "infra-file", cfg.InfraDataFile, "infrastructure data file (not required for docker provider)") - flags.StringVar(&cfg.DeployKeyFile, "deploy-key", cfg.DeployKeyFile, "path to deploy private key file") - flags.StringVar(&cfg.FireAPIKey, "fireblocks-api-key", cfg.FireAPIKey, "FireBlocks api key") - flags.StringVar(&cfg.FireKeyPath, "fireblocks-key-path", cfg.FireKeyPath, "FireBlocks RSA private key path") - flags.StringVar(&cfg.IliadImgTag, "iliad-image-tag", cfg.IliadImgTag, "Iliad docker images tag (iliad). Defaults to working dir git commit.") - flags.StringToStringVar(&cfg.RPCOverrides, "rpc-overrides", cfg.RPCOverrides, "Public chain rpc overrides: '=,'") - flags.StringVar(&cfg.TracingEndpoint, "tracing-endpoint", cfg.TracingEndpoint, "Tracing endpoint") - flags.StringVar(&cfg.TracingHeaders, "tracing-headers", cfg.TracingHeaders, "Tracing headers") -} - -func bindE2EFlags(flags *pflag.FlagSet, cfg *app.E2ETestConfig) { - flags.BoolVar(&cfg.Preserve, "preserve", cfg.Preserve, "preserve infrastructure after test") -} - -func bindPromFlags(flags *pflag.FlagSet, cfg *agent.Secrets) { - flags.StringVar(&cfg.URL, "prom-url", cfg.URL, "prometheus url (only required if prometheus==true)") - flags.StringVar(&cfg.User, "prom-user", cfg.User, "prometheus user") - flags.StringVar(&cfg.Pass, "prom-password", cfg.Pass, "prometheus password") -} - -func bindUpgradeFlags(flags *pflag.FlagSet, cfg *types.UpgradeConfig) { - flags.StringVar(&cfg.ServiceRegexp, "services", cfg.ServiceRegexp, "Regexp applied to services per VM. Any match results in the VM being upgraded (all services on that VM are upgraded, not only matching services)") -} - -func bindCreate3DeployFlags(flags *pflag.FlagSet, cfg *app.Create3DeployConfig) { - flags.Uint64Var(&cfg.ChainID, "chain-id", cfg.ChainID, "chain id of the chain to deploy to") -} - -func bindKeyCreateFlags(cmd *cobra.Command, cfg *key.UploadConfig) { - cmd.Flags().StringVar(&cfg.Name, "name", cfg.Name, "key name: either node name or eoa account type") - cmd.Flags().StringVar((*string)(&cfg.Type), "type", string(cfg.Type), "key type: validator, p2p_execution, p2p_consensus, eoa") - - _ = cmd.MarkFlagRequired("name") - _ = cmd.MarkFlagRequired("type") -} diff --git a/e2e/docker/compose.yaml.tmpl b/e2e/docker/compose.yaml.tmpl deleted file mode 100644 index 45c07066..00000000 --- a/e2e/docker/compose.yaml.tmpl +++ /dev/null @@ -1,130 +0,0 @@ -version: '2.4' -networks: - {{ .NetworkName }}: - labels: - e2e: true - driver: bridge - {{- if .Network }} - ipam: - driver: default - config: - - subnet: {{ .NetworkCIDR }} - {{- end }} - -services: -{{- range .Nodes }} - {{ .Name }}: - labels: - e2e: true - container_name: {{ .Name }} - image: {{ .Version }} - init: true - ports: - - {{ if $.BindAll }}26656:{{end}}26656 - - {{ if .ProxyPort }}{{ .ProxyPort }}:{{ end }}26657 -{{- if .PrometheusProxyPort }} - - {{ .PrometheusProxyPort }}:26660 -{{- end }} - - 6060 - volumes: - - ./{{ .Name }}:/iliad - depends_on: - {{ index $.NodeIliadEVMs .Name }}: - condition: service_healthy - networks: - {{ $.NetworkName }}: - {{ if $.Network }}ipv4_address: {{ .InternalIP }}{{ end }} -{{end}} - -{{- range .Anvils }} - # Initialises geth files and folder from provided genesis file. - {{ .Chain.Name }}: - labels: - e2e: true - container_name: {{ .Chain.Name }} - platform: linux/amd64 - image: ghcr.io/foundry-rs/foundry:latest - entrypoint: - - anvil - - --host=0.0.0.0 - - --chain-id={{ .Chain.ChainID }} - - --block-time={{.Chain.BlockPeriod.Seconds}} - - --silent - {{ if .LoadState }}- --load-state=/anvil/state.json{{ end }} - ports: - - {{ if .ProxyPort }}{{ .ProxyPort }}:{{ end }}8545 - networks: - {{ $.NetworkName }}: - {{ if $.Network }}ipv4_address: {{ .InternalIP }}{{ end }} - {{ if .LoadState }} - volumes: - - {{ .LoadState }}:/anvil/state.json - {{ end }} -{{- end}} - - # Use geth as the iliad EVMs. -{{- range .IliadEVMs }} - # Initialises geth files and folder from provided genesis file. - {{ .InstanceName }}-init: - labels: - e2e: true - container_name: {{ .InstanceName }}-init - image: "ethereum/client-go:{{ $.GethTag }}" - command: --state.scheme={{ if .IsArchive }}hash{{ else }}path{{ end }} --datadir=/geth init /geth/genesis.json - volumes: - - ./{{ .InstanceName }}:/geth - networks: - {{ $.NetworkName }}: - - {{ .InstanceName }}: - labels: - e2e: true - container_name: {{ .InstanceName }} - image: "ethereum/client-go:{{ $.GethTag }}" - command: - - --config=/geth/config.toml - # Flags not available via config.toml - - --nat=extip:{{ .AdvertisedIP }} - - --pprof - - --pprof.addr=0.0.0.0 - - --metrics - {{ if .IsArchive }}- --gcmode=archive{{ end }} - ports: - - {{ if $.BindAll }}8551:{{end}}8551 - - {{ if .ProxyPort }}{{ .ProxyPort }}:{{ end }}8545 - - {{ if $.BindAll }}30303:{{end}}30303 - - 8546 - - 6060 - depends_on: - {{ .InstanceName }}-init: - condition: service_completed_successfully - healthcheck: - test: "nc -z localhost 8545" - interval: 1s - retries: 30 - volumes: - - ./{{ .InstanceName }}:/geth - networks: - {{ $.NetworkName }}: - {{ if $.Network }}ipv4_address: {{ .AdvertisedIP }}{{ end }} -{{end}} - -{{- if .Prometheus }} - prometheus: - labels: - e2e: true - container_name: prometheus - image: prom/prometheus:latest - command: - - --config.file=/etc/prometheus/prometheus.yml - - --web.console.libraries=/usr/share/prometheus/console_libraries - - --web.console.templates=/usr/share/prometheus/consoles - - --enable-feature=exemplar-storage - - --enable-feature=agent - restart: unless-stopped - volumes: - - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - networks: - {{ $.NetworkName }}: - {{ if $.Network }}ipv4_address: 10.186.73.202{{ end }} -{{ end }} diff --git a/e2e/docker/data.go b/e2e/docker/data.go deleted file mode 100644 index 689f5c64..00000000 --- a/e2e/docker/data.go +++ /dev/null @@ -1,62 +0,0 @@ -package docker - -import ( - "fmt" - "net" - - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/lib/errors" -) - -const ( - ipPrefix = "10.186.73." // See github.com/cometbft/cometbft/test/e2e/pkg for reference - startIPSuffix = 100 - startPort = 8000 -) - -var localhost = net.ParseIP("127.0.0.1") //nolint:gochecknoglobals // Static IP - -// NewInfraData returns a new InfrastructureData for the given manifest. -// In addition to normal. -func NewInfraData(manifest types.Manifest) (types.InfrastructureData, error) { - infd, err := e2e.NewDockerInfrastructureData(manifest.Manifest) - if err != nil { - return types.InfrastructureData{}, errors.Wrap(err, "creating docker infrastructure data") - } - - // IP generator - ipSuffix := startIPSuffix - nextInternalIP := func() net.IP { - defer func() { ipSuffix++ }() - return net.ParseIP(fmt.Sprintf(ipPrefix+"%d", ipSuffix)) - } - - // Port generator - port := startPort - nextPort := func() uint32 { - defer func() { port++ }() - return uint32(port) - } - - for name := range manifest.IliadEVMs() { - infd.Instances[name] = e2e.InstanceData{ - IPAddress: nextInternalIP(), - ExtIPAddress: localhost, - Port: nextPort(), - } - } - - for _, name := range manifest.AnvilChains { - infd.Instances[name] = e2e.InstanceData{ - IPAddress: nextInternalIP(), - ExtIPAddress: localhost, - Port: nextPort(), - } - } - - return types.InfrastructureData{ - InfrastructureData: infd, - }, nil -} diff --git a/e2e/docker/docker.go b/e2e/docker/docker.go deleted file mode 100644 index dfbd5258..00000000 --- a/e2e/docker/docker.go +++ /dev/null @@ -1,228 +0,0 @@ -package docker - -import ( - "bytes" - "context" - "fmt" - "os" - "path/filepath" - "runtime" - "sync" - "text/template" - - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - "github.com/cometbft/cometbft/test/e2e/pkg/exec" - "github.com/cometbft/cometbft/test/e2e/pkg/infra" - cmtdocker "github.com/cometbft/cometbft/test/e2e/pkg/infra/docker" - - "github.com/piplabs/story/e2e/app/geth" - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/log" - - _ "embed" -) - -const ProviderName = "docker" - -// composeTmpl is our own custom docker compose template. This differs from cometBFT's. -// -//go:embed compose.yaml.tmpl -var composeTmpl []byte - -var _ types.InfraProvider = (*Provider)(nil) - -// Provider wraps the cometBFT docker provider, writing a different compose file. -type Provider struct { - *cmtdocker.Provider - servicesOnce sync.Once - testnet types.Testnet - iliadTag string -} - -func (*Provider) Clean(ctx context.Context) error { - log.Info(ctx, "Removing docker containers and networks") - - for _, cmd := range CleanCmds(false, runtime.GOOS == "linux") { - err := exec.Command(ctx, "bash", "-c", cmd) - if err != nil { - return errors.Wrap(err, "remove docker containers") - } - } - - return nil -} - -// NewProvider returns a new Provider. -func NewProvider(testnet types.Testnet, infd types.InfrastructureData, imgTag string) *Provider { - return &Provider{ - Provider: &cmtdocker.Provider{ - ProviderData: infra.ProviderData{ - Testnet: testnet.Testnet, - InfrastructureData: infd.InfrastructureData, - }, - }, - testnet: testnet, - iliadTag: imgTag, - } -} - -// Setup generates the docker-compose file and write it to disk, erroring if -// any of these operations fail. -func (p *Provider) Setup() error { - def := ComposeDef{ - Network: true, - NetworkName: p.testnet.Name, - NetworkCIDR: p.testnet.IP.String(), - BindAll: false, - Nodes: p.testnet.Nodes, - IliadEVMs: p.testnet.IliadEVMs, - Anvils: p.testnet.AnvilChains, - Prometheus: p.testnet.Prometheus, - IliadTag: p.iliadTag, - } - - bz, err := GenerateComposeFile(def) - if err != nil { - return errors.Wrap(err, "generate compose file") - } - - err = os.WriteFile(filepath.Join(p.Testnet.Dir, "docker-compose.yml"), bz, 0o644) - if err != nil { - return errors.Wrap(err, "write compose file") - } - - return nil -} - -func (*Provider) Upgrade(context.Context, types.UpgradeConfig) error { - return errors.New("upgrade not supported for docker provider") -} - -func (p *Provider) StartNodes(ctx context.Context, nodes ...*e2e.Node) error { - var err error - p.servicesOnce.Do(func() { - svcs := additionalServices(p.testnet) - log.Info(ctx, "Starting additional services", "names", svcs) - - err = cmtdocker.ExecCompose(ctx, p.Testnet.Dir, "create") // This fails if containers not available. - if err != nil { - err = errors.Wrap(err, "create containers") - return - } - - err = cmtdocker.ExecCompose(ctx, p.Testnet.Dir, append([]string{"up", "-d"}, svcs...)...) - if err != nil { - err = errors.Wrap(err, "start additional services") - return - } - }) - if err != nil { - return err - } - - // if there are no iliad nodes available - if len(nodes) == 0 { - panic("no nodes to start") - } - - // Start all requested nodes (use --no-deps to avoid starting the additional services again). - nodeNames := make([]string, len(nodes)) - for i, n := range nodes { - nodeNames[i] = n.Name - } - err = cmtdocker.ExecCompose(ctx, p.Testnet.Dir, append([]string{"up", "-d", "--no-deps"}, nodeNames...)...) - if err != nil { - return errors.Wrap(err, "start nodes") - } - - return nil -} - -type ComposeDef struct { - Network bool - NetworkName string - NetworkCIDR string - BindAll bool - - Nodes []*e2e.Node - IliadEVMs []types.IliadEVM - Anvils []types.AnvilChain - - IliadTag string - Prometheus bool -} - -func (ComposeDef) GethTag() string { - return geth.Version -} - -// NodeIliadEVMs returns a map of node name to IliadEVM instance name; map[node_name]iliad_evm. -func (c ComposeDef) NodeIliadEVMs() map[string]string { - resp := make(map[string]string) - for i, node := range c.Nodes { - evm := c.IliadEVMs[0].InstanceName - if len(c.IliadEVMs) == len(c.Nodes) { - evm = c.IliadEVMs[i].InstanceName - } - resp[node.Name] = evm - } - - return resp -} - -func GenerateComposeFile(def ComposeDef) ([]byte, error) { - tmpl, err := template.New("compose").Parse(string(composeTmpl)) - if err != nil { - return nil, errors.Wrap(err, "parse template") - } - - var buf bytes.Buffer - err = tmpl.Execute(&buf, def) - if err != nil { - return nil, errors.Wrap(err, "execute template") - } - - return buf.Bytes(), nil -} - -// CleanCmds returns generic docker commands to clean up docker containers and networks. -// This bypasses the need to a specific docker-compose context. -func CleanCmds(sudo bool, isLinux bool) []string { - // GNU xargs requires the -r flag to not run when input is empty, macOS - // does this by default. Ugly, but works. - xargsR := "" - if isLinux { - xargsR = "-r" - } - - // Some environments need sudo to run docker commands. - perm := "" - if sudo { - perm = "sudo" - } - - return []string{ - fmt.Sprintf("%s docker container ls -qa --filter label=e2e | xargs %v %s docker container rm -f", - perm, xargsR, perm), - fmt.Sprintf("%s docker network ls -q --filter label=e2e | xargs %v %s docker network rm", - perm, xargsR, perm), - } -} - -// additionalServices returns additional (to iliad) docker-compose services to start. -func additionalServices(testnet types.Testnet) []string { - var resp []string - if testnet.Prometheus { - resp = append(resp, "prometheus") - } - - for _, iliadEVM := range testnet.IliadEVMs { - resp = append(resp, iliadEVM.InstanceName) - } - for _, anvil := range testnet.AnvilChains { - resp = append(resp, anvil.Chain.Name) - } - - return resp -} diff --git a/e2e/docker/docker_test.go b/e2e/docker/docker_test.go deleted file mode 100644 index fbaeccfe..00000000 --- a/e2e/docker/docker_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package docker_test - -import ( - "net" - "os" - "path/filepath" - "testing" - "time" - - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/e2e/docker" - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/lib/evmchain" - "github.com/piplabs/story/lib/netconf" -) - -//go:generate go test . -golden -clean - -func TestComposeTemplate(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - tag string - isEmpheral bool - }{ - { - name: "main_network", - tag: "main", - isEmpheral: false, - }, - { - name: "empheral_network", - tag: "main", - isEmpheral: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - _, ipNet, err := net.ParseCIDR("10.186.73.0/24") - require.NoError(t, err) - - key, err := crypto.HexToECDSA("59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d") - require.NoError(t, err) - en := enode.NewV4(&key.PublicKey, ipNet.IP, 30303, 30303) - - dir := t.TempDir() - testnet := types.Testnet{ - Testnet: &e2e.Testnet{ - Name: "test", - IP: ipNet, - Dir: dir, - Prometheus: true, - Nodes: []*e2e.Node{{ - Name: "node0", - Version: "iliadops/iliad:" + test.tag, - InternalIP: ipNet.IP, - ProxyPort: 8584, - }}, - }, - IliadEVMs: []types.IliadEVM{ - { - Chain: types.IliadEVMByNetwork(netconf.Simnet), - InstanceName: "iliad_evm_0", - AdvertisedIP: ipNet.IP, - ProxyPort: 8000, - NodeKey: key, - Enode: en, - Peers: []*enode.Node{en}, - }, - }, - AnvilChains: []types.AnvilChain{ - { - Chain: types.EVMChain{Metadata: evmchain.Metadata{ - ChainID: 99, - Name: "mock_rollup", - BlockPeriod: time.Second, - }}, - InternalIP: ipNet.IP, - ProxyPort: 9000, - }, - { - Chain: types.EVMChain{Metadata: evmchain.Metadata{ - ChainID: 1, - Name: "mock_l1", - BlockPeriod: time.Hour, - }}, - InternalIP: ipNet.IP, - ProxyPort: 9000, - LoadState: "path/to/anvil/state.json", - }, - }, - } - - // If the network is empheral, we use the devnet configuration. - if test.isEmpheral { - testnet.Network = netconf.Devnet - } - - p := docker.NewProvider(testnet, types.InfrastructureData{}, test.tag) - require.NoError(t, err) - - require.NoError(t, p.Setup()) - - _, err = os.ReadFile(filepath.Join(dir, "docker-compose.yml")) - require.NoError(t, err) - }) - } -} diff --git a/e2e/docker/testdata/TestComposeTemplate_commit_no_explorer.golden b/e2e/docker/testdata/TestComposeTemplate_commit_no_explorer.golden deleted file mode 100644 index 82bd6c91..00000000 --- a/e2e/docker/testdata/TestComposeTemplate_commit_no_explorer.golden +++ /dev/null @@ -1,138 +0,0 @@ -version: '2.4' -networks: - test: - labels: - e2e: true - driver: bridge - ipam: - driver: default - config: - - subnet: 10.186.73.0/24 - -services: - node0: - labels: - e2e: true - container_name: node0 - image: iliadops/iliad:7d1ae53 - init: true - ports: - - 26656 - - 8584:26657 - - 6060 - volumes: - - ./node0:/iliad - depends_on: - iliad_evm_0: - condition: service_healthy - networks: - test: - ipv4_address: 10.186.73.0 - - # Initialises geth files and folder from provided genesis file. - mock_rollup: - labels: - e2e: true - container_name: mock_rollup - platform: linux/amd64 - image: ghcr.io/foundry-rs/foundry:latest - entrypoint: - - anvil - - --host=0.0.0.0 - - --chain-id=99 - - --block-time=1 - - --silent - - ports: - - 9000:8545 - networks: - test: - ipv4_address: 10.186.73.0 - - # Initialises geth files and folder from provided genesis file. - mock_l1: - labels: - e2e: true - container_name: mock_l1 - platform: linux/amd64 - image: ghcr.io/foundry-rs/foundry:latest - entrypoint: - - anvil - - --host=0.0.0.0 - - --chain-id=1 - - --block-time=3600 - - --silent - - --load-state=/anvil/state.json - ports: - - 9000:8545 - networks: - test: - ipv4_address: 10.186.73.0 - - volumes: - - path/to/anvil/state.json:/anvil/state.json - - - # Use geth as the iliad EVMs. - # Initialises geth files and folder from provided genesis file. - iliad_evm_0-init: - labels: - e2e: true - container_name: iliad_evm_0-init - image: "ethereum/client-go:v1.14.2" - command: --state.scheme=path --datadir=/geth init /geth/genesis.json - volumes: - - ./iliad_evm_0:/geth - networks: - test: - - iliad_evm_0: - labels: - e2e: true - container_name: iliad_evm_0 - image: "ethereum/client-go:v1.14.2" - command: - - --config=/geth/config.toml - # Flags not available via config.toml - - --nat=extip:10.186.73.0 - - --pprof - - --pprof.addr=0.0.0.0 - - --metrics - - ports: - - 8551 - - 8000:8545 - - 30303 - - 8546 - - 6060 - depends_on: - iliad_evm_0-init: - condition: service_completed_successfully - healthcheck: - test: "nc -z localhost 8545" - interval: 1s - retries: 30 - volumes: - - ./iliad_evm_0:/geth - networks: - test: - ipv4_address: 10.186.73.0 - - prometheus: - labels: - e2e: true - container_name: prometheus - image: prom/prometheus:latest - command: - - --config.file=/etc/prometheus/prometheus.yml - - --web.console.libraries=/usr/share/prometheus/console_libraries - - --web.console.templates=/usr/share/prometheus/consoles - - --enable-feature=exemplar-storage - - --enable-feature=agent - restart: unless-stopped - volumes: - - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - networks: - test: - ipv4_address: 10.186.73.202 - diff --git a/e2e/docker/testdata/TestComposeTemplate_empheral_network.golden b/e2e/docker/testdata/TestComposeTemplate_empheral_network.golden deleted file mode 100644 index 51acee09..00000000 --- a/e2e/docker/testdata/TestComposeTemplate_empheral_network.golden +++ /dev/null @@ -1,138 +0,0 @@ -version: '2.4' -networks: - test: - labels: - e2e: true - driver: bridge - ipam: - driver: default - config: - - subnet: 10.186.73.0/24 - -services: - node0: - labels: - e2e: true - container_name: node0 - image: iliadops/iliad:main - init: true - ports: - - 26656 - - 8584:26657 - - 6060 - volumes: - - ./node0:/iliad - depends_on: - iliad_evm_0: - condition: service_healthy - networks: - test: - ipv4_address: 10.186.73.0 - - # Initialises geth files and folder from provided genesis file. - mock_rollup: - labels: - e2e: true - container_name: mock_rollup - platform: linux/amd64 - image: ghcr.io/foundry-rs/foundry:latest - entrypoint: - - anvil - - --host=0.0.0.0 - - --chain-id=99 - - --block-time=1 - - --silent - - ports: - - 9000:8545 - networks: - test: - ipv4_address: 10.186.73.0 - - # Initialises geth files and folder from provided genesis file. - mock_l1: - labels: - e2e: true - container_name: mock_l1 - platform: linux/amd64 - image: ghcr.io/foundry-rs/foundry:latest - entrypoint: - - anvil - - --host=0.0.0.0 - - --chain-id=1 - - --block-time=3600 - - --silent - - --load-state=/anvil/state.json - ports: - - 9000:8545 - networks: - test: - ipv4_address: 10.186.73.0 - - volumes: - - path/to/anvil/state.json:/anvil/state.json - - - # Use geth as the iliad EVMs. - # Initialises geth files and folder from provided genesis file. - iliad_evm_0-init: - labels: - e2e: true - container_name: iliad_evm_0-init - image: "ethereum/client-go:v1.14.2" - command: --state.scheme=path --datadir=/geth init /geth/genesis.json - volumes: - - ./iliad_evm_0:/geth - networks: - test: - - iliad_evm_0: - labels: - e2e: true - container_name: iliad_evm_0 - image: "ethereum/client-go:v1.14.2" - command: - - --config=/geth/config.toml - # Flags not available via config.toml - - --nat=extip:10.186.73.0 - - --pprof - - --pprof.addr=0.0.0.0 - - --metrics - - ports: - - 8551 - - 8000:8545 - - 30303 - - 8546 - - 6060 - depends_on: - iliad_evm_0-init: - condition: service_completed_successfully - healthcheck: - test: "nc -z localhost 8545" - interval: 1s - retries: 30 - volumes: - - ./iliad_evm_0:/geth - networks: - test: - ipv4_address: 10.186.73.0 - - prometheus: - labels: - e2e: true - container_name: prometheus - image: prom/prometheus:latest - command: - - --config.file=/etc/prometheus/prometheus.yml - - --web.console.libraries=/usr/share/prometheus/console_libraries - - --web.console.templates=/usr/share/prometheus/consoles - - --enable-feature=exemplar-storage - - --enable-feature=agent - restart: unless-stopped - volumes: - - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - networks: - test: - ipv4_address: 10.186.73.202 - diff --git a/e2e/docker/testdata/TestComposeTemplate_main_explorer.golden b/e2e/docker/testdata/TestComposeTemplate_main_explorer.golden deleted file mode 100644 index 5d5a1b12..00000000 --- a/e2e/docker/testdata/TestComposeTemplate_main_explorer.golden +++ /dev/null @@ -1,137 +0,0 @@ -version: '2.4' -networks: - test: - labels: - e2e: true - driver: bridge - ipam: - driver: default - config: - - subnet: 10.186.73.0/24 - -services: - node0: - labels: - e2e: true - container_name: node0 - image: iliadops/iliad:main - init: true - ports: - - 26656 - - 8584:26657 - - 6060 - volumes: - - ./node0:/iliad - depends_on: - iliad_evm_0: - condition: service_healthy - networks: - test: - ipv4_address: 10.186.73.0 - - # Initialises geth files and folder from provided genesis file. - mock_rollup: - labels: - e2e: true - container_name: mock_rollup - platform: linux/amd64 - image: ghcr.io/foundry-rs/foundry:latest - entrypoint: - - anvil - - --host=0.0.0.0 - - --chain-id=99 - - --block-time=1 - - --silent - - ports: - - 9000:8545 - networks: - test: - ipv4_address: 10.186.73.0 - - # Initialises geth files and folder from provided genesis file. - mock_l1: - labels: - e2e: true - container_name: mock_l1 - platform: linux/amd64 - image: ghcr.io/foundry-rs/foundry:latest - entrypoint: - - anvil - - --host=0.0.0.0 - - --chain-id=1 - - --block-time=3600 - - --silent - - --load-state=/anvil/state.json - ports: - - 9000:8545 - networks: - test: - ipv4_address: 10.186.73.0 - - volumes: - - path/to/anvil/state.json:/anvil/state.json - - - # Use geth as the iliad EVMs. - # Initialises geth files and folder from provided genesis file. - iliad_evm_0-init: - labels: - e2e: true - container_name: iliad_evm_0-init - image: "ethereum/client-go:v1.14.2" - command: --state.scheme=path --datadir=/geth init /geth/genesis.json - volumes: - - ./iliad_evm_0:/geth - networks: - test: - - iliad_evm_0: - labels: - e2e: true - container_name: iliad_evm_0 - image: "ethereum/client-go:v1.14.2" - command: - - --config=/geth/config.toml - # Flags not available via config.toml - - --nat=extip:10.186.73.0 - - --pprof - - --pprof.addr=0.0.0.0 - - --metrics - - ports: - - 8551 - - 8000:8545 - - 30303 - - 8546 - - 6060 - depends_on: - iliad_evm_0-init: - condition: service_completed_successfully - healthcheck: - test: "nc -z localhost 8545" - interval: 1s - retries: 30 - volumes: - - ./iliad_evm_0:/geth - networks: - test: - ipv4_address: 10.186.73.0 - - prometheus: - labels: - e2e: true - container_name: prometheus - image: prom/prometheus:latest - command: - - --config.file=/etc/prometheus/prometheus.yml - - --web.console.libraries=/usr/share/prometheus/console_libraries - - --web.console.templates=/usr/share/prometheus/consoles - - --enable-feature=exemplar-storage - - --enable-feature=agent - restart: unless-stopped - volumes: - - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - networks: - test: - ipv4_address: 10.186.73.202 diff --git a/e2e/main.go b/e2e/main.go deleted file mode 100644 index bc3a0816..00000000 --- a/e2e/main.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -import ( - e2ecmd "github.com/piplabs/story/e2e/cmd" - libcmd "github.com/piplabs/story/lib/cmd" -) - -func main() { - libcmd.Main(e2ecmd.New()) -} diff --git a/e2e/manifests/ci.toml b/e2e/manifests/ci.toml deleted file mode 100644 index b0a0701e..00000000 --- a/e2e/manifests/ci.toml +++ /dev/null @@ -1,23 +0,0 @@ -network = "devnet" -anvil_chains = ["mock_l2", "mock_l1"] - -multi_iliad_evms = true - -[node.validator01] -[node.validator02] -[node.validator03] -[node.validator04] -start_at = 20 -state_sync = true - -[node.full01] -mode = "full" -perturb = ["restart"] - -# Trigger validator updates at height 10 -[validator_update.10] -full01 = 2 # Add full01 as validator by depositing 2 ether $ILIAD - -# Additional perturbations. -[perturb] -validator02_evm = ["stopstart"] diff --git a/e2e/manifests/devnet0.toml b/e2e/manifests/devnet0.toml deleted file mode 100644 index a17a15bc..00000000 --- a/e2e/manifests/devnet0.toml +++ /dev/null @@ -1,7 +0,0 @@ -# Devnet0 is a tiny devnet. Only a single validator and two anvils. -# This is aimed at local dev environments. - -network = "devnet" -anvil_chains = ["mock_op", "mock_arb"] - -[node.validator01] diff --git a/e2e/manifests/devnet1.toml b/e2e/manifests/devnet1.toml deleted file mode 100644 index c9271bf9..00000000 --- a/e2e/manifests/devnet1.toml +++ /dev/null @@ -1,9 +0,0 @@ -# Devnet1 is the simple multi-validator devnet. It contains 2 validators. -network = "devnet" -anvil_chains = ["mock_l1", "mock_l2"] - -multi_iliad_evms = true -prometheus = true - -[node.validator01] -[node.validator02] diff --git a/e2e/manifests/devnet2.toml b/e2e/manifests/devnet2.toml deleted file mode 100644 index 664256ba..00000000 --- a/e2e/manifests/devnet2.toml +++ /dev/null @@ -1,9 +0,0 @@ -# Devnet2 is the smallest devnet possible. It only a single validator. -network = "devnet" -anvil_chains = ["mock_l1", "mock_l2"] - -multi_iliad_evms = true -prometheus = true - -[node.validator01] -[node.validator02] diff --git a/e2e/manifests/mainnet.toml b/e2e/manifests/mainnet.toml deleted file mode 100644 index 230dfae8..00000000 --- a/e2e/manifests/mainnet.toml +++ /dev/null @@ -1,4 +0,0 @@ -network = "mainnet" -public_chains = ["ethereum"] - -prometheus = true diff --git a/e2e/manifests/simple.toml b/e2e/manifests/simple.toml deleted file mode 100644 index df85f16c..00000000 --- a/e2e/manifests/simple.toml +++ /dev/null @@ -1,7 +0,0 @@ -network = "devnet" -anvil_chains = ["mock_l2", "mock_l1"] - -[node.validator01] -[node.validator02] -[node.validator03] -[node.validator04] diff --git a/e2e/manifests/staging.toml b/e2e/manifests/staging.toml deleted file mode 100644 index dbd54106..00000000 --- a/e2e/manifests/staging.toml +++ /dev/null @@ -1,22 +0,0 @@ -network = "staging" -public_chains = ["op_sepolia"] -anvil_chains = ["slow_l1"] -multi_iliad_evms = true -prometheus = true - -[node.validator01] -[node.validator02] - -[node.seed01] -mode = "seed" - -[node.fullnode01] -mode = "archive" - -# Testing long-lived keys on staging while testnet doens't exist. Remove this. -[keys.validator01] -validator = "0xD6CD71dF91a6886f69761826A9C4D123178A8d9D" -p2p_consensus = "324F0A865DB7EAFBF53A387FC74E58169EDFA6C4" - -[keys.validator01_evm] -p2p_execution = "0x86e640Ac9c8c693BBFFd2E31f91522b95c1274BF" diff --git a/e2e/manifests/static.go b/e2e/manifests/static.go deleted file mode 100644 index 86d713b3..00000000 --- a/e2e/manifests/static.go +++ /dev/null @@ -1,23 +0,0 @@ -package manifests - -import ( - _ "embed" -) - -var ( - //go:embed devnet0.toml - devnet0 []byte - - //go:embed testnet.toml - testnet []byte -) - -// Devnet0 returns the devnet0.toml manifest bytes. -func Devnet0() []byte { - return devnet0 -} - -// Testnet returns the testnet.toml manifest bytes. -func Testnet() []byte { - return testnet -} diff --git a/e2e/manifests/testnet.toml b/e2e/manifests/testnet.toml deleted file mode 100644 index 54c8d44d..00000000 --- a/e2e/manifests/testnet.toml +++ /dev/null @@ -1,54 +0,0 @@ -network = "testnet" -public_chains = ["holesky","op_sepolia","arb_sepolia"] - -multi_iliad_evms = true -prometheus = true - -[node.validator01] -[node.validator02] -[node.validator03] -[node.validator04] - -[node.seed01] -mode = "seed" -[node.seed02] -mode = "seed" - -[node.fullnode01] -mode = "full" -[node.fullnode02] -mode = "full" -[node.fullnode03] -mode = "full" -[node.fullnode04] -mode = "full" -[node.fullnode05] -mode = "full" - -[node.archive01] -mode = "archive" - - -[keys.validator01] -validator = "0x5e7B5b243824E15796c33a8829F7807F2C5EeDc0" - -[keys.validator02] -validator = "0xD10A6A65C5586aF6093A4F993C3d609758c2a32b" - -[keys.validator03] -validator = "0xf64deF685fF01761D7b5D58452016D0dC9fa43eF" - -[keys.validator04] -validator = "0x34E0afB2FC3f43D469d9C07436c1A885A0979922" - -[keys.seed01] -p2p_consensus = "9644D22FCC9D3966CFF5789E6C411FDD40B101ED" - -[keys.seed01_evm] -p2p_execution = "0xA49cdBbFfF2fD0a3bD5a5780b5a4eB717A3D2c00" - -[keys.seed02] -p2p_consensus = "A839711D734491E1BA99A69E3FEEEBB965223792" - -[keys.seed02_evm] -p2p_execution = "0xf6d3676305dBf0Ba4BC97ad17833792dE6dC4AB7" diff --git a/e2e/run-multiple.sh b/e2e/run-multiple.sh deleted file mode 100755 index 5cf1091c..00000000 --- a/e2e/run-multiple.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -# -# This is a convenience script that takes a list of testnet manifests -# as arguments and runs each one of them sequentially. If a testnet -# fails, the container logs are dumped to stdout along with the testnet -# manifest, but the remaining testnets are still run. -# -# This is mostly used to run generated networks in nightly CI jobs. -# - -set -euo pipefail - -if [[ $# == 0 ]]; then - echo "Usage: $0 [MANIFEST...]" >&2 - exit 1 -fi - -echo "🌊==> Running e2e tests:" "$@" - -for MANIFEST in "$@"; do - START=$SECONDS - echo "🌊==> Running manifest: $MANIFEST" - - if ! e2e -f "$MANIFEST"; then - echo "🌊==> ❌ Testnet $MANIFEST failed, dumping manifest..." - cat "$MANIFEST" - - echo "🌊==> Dumping failed container logs to failed-logs.txt..." - e2e -f "$MANIFEST" logs > failed-logs.txt - - echo "🌊==> Displaying failed container error and warn logs..." - grep -iE "(panic|erro|warn)" failed-logs.txt || echo "No errors or warns found" - - echo "🌊==> Cleaning up failed manifest $MANIFEST..." - e2e -f "$MANIFEST" clean - - echo "🌊==> ❌ Manifest $MANIFEST failed..." - exit 1 - fi - - echo "🌊==> ✅ Completed manifest $MANIFEST in $(( SECONDS - START ))s" - echo "" -done - -echo "🌊==> 🎉 All manifests successful " diff --git a/e2e/test/app_test.go b/e2e/test/app_test.go deleted file mode 100644 index 79f2df97..00000000 --- a/e2e/test/app_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package e2e_test - -import ( - "context" - "encoding/hex" - "testing" - "time" - - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/lib/netconf" -) - -// Tests that the app hash (as reported by the app) matches the last -// block and the node sync status. -func TestApp_Hash(t *testing.T) { - t.Parallel() - ctx := context.Background() - testNode(t, func(t *testing.T, _ netconf.Network, node *e2e.Node) { - t.Helper() - client, err := node.Client() - require.NoError(t, err) - - info, err := client.ABCIInfo(ctx) - require.NoError(t, err) - require.NotEmpty(t, info.Response.LastBlockAppHash, "expected app to return app hash") - - // In next-block execution, the app hash is stored in the next block - requestedHeight := info.Response.LastBlockHeight + 1 - - require.Eventually(t, func() bool { - status, err := client.Status(ctx) - require.NoError(t, err) - require.NotZero(t, status.SyncInfo.LatestBlockHeight) - - return status.SyncInfo.LatestBlockHeight >= requestedHeight - }, 5*time.Second, 500*time.Millisecond) - - block, err := client.Block(ctx, &requestedHeight) - require.NoError(t, err) - require.Equal(t, - hex.EncodeToString(info.Response.LastBlockAppHash), - hex.EncodeToString(block.Block.AppHash.Bytes()), - "app hash does not match last block's app hash") - }) -} diff --git a/e2e/test/block_test.go b/e2e/test/block_test.go deleted file mode 100644 index 4346f903..00000000 --- a/e2e/test/block_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package e2e_test - -import ( - "context" - "testing" - - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/lib/netconf" -) - -// Tests that block headers are identical across nodes where present. -func TestBlock_Header(t *testing.T) { - t.Parallel() - ctx := context.Background() - blocks := fetchBlockChain(ctx, t) - - testNode(t, func(t *testing.T, _ netconf.Network, node *e2e.Node) { - t.Helper() - if node.Mode == e2e.ModeSeed { - return - } - - client, err := node.Client() - require.NoError(t, err) - status, err := client.Status(ctx) - require.NoError(t, err) - - first := status.SyncInfo.EarliestBlockHeight - last := status.SyncInfo.LatestBlockHeight - if node.RetainBlocks > 0 { - first++ // avoid race conditions with block pruning - } - - for _, block := range blocks { - if block.Header.Height < first { - continue - } - if block.Header.Height > last { - break - } - resp, err := client.Block(ctx, &block.Header.Height) - require.NoError(t, err) - - require.Equal(t, block, resp.Block, - "block mismatch for height %d", block.Header.Height) - - require.NoError(t, resp.Block.ValidateBasic(), - "block at height %d is invalid", block.Header.Height) - } - }) -} - -// Tests that the node contains the expected block range. -func TestBlock_Range(t *testing.T) { - t.Parallel() - ctx := context.Background() - - testNode(t, func(t *testing.T, _ netconf.Network, node *e2e.Node) { - t.Helper() - if node.Mode == e2e.ModeSeed { - return - } - - client, err := node.Client() - require.NoError(t, err) - status, err := client.Status(ctx) - require.NoError(t, err) - - first := status.SyncInfo.EarliestBlockHeight - last := status.SyncInfo.LatestBlockHeight - - switch { - case node.StateSync: - assert.Greater(t, first, node.Testnet.InitialHeight, - "state synced nodes should not contain network's initial height") - - case node.RetainBlocks > 0 && int64(node.RetainBlocks) < (last-node.Testnet.InitialHeight+1): - // Delta handles race conditions in reading first/last heights. - assert.InDelta(t, node.RetainBlocks, last-first+1, 1, - "node not pruning expected blocks") - - default: - assert.Equal(t, node.Testnet.InitialHeight, first, - "node's first block should be network's initial height") - } - - for h := first; h <= last; h++ { - resp, err := client.Block(ctx, &(h)) - if err != nil && node.RetainBlocks > 0 && h == first { - // Ignore errors in first block if node is pruning blocks due to race conditions. - continue - } - require.NoError(t, ctx.Err(), "Timeout fetching block range: %d", h) - require.NoError(t, err) - assert.Equal(t, h, resp.Block.Height) - } - - for h := node.Testnet.InitialHeight; h < first; h++ { - _, err := client.Block(ctx, &(h)) - require.NoError(t, ctx.Err(), "Timeout fetching initial range: %d", h) - require.Error(t, err) - } - }) -} diff --git a/e2e/test/e2e_test.go b/e2e/test/e2e_test.go deleted file mode 100644 index b396880a..00000000 --- a/e2e/test/e2e_test.go +++ /dev/null @@ -1,233 +0,0 @@ -package e2e_test - -import ( - "context" - "encoding/json" - "os" - "path/filepath" - "sync" - "testing" - - rpchttp "github.com/cometbft/cometbft/rpc/client/http" - rpctypes "github.com/cometbft/cometbft/rpc/core/types" - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - cmttypes "github.com/cometbft/cometbft/types" - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/e2e/app" - "github.com/piplabs/story/e2e/docker" - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/e2e/vmcompose" - "github.com/piplabs/story/lib/ethclient" - "github.com/piplabs/story/lib/log" - "github.com/piplabs/story/lib/netconf" -) - -//nolint:gochecknoglobals // This was copied from cometbft/test/e2e/test/e2e_test.go -var ( - endpointsCache = map[string]map[string]string{} - networkCache = map[string]netconf.Network{} - testnetCache = map[string]types.Testnet{} - testnetCacheMtx = sync.Mutex{} - blocksCache = map[string][]*cmttypes.Block{} - blocksCacheMtx = sync.Mutex{} -) - -type testFunc struct { - TestNode func(*testing.T, netconf.Network, *e2e.Node) - TestIliadEVM func(*testing.T, ethclient.Client) - TestNetwork func(*testing.T, netconf.Network) -} - -func testNode(t *testing.T, fn func(*testing.T, netconf.Network, *e2e.Node)) { - t.Helper() - test(t, testFunc{TestNode: fn}) -} - -func testIliadEVM(t *testing.T, fn func(*testing.T, ethclient.Client)) { - t.Helper() - test(t, testFunc{TestIliadEVM: fn}) -} - -// test runs tests for testnet nodes. The callback functions are respectively given a -// single node to test, running as a subtest in parallel with other subtests. -// -// The testnet manifest must be given as the envvar E2E_MANIFEST. If not set, -// these tests are skipped so that they're not picked up during normal unit -// test runs. If E2E_NODE is also set, only the specified node is tested, -// otherwise all nodes are tested. -func test(t *testing.T, testFunc testFunc) { - t.Helper() - - testnet, network, endpoints := loadEnv(t) - nodes := testnet.Nodes - - if name := os.Getenv(app.EnvE2ENode); name != "" { - node := testnet.LookupNode(name) - require.NotNil(t, node, "node %q not found in testnet %q", name, testnet.Name) - nodes = []*e2e.Node{node} - } - - log.Info(context.Background(), "Running tests for testnet", - "testnet", testnet.Name, - "nodes", len(nodes), - ) - for _, node := range nodes { - if node.Stateless() { - continue - } else if testFunc.TestNode == nil { - continue - } - - t.Run(node.Name, func(t *testing.T) { - t.Parallel() - testFunc.TestNode(t, network, node) - }) - } - - if testFunc.TestIliadEVM != nil { - for _, chain := range network.Chains { - if !netconf.IsIliadExecution(network.ID, chain.ID) { - continue - } - - rpc, found := endpoints[chain.Name] - require.True(t, found) - - client, err := ethclient.Dial(chain.Name, rpc) - require.NoError(t, err) - - t.Run(chain.Name, func(t *testing.T) { - t.Parallel() - testFunc.TestIliadEVM(t, client) - }) - } - } - - if testFunc.TestNetwork != nil { - t.Run("network", func(t *testing.T) { - t.Parallel() - testFunc.TestNetwork(t, network) - }) - } -} - -// loadEnv loads the testnet and network based on env vars. -// - -func loadEnv(t *testing.T) (types.Testnet, netconf.Network, map[string]string) { - t.Helper() - - manifestFile := os.Getenv(app.EnvE2EManifest) - if manifestFile == "" { - t.Skip(app.EnvE2EManifest + " not set, not an end-to-end test run") - } - if !filepath.IsAbs(manifestFile) { - require.Fail(t, app.EnvE2EManifest+" must be an absolute path", "got", manifestFile) - } - - ifdType := os.Getenv(app.EnvInfraType) - ifdFile := os.Getenv(app.EnvInfraFile) - if ifdType != docker.ProviderName && ifdFile == "" { - require.Fail(t, app.EnvInfraFile+" not set while INFRASTRUCTURE_TYPE="+ifdType) - } else if ifdType != docker.ProviderName && !filepath.IsAbs(ifdFile) { - require.Fail(t, app.EnvInfraFile+" must be an absolute path", "got", ifdFile) - } - - testnetCacheMtx.Lock() - defer testnetCacheMtx.Unlock() - if testnet, ok := testnetCache[manifestFile]; ok { - return testnet, networkCache[manifestFile], endpointsCache[manifestFile] - } - m, err := app.LoadManifest(manifestFile) - require.NoError(t, err) - - var ifd types.InfrastructureData - switch ifdType { - case docker.ProviderName: - ifd, err = docker.NewInfraData(m) - case vmcompose.ProviderName: - ifd, err = vmcompose.LoadData(ifdFile) - default: - require.Fail(t, "unsupported infrastructure type", ifdType) - } - require.NoError(t, err) - - cfg := app.DefinitionConfig{ - ManifestFile: manifestFile, - } - testnet, err := app.TestnetFromManifest(context.Background(), m, ifd, cfg) - require.NoError(t, err) - testnetCache[manifestFile] = testnet - - endpointsFile := os.Getenv(app.EnvE2ERPCEndpoints) - if endpointsFile == "" { - t.Fatalf(app.EnvE2ERPCEndpoints + " not set") - } - bz, err := os.ReadFile(endpointsFile) - require.NoError(t, err) - - endpoints := map[string]string{} - require.NoError(t, json.Unmarshal(bz, &endpoints)) - endpointsCache[manifestFile] = endpoints - - network := netconf.Network{ - ID: testnet.Network, - Chains: []netconf.Chain{}, - } - - return testnet, network, endpoints -} - -// fetchBlockChain fetches a complete, up-to-date block history from -// the freshest testnet archive node. -func fetchBlockChain(ctx context.Context, t *testing.T) []*cmttypes.Block { - t.Helper() - - testnet, _, _ := loadEnv(t) - - // Find the freshest archive node - var ( - client *rpchttp.HTTP - status *rpctypes.ResultStatus - ) - for _, node := range testnet.ArchiveNodes() { - c, err := node.Client() - require.NoError(t, err) - s, err := c.Status(ctx) - require.NoError(t, err) - if status == nil || s.SyncInfo.LatestBlockHeight > status.SyncInfo.LatestBlockHeight { - client = c - status = s - } - } - require.NotNil(t, client, "couldn't find an archive node") - - // Fetch blocks. Look for existing block history in the block cache, and - // extend it with any new blocks that have been produced. - blocksCacheMtx.Lock() - defer blocksCacheMtx.Unlock() - - from := status.SyncInfo.EarliestBlockHeight - to := status.SyncInfo.LatestBlockHeight - blocks, ok := blocksCache[testnet.Name] - if !ok { - blocks = make([]*cmttypes.Block, 0, to-from+1) - } - if len(blocks) > 0 { - from = blocks[len(blocks)-1].Height + 1 - } - - for h := from; h <= to; h++ { - resp, err := client.Block(ctx, &(h)) - require.NoError(t, ctx.Err(), "Timeout fetching all blocks: %d of %d", h, to) - require.NoError(t, err) - require.NotNil(t, resp.Block) - require.Equal(t, h, resp.Block.Height, "unexpected block height %v", resp.Block.Height) - blocks = append(blocks, resp.Block) - } - require.NotEmpty(t, blocks, "blockchain does not contain any blocks") - blocksCache[testnet.Name] = blocks - - return blocks -} diff --git a/e2e/test/geth_test.go b/e2e/test/geth_test.go deleted file mode 100644 index 96c6adb1..00000000 --- a/e2e/test/geth_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package e2e_test - -import ( - "context" - "math/big" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/e2e/app/geth" - "github.com/piplabs/story/lib/ethclient" -) - -// TestGethConfig ensure that the geth config is setup correctly. -func TestGethConfig(t *testing.T) { - t.Parallel() - testIliadEVM(t, func(t *testing.T, client ethclient.Client) { - t.Helper() - ctx := context.Background() - - cfg := geth.MakeGethConfig(geth.Config{}) - - block, err := client.BlockByNumber(ctx, big.NewInt(1)) - require.NoError(t, err) - - require.EqualValues(t, int(cfg.Eth.Miner.GasCeil), int(block.GasLimit())) - require.Equal(t, big.NewInt(0), block.Difficulty()) - }) -} diff --git a/e2e/types/chain.go b/e2e/types/chain.go deleted file mode 100644 index 5f4a0612..00000000 --- a/e2e/types/chain.go +++ /dev/null @@ -1,94 +0,0 @@ -package types - -import ( - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/evmchain" - "github.com/piplabs/story/lib/netconf" -) - -//nolint:gochecknoglobals // Static mappings -var ( - chainEthereum = EVMChain{ - Metadata: mustMetadata(evmchain.IDEthereum), - IsPublic: true, - } - - chainHolesky = EVMChain{ - Metadata: mustMetadata(evmchain.IDHolesky), - IsPublic: true, - } - - chainArbSepolia = EVMChain{ - Metadata: mustMetadata(evmchain.IDArbSepolia), - IsPublic: true, - } - - chainOpSepolia = EVMChain{ - Metadata: mustMetadata(evmchain.IDOpSepolia), - IsPublic: true, - } -) - -// IliadEVMByNetwork returns the Iliad evm chain definition by netconf network. -func IliadEVMByNetwork(network netconf.ID) EVMChain { - return EVMChain{ - Metadata: mustMetadata(network.Static().IliadExecutionChainID), - } -} - -// AnvilChainsByNames returns the Anvil evm chain definitions by names. -func AnvilChainsByNames(names []string) ([]EVMChain, error) { - var chains []EVMChain - for _, name := range names { - meta, ok := evmchain.MetadataByName(name) - if !ok { - return nil, errors.New("unknown anvil chain", "name", name) - } - chains = append(chains, EVMChain{ - Metadata: meta, - }) - } - - return chains, nil -} - -// PublicChainByName returns the public chain definition by name. -func PublicChainByName(name string) (EVMChain, error) { - switch name { - case chainHolesky.Name: - return chainHolesky, nil - case chainArbSepolia.Name: - return chainArbSepolia, nil - case chainOpSepolia.Name: - return chainOpSepolia, nil - case chainEthereum.Name: - return chainEthereum, nil - default: - return EVMChain{}, errors.New("unknown chain name") - } -} - -// PublicRPCByName returns the public chain RPC address by name. -func PublicRPCByName(name string) string { - switch name { - case chainHolesky.Name: - return "https://ethereum-holesky-rpc.publicnode.com" - case chainArbSepolia.Name: - return "https://sepolia-rollup.arbitrum.io/rpc" - case chainOpSepolia.Name: - return "https://sepolia.optimism.io" - case chainEthereum.Name: - return "https://ethereum-rpc.publicnode.com" - default: - return "" - } -} - -func mustMetadata(chainID uint64) evmchain.Metadata { - meta, ok := evmchain.MetadataByID(chainID) - if !ok { - panic("unknown chain ID") - } - - return meta -} diff --git a/e2e/types/infra.go b/e2e/types/infra.go deleted file mode 100644 index dbef198d..00000000 --- a/e2e/types/infra.go +++ /dev/null @@ -1,53 +0,0 @@ -package types - -import ( - "context" - - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - "github.com/cometbft/cometbft/test/e2e/pkg/infra" -) - -func DefaultUpgradeConfig() UpgradeConfig { - return UpgradeConfig{ - ServiceRegexp: ".*", - } -} - -type UpgradeConfig struct { - ServiceRegexp string -} - -type InfraProvider interface { - infra.Provider - - Upgrade(ctx context.Context, cfg UpgradeConfig) error - - // Clean deletes all containers, networks, and data on disk. - Clean(ctx context.Context) error -} - -// InfrastructureData wraps e2e.InfrastructureData with additional iliad-specific fields. -type InfrastructureData struct { - e2e.InfrastructureData - - // VMs maps the VM name to its instance data. - // Note this differs from e2e.InfrastructureData.Instances, which maps the service names to its instance data. - VMs map[string]e2e.InstanceData -} - -// ServicesByInstance returns the set of services associated to the instance. -func (d InfrastructureData) ServicesByInstance(data e2e.InstanceData) map[string]bool { - resp := make(map[string]bool) - for serviceName, instance := range d.Instances { - if instancesEqual(data, instance) { - resp[serviceName] = true - } - } - - return resp -} - -// instancesEqual returns true if the two instances are equal, as identified by IPs. -func instancesEqual(a, b e2e.InstanceData) bool { - return a.IPAddress.Equal(b.IPAddress) && a.ExtIPAddress.Equal(b.ExtIPAddress) -} diff --git a/e2e/types/manifest.go b/e2e/types/manifest.go deleted file mode 100644 index 8074b5c6..00000000 --- a/e2e/types/manifest.go +++ /dev/null @@ -1,112 +0,0 @@ -package types - -import ( - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - - "github.com/piplabs/story/e2e/app/key" - "github.com/piplabs/story/lib/netconf" -) - -// Mode defines the iliad consensus node mode. -// Nodes are in general full nodes (light nodes are not supported yet). -// In some cases, additional roles are defined: validator, archive, seed. -// -// Note that the execution clients only have two modes: "default" and "archive". -// -// e2e.Mode is extended so ModeArchive can be added transparently. -type Mode = e2e.Mode - -const ( - // ModeValidator defines a validator node. - // It's validator key has staked tokens and it actively participates in consensus and is subject to rewards and penalties. - // It must always be online, otherwise it will get stashed/jailed. - // [genesis_validator_set=true,pruning=default,consensus=default,special_p2p=false]. - // Note technically a validator node is also a "full node". - ModeValidator = e2e.ModeValidator - - // ModeArchive defines an archive node. - // It stores all historical blocks and state, it doesn't delete anything ever. It will require TBs of disk. - // [genesis_validator_set=false,pruning=none,consensus=default,special_p2p=false]. - // Note technically an archive node is also a "full node". - ModeArchive Mode = "archive" - - // ModeSeed defines a seed node. It must have a long-lived p2p pubkey and address (encoded in repo). - // It acts as notice board for external nodes to learn about the network and connect to publicly available nodes. - // It crawls the network regularly, making it available to new nodes. - // [genesis_validator_set=false,pruning=default,consensus=default,special_p2p=true]. - // Note technically a seed node is also a "full node". - ModeSeed = e2e.ModeSeed - - // ModeFull defines a full node. A full node a normal node without a special role. - // [genesis_validator_set=false,pruning=default,consensus=default,special_p2p=false]. - ModeFull = e2e.ModeFull - - // ModeLight defines a light node. This isn't used yet. - // [genesis_validator_set=false,pruning=no_data,consensus=light,special_p2p=false] - // Only light nodes are not also full nodes. - ModeLight = e2e.ModeLight -) - -// Perturb defines non-cometBFT perturbations of components like iliad_evm. -type Perturb string - -const ( - // PerturbRestart defines a perturbation that restarts a docker container. - PerturbRestart Perturb = "restart" - // PerturbStopStart defines a perturbation that stops and then starts a docker container. - PerturbStopStart Perturb = "stopstart" -) - -// Manifest wraps e2e.Manifest with additional iliad-specific fields. -// - -type Manifest struct { - e2e.Manifest - - Network netconf.ID `toml:"network"` - - // AnvilChains defines the anvil chains to deploy; mock_l1, mock_l2, etc. - AnvilChains []string `toml:"anvil_chains"` - - // PublicChains defines the public chains to connect to; arb_sepolia, etc. - PublicChains []string `toml:"public_chains"` - - // MultiIliadEVMs defines whether to deploy one or multiple Iliad EVMs. - MultiIliadEVMs bool `toml:"multi_iliad_evms"` - - // Keys contains long-lived private keys (address by type) by node name. - Keys map[string]map[key.Type]string `toml:"keys"` - - // Perturb defines additional (non-cometBFT) perturbations by service name. - Perturb map[string][]Perturb `json:"perturb"` -} - -// Seeds returns a map of seed nodes by name. -func (m Manifest) Seeds() map[string]bool { - resp := make(map[string]bool) - for name, node := range m.Nodes { - if Mode(node.Mode) == ModeSeed { - resp[name] = true - } - } - - return resp -} - -// IliadEVMs returns a map of iliad evm instances names by to deploy. -// If only a single Iliad EVM is to be deployed, the name is "iliad_evm". -// Otherwise, the names are "_evm". -func (m Manifest) IliadEVMs() map[string]bool { - if !m.MultiIliadEVMs { - return map[string]bool{ - "iliad_evm": false, - } - } - - resp := make(map[string]bool) - for name, node := range m.Nodes { - resp[name+"_evm"] = Mode(node.Mode) == ModeArchive - } - - return resp -} diff --git a/e2e/types/testnet.go b/e2e/types/testnet.go deleted file mode 100644 index a9b14d45..00000000 --- a/e2e/types/testnet.go +++ /dev/null @@ -1,181 +0,0 @@ -package types - -import ( - "crypto/ecdsa" - "crypto/rand" - "encoding/hex" - "math/big" - "net" - "strings" - "sync/atomic" - - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/p2p/enode" - - "github.com/piplabs/story/lib/evmchain" - "github.com/piplabs/story/lib/netconf" -) - -// Testnet wraps e2e.Testnet with additional iliad-specific fields. -type Testnet struct { - *e2e.Testnet - Network netconf.ID - IliadEVMs []IliadEVM - AnvilChains []AnvilChain - PublicChains []PublicChain - Perturb map[string][]Perturb -} - -// RandomIliadAddr returns a random iliad address for cometBFT rpc clients. -// It uses the internal IP address of a random node that isn't delayed or a seed. -func (t Testnet) RandomIliadAddr() string { - var eligible []string - for _, node := range t.Nodes { - if node.StartAt != 0 || node.Mode == ModeSeed { - continue // Skip delayed nodes or seed nodes - } - - eligible = append(eligible, node.AddressRPC()) - } - - if len(eligible) == 0 { - return "" - } - - randIdx, err := rand.Int(rand.Reader, big.NewInt(int64(len(eligible)))) - if err != nil { - return "" - } - - return eligible[randIdx.Uint64()] -} - -// BroadcastIliadEVM returns a Iliad EVM to use for e2e app tx broadcasts. -// It prefers a validator nodes since we have an issue with mempool+p2p+startup where -// txs get stuck in non-validator mempool immediately after startup if not connected to peers yet. -// Also avoid validators that are not started immediately. -func (t Testnet) BroadcastIliadEVM() IliadEVM { - isDelayed := func(evm string) bool { - for _, node := range t.Nodes { - if node.StartAt > 0 && strings.Contains(evm, node.Name) { - return true - } - } - - return false - } - - for _, evm := range t.IliadEVMs { - if strings.Contains(evm.InstanceName, "validator") && !isDelayed(evm.InstanceName) { - return evm - } - } - - return t.IliadEVMs[0] -} - -// BroadcastNode returns a iliad node to use for RPC queries broadcasts. -// It prefers a validator nodes since we have an issue with mempool+p2p+startup where -// txs get stuck in non-validator mempool immediately after startup if not connected to peers yet. -// Also avoid validators that are not started immediately. -func (t Testnet) BroadcastNode() *e2e.Node { - for _, node := range t.Nodes { - if !strings.Contains(node.Name, "validator") { - continue - } - if node.StartAt > 0 { - continue - } - - return node - } - - return t.Nodes[0] -} - -// HasPerturbations returns whether the network has any perturbations. -func (t Testnet) HasPerturbations() bool { - if len(t.Perturb) > 0 { - return true - } - - return t.Testnet.HasPerturbations() -} - -func (t Testnet) HasIliadEVM() bool { - return len(t.IliadEVMs) > 0 -} - -// EVMChain represents a EVM chain in a iliad network. -type EVMChain struct { - evmchain.Metadata - IsPublic bool -} - -// IliadEVM represents a iliad evm instance in a iliad network. Similar to e2e.Node for iliad instances. -type IliadEVM struct { - Chain EVMChain // For netconf (all instances must have the same chain) - InstanceName string // For docker container name - AdvertisedIP net.IP // For setting up NAT on geth bootnode - ProxyPort uint32 // For binding - InternalRPC string // For JSON-RPC queries from client - ExternalRPC string // For JSON-RPC queries from e2e app. - IsArchive bool // Whether this instance is in archive mode - JWTSecret string // JWT secret for authentication - - // P2P networking - NodeKey *ecdsa.PrivateKey // Private key - Enode *enode.Node // Public key - Peers []*enode.Node // Peer public keys -} - -// NodeKeyHex returns the hex-encoded node key. Used for geth's config. -func (o IliadEVM) NodeKeyHex() string { - return hex.EncodeToString(crypto.FromECDSA(o.NodeKey)) -} - -// AnvilChain represents an anvil chain instance in a iliad network. -type AnvilChain struct { - Chain EVMChain // For netconf - InternalIP net.IP // For docker container IP - ProxyPort uint32 // For binding - InternalRPC string // For JSON-RPC queries from client - ExternalRPC string // For JSON-RPC queries from e2e app. - LoadState string // File path to load anvil state from -} - -// PublicChain represents a public chain in a iliad network. -type PublicChain struct { - chain EVMChain // For netconf - rpcAddresses []string // For JSON-RPC queries from client/e2e app. - next *atomic.Int32 // For round-robin RPC address selection -} - -func NewPublicChain(chain EVMChain, rpcAddresses []string) PublicChain { - return PublicChain{ - chain: chain, - rpcAddresses: rpcAddresses, - next: new(atomic.Int32), - } -} - -// Chain returns the EVM chain. -func (c PublicChain) Chain() EVMChain { - return c.chain -} - -// NextRPCAddress returns the next RPC address in the list. -func (c PublicChain) NextRPCAddress() string { - i := c.next.Load() - defer func() { - c.next.Store(i + 1) - }() - - l := len(c.rpcAddresses) - if l == 0 { - return "" - } - - return strings.TrimSpace(c.rpcAddresses[int(i)%l]) -} diff --git a/e2e/types/testnet_test.go b/e2e/types/testnet_test.go deleted file mode 100644 index 531418fb..00000000 --- a/e2e/types/testnet_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package types_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/e2e/types" -) - -func TestNextRPCAddress(t *testing.T) { - t.Parallel() - c := types.NewPublicChain(types.EVMChain{}, []string{"1 ", " 2", "3"}) - - require.Equal(t, "1", c.NextRPCAddress()) - require.Equal(t, "2", c.NextRPCAddress()) - require.Equal(t, "3", c.NextRPCAddress()) - require.Equal(t, "1", c.NextRPCAddress()) -} diff --git a/e2e/vmcompose/data.go b/e2e/vmcompose/data.go deleted file mode 100644 index d0d602e5..00000000 --- a/e2e/vmcompose/data.go +++ /dev/null @@ -1,89 +0,0 @@ -package vmcompose - -import ( - "encoding/json" - "net" - "os" - "regexp" - - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/evmchain" -) - -var iliadEvmRegx = regexp.MustCompile(".*_evm") - -const ( - evmPort = 8545 - iliadPort = 26657 -) - -type vmJSON struct { - Name string `json:"name"` - IP string `json:"ip"` - ExternalIP string `json:"external_ip,omitempty"` -} -type dataJSON struct { - NetworkCIDR string `json:"network_cidr"` - VMs []vmJSON `json:"vms"` - ServicesByVM map[string]string `json:"services_by_vm"` // map[service_name]vm_name -} - -// LoadData returns the vmcompose infrastructure data from the given path. -func LoadData(path string) (types.InfrastructureData, error) { - bz, err := os.ReadFile(path) - if err != nil { - return types.InfrastructureData{}, errors.Wrap(err, "read file") - } - - var data dataJSON - err = json.Unmarshal(bz, &data) - if err != nil { - return types.InfrastructureData{}, errors.Wrap(err, "unmarshal json") - } - - vmsByName := make(map[string]e2e.InstanceData) - for _, vm := range data.VMs { - ip := net.ParseIP(vm.IP) - externalIP := net.ParseIP(vm.ExternalIP) - - vmsByName[vm.Name] = e2e.InstanceData{ - IPAddress: ip, - ExtIPAddress: externalIP, - } - } - - instances := make(map[string]e2e.InstanceData) - for serviceName, vmName := range data.ServicesByVM { - vm, ok := vmsByName[vmName] - if !ok { - return types.InfrastructureData{}, errors.New("vm not found", "name", vmName) - } - - // Default ports, as VMs don't support overlapping ports. - port := iliadPort - if iliadEvmRegx.MatchString(serviceName) { - port = evmPort - } else if _, ok := evmchain.MetadataByName(serviceName); ok { - port = evmPort - } - - instances[serviceName] = e2e.InstanceData{ - IPAddress: vm.IPAddress, - ExtIPAddress: vm.ExtIPAddress, - Port: uint32(port), - } - } - - return types.InfrastructureData{ - InfrastructureData: e2e.InfrastructureData{ - Path: path, - Provider: ProviderName, - Instances: instances, - Network: data.NetworkCIDR, - }, - VMs: vmsByName, - }, nil -} diff --git a/e2e/vmcompose/provider.go b/e2e/vmcompose/provider.go deleted file mode 100644 index 8cb8a7e9..00000000 --- a/e2e/vmcompose/provider.go +++ /dev/null @@ -1,397 +0,0 @@ -package vmcompose - -import ( - "context" - "fmt" - "os" - "os/exec" - "path/filepath" - "regexp" - "strings" - "sync" - - "golang.org/x/sync/errgroup" - - e2e "github.com/cometbft/cometbft/test/e2e/pkg" - - "github.com/piplabs/story/e2e/app/agent" - "github.com/piplabs/story/e2e/docker" - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/evmchain" - "github.com/piplabs/story/lib/log" -) - -const ProviderName = "vmcompose" - -var _ types.InfraProvider = (*Provider)(nil) - -type Provider struct { - Testnet types.Testnet - Data types.InfrastructureData - once sync.Once - iliadTag string -} - -func NewProvider(testnet types.Testnet, data types.InfrastructureData, imgTag string) *Provider { - return &Provider{ - Testnet: testnet, - Data: data, - iliadTag: imgTag, - } -} - -// Setup generates the docker-compose file for each VM IP. -func (p *Provider) Setup() error { - // Group infra services by VM IP - for vmIP, services := range groupByVM(p.Data.Instances) { - // Get all iliad nodes in this VM - var nodes []*e2e.Node - var iliads []string - for _, node := range p.Testnet.Nodes { - if services[node.Name] { - nodes = append(nodes, node) - iliads = append(iliads, node.Name) - } - } - - var geths []string - for _, iliadEVM := range p.Testnet.IliadEVMs { - if services[iliadEVM.InstanceName] { - geths = append(geths, iliadEVM.InstanceName) - } - } - - // Get all iliadEVMs in this VM - var iliadEVMs []types.IliadEVM - for _, iliadEVM := range p.Testnet.IliadEVMs { - if services[iliadEVM.InstanceName] { - iliadEVMs = append(iliadEVMs, iliadEVM) - } - } - - // Get all anvil chains in this VM - var anvilChains []types.AnvilChain - for _, anvilChain := range p.Testnet.AnvilChains { - if services[anvilChain.Chain.Name] { - anvilChains = append(anvilChains, anvilChain) - } - } - - def := docker.ComposeDef{ - Network: false, - BindAll: true, - NetworkName: p.Testnet.Name, - NetworkCIDR: p.Testnet.IP.String(), - Nodes: nodes, - IliadEVMs: iliadEVMs, - Anvils: anvilChains, - Prometheus: p.Testnet.Prometheus, - IliadTag: p.iliadTag, - } - compose, err := docker.GenerateComposeFile(def) - if err != nil { - return errors.Wrap(err, "generate compose file") - } - - err = os.WriteFile(filepath.Join(p.Testnet.Dir, vmComposeFile(vmIP)), compose, 0o644) - if err != nil { - return errors.Wrap(err, "write compose file") - } - - if !p.Testnet.Prometheus { - continue // No need to generate prometheus config - } - - // Update custom prometheus.yml config for this VM - promCfgFile := filepath.Join(p.Testnet.Dir, "prometheus", "prometheus.yml") - agentCfg, err := os.ReadFile(promCfgFile) - if err != nil { - return errors.Wrap(err, "read prometheus config") - } - - hostname := vmIP // TODO: Add hostnames to infra instances. - agentCfg = agent.ConfigForHost(agentCfg, hostname, iliads, geths) - err = os.WriteFile(filepath.Join(p.Testnet.Dir, vmAgentFile(vmIP)), agentCfg, 0o644) - if err != nil { - return errors.Wrap(err, "write compose file") - } - } - - return nil -} - -// Upgrade copies some of the local e2e generated artifacts to the VMs and starts the docker-compose services. -func (p *Provider) Upgrade(ctx context.Context, cfg types.UpgradeConfig) error { - log.Info(ctx, "Upgrading docker-compose on VMs", "image", p.Testnet.UpgradeVersion) - - filesByService := make(map[string][]string) - addFile := func(service string, paths ...string) { - filesByService[service] = append(filesByService[service], filepath.Join(paths...)) - } - - // Include iliad config - for _, node := range p.Testnet.Nodes { - addFile(node.Name, "config", "iliad.toml") - addFile(node.Name, "config", "config.toml") - addFile(node.Name, "config", "jwtsecret") - addFile(node.Name, "config", "priv_validator_key.json") - addFile(node.Name, "config", "node_key.json") - } - - // Include geth config - for _, iliadEVM := range p.Testnet.IliadEVMs { - addFile(iliadEVM.InstanceName, "config.toml") - addFile(iliadEVM.InstanceName, "geth", "jwtsecret") - addFile(iliadEVM.InstanceName, "geth", "nodekey") - } - - addFile("prometheus", "prometheus.yml") // Prometheus isn't a "service", so not actually copied - - // Do initial sequential ssh to each VM, ensure we can connect. - for vmName, instance := range p.Data.VMs { - if !matchAny(cfg, p.Data.ServicesByInstance(instance)) { - log.Debug(ctx, "Skipping vm upgrade, no matching services", "vm", vmName, "regexp", cfg.ServiceRegexp) - continue - } - - log.Debug(ctx, "Ensuring VM SSH connection", "vm", vmName) - if err := execOnVM(ctx, vmName, "ls"); err != nil { - return errors.Wrap(err, "test exec on vm", "vm", vmName) - } - } - - // Then upgrade VMs in parallel - eg, ctx := errgroup.WithContext(ctx) - for vmName, instance := range p.Data.VMs { - services := p.Data.ServicesByInstance(instance) - if !matchAny(cfg, services) { - continue - } - - eg.Go(func() error { - log.Debug(ctx, "Copying artifacts", "vm", vmName, "count", len(filesByService)) - for service, filePaths := range filesByService { - if !services[service] { - continue - } - for _, filePath := range filePaths { - localPath := filepath.Join(p.Testnet.Dir, service, filePath) - remotePath := filepath.Join("/iliad", p.Testnet.Name, service, filePath) - if err := copyFileToVM(ctx, vmName, localPath, remotePath); err != nil { - return errors.Wrap(err, "copy file", "vm", vmName, "service", service, "file", filePath) - } - } - } - - log.Debug(ctx, "Copying docker-compose.yml", "vm", vmName) - composeFile := vmComposeFile(instance.IPAddress.String()) - localComposePath := filepath.Join(p.Testnet.Dir, composeFile) - remoteComposePath := filepath.Join("/iliad", p.Testnet.Name, composeFile) - if err := copyFileToVM(ctx, vmName, localComposePath, remoteComposePath); err != nil { - return errors.Wrap(err, "copy docker compose", "vm", vmName) - } - - // Figure out whether we need to call "docker compose down" before "docker compose up" - var maybeDown string - if services := p.Data.ServicesByInstance(instance); containsEVM(services) { - if containsAnvil(services) { - return errors.New("cannot upgrade VM with both iliad_evm and anvil containers since iliad_evm needs downing and anvil cannot be restarted", "vm", vmName) - } - maybeDown = "sudo docker compose down && " - } - - startCmd := fmt.Sprintf("cd /iliad/%s && "+ - "sudo mv %s docker-compose.yaml && "+ - maybeDown+ - "sudo docker compose up -d", - p.Testnet.Name, composeFile) - - log.Debug(ctx, "Executing docker-compose up", "vm", vmName) - if err := execOnVM(ctx, vmName, startCmd); err != nil { - return errors.Wrap(err, "compose up", "vm", vmName) - } - - return nil - }) - } - - if err := eg.Wait(); err != nil { - return errors.Wrap(err, "wait errgroup") - } - - return nil -} - -// matchAny returns true if the pattern matches any of the services in the services map. -// An empty pattern returns true, matching anything. -func matchAny(cfg types.UpgradeConfig, services map[string]bool) bool { - if cfg.ServiceRegexp == "" { - return true - } - - for service := range services { - matched, _ := regexp.MatchString(cfg.ServiceRegexp, service) - if matched { - return true - } - } - - return false -} - -func (p *Provider) StartNodes(ctx context.Context, _ ...*e2e.Node) error { - var onceErr error - p.once.Do(func() { - log.Info(ctx, "Copying artifacts to VMs") - for vmName := range p.Data.VMs { - err := copyToVM(ctx, vmName, p.Testnet.Dir) - if err != nil { - onceErr = errors.Wrap(err, "copy files", "vm", vmName) - return - } - } - - log.Info(ctx, "Starting VM deployments") - // TODO: Only start additional services and then start iliad as per above StartNodes. - for vmName, instance := range p.Data.VMs { - composeFile := vmComposeFile(instance.IPAddress.String()) - agentFile := vmAgentFile(instance.IPAddress.String()) - startCmd := fmt.Sprintf("cd /iliad/%s && "+ - "sudo mv %s docker-compose.yaml && "+ - "sudo mv %s prometheus/prometheus.yml && "+ - "sudo docker compose up -d", - p.Testnet.Name, composeFile, agentFile) - - err := execOnVM(ctx, vmName, startCmd) - if err != nil { - onceErr = errors.Wrap(err, "compose up", "vm", vmName) - return - } - } - }) - - return onceErr -} - -func (p *Provider) Clean(ctx context.Context) error { - log.Info(ctx, "Deleting existing VM deployments including data") - for vmName := range p.Data.VMs { - for _, cmd := range docker.CleanCmds(true, true) { - err := execOnVM(ctx, vmName, cmd) - if err != nil { - return errors.Wrap(err, "clean docker containers", "vm", vmName) - } - - err = execOnVM(ctx, vmName, "sudo rm -rf /iliad/*") - if err != nil { - return errors.Wrap(err, "clean docker containers", "vm", vmName) - } - } - } - - return nil -} - -func (*Provider) StopTestnet(context.Context) error { - return errors.New("not implemented") -} - -func (p *Provider) GetInfrastructureData() *e2e.InfrastructureData { - return &p.Data.InfrastructureData -} - -func groupByVM(instances map[string]e2e.InstanceData) map[string]map[string]bool { - resp := make(map[string]map[string]bool) // map[vm_ip]map[service_name]true - for serviceName, instance := range instances { - ip := instance.IPAddress.String() - m, ok := resp[ip] - if !ok { - m = make(map[string]bool) - } - m[serviceName] = true - resp[ip] = m - } - - return resp -} - -func execOnVM(ctx context.Context, vmName string, cmd string) error { - ssh := fmt.Sprintf("gcloud compute ssh --zone=us-east1-c %s --quiet -- \"%s\"", vmName, cmd) - - out, err := exec.CommandContext(ctx, "bash", "-c", ssh).CombinedOutput() - if err != nil { - return errors.Wrap(err, "exec on VM", "output", string(out), "cmd", ssh) - } - - return nil -} - -func copyToVM(ctx context.Context, vmName string, path string) error { - tarscp := fmt.Sprintf("tar czf - %s | gcloud compute ssh --zone=us-east1-c %s --quiet -- \"cd /iliad && tar xzf -\"", filepath.Base(path), vmName) - - cmd := exec.CommandContext(ctx, "bash", "-c", tarscp) - cmd.Dir = filepath.Dir(path) - if out, err := cmd.CombinedOutput(); err != nil { - return errors.Wrap(err, "copy to VM", "output", string(out)) - } - - return nil -} - -func copyFileToVM(ctx context.Context, vmName string, localPath string, remotePath string) error { - scp := fmt.Sprintf("gcloud compute scp --zone=us-east1-c --quiet %s %s:%s", localPath, vmName, remotePath) - cmd := exec.CommandContext(ctx, "bash", "-c", scp) - if out, err := cmd.CombinedOutput(); err != nil { - return errors.Wrap(err, "copy to VM", "output", string(out), "cmd", scp) - } - - return nil -} - -func vmAgentFile(internalIP string) string { - return "prometheus/" + strings.ReplaceAll(internalIP, ".", "_") + "_prometheus.yml" -} - -func vmComposeFile(internalIP string) string { - return strings.ReplaceAll(internalIP, ".", "_") + "_compose.yaml" -} - -// containsEVM returns true if the services map contains an iliad evm. -// TODO: This isn't very robust. -func containsEVM(services map[string]bool) bool { - for service, ok := range services { - if !ok { - continue - } - if strings.Contains(service, "evm") { - return true - } - } - - return false -} - -// containsAnvil returns true if the services map contains an anvil chain. -func containsAnvil(services map[string]bool) bool { - anvils := map[uint64]bool{ - evmchain.IDMockL1Fast: true, - evmchain.IDMockL1Slow: true, - evmchain.IDMockL2: true, - } - for service, ok := range services { - if !ok { - continue - } - meta, ok := evmchain.MetadataByName(service) - if !ok { - continue - } else if !anvils[meta.ChainID] { - continue - } - - return true - } - - return false -} diff --git a/e2e/vmcompose/provider_internal_test.go b/e2e/vmcompose/provider_internal_test.go deleted file mode 100644 index f651854f..00000000 --- a/e2e/vmcompose/provider_internal_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package vmcompose - -import ( - "encoding/json" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" -) - -// SetupDataFixtures returns the test fixture filenames of manifest and infrastructure data files. -// This accesses private types, so it's in the same package as the types. -func SetupDataFixtures(t *testing.T) (string, string) { - t.Helper() - - // Write manifest to disk - manifest := ` -network = "devnet" -anvil_chains = ["mock_l1"] -multi_iliad_evms = true - -[node.validator01] -[node.validator02] - -[node.seed01] -mode = "seed" - -[node.fullnode01] -mode = "full" -` - manifestFile := filepath.Join(t.TempDir(), "test.toml") - err := os.WriteFile(manifestFile, []byte(manifest), 0o644) - require.NoError(t, err) - - const vm1, vm2, vm3, vm4, vm5, vm6 = "vm1", "vm2", "vm3", "vm4", "vm5", "vm6" - - dataJSON := dataJSON{ - NetworkCIDR: "127.0.0.1/24", - VMs: []vmJSON{ - {Name: vm1, IP: "127.0.0.1"}, - {Name: vm2, IP: "127.0.0.2"}, - {Name: vm3, IP: "127.0.0.3"}, - {Name: vm4, IP: "127.0.0.4"}, - {Name: vm5, IP: "127.0.0.5"}, - {Name: vm6, IP: "127.0.0.6"}, - }, - ServicesByVM: map[string]string{ - "validator01": vm1, - "validator01_evm": vm1, - - "validator02": vm2, - "validator02_evm": vm2, - - "mock_l1": vm3, - - "seed01": vm4, - "seed01_evm": vm4, - - "fullnode01": vm5, - "fullnode01_evm": vm5, - }, - } - - // Write raw data json to disk - bz, err := json.Marshal(dataJSON) - require.NoError(t, err) - dataFile := filepath.Join(t.TempDir(), "data.json") - err = os.WriteFile(dataFile, bz, 0o644) - require.NoError(t, err) - - return manifestFile, dataFile -} diff --git a/e2e/vmcompose/provider_test.go b/e2e/vmcompose/provider_test.go deleted file mode 100644 index e1f87eb6..00000000 --- a/e2e/vmcompose/provider_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package vmcompose_test - -import ( - "context" - "os" - "path/filepath" - "regexp" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/e2e/app" - "github.com/piplabs/story/e2e/vmcompose" - "github.com/piplabs/story/lib/tutil" -) - -//go:generate go test . -golden -clean - -func TestSetup(t *testing.T) { - t.Parallel() - manifestFile, dataFile := vmcompose.SetupDataFixtures(t) - - def, err := app.MakeDefinition(context.Background(), app.DefinitionConfig{ - ManifestFile: manifestFile, - InfraProvider: vmcompose.ProviderName, - InfraDataFile: dataFile, - IliadImgTag: "7d1ae53", - }, "") - require.NoError(t, err) - - err = os.MkdirAll(def.Testnet.Dir, 0o755) - require.NoError(t, err) - - err = def.Infra.Setup() - require.NoError(t, err) - - files, err := filepath.Glob(filepath.Join(def.Testnet.Dir, "*compose.yaml")) - require.NoError(t, err) - - for _, file := range files { - t.Run(filepath.Base(file), func(t *testing.T) { - t.Parallel() - bz, err := os.ReadFile(file) - require.NoError(t, err) - - // Replace non-deterministic fields with placeholders - - re1 := regexp.MustCompile(`--nodekeyhex=([0-9a-fA-F]+)`) - bz = re1.ReplaceAll(bz, []byte("--nodekeyhex=")) - - re2 := regexp.MustCompile(`enode://([0-9a-fA-F]+)`) - bz = re2.ReplaceAll(bz, []byte("enode://")) - - tutil.RequireGoldenBytes(t, bz) - }) - } -} diff --git a/e2e/vmcompose/testdata/TestSetup_127_0_0_1_compose.yaml.golden b/e2e/vmcompose/testdata/TestSetup_127_0_0_1_compose.yaml.golden deleted file mode 100644 index 3ef432a2..00000000 --- a/e2e/vmcompose/testdata/TestSetup_127_0_0_1_compose.yaml.golden +++ /dev/null @@ -1,73 +0,0 @@ -version: '2.4' -networks: - test: - labels: - e2e: true - driver: bridge - -services: - validator01: - labels: - e2e: true - container_name: validator01 - image: iliadops/iliad:7d1ae53 - init: true - ports: - - 26656:26656 - - 26657:26657 - - 6060 - volumes: - - ./validator01:/iliad - depends_on: - validator01_evm: - condition: service_healthy - networks: - test: - - - - # Use geth as the iliad EVMs. - # Initialises geth files and folder from provided genesis file. - validator01_evm-init: - labels: - e2e: true - container_name: validator01_evm-init - image: "ethereum/client-go:v1.14.5" - command: --state.scheme=path --datadir=/geth init /geth/genesis.json - volumes: - - ./validator01_evm:/geth - networks: - test: - - validator01_evm: - labels: - e2e: true - container_name: validator01_evm - image: "ethereum/client-go:v1.14.5" - command: - - --config=/geth/config.toml - # Flags not available via config.toml - - --nat=extip: - - --pprof - - --pprof.addr=0.0.0.0 - - --metrics - - ports: - - 8551:8551 - - 8545:8545 - - 30303:30303 - - 8546 - - 6060 - depends_on: - validator01_evm-init: - condition: service_completed_successfully - healthcheck: - test: "nc -z localhost 8545" - interval: 1s - retries: 30 - volumes: - - ./validator01_evm:/geth - networks: - test: - - diff --git a/e2e/vmcompose/testdata/TestSetup_127_0_0_2_compose.yaml.golden b/e2e/vmcompose/testdata/TestSetup_127_0_0_2_compose.yaml.golden deleted file mode 100644 index 86642051..00000000 --- a/e2e/vmcompose/testdata/TestSetup_127_0_0_2_compose.yaml.golden +++ /dev/null @@ -1,73 +0,0 @@ -version: '2.4' -networks: - test: - labels: - e2e: true - driver: bridge - -services: - validator02: - labels: - e2e: true - container_name: validator02 - image: iliadops/iliad:7d1ae53 - init: true - ports: - - 26656:26656 - - 26657:26657 - - 6060 - volumes: - - ./validator02:/iliad - depends_on: - validator02_evm: - condition: service_healthy - networks: - test: - - - - # Use geth as the iliad EVMs. - # Initialises geth files and folder from provided genesis file. - validator02_evm-init: - labels: - e2e: true - container_name: validator02_evm-init - image: "ethereum/client-go:v1.14.5" - command: --state.scheme=path --datadir=/geth init /geth/genesis.json - volumes: - - ./validator02_evm:/geth - networks: - test: - - validator02_evm: - labels: - e2e: true - container_name: validator02_evm - image: "ethereum/client-go:v1.14.5" - command: - - --config=/geth/config.toml - # Flags not available via config.toml - - --nat=extip: - - --pprof - - --pprof.addr=0.0.0.0 - - --metrics - - ports: - - 8551:8551 - - 8545:8545 - - 30303:30303 - - 8546 - - 6060 - depends_on: - validator02_evm-init: - condition: service_completed_successfully - healthcheck: - test: "nc -z localhost 8545" - interval: 1s - retries: 30 - volumes: - - ./validator02_evm:/geth - networks: - test: - - diff --git a/e2e/vmcompose/testdata/TestSetup_127_0_0_3_compose.yaml.golden b/e2e/vmcompose/testdata/TestSetup_127_0_0_3_compose.yaml.golden deleted file mode 100644 index bfe4bdc5..00000000 --- a/e2e/vmcompose/testdata/TestSetup_127_0_0_3_compose.yaml.golden +++ /dev/null @@ -1,33 +0,0 @@ -version: '2.4' -networks: - test: - labels: - e2e: true - driver: bridge - -services: - # Initialises geth files and folder from provided genesis file. - mock_l1: - labels: - e2e: true - container_name: mock_l1 - platform: linux/amd64 - image: ghcr.io/foundry-rs/foundry:latest - entrypoint: - - anvil - - --host=0.0.0.0 - - --chain-id=1652 - - --block-time=1 - - --silent - - --load-state=/anvil/state.json - ports: - - 8545:8545 - networks: - test: - - - volumes: - - ./anvil/state.json:/anvil/state.json - - - # Use geth as the iliad EVMs. diff --git a/e2e/vmcompose/testdata/TestSetup_127_0_0_4_compose.yaml.golden b/e2e/vmcompose/testdata/TestSetup_127_0_0_4_compose.yaml.golden deleted file mode 100644 index 01a8684d..00000000 --- a/e2e/vmcompose/testdata/TestSetup_127_0_0_4_compose.yaml.golden +++ /dev/null @@ -1,73 +0,0 @@ -version: '2.4' -networks: - test: - labels: - e2e: true - driver: bridge - -services: - seed01: - labels: - e2e: true - container_name: seed01 - image: iliadops/iliad:7d1ae53 - init: true - ports: - - 26656:26656 - - 26657:26657 - - 6060 - volumes: - - ./seed01:/iliad - depends_on: - seed01_evm: - condition: service_healthy - networks: - test: - - - - # Use geth as the iliad EVMs. - # Initialises geth files and folder from provided genesis file. - seed01_evm-init: - labels: - e2e: true - container_name: seed01_evm-init - image: "ethereum/client-go:v1.14.5" - command: --state.scheme=path --datadir=/geth init /geth/genesis.json - volumes: - - ./seed01_evm:/geth - networks: - test: - - seed01_evm: - labels: - e2e: true - container_name: seed01_evm - image: "ethereum/client-go:v1.14.5" - command: - - --config=/geth/config.toml - # Flags not available via config.toml - - --nat=extip: - - --pprof - - --pprof.addr=0.0.0.0 - - --metrics - - ports: - - 8551:8551 - - 8545:8545 - - 30303:30303 - - 8546 - - 6060 - depends_on: - seed01_evm-init: - condition: service_completed_successfully - healthcheck: - test: "nc -z localhost 8545" - interval: 1s - retries: 30 - volumes: - - ./seed01_evm:/geth - networks: - test: - - diff --git a/e2e/vmcompose/testdata/TestSetup_127_0_0_5_compose.yaml.golden b/e2e/vmcompose/testdata/TestSetup_127_0_0_5_compose.yaml.golden deleted file mode 100644 index 11336b49..00000000 --- a/e2e/vmcompose/testdata/TestSetup_127_0_0_5_compose.yaml.golden +++ /dev/null @@ -1,73 +0,0 @@ -version: '2.4' -networks: - test: - labels: - e2e: true - driver: bridge - -services: - fullnode01: - labels: - e2e: true - container_name: fullnode01 - image: iliadops/iliad:7d1ae53 - init: true - ports: - - 26656:26656 - - 26657:26657 - - 6060 - volumes: - - ./fullnode01:/iliad - depends_on: - fullnode01_evm: - condition: service_healthy - networks: - test: - - - - # Use geth as the iliad EVMs. - # Initialises geth files and folder from provided genesis file. - fullnode01_evm-init: - labels: - e2e: true - container_name: fullnode01_evm-init - image: "ethereum/client-go:v1.14.5" - command: --state.scheme=path --datadir=/geth init /geth/genesis.json - volumes: - - ./fullnode01_evm:/geth - networks: - test: - - fullnode01_evm: - labels: - e2e: true - container_name: fullnode01_evm - image: "ethereum/client-go:v1.14.5" - command: - - --config=/geth/config.toml - # Flags not available via config.toml - - --nat=extip: - - --pprof - - --pprof.addr=0.0.0.0 - - --metrics - - ports: - - 8551:8551 - - 8545:8545 - - 30303:30303 - - 8546 - - 6060 - depends_on: - fullnode01_evm-init: - condition: service_completed_successfully - healthcheck: - test: "nc -z localhost 8545" - interval: 1s - retries: 30 - volumes: - - ./fullnode01_evm:/geth - networks: - test: - - diff --git a/e2e/vmcompose/testdata/TestSetup_127_0_0_6_compose.yaml.golden b/e2e/vmcompose/testdata/TestSetup_127_0_0_6_compose.yaml.golden deleted file mode 100644 index 8679bc04..00000000 --- a/e2e/vmcompose/testdata/TestSetup_127_0_0_6_compose.yaml.golden +++ /dev/null @@ -1,9 +0,0 @@ -version: '2.4' -networks: - test: - labels: - e2e: true - driver: bridge - -services: - diff --git a/go.mod b/go.mod index 71e2cf16..cc16f3bf 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( cosmossdk.io/orm v1.0.0-beta.3 cosmossdk.io/store v1.1.0 cosmossdk.io/x/tx v0.13.3 - github.com/BurntSushi/toml v1.3.2 github.com/bufbuild/buf v1.31.0 github.com/charmbracelet/log v0.4.0 github.com/cometbft/cometbft v0.38.9 @@ -32,7 +31,6 @@ require ( github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/leodido/go-conventionalcommits v0.12.0 github.com/muesli/termenv v0.15.2 - github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 @@ -43,7 +41,7 @@ require ( go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.27.0 go.uber.org/goleak v1.3.0 go.uber.org/mock v0.4.0 - golang.org/x/sync v0.7.0 + golang.org/x/sync v0.7.0 // indirect golang.org/x/tools v0.21.0 google.golang.org/grpc v1.64.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.4.0 @@ -117,7 +115,6 @@ require ( github.com/fatih/color v1.16.0 // indirect github.com/felixge/fgprof v0.9.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fjl/memsize v0.0.2 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect github.com/go-chi/chi/v5 v5.0.12 // indirect @@ -132,7 +129,6 @@ require ( github.com/gofrs/uuid/v5 v5.1.0 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/glog v1.2.0 // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -148,7 +144,6 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect - github.com/hashicorp/go-bexpr v0.1.10 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-metrics v0.5.3 // indirect @@ -157,15 +152,12 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/hdevalence/ed25519consensus v0.1.0 // indirect - github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.2.4 // indirect github.com/huandu/skiplist v1.2.0 // indirect - github.com/huin/goupnp v1.3.0 // indirect github.com/iancoleman/strcase v0.3.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jdx/go-netrc v1.0.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/klauspost/compress v1.17.8 // indirect @@ -184,14 +176,12 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 - github.com/mitchellh/pointerstructure v1.2.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/muesli/reflow v0.3.0 // indirect - github.com/naoina/go-stringutil v0.1.0 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect github.com/oklog/run v1.1.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect @@ -230,7 +220,6 @@ require ( github.com/tklauser/numcpus v0.6.1 // indirect github.com/urfave/cli/v2 v2.27.2 // indirect github.com/vbatts/tar-split v0.11.5 // indirect - github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.8 // indirect @@ -255,7 +244,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gotest.tools/v3 v3.5.1 nhooyr.io/websocket v1.8.6 // indirect pgregory.net/rapid v1.1.0 // indirect diff --git a/go.sum b/go.sum index 1d8b7912..1b2ad6f9 100644 --- a/go.sum +++ b/go.sum @@ -220,8 +220,6 @@ github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwR github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -936,7 +934,6 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= @@ -967,10 +964,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo= -github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks= -github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= -github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0= -github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= diff --git a/lib/anvil/accounts.go b/lib/anvil/accounts.go deleted file mode 100644 index 268db236..00000000 --- a/lib/anvil/accounts.go +++ /dev/null @@ -1,152 +0,0 @@ -package anvil - -import ( - "crypto/ecdsa" - "strings" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -//nolint:gochecknoglobals // Static keys and addresses -var ( - acc0 = addr("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") - acc1 = addr("0x70997970C51812dc3A010C7d01b50e0d17dc79C8") - acc2 = addr("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC") - acc3 = addr("0x90F79bf6EB2c4f870365E785982E1f101E93b906") - acc4 = addr("0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65") - acc5 = addr("0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc") - acc6 = addr("0x976EA74026E726554dB657fA54763abd0C3a0aa9") - acc7 = addr("0x14dC79964da2C08b23698B3D3cc7Ca32193d9955") - acc8 = addr("0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f") - acc9 = addr("0xa0Ee7A142d267C1f36714E4a8F75612F20a79720") - - pk0 = mustHexToKey("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") - pk1 = mustHexToKey("0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d") - pk2 = mustHexToKey("0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a") - pk3 = mustHexToKey("0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6") - pk4 = mustHexToKey("0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a") - pk5 = mustHexToKey("0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba") - pk6 = mustHexToKey("0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e") - pk7 = mustHexToKey("0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356") - pk8 = mustHexToKey("0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97") - pk9 = mustHexToKey("0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6") -) - -// -// Accounts. -// - -func DevAccount0() common.Address { - return acc0 -} - -func DevAccount1() common.Address { - return acc1 -} - -func DevAccount2() common.Address { - return acc2 -} - -func DevAccount3() common.Address { - return acc3 -} - -func DevAccount4() common.Address { - return acc4 -} - -func DevAccount5() common.Address { - return acc5 -} - -func DevAccount6() common.Address { - return acc6 -} - -func DevAccount7() common.Address { - return acc7 -} - -func DevAccount8() common.Address { - return acc8 -} - -func DevAccount9() common.Address { - return acc9 -} - -func IsDevAccount(addr common.Address) bool { - switch addr { - case acc0, acc1, acc2, acc3, acc4, acc5, acc6, acc7, acc8, acc9: - return true - } - - return false -} - -// -// Private keys. -// - -func DevPrivateKey0() *ecdsa.PrivateKey { - return pk0 -} - -func DevPrivateKey1() *ecdsa.PrivateKey { - return pk1 -} - -func DevPrivateKey2() *ecdsa.PrivateKey { - return pk2 -} - -func DevPrivateKey3() *ecdsa.PrivateKey { - return pk3 -} - -func DevPrivateKey4() *ecdsa.PrivateKey { - return pk4 -} - -func DevPrivateKey5() *ecdsa.PrivateKey { - return pk5 -} - -func DevPrivateKey6() *ecdsa.PrivateKey { - return pk6 -} - -func DevPrivateKey7() *ecdsa.PrivateKey { - return pk7 -} - -func DevPrivateKey8() *ecdsa.PrivateKey { - return pk8 -} - -func DevPrivateKey9() *ecdsa.PrivateKey { - return pk9 -} - -func DevPrivateKeys() []*ecdsa.PrivateKey { - return []*ecdsa.PrivateKey{pk0, pk1, pk2, pk3, pk4, pk5, pk6, pk7, pk8, pk9} -} - -// -// Utils. -// - -func addr(hex string) common.Address { - return common.HexToAddress(hex) -} - -func mustHexToKey(privKeyHex string) *ecdsa.PrivateKey { - privKey, err := crypto.HexToECDSA(strings.TrimPrefix(privKeyHex, "0x")) - if err != nil { - panic(err) - } - - return privKey -} diff --git a/lib/anvil/anvil.go b/lib/anvil/anvil.go deleted file mode 100644 index 610672fc..00000000 --- a/lib/anvil/anvil.go +++ /dev/null @@ -1,179 +0,0 @@ -package anvil - -import ( - "bytes" - "context" - "net" - "os" - "os/exec" - "path/filepath" - "text/template" - "time" - - "github.com/piplabs/story/e2e/app/static" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/ethclient" - "github.com/piplabs/story/lib/log" - - _ "embed" -) - -// Start starts a genesis anvil node and returns an ethclient and a stop function or an error. -// The dir parameter is the location of the docker compose. -// If useLogProxy is true, all requests are routed via a reserve proxy that logs all requests, which will be printed -// at stop. -func Start(ctx context.Context, dir string, chainID uint64) (ethclient.Client, string, func(), error) { - ctx, cancel := context.WithTimeout(ctx, time.Minute) // Allow 1 minute for edge case of pulling images. - defer cancel() - if !composeDown(ctx, dir) { - return nil, "", nil, errors.New("failure to clean up previous anvil instance") - } - - // Ensure ports are available - port, err := getAvailablePort() - if err != nil { - return nil, "", nil, errors.Wrap(err, "get available port") - } - - if err := writeComposeFile(dir, chainID, port); err != nil { - return nil, "", nil, errors.Wrap(err, "write compose file") - } - - if err := writeAnvilState(dir); err != nil { - return nil, "", nil, errors.Wrap(err, "write anvil state") - } - - log.Info(ctx, "Starting anvil") - - out, err := execCmd(ctx, dir, "docker", "compose", "up", "-d", "--remove-orphans") - if err != nil { - return nil, "", nil, errors.Wrap(err, "docker compose up: "+out) - } - - endpoint := "http://localhost:" + port - - ethCl, err := ethclient.Dial("anvil", endpoint) - if err != nil { - return nil, "", nil, errors.Wrap(err, "new eth client") - } - - //nolint:contextcheck // Fresh stop context since above context might be canceled. - stop := func() { - stopCtx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - composeDown(stopCtx, dir) - } - - // Wait up to 10 secs for RPC to be available - const retry = 10 - for i := range retry { - if i == retry-1 { - stop() - return nil, "", nil, errors.New("wait for RPC timed out") - } - - select { - case <-ctx.Done(): - return nil, "", nil, errors.Wrap(ctx.Err(), "timeout") - case <-time.After(time.Millisecond * 500): - } - - _, err := ethCl.BlockNumber(ctx) - if err == nil { - break - } - - log.Warn(ctx, "Anvil: waiting for RPC to be available", err) - } - - log.Info(ctx, "Anvil: RPC is available", "addr", endpoint) - - return ethCl, endpoint, stop, nil -} - -// composeDown runs docker-compose down in the provided directory. -func composeDown(ctx context.Context, dir string) bool { - if _, err := os.Stat(dir + "/compose.yaml"); os.IsNotExist(err) { - return true - } - - out, err := execCmd(ctx, dir, "docker", "compose", "down") - if err != nil { - log.Error(ctx, "Error: docker compose down", err, "out", out) - return false - } - - log.Debug(ctx, "Anvil: docker compose down: ok") - - return true -} - -func execCmd(ctx context.Context, dir string, cmd string, args ...string) (string, error) { - c := exec.CommandContext(ctx, cmd, args...) - c.Dir = dir - - out, err := c.CombinedOutput() - if err != nil { - return string(out), errors.Wrap(err, "exec", "out", string(out)) - } - - return string(out), nil -} - -func writeAnvilState(dir string) error { - anvilStateFile := filepath.Join(dir, "state.json") - if err := os.WriteFile(anvilStateFile, static.GetDevnetAnvilState(), 0o644); err != nil { - return errors.Wrap(err, "write anvil state") - } - - return nil -} - -//go:embed compose.yaml.tmpl -var composeTpl []byte - -func writeComposeFile(dir string, chainID uint64, port string) error { - tpl, err := template.New("").Parse(string(composeTpl)) - if err != nil { - return errors.Wrap(err, "parse compose template") - } - - var buf bytes.Buffer - err = tpl.Execute(&buf, struct { - ChainID uint64 - Port string - }{ - ChainID: chainID, - Port: port, - }) - if err != nil { - return errors.Wrap(err, "execute compose template") - } - - err = os.WriteFile(dir+"/compose.yaml", buf.Bytes(), 0644) - if err != nil { - return errors.Wrap(err, "write compose file") - } - - return nil -} - -func getAvailablePort() (string, error) { - addr, err := net.ResolveTCPAddr("tcp", "localhost:0") - if err != nil { - return "", errors.Wrap(err, "resolve addr") - } - - l, err := net.ListenTCP("tcp", addr) - if err != nil { - return "", errors.Wrap(err, "listen") - } - defer l.Close() - - _, port, err := net.SplitHostPort(l.Addr().String()) - if err != nil { - return "", errors.Wrap(err, "split host port") - } - - return port, nil -} diff --git a/lib/anvil/compose.yaml.tmpl b/lib/anvil/compose.yaml.tmpl deleted file mode 100644 index 7ac08ae7..00000000 --- a/lib/anvil/compose.yaml.tmpl +++ /dev/null @@ -1,17 +0,0 @@ -version: '2.4' - -services: - anvil: - labels: - anvil: true - image: ghcr.io/foundry-rs/foundry:latest - entrypoint: - - anvil - - --host=0.0.0.0 - - --chain-id={{ .ChainID }} - - --silent - - --load-state=/anvil/state.json - ports: - - {{ .Port }}:8545 - volumes: - - ./state.json:/anvil/state.json diff --git a/lib/anvil/utils.go b/lib/anvil/utils.go deleted file mode 100644 index e7f16411..00000000 --- a/lib/anvil/utils.go +++ /dev/null @@ -1,31 +0,0 @@ -package anvil - -import ( - "context" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/ethclient" - - "github.com/piplabs/story/lib/errors" -) - -// FundAccounts funds the anvil account via the anvil_setBalance RPC method. -func FundAccounts(ctx context.Context, rpc string, amount *big.Int, accounts ...common.Address) error { - client, err := ethclient.Dial(rpc) - if err != nil { - return err - } - defer client.Close() - - for _, account := range accounts { - result := make(map[string]any) - err = client.Client().CallContext(ctx, &result, "anvil_setBalance", account, hexutil.EncodeBig(amount)) - if err != nil { - return errors.Wrap(err, "set balance", "account", account) - } - } - - return nil -} diff --git a/lib/contracts/address.go b/lib/contracts/address.go deleted file mode 100644 index ad815701..00000000 --- a/lib/contracts/address.go +++ /dev/null @@ -1,70 +0,0 @@ -package contracts - -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - - "github.com/piplabs/story/e2e/app/eoa" - "github.com/piplabs/story/lib/create3" - "github.com/piplabs/story/lib/netconf" -) - -// -// Create3Factory. -// - -func MainnetCreate3Factory() common.Address { - return crypto.CreateAddress(eoa.MustAddress(netconf.Mainnet, eoa.RoleCreate3Deployer), 0) -} - -func TestnetCreate3Factory() common.Address { - return crypto.CreateAddress(eoa.MustAddress(netconf.Testnet, eoa.RoleCreate3Deployer), 0) -} - -func StagingCreate3Factory() common.Address { - return crypto.CreateAddress(eoa.MustAddress(netconf.Staging, eoa.RoleCreate3Deployer), 0) -} - -func DevnetCreate3Factory() common.Address { - return crypto.CreateAddress(eoa.MustAddress(netconf.Devnet, eoa.RoleCreate3Deployer), 0) -} - -// -// IPTokenStaking. -// - -func MainnetIPTokenStaking() common.Address { - return create3.Address(MainnetCreate3Factory(), IPTokenStakingSalt(netconf.Mainnet), eoa.MustAddress(netconf.Mainnet, eoa.RoleDeployer)) -} - -func TestnetIPTokenStaking() common.Address { - return create3.Address(TestnetCreate3Factory(), IPTokenStakingSalt(netconf.Testnet), eoa.MustAddress(netconf.Testnet, eoa.RoleDeployer)) -} - -func StagingIPTokenStaking() common.Address { - return create3.Address(StagingCreate3Factory(), IPTokenStakingSalt(netconf.Staging), eoa.MustAddress(netconf.Staging, eoa.RoleDeployer)) -} - -func DevnetIPTokenStaking() common.Address { - return create3.Address(DevnetCreate3Factory(), IPTokenStakingSalt(netconf.Devnet), eoa.MustAddress(netconf.Devnet, eoa.RoleDeployer)) -} - -// -// Salts. -// - -func IPTokenStakingSalt(network netconf.ID) string { - // only portal salts are versioned - return salt(network, "iptokenstaking-"+network.Version()) -} - -// -// Utils. -// - -// salt generates a salt for a contract deployment. For ephemeral networks, -// the salt includes a random per-run suffix. For persistent networks, the -// sale is static. -func salt(network netconf.ID, contract string) string { - return string(network) + "-" + contract -} diff --git a/lib/contracts/create3/deploy.go b/lib/contracts/create3/deploy.go deleted file mode 100644 index 35ef5802..00000000 --- a/lib/contracts/create3/deploy.go +++ /dev/null @@ -1,174 +0,0 @@ -package create3 - -import ( - "context" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - ethtypes "github.com/ethereum/go-ethereum/core/types" - - "github.com/piplabs/story/contracts/bindings" - "github.com/piplabs/story/e2e/app/eoa" - "github.com/piplabs/story/lib/contracts" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/ethclient/ethbackend" - "github.com/piplabs/story/lib/netconf" -) - -type DeploymentConfig struct { - Deployer common.Address -} - -func (cfg DeploymentConfig) Validate() error { - if (cfg.Deployer == common.Address{}) { - return errors.New("deployer is zero") - } - - return nil -} - -func getDeployCfg(chainID uint64, network netconf.ID) (DeploymentConfig, error) { - if network == netconf.Devnet { - return devnetCfg(), nil - } - - if network == netconf.Mainnet { - return mainnetCfg(), nil - } - - if network == netconf.Testnet { - return testnetCfg(), nil - } - - if network == netconf.Staging { - return stagingCfg(), nil - } - - return DeploymentConfig{}, errors.New("unsupported chain for network", "chain_id", chainID, "network", network) -} - -func mainnetCfg() DeploymentConfig { - return DeploymentConfig{ - Deployer: eoa.MustAddress(netconf.Mainnet, eoa.RoleCreate3Deployer), - } -} - -func testnetCfg() DeploymentConfig { - return DeploymentConfig{ - Deployer: eoa.MustAddress(netconf.Testnet, eoa.RoleCreate3Deployer), - } -} - -func stagingCfg() DeploymentConfig { - return DeploymentConfig{ - Deployer: eoa.MustAddress(netconf.Staging, eoa.RoleCreate3Deployer), - } -} - -func devnetCfg() DeploymentConfig { - return DeploymentConfig{ - Deployer: eoa.MustAddress(netconf.Devnet, eoa.RoleCreate3Deployer), - } -} - -func AddrForNetwork(network netconf.ID) (common.Address, bool) { - switch network { - case netconf.Mainnet: - return contracts.MainnetCreate3Factory(), true - case netconf.Testnet: - return contracts.TestnetCreate3Factory(), true - case netconf.Staging: - return contracts.StagingCreate3Factory(), true - case netconf.Devnet: - return contracts.DevnetCreate3Factory(), true - default: - return common.Address{}, false - } -} - -// IsDeployed checks if the Create3 factory contract is deployed to the provided backend -// to its expected network address. -func IsDeployed(ctx context.Context, network netconf.ID, backend *ethbackend.Backend) (bool, common.Address, error) { - addr, ok := AddrForNetwork(network) - if !ok { - return false, addr, errors.New("unsupported network", "network", network) - } - - code, err := backend.CodeAt(ctx, addr, nil) - if err != nil { - return false, addr, errors.Wrap(err, "code at", "address", addr) - } - - if len(code) == 0 { - return false, addr, nil - } - - if hexutil.Encode(code) != bindings.Create3DeployedBytecode { - chain, chainID := backend.Chain() - return false, addr, errors.New("unexpected code at factory", "address", addr, "chain", chain, "chain_id", chainID) - } - - return true, addr, nil -} - -// DeployIfNeeded deploys a new Create3 factory contract if it is not already deployed. -func DeployIfNeeded(ctx context.Context, network netconf.ID, backend *ethbackend.Backend) (common.Address, *ethtypes.Receipt, error) { - deployed, addr, err := IsDeployed(ctx, network, backend) - if err != nil { - return common.Address{}, nil, errors.Wrap(err, "is deployed") - } - if deployed { - return addr, nil, nil - } - - return Deploy(ctx, network, backend) -} - -// Deploy deploys a new Create3 factory contract and returns the address and receipt. -// It only allows deployments to explicitly supported chains. -func Deploy(ctx context.Context, network netconf.ID, backend *ethbackend.Backend) (common.Address, *ethtypes.Receipt, error) { - chainID, err := backend.ChainID(ctx) - if err != nil { - return common.Address{}, nil, errors.Wrap(err, "chain id") - } - - cfg, err := getDeployCfg(chainID.Uint64(), network) - if err != nil { - return common.Address{}, nil, errors.Wrap(err, "get deployment config") - } - - return deploy(ctx, cfg, backend) -} - -func deploy(ctx context.Context, cfg DeploymentConfig, backend *ethbackend.Backend) (common.Address, *ethtypes.Receipt, error) { - if err := cfg.Validate(); err != nil { - return common.Address{}, nil, errors.Wrap(err, "validate config") - } - - txOpts, err := backend.BindOpts(ctx, cfg.Deployer) - if err != nil { - return common.Address{}, nil, errors.Wrap(err, "bind opts") - } - - nonce, err := backend.PendingNonceAt(ctx, cfg.Deployer) - if err != nil { - return common.Address{}, nil, errors.Wrap(err, "pending nonce") - } else if nonce != 0 { - return common.Address{}, nil, errors.New("nonce not zero") - } - - addr, tx, _, err := bindings.DeployCreate3(txOpts, backend) - if err != nil { - return common.Address{}, nil, errors.Wrap(err, "deploy create3") - } - - receipt, err := bind.WaitMined(ctx, backend, tx) - if err != nil { - return common.Address{}, nil, errors.Wrap(err, "wait mined") - } else if receipt.Status != ethtypes.ReceiptStatusSuccessful { - return common.Address{}, nil, errors.New("receipt status failed") - } - - return addr, receipt, nil -} diff --git a/lib/contracts/utils.go b/lib/contracts/utils.go deleted file mode 100644 index 7132083f..00000000 --- a/lib/contracts/utils.go +++ /dev/null @@ -1,23 +0,0 @@ -package contracts - -import ( - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common/hexutil" - - "github.com/piplabs/story/lib/errors" -) - -// PackInitCode packs the init code for a contract deployment. -func PackInitCode(abi *abi.ABI, bytecodeHex string, params ...any) ([]byte, error) { - input, err := abi.Pack("", params...) - if err != nil { - return nil, errors.Wrap(err, "pack init code") - } - - bytecode, err := hexutil.Decode(bytecodeHex) - if err != nil { - return nil, errors.Wrap(err, "decode bytecode") - } - - return append(bytecode, input...), nil -} diff --git a/lib/create3/create3.go b/lib/create3/create3.go deleted file mode 100644 index dfbb2091..00000000 --- a/lib/create3/create3.go +++ /dev/null @@ -1,42 +0,0 @@ -package create3 - -import ( - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" -) - -// HashSalt returns the [32]byte hash of the salt string. -func HashSalt(s string) [32]byte { - var h [32]byte - copy(h[:], crypto.Keccak256([]byte(s))) - - return h -} - -// Iliad's Create3 factory namespaces salts by deployer. -func namespacedSalt(deployer common.Address, salt string) [32]byte { - var h [32]byte - copy(h[:], crypto.Keccak256(deployer.Bytes(), crypto.Keccak256([]byte(salt)))) - - return h -} - -//nolint:gochecknoglobals // static hash -var ( - // proxyBytecodeHash is the hash bytecode of the intermediate proxy contract the Create3 factory deploys. - proxyBytecodeHash = crypto.Keccak256(hexutil.MustDecode("0x67363d3d37363d34f03d5260086018f3")) -) - -// Address returns the Create3 address for the given factory, salt, and deployer. -func Address( - factory common.Address, - salt string, - deployer common.Address, -) common.Address { - // First, get the CREATE2 intermediate proxy address - proxyAddr := crypto.CreateAddress2(factory, namespacedSalt(deployer, salt), proxyBytecodeHash) - - // Return the CREATE address the proxy deploys to - return crypto.CreateAddress(proxyAddr, 1) -} diff --git a/lib/create3/create3_test.go b/lib/create3/create3_test.go deleted file mode 100644 index 48234895..00000000 --- a/lib/create3/create3_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package create3_test - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/e2e/app/eoa" - "github.com/piplabs/story/lib/create3" - "github.com/piplabs/story/lib/netconf" -) - -func TestHashSalt(t *testing.T) { - t.Parallel() - - hash := create3.HashSalt("eip1967.proxy.implementation") - - require.Equal( - t, - // keccak-256 hash of "eip1967.proxy.implementation" - "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbd", - common.Bytes2Hex(hash[:]), - ) -} - -func TestAddress(t *testing.T) { - t.Parallel() - - // test case is Devent proxy admin deployment - - factory := common.HexToAddress("0x5FbDB2315678afecb367f032d93F642f64180aa3") - expected := common.HexToAddress("0xd8dc3f2817F4d87200443FBaEdE5ab8D5d742465") - require.Equal(t, expected, create3.Address(factory, "devnet-proxy-admin", eoa.MustAddress(netconf.Devnet, eoa.RoleDeployer))) -} diff --git a/lib/ethclient/ethbackend/backend.go b/lib/ethclient/ethbackend/backend.go deleted file mode 100644 index 933e5431..00000000 --- a/lib/ethclient/ethbackend/backend.go +++ /dev/null @@ -1,311 +0,0 @@ -package ethbackend - -import ( - "context" - "crypto/ecdsa" - "crypto/rand" - "encoding/hex" - "math/big" - "time" - - k1 "github.com/cometbft/cometbft/crypto/secp256k1" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - - "github.com/piplabs/story/lib/anvil" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/ethclient" - "github.com/piplabs/story/lib/fireblocks" - "github.com/piplabs/story/lib/k1util" - "github.com/piplabs/story/lib/log" - "github.com/piplabs/story/lib/txmgr" -) - -type account struct { - from common.Address - privateKey *ecdsa.PrivateKey // Either local private key is set, - fireCl fireblocks.Client // or, Fireblocks is used - txMgr txmgr.TxManager -} - -type Backend struct { - ethclient.Client - - accounts map[common.Address]account - chainName string - chainID uint64 - blockPeriod time.Duration -} - -// NewFireBackend returns a backend that supports all accounts supported by the configured fireblocks client. -// Note that private keys can still be added via AddAccount. -func NewFireBackend(ctx context.Context, chainName string, chainID uint64, blockPeriod time.Duration, ethCl ethclient.Client, fireCl fireblocks.Client) (*Backend, error) { - accs, err := fireCl.Accounts(ctx) - if err != nil { - return nil, errors.Wrap(err, "fireblocks accounts") - } - - accounts := make(map[common.Address]account) - for addr := range accs { - txMgr, err := newFireblocksTxMgr(ethCl, chainName, chainID, blockPeriod, addr, fireCl) - if err != nil { - return nil, errors.Wrap(err, "new txmgr") - } - - accounts[addr] = account{ - from: addr, - fireCl: fireCl, - txMgr: txMgr, - } - } - - return &Backend{ - Client: ethCl, - accounts: accounts, - chainName: chainName, - chainID: chainID, - blockPeriod: blockPeriod, - }, nil -} - -// NewAnvilBackend returns a backend with all pre-funded anvil dev accounts. -func NewAnvilBackend(chainName string, chainID uint64, blockPeriod time.Duration, ethCl ethclient.Client) (*Backend, error) { - return NewBackend(chainName, chainID, blockPeriod, ethCl, anvil.DevPrivateKeys()...) -} - -// NewBackend returns a new backend backed by in-memory private keys. -func NewBackend(chainName string, chainID uint64, blockPeriod time.Duration, ethCl ethclient.Client, privateKeys ...*ecdsa.PrivateKey) (*Backend, error) { - accounts := make(map[common.Address]account) - for _, pk := range privateKeys { - txMgr, err := newTxMgr(ethCl, chainName, chainID, blockPeriod, pk) - if err != nil { - return nil, errors.Wrap(err, "new txmgr") - } - - addr := crypto.PubkeyToAddress(pk.PublicKey) - accounts[addr] = account{ - from: addr, - privateKey: pk, - txMgr: txMgr, - } - } - - return &Backend{ - Client: ethCl, - accounts: accounts, - chainName: chainName, - chainID: chainID, - blockPeriod: blockPeriod, - }, nil -} - -// AddAccount adds a in-memory private key account to the backend. -// Note this can be called even if other accounts are fireblocks based. -func (b *Backend) AddAccount(privkey *ecdsa.PrivateKey) (common.Address, error) { - txMgr, err := newTxMgr(b.Client, b.chainName, b.chainID, b.blockPeriod, privkey) - if err != nil { - return common.Address{}, errors.Wrap(err, "new txmgr") - } - - addr := crypto.PubkeyToAddress(privkey.PublicKey) - - b.accounts[addr] = account{ - from: addr, - privateKey: privkey, - txMgr: txMgr, - } - - return addr, nil -} - -func (b *Backend) Chain() (string, uint64) { - return b.chainName, b.chainID -} - -func (b *Backend) Sign(ctx context.Context, from common.Address, input [32]byte) ([65]byte, error) { - acc, ok := b.accounts[from] - if !ok { - return [65]byte{}, errors.New("unknown from address", "from", from) - } else if acc.privateKey == nil { - return acc.fireCl.Sign(ctx, input, from) - } - - pk := k1.PrivKey(crypto.FromECDSA(acc.privateKey)) - - return k1util.Sign(pk, input) -} - -func (b *Backend) PublicKey(from common.Address) (*ecdsa.PublicKey, error) { - acc, ok := b.accounts[from] - if !ok { - return nil, errors.New("unknown from address", "from", from) - } - - return &acc.privateKey.PublicKey, nil -} - -func (b *Backend) Send(ctx context.Context, from common.Address, candidate txmgr.TxCandidate) (*ethtypes.Transaction, *ethtypes.Receipt, error) { - acc, ok := b.accounts[from] - if !ok { - return nil, nil, errors.New("unknown from address", "from", from) - } - - return acc.txMgr.Send(ctx, candidate) -} - -// WaitMined waits for the transaction to be mined and asserts the receipt is successful. -func (b *Backend) WaitMined(ctx context.Context, tx *ethtypes.Transaction) (*ethtypes.Receipt, error) { - rec, err := bind.WaitMined(ctx, b, tx) - if err != nil { - return nil, errors.Wrap(err, "wait mined", "chain", b.chainName) - } else if rec.Status != ethtypes.ReceiptStatusSuccessful { - return rec, errors.New("receipt status unsuccessful", "status", rec.Status) - } - - return rec, nil -} - -// BindOpts returns a new TransactOpts for interacting with bindings based contracts for the provided account. -// The TransactOpts are partially stubbed, since txmgr handles nonces and signing. -// -// Do not cache or store the TransactOpts, as they are not safe for concurrent use (pointer). -// Rather create a new TransactOpts for each transaction. -func (b *Backend) BindOpts(ctx context.Context, from common.Address) (*bind.TransactOpts, error) { - if header, err := b.HeaderByNumber(ctx, nil); err != nil { - return nil, errors.Wrap(err, "header by number") - } else if header.BaseFee == nil { - return nil, errors.New("only dynamic transaction Backends supported") - } - - _, ok := b.accounts[from] - if !ok { - return nil, errors.New("unknown from address", "from", from) - } - - // Stub nonce and signer since txmgr will handle this. - // Bindings will estimate gas. - return &bind.TransactOpts{ - From: from, - Nonce: big.NewInt(1), - Signer: func(from common.Address, tx *ethtypes.Transaction) (*ethtypes.Transaction, error) { - resp, err := tx.WithSignature(backendStubSigner{}, from[:]) - if err != nil { - return nil, errors.Wrap(err, "with signature") - } - - return resp, nil - }, - Context: log.WithCtx(ctx, "dest_chain", b.chainName, "from_addr", from.Hex()[:10]), - }, nil -} - -// SendTransaction intercepts the tx that bindings generates, extracts the from address (assuming the -// backendStubSigner was used), the strips fields, and passes it to the txmgr for reliable broadcasting. -func (b *Backend) SendTransaction(ctx context.Context, in *ethtypes.Transaction) error { - from, err := backendStubSigner{}.Sender(in) - if err != nil { - return errors.Wrap(err, "from signer sender") - } - - acc, ok := b.accounts[from] - if !ok { - return errors.New("unknown from address", "from", from) - } - - candidate := txmgr.TxCandidate{ - TxData: in.Data(), - To: in.To(), - GasLimit: in.Gas(), - Value: in.Value(), - } - - ctx = log.WithCtx(ctx, "req_id", randomHex7(), "chain", b.chainName) - - out, resp, err := acc.txMgr.Send(ctx, candidate) - if err != nil { - return errors.Wrap(err, "txmgr send tx") - } - - // TODO: Maybe remove this as it is very noisy. - log.Debug(ctx, "Backend sent tx", - "nonce", out.Nonce(), - "gas_used", resp.GasUsed, - "status", resp.Status, - "height", resp.BlockNumber.Uint64(), - ) - - *in = *out //nolint:govet // Copy lock (caches) isn't a problem since we are overwriting the object. - - return nil -} - -func randomHex7() string { - bytes := make([]byte, 4) - _, _ = rand.Read(bytes) - hexString := hex.EncodeToString(bytes) - - // Trim the string to 7 characters - if len(hexString) > 7 { - hexString = hexString[:7] - } - - return hexString -} - -var _ ethtypes.Signer = backendStubSigner{} - -// backendStubSigner is a stub signer that is used by bindings to sign transactions for a Backend. -// It just encodes the from address into the signature, since there is no other way to pass the from -// address from TxOpts to the Backend. The Backend then extracts the from address, -// gets the account txmgr, and sends the transaction. -type backendStubSigner struct { - ethtypes.Signer -} - -func (backendStubSigner) ChainID() *big.Int { - return new(big.Int) -} - -func (backendStubSigner) Sender(tx *ethtypes.Transaction) (common.Address, error) { - // Convert R,S,V into a 20 byte signature into: - // R: 8 bytes uint64 - // S: 8 bytes uint64 - // V: 4 bytes uint32 - - v, r, s := tx.RawSignatureValues() - - addrLen := len(common.Address{}) - - if len(r.Bytes()) > addrLen { - return common.Address{}, errors.New("invalid r length", "length", len(r.Bytes())) - } - if s.Uint64() != 0 { - return common.Address{}, errors.New("non-empty s [BUG]", "length", len(s.Bytes())) - } - if v.Uint64() != 0 { - return common.Address{}, errors.New("non-empty v [BUG]", "length", len(v.Bytes())) - } - - addr := make([]byte, addrLen) - // big.Int.Bytes() truncates leading zeros, so we need to left pad the address to 20 bytes. - pad := addrLen - len(r.Bytes()) - copy(addr[pad:], r.Bytes()) - - return common.Address(addr), nil -} - -//nolint:nonamedreturns // Ambiguous return values -func (backendStubSigner) SignatureValues(_ *ethtypes.Transaction, sig []byte) (r, s, v *big.Int, err error) { - if len(sig) != len(common.Address{}) { - return nil, nil, nil, errors.New("invalid from signature length", "length", len(sig)) - } - - // Set the 20 byte signature (from address) as R - r = new(big.Int).SetBytes(sig) - s = new(big.Int) // 0 - v = new(big.Int) // 0 - - return r, s, v, nil -} diff --git a/lib/ethclient/ethbackend/backend_internal_test.go b/lib/ethclient/ethbackend/backend_internal_test.go deleted file mode 100644 index d49e810e..00000000 --- a/lib/ethclient/ethbackend/backend_internal_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package ethbackend - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" - fuzz "github.com/google/gofuzz" - "github.com/stretchr/testify/require" -) - -func Test(t *testing.T) { - t.Parallel() - tests := []struct { - name string - txData ethtypes.TxData - fromHex string - }{ - { - name: "legacy tx", - txData: ðtypes.LegacyTx{}, - }, - { - name: "dynamic fee tx", - txData: ðtypes.DynamicFeeTx{}, - }, - { - name: "legacy tx with zero prefix", - txData: ðtypes.LegacyTx{}, - fromHex: "0x002985c832a67c0b31a05e909f443b641624da52", - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - f := fuzz.New().NilChance(0).NumElements(1, 8) - - var from common.Address - f.Fuzz(&from) - f.Fuzz(test.txData) - - if test.fromHex != "" { - from = common.HexToAddress(test.fromHex) - } - - signer := backendStubSigner{} - - tx := ethtypes.NewTx(test.txData) - tx2, err := tx.WithSignature(signer, from.Bytes()) - require.NoError(t, err) - - from2, err := signer.Sender(tx2) - require.NoError(t, err) - - require.Equal(t, from, from2) - }) - } -} diff --git a/lib/ethclient/ethbackend/backends.go b/lib/ethclient/ethbackend/backends.go deleted file mode 100644 index 4d0200ca..00000000 --- a/lib/ethclient/ethbackend/backends.go +++ /dev/null @@ -1,238 +0,0 @@ -package ethbackend - -import ( - "context" - "crypto/ecdsa" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/ethclient" - "github.com/piplabs/story/lib/fireblocks" - "github.com/piplabs/story/lib/netconf" - "github.com/piplabs/story/lib/txmgr" -) - -const ( - interval = 3 -) - -// Backends is a wrapper around a set of Backends, one for each chain. -// At this point, it only supports "a single account for all Backends". -// -// See Backends godoc for more information. -type Backends struct { - backends map[uint64]*Backend -} - -// NewFireBackends returns a multi-backends backed by fireblocks keys that supports configured all chains. -func NewFireBackends(ctx context.Context, testnet types.Testnet, fireCl fireblocks.Client) (Backends, error) { - inner := make(map[uint64]*Backend) - - // Configure iliad EVM Backend - if testnet.HasIliadEVM() { - chain := testnet.BroadcastIliadEVM() - ethCl, err := ethclient.Dial(chain.Chain.Name, chain.ExternalRPC) - if err != nil { - return Backends{}, errors.Wrap(err, "dial") - } - - inner[chain.Chain.ChainID], err = NewFireBackend(ctx, chain.Chain.Name, chain.Chain.ChainID, chain.Chain.BlockPeriod, ethCl, fireCl) - if err != nil { - return Backends{}, errors.Wrap(err, "new iliad Backend") - } - } - - // Configure anvil EVM Backends - for _, chain := range testnet.AnvilChains { - ethCl, err := ethclient.Dial(chain.Chain.Name, chain.ExternalRPC) - if err != nil { - return Backends{}, errors.Wrap(err, "dial") - } - - inner[chain.Chain.ChainID], err = NewFireBackend(ctx, chain.Chain.Name, chain.Chain.ChainID, chain.Chain.BlockPeriod, ethCl, fireCl) - if err != nil { - return Backends{}, errors.Wrap(err, "new anvil Backend") - } - } - - // Configure public EVM Backends - for _, chain := range testnet.PublicChains { - ethCl, err := ethclient.Dial(chain.Chain().Name, chain.NextRPCAddress()) - if err != nil { - return Backends{}, errors.Wrap(err, "dial") - } - - inner[chain.Chain().ChainID], err = NewFireBackend(ctx, chain.Chain().Name, chain.Chain().ChainID, chain.Chain().BlockPeriod, ethCl, fireCl) - if err != nil { - return Backends{}, errors.Wrap(err, "new public Backend") - } - } - - return Backends{ - backends: inner, - }, nil -} - -// NewBackends returns a multi-backends backed by in-memory keys that supports configured all chains. -func NewBackends(testnet types.Testnet, deployKeyFile string) (Backends, error) { - var err error - - var publicDeployKey *ecdsa.PrivateKey - if testnet.Network == netconf.Devnet { - if deployKeyFile != "" { - return Backends{}, errors.New("deploy key not supported in devnet") - } - } else if testnet.Network == netconf.Staging { - publicDeployKey, err = crypto.LoadECDSA(deployKeyFile) - } else { - return Backends{}, errors.New("unknown network") - } - if err != nil { - return Backends{}, errors.Wrap(err, "load deploy key") - } - - inner := make(map[uint64]*Backend) - - // Configure iliad EVM Backend - { - chain := testnet.BroadcastIliadEVM() - ethCl, err := ethclient.Dial(chain.Chain.Name, chain.ExternalRPC) - if err != nil { - return Backends{}, errors.Wrap(err, "dial") - } - - // dev iliad evm uses same dev accounts as anvil - // TODO: do not use dev anvil backend for prod iliad evms - backend, err := NewAnvilBackend(chain.Chain.Name, chain.Chain.ChainID, chain.Chain.BlockPeriod, ethCl) - if err != nil { - return Backends{}, errors.Wrap(err, "new iliad Backend") - } - - inner[chain.Chain.ChainID] = backend - } - - // Configure anvil EVM Backends - for _, chain := range testnet.AnvilChains { - ethCl, err := ethclient.Dial(chain.Chain.Name, chain.ExternalRPC) - if err != nil { - return Backends{}, errors.Wrap(err, "dial") - } - - backend, err := NewAnvilBackend(chain.Chain.Name, chain.Chain.ChainID, chain.Chain.BlockPeriod, ethCl) - if err != nil { - return Backends{}, errors.Wrap(err, "new anvil Backend") - } - - inner[chain.Chain.ChainID] = backend - } - - // Configure public EVM Backends - for _, chain := range testnet.PublicChains { - if publicDeployKey == nil { - return Backends{}, errors.New("public deploy key required") - } - ethCl, err := ethclient.Dial(chain.Chain().Name, chain.NextRPCAddress()) - if err != nil { - return Backends{}, errors.Wrap(err, "dial") - } - - inner[chain.Chain().ChainID], err = NewBackend(chain.Chain().Name, chain.Chain().ChainID, chain.Chain().BlockPeriod, ethCl, publicDeployKey) - if err != nil { - return Backends{}, errors.Wrap(err, "new public Backend") - } - } - - return Backends{ - backends: inner, - }, nil -} - -func (b Backends) All() map[uint64]*Backend { - return b.backends -} - -func (b Backends) Backend(sourceChainID uint64) (*Backend, error) { - backend, ok := b.backends[sourceChainID] - if !ok { - return nil, errors.New("unknown chain", "chain", sourceChainID) - } - - return backend, nil -} - -// BindOpts is a convenience function that an accounts' bind.TransactOpts and Backend for a given chain. -func (b Backends) BindOpts(ctx context.Context, sourceChainID uint64, addr common.Address) (*bind.TransactOpts, *Backend, error) { - backend, ok := b.backends[sourceChainID] - if !ok { - return nil, nil, errors.New("unknown chain", "chain", sourceChainID) - } - - opts, err := backend.BindOpts(ctx, addr) - if err != nil { - return nil, nil, errors.Wrap(err, "bind opts") - } - - return opts, backend, nil -} - -func (b Backends) RPCClients() map[uint64]ethclient.Client { - clients := make(map[uint64]ethclient.Client) - for chainID, backend := range b.backends { - clients[chainID] = backend.Client - } - - return clients -} - -func newFireblocksTxMgr(ethCl ethclient.Client, chainName string, chainID uint64, blockPeriod time.Duration, from common.Address, fireCl fireblocks.Client) (txmgr.TxManager, error) { - // creates our new CLI config for our tx manager - defaults := txmgr.DefaultSenderFlagValues - defaults.NetworkTimeout = time.Minute * 5 - cliConfig := txmgr.NewCLIConfig( - chainID, - blockPeriod/interval, - defaults, - ) - - // get the config for our tx manager - cfg, err := txmgr.NewConfigWithSigner(cliConfig, fireCl.Sign, from, ethCl) - if err != nil { - return nil, errors.Wrap(err, "new config") - } - - // create a simple tx manager from our config - txMgr, err := txmgr.NewSimple(chainName, cfg) - if err != nil { - return nil, errors.Wrap(err, "new simple") - } - - return txMgr, nil -} - -func newTxMgr(ethCl ethclient.Client, chainName string, chainID uint64, blockPeriod time.Duration, privateKey *ecdsa.PrivateKey) (txmgr.TxManager, error) { - // creates our new CLI config for our tx manager - cliConfig := txmgr.NewCLIConfig( - chainID, - blockPeriod/interval, - txmgr.DefaultSenderFlagValues, - ) - - // get the config for our tx manager - cfg, err := txmgr.NewConfig(cliConfig, privateKey, ethCl) - if err != nil { - return nil, errors.Wrap(err, "new config") - } - - // create a simple tx manager from our config - txMgr, err := txmgr.NewSimple(chainName, cfg) - if err != nil { - return nil, errors.Wrap(err, "new simple") - } - - return txMgr, nil -} diff --git a/lib/ethclient/ethbackend/wait.go b/lib/ethclient/ethbackend/wait.go deleted file mode 100644 index d31e6e04..00000000 --- a/lib/ethclient/ethbackend/wait.go +++ /dev/null @@ -1,66 +0,0 @@ -package ethbackend - -import ( - "context" - "sync" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - ethtypes "github.com/ethereum/go-ethereum/core/types" - - "github.com/piplabs/story/lib/errors" -) - -// NewWaiter returns a new Waiter. -func (b Backends) NewWaiter() *Waiter { - return &Waiter{ - b: b, - waiting: make(chan struct{}), - txs: make(map[uint64][]*ethtypes.Transaction), - } -} - -// Waiter is a convenience struct to easily wait for multiple transactions to be mined. -// Adding is thread safe, but it panics if Add is called after Wait. -type Waiter struct { - b Backends - mu sync.Mutex - waiting chan struct{} - txs map[uint64][]*ethtypes.Transaction -} - -func (w *Waiter) Add(chainID uint64, tx *ethtypes.Transaction) { - timer := time.NewTicker(time.Millisecond) - defer timer.Stop() - var locked bool - for !locked { - select { - case <-timer.C: - locked = w.mu.TryLock() - case <-w.waiting: - panic("waiting for a transaction already in progress") - } - } - defer w.mu.Unlock() - - w.txs[chainID] = append(w.txs[chainID], tx) -} - -func (w *Waiter) Wait(ctx context.Context) error { - w.mu.Lock() - defer w.mu.Unlock() - close(w.waiting) - - for chainID, txs := range w.txs { - for _, tx := range txs { - rec, err := bind.WaitMined(ctx, w.b.backends[chainID], tx) - if err != nil { - return errors.Wrap(err, "wait mined", "chain_id", chainID) - } else if rec.Status != ethtypes.ReceiptStatusSuccessful { - return errors.New("tx status unsuccessful", "chain_id", chainID) - } - } - } - - return nil -} diff --git a/lib/netconf/netconf_test.go b/lib/netconf/netconf_test.go index 4c8ca205..a5e53908 100644 --- a/lib/netconf/netconf_test.go +++ b/lib/netconf/netconf_test.go @@ -1,139 +1,14 @@ package netconf_test import ( - "context" - "encoding/hex" - "flag" - "fmt" - "sort" - "strings" "testing" - "github.com/BurntSushi/toml" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/stretchr/testify/require" - "github.com/piplabs/story/e2e/app/key" - "github.com/piplabs/story/e2e/manifests" - "github.com/piplabs/story/e2e/types" - "github.com/piplabs/story/lib/k1util" "github.com/piplabs/story/lib/netconf" - "github.com/piplabs/story/lib/tutil" ) -//go:generate go test -golden -run=TestGenConsSeeds - -// TestGenConsSeeds generates /consensus-seeds.txt by loading e2e manifests and parsing seed* p2p_consensus keys. -func TestGenConsSeeds(t *testing.T) { - t.Parallel() - tests := []struct { - network netconf.ID - manifestFunc func() []byte - }{ - { - network: netconf.Testnet, - manifestFunc: manifests.Testnet, - }, - } - for _, test := range tests { - t.Run(test.network.String(), func(t *testing.T) { - t.Parallel() - var manifest types.Manifest - //nolint:musttag // ignore perturb json - _, err := toml.Decode(string(test.manifestFunc()), &manifest) - require.NoError(t, err) - - var peers []string - for _, node := range sortedKeys(manifest.Keys) { - if !strings.HasPrefix(node, "seed") { - continue - } - - for typ, addr := range manifest.Keys[node] { - if typ != key.P2PConsensus { - continue - } - - peers = append(peers, fmt.Sprintf("%s@%s.%s.storyprotocol.xyz", addr, node, test.network)) // ABCDE123@seed01.staging.storyprotocol.xyz - } - } - - seeds := strings.Join(peers, "\n") - seedsFile := fmt.Sprintf("../%s/consensus-seeds.txt", test.network) - tutil.RequireGoldenBytes(t, []byte(seeds), tutil.WithFilename(seedsFile)) - }) - } -} - -var genExecutionSeeds = flag.Bool("gen-execution-seeds", false, "Enable to generate execution-seeds.txt. Note this requires GCP secret manager read-access") - -func TestGenExecutionSeeds(t *testing.T) { - t.Parallel() - if !*genExecutionSeeds { - t.Skip("Skipping since --gen-execution-seeds=false") - return - } - - tests := []struct { - network netconf.ID - manifestFunc func() []byte - }{ - { - network: netconf.Testnet, - manifestFunc: manifests.Testnet, - }, - } - for _, test := range tests { - t.Run(test.network.String(), func(t *testing.T) { - t.Parallel() - ctx := context.Background() - var manifest types.Manifest - //nolint:musttag // ignore perturb json - _, err := toml.Decode(string(test.manifestFunc()), &manifest) - require.NoError(t, err) - - var peers []string - for _, node := range sortedKeys(manifest.Keys) { - if !strings.HasPrefix(node, "seed") { - continue - } - - for typ, addr := range manifest.Keys[node] { - if typ != key.P2PExecution { - continue - } - - key, err := key.Download(ctx, test.network, node, typ, addr) - require.NoError(t, err) - - stdPrivKey, err := key.ECDSA() - require.NoError(t, err) - - pubkey64 := k1util.PubKeyToBytes64(&stdPrivKey.PublicKey) - pubkeyHex := hex.EncodeToString(pubkey64) - nodeName := strings.TrimSuffix(node, "_evm") - - peers = append(peers, fmt.Sprintf("enode://%s@%s.%s.storyprotocol:30303", pubkeyHex, nodeName, test.network)) - } - } - - seeds := strings.Join(peers, "\n") - seedsFile := fmt.Sprintf("../%s/execution-seeds.txt", test.network) - tutil.RequireGoldenBytes(t, []byte(seeds), tutil.WithFilename(seedsFile)) - }) - } -} - -func sortedKeys[T any](m map[string]T) []string { - keys := make([]string, 0, len(m)) - for k := range m { - keys = append(keys, k) - } - sort.Strings(keys) - - return keys -} - func TestConsensusSeeds(t *testing.T) { t.Parallel() diff --git a/lib/txmgr/README.md b/lib/txmgr/README.md deleted file mode 100644 index afef5d2b..00000000 --- a/lib/txmgr/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# txmgr package - -This package has been copied and modified from the [optimism](https://github.com/ethereum-optimism/optimism/tree/develop/op-service/txmgr) repository. diff --git a/lib/txmgr/config.go b/lib/txmgr/config.go deleted file mode 100644 index 7733c122..00000000 --- a/lib/txmgr/config.go +++ /dev/null @@ -1,309 +0,0 @@ -package txmgr - -import ( - "context" - "crypto/ecdsa" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/ethclient" -) - -type DefaultFlagValues struct { - NumConfirmations uint64 - SafeAbortNonceTooLowCount uint64 - FeeLimitMultiplier uint64 - FeeLimitThresholdGwei float64 - ResubmissionTimeout time.Duration - NetworkTimeout time.Duration - TxSendTimeout time.Duration - TxNotInMempoolTimeout time.Duration -} - -type CLIConfig struct { - ChainID uint64 - NumConfirmations uint64 - SafeAbortNonceTooLowCount uint64 - FeeLimitMultiplier uint64 - FeeLimitThresholdGwei float64 - MinBaseFeeGwei float64 - MinTipCapGwei float64 - ResubmissionTimeout time.Duration - ReceiptQueryInterval time.Duration - NetworkTimeout time.Duration - TxSendTimeout time.Duration - TxNotInMempoolTimeout time.Duration -} - -var ( - //nolint:gochecknoglobals // should be configurable - DefaultSenderFlagValues = DefaultFlagValues{ - NumConfirmations: uint64(1), - SafeAbortNonceTooLowCount: uint64(3), - FeeLimitMultiplier: uint64(5), - FeeLimitThresholdGwei: 100.0, - ResubmissionTimeout: 48 * time.Second, - NetworkTimeout: 30 * time.Second, - TxSendTimeout: 20 * time.Minute, - TxNotInMempoolTimeout: 2 * time.Minute, - } -) - -func NewCLIConfig(chainID uint64, interval time.Duration, defaults DefaultFlagValues) CLIConfig { - return CLIConfig{ - NumConfirmations: defaults.NumConfirmations, - SafeAbortNonceTooLowCount: defaults.SafeAbortNonceTooLowCount, - FeeLimitMultiplier: defaults.FeeLimitMultiplier, - FeeLimitThresholdGwei: defaults.FeeLimitThresholdGwei, - ResubmissionTimeout: defaults.ResubmissionTimeout, - NetworkTimeout: defaults.NetworkTimeout, - TxSendTimeout: defaults.TxSendTimeout, - TxNotInMempoolTimeout: defaults.TxNotInMempoolTimeout, - ReceiptQueryInterval: interval, - ChainID: chainID, - } -} - -func (m CLIConfig) Check() error { - if m.NumConfirmations == 0 { - return errors.New("numConfirmations must not be 0") - } - if m.NetworkTimeout == 0 { - return errors.New("must provide NetworkTimeout") - } - if m.FeeLimitMultiplier == 0 { - return errors.New("must provide FeeLimitMultiplier") - } - if m.MinBaseFeeGwei < m.MinTipCapGwei { - return errors.New("minBaseFee smaller than minTipCap", - m.MinBaseFeeGwei, m.MinTipCapGwei) - } - if m.ResubmissionTimeout == 0 { - return errors.New("must provide ResubmissionTimeout") - } - if m.ReceiptQueryInterval == 0 { - return errors.New("must provide ReceiptQueryInterval") - } - if m.TxNotInMempoolTimeout == 0 { - return errors.New("must provide TxNotInMempoolTimeout") - } - if m.SafeAbortNonceTooLowCount == 0 { - return errors.New("safeAbortNonceTooLowCount must not be 0") - } - - return nil -} - -func externalSignerFn(external ExternalSigner, address common.Address, chainID uint64) SignerFn { - signer := types.LatestSignerForChainID(big.NewInt(int64(chainID))) - return func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) { - if address != from { - return nil, bind.ErrNotAuthorized - } - - sig, err := external(ctx, signer.Hash(tx), from) - if err != nil { - return nil, errors.Wrap(err, "external sign") - } - - res, err := tx.WithSignature(signer, sig[:]) - if err != nil { - return nil, errors.Wrap(err, "set signature") - } - - return res, nil - } -} - -// privateKeySignerFn returns a SignerFn that signs transactions with the given private key. -func privateKeySignerFn(key *ecdsa.PrivateKey, chainID uint64) SignerFn { - from := crypto.PubkeyToAddress(key.PublicKey) - signer := types.LatestSignerForChainID(big.NewInt(int64(chainID))) - - return func(_ context.Context, address common.Address, tx *types.Transaction) (*types.Transaction, error) { - if address != from { - return nil, bind.ErrNotAuthorized - } - signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key) - if err != nil { - return nil, errors.Wrap(err, "could not sign transaction") - } - res, err := tx.WithSignature(signer, signature) - if err != nil { - return nil, errors.Wrap(err, "could not sign transaction") - } - - return res, nil - } -} - -// ExternalSigner is a function that signs a transaction with a given address and returns the signature. -type ExternalSigner func(context.Context, common.Hash, common.Address) ([65]byte, error) - -// SignerFn is a generic transaction signing function. It may be a remote signer so it takes a context. -// It also takes the address that should be used to sign the transaction with. -type SignerFn func(context.Context, common.Address, *types.Transaction) (*types.Transaction, error) - -// SignerFactory creates a SignerFn that is bound to a specific chainID. -type SignerFactory func(chainID *big.Int) SignerFn - -// Config houses parameters for altering the behavior of a simple. -type Config struct { - Backend ethclient.Client - - // ResubmissionTimeout is the interval at which, if no previously - // published transaction has been mined, the new tx with a bumped gas - // price will be published. Only one publication at MaxGasPrice will be - // attempted. - ResubmissionTimeout time.Duration - - // The multiplier applied to fee suggestions to put a hard limit on fee increases. - FeeLimitMultiplier uint64 - - // Minimum threshold (in Wei) at which the FeeLimitMultiplier takes effect. - // On low-fee networks, like test networks, this allows for arbitrary fee bumps - // below this threshold. - FeeLimitThreshold *big.Int - - // Minimum base fee (in Wei) to assume when determining tx fees. - MinBaseFee *big.Int - - // Minimum tip cap (in Wei) to enforce when determining tx fees. - MinTipCap *big.Int - - // ChainID is the chain ID of the L1 chain. - ChainID *big.Int - - // TxSendTimeout is how long to wait for sending a transaction. - // By default, it is unbounded. If set, this is recommended to be at least 20 minutes. - TxSendTimeout time.Duration - - // TxNotInMempoolTimeout is how long to wait before aborting a transaction doSend if the transaction does not - // make it to the mempool. If the tx is in the mempool, TxSendTimeout is used instead. - TxNotInMempoolTimeout time.Duration - - // NetworkTimeout is the allowed duration for a single network request. - // This is intended to be used for network requests that can be replayed. - // todo(lazar): this should be handled by eth client - NetworkTimeout time.Duration - - // RequireQueryInterval is the interval at which the tx manager will - // query the backend to check for confirmations after a tx at a - // specific gas price has been published. - ReceiptQueryInterval time.Duration - - // NumConfirmations specifies how many blocks are need to consider a - // transaction confirmed. - NumConfirmations uint64 - - // SafeAbortNonceTooLowCount specifies how many ErrNonceTooLow observations - // are required to give up on a tx at a particular nonce without receiving - // confirmation. - SafeAbortNonceTooLowCount uint64 - - // Signer is used to sign transactions when the gas price is increased. - Signer SignerFn - - From common.Address -} - -// NewConfigWithSigner returns a new txmgr config from the given CLI config and external signer. -func NewConfigWithSigner(cfg CLIConfig, external ExternalSigner, from common.Address, client ethclient.Client) (Config, error) { - signer := externalSignerFn(external, from, cfg.ChainID) - - return newConfig(cfg, signer, from, client) -} - -// NewConfig returns a new txmgr config from the given CLI config and private key. -func NewConfig(cfg CLIConfig, privateKey *ecdsa.PrivateKey, client ethclient.Client) (Config, error) { - signer := privateKeySignerFn(privateKey, cfg.ChainID) - addr := crypto.PubkeyToAddress(privateKey.PublicKey) - - return newConfig(cfg, signer, addr, client) -} - -func newConfig(cfg CLIConfig, signer SignerFn, from common.Address, client ethclient.Client) (Config, error) { - if err := cfg.Check(); err != nil { - return Config{}, errors.New("invalid config", err) - } - - feeLimitThreshold, err := GweiToWei(cfg.FeeLimitThresholdGwei) - if err != nil { - return Config{}, errors.Wrap(err, "invalid fee limit threshold") - } - - minBaseFee, err := GweiToWei(cfg.MinBaseFeeGwei) - if err != nil { - return Config{}, errors.Wrap(err, "invalid min base fee") - } - - minTipCap, err := GweiToWei(cfg.MinTipCapGwei) - if err != nil { - return Config{}, errors.Wrap(err, "invalid min tip cap") - } - - chainID := big.NewInt(int64(cfg.ChainID)) - - return Config{ - Backend: client, - ResubmissionTimeout: cfg.ResubmissionTimeout, - FeeLimitMultiplier: cfg.FeeLimitMultiplier, - FeeLimitThreshold: feeLimitThreshold, - MinBaseFee: minBaseFee, - MinTipCap: minTipCap, - ChainID: chainID, - TxSendTimeout: cfg.TxSendTimeout, - TxNotInMempoolTimeout: cfg.TxNotInMempoolTimeout, - NetworkTimeout: cfg.NetworkTimeout, - ReceiptQueryInterval: cfg.ReceiptQueryInterval, - NumConfirmations: cfg.NumConfirmations, - SafeAbortNonceTooLowCount: cfg.SafeAbortNonceTooLowCount, - Signer: signer, - From: from, - }, nil -} - -func (m Config) Check() error { - if m.Backend == nil { - return errors.New("must provide the backend") - } - if m.NumConfirmations == 0 { - return errors.New("numConfirmations must not be 0") - } - if m.NetworkTimeout == 0 { - return errors.New("must provide NetworkTimeout") - } - if m.FeeLimitMultiplier == 0 { - return errors.New("must provide FeeLimitMultiplier") - } - if m.MinBaseFee != nil && m.MinTipCap != nil && m.MinBaseFee.Cmp(m.MinTipCap) == -1 { - return errors.New("minBaseFee smaller than minTipCap", - m.MinBaseFee, m.MinTipCap) - } - if m.ResubmissionTimeout == 0 { - return errors.New("must provide ResubmissionTimeout") - } - if m.ReceiptQueryInterval == 0 { - return errors.New("must provide ReceiptQueryInterval") - } - if m.TxNotInMempoolTimeout == 0 { - return errors.New("must provide TxNotInMempoolTimeout") - } - if m.SafeAbortNonceTooLowCount == 0 { - return errors.New("safeAbortNonceTooLowCount must not be 0") - } - if m.Signer == nil { - return errors.New("must provide the Signer") - } - if m.ChainID == nil { - return errors.New("must provide the chainID") - } - - return nil -} diff --git a/lib/txmgr/ether.go b/lib/txmgr/ether.go deleted file mode 100644 index 6a8d30c5..00000000 --- a/lib/txmgr/ether.go +++ /dev/null @@ -1,30 +0,0 @@ -package txmgr - -import ( - "math" - "math/big" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/params" - - "github.com/piplabs/story/lib/errors" -) - -// GweiToWei converts a float64 GWei value into a big.Int Wei value. -func GweiToWei(gwei float64) (*big.Int, error) { - if math.IsNaN(gwei) || math.IsInf(gwei, 0) { - return nil, errors.New("invalid gwei value", gwei) - } - - // convert float GWei value into integer Wei value - wei, _ := new(big.Float).Mul( - big.NewFloat(gwei), - big.NewFloat(params.GWei)). - Int(nil) - - if wei.Cmp(abi.MaxUint256) == 1 { - return nil, errors.New("gwei value larger than max uint256") - } - - return wei, nil -} diff --git a/lib/txmgr/price_bump_internal_test.go b/lib/txmgr/price_bump_internal_test.go deleted file mode 100644 index 7c003172..00000000 --- a/lib/txmgr/price_bump_internal_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package txmgr - -import ( - "context" - "math/big" - "strconv" - "testing" - - "github.com/stretchr/testify/require" -) - -type priceBumpTest struct { - prevGasTip int64 - prevBaseFee int64 - newGasTip int64 - newBaseFee int64 - expectedTip int64 - expectedFC int64 -} - -func (tc *priceBumpTest) run(t *testing.T) { - t.Helper() - prevFC := calcGasFeeCap(big.NewInt(tc.prevBaseFee), big.NewInt(tc.prevGasTip)) - - tip, fc := updateFees(context.Background(), big.NewInt(tc.prevGasTip), prevFC, big.NewInt(tc.newGasTip), - big.NewInt(tc.newBaseFee)) - - require.Equal(t, tc.expectedTip, tip.Int64(), "tip must be as expected") - require.Equal(t, tc.expectedFC, fc.Int64(), "fee cap must be as expected") -} - -func TestUpdateFees(t *testing.T) { - t.Parallel() - require.Equal(t, int64(10), PriceBump, "test must be updated if priceBump is adjusted") - tests := []priceBumpTest{ - { - prevGasTip: 100, prevBaseFee: 1000, - newGasTip: 90, newBaseFee: 900, - expectedTip: 110, expectedFC: 2310, - }, - - { - prevGasTip: 100, prevBaseFee: 1000, - newGasTip: 101, newBaseFee: 1000, - expectedTip: 110, expectedFC: 2310, - }, - - { - prevGasTip: 100, prevBaseFee: 1000, - newGasTip: 100, newBaseFee: 1001, - expectedTip: 110, expectedFC: 2310, - }, - - { - prevGasTip: 100, prevBaseFee: 1000, - newGasTip: 101, newBaseFee: 900, - expectedTip: 110, expectedFC: 2310, - }, - - { - prevGasTip: 100, prevBaseFee: 1000, - newGasTip: 90, newBaseFee: 1010, - expectedTip: 110, expectedFC: 2310, - }, - - { - prevGasTip: 100, prevBaseFee: 1000, - newGasTip: 101, newBaseFee: 2000, - expectedTip: 110, expectedFC: 4110, - }, - - { - prevGasTip: 100, prevBaseFee: 1000, - newGasTip: 120, newBaseFee: 900, - expectedTip: 120, expectedFC: 2310, - }, - - { - prevGasTip: 100, prevBaseFee: 1000, - newGasTip: 120, newBaseFee: 1100, - expectedTip: 120, expectedFC: 2320, - }, - - { - prevGasTip: 100, prevBaseFee: 1000, - newGasTip: 120, newBaseFee: 1140, - expectedTip: 120, expectedFC: 2400, - }, - - { - prevGasTip: 100, prevBaseFee: 1000, - newGasTip: 120, newBaseFee: 1200, - expectedTip: 120, expectedFC: 2520, - }, - } - for i, test := range tests { - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - test.run(t) - }) - } -} diff --git a/lib/txmgr/send_state.go b/lib/txmgr/send_state.go deleted file mode 100644 index ce3759d3..00000000 --- a/lib/txmgr/send_state.go +++ /dev/null @@ -1,127 +0,0 @@ -package txmgr - -import ( - "strings" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" -) - -// SendState tracks information about the publication state of a given txn. In -// this context, a txn may correspond to multiple different txn hashes due to -// varying gas prices, though we treat them all as the same logical txn. This -// struct is primarily used to determine whether the txmgr should abort a -// given txn. -type SendState struct { - minedTxs map[common.Hash]struct{} - mu sync.RWMutex - now func() time.Time - - // Config - nonceTooLowCount uint64 - txInMempoolDeadline time.Time // deadline to abort at if no transactions are in the mempool - - // Counts of the different types of errors - successFullPublishCount uint64 // nil error => tx made it to the mempool - safeAbortNonceTooLowCount uint64 // nonce too low error - - // Miscellaneous tracking - bumpCount int // number of times we have bumped the gas price -} - -// NewSendStateWithNow creates a new doSend state with the provided clock. -func NewSendStateWithNow(nonceTooLowCount uint64, timeout time.Duration, - now func() time.Time) *SendState { - if nonceTooLowCount == 0 { - panic("txmgr: nonceTooLowCount cannot be zero") - } - - return &SendState{ - minedTxs: make(map[common.Hash]struct{}), - safeAbortNonceTooLowCount: nonceTooLowCount, - txInMempoolDeadline: now().Add(timeout), - now: now, - } -} - -// NewSendState creates a new doSend state. -func NewSendState(nonceTooLowCount uint64, timeout time.Duration) *SendState { - return NewSendStateWithNow(nonceTooLowCount, timeout, time.Now) -} - -// ProcessSendError should be invoked with the error returned for each -// publication. It is safe to call this method with nil or arbitrary errors. -func (s *SendState) ProcessSendError(err error) { - s.mu.Lock() - defer s.mu.Unlock() - - // Record the type of error - switch { - case err == nil: - s.successFullPublishCount++ - case strings.Contains(err.Error(), core.ErrNonceTooLow.Error()): - s.nonceTooLowCount++ - } -} - -// TxMined records that the txn with txnHash has been mined and is await -// confirmation. It is safe to call this function multiple times. -func (s *SendState) TxMined(txHash common.Hash) { - s.mu.Lock() - defer s.mu.Unlock() - - s.minedTxs[txHash] = struct{}{} -} - -// TxNotMined records that the txn with txnHash has not been mined or has been -// reorg'd out. It is safe to call this function multiple times. -func (s *SendState) TxNotMined(txHash common.Hash) { - s.mu.Lock() - defer s.mu.Unlock() - - _, wasMined := s.minedTxs[txHash] - delete(s.minedTxs, txHash) - - // If the txn got reorged and left us with no mined txns, reset the nonce - // too low count, otherwise we might abort too soon when processing the next - // error. If the nonce too low errors persist, we want to ensure we wait out - // the full safe abort count to ensure we have a sufficient number of - // observations. - if len(s.minedTxs) == 0 && wasMined { - s.nonceTooLowCount = 0 - } -} - -// ShouldAbortImmediately returns true if the txmgr should give up on trying a -// given txn with the target nonce. -// This occurs when the set of errors recorded indicates that no further progress can be made -// on this transaction. -func (s *SendState) ShouldAbortImmediately() bool { - s.mu.RLock() - defer s.mu.RUnlock() - - // Never abort if our latest sample reports having at least one mined txn. - if len(s.minedTxs) > 0 { - return false - } - - // If we have exceeded the nonce too low count, abort - if s.nonceTooLowCount >= s.safeAbortNonceTooLowCount || - // If we have not published a transaction in the allotted time, abort - (s.successFullPublishCount == 0 && s.now().After(s.txInMempoolDeadline)) { - return true - } - - return false -} - -// IsWaitingForConfirmation returns true if we have at least one confirmation on -// one of our txs. -func (s *SendState) IsWaitingForConfirmation() bool { - s.mu.RLock() - defer s.mu.RUnlock() - - return len(s.minedTxs) > 0 -} diff --git a/lib/txmgr/send_state_test.go b/lib/txmgr/send_state_test.go deleted file mode 100644 index 0decc7cb..00000000 --- a/lib/txmgr/send_state_test.go +++ /dev/null @@ -1,205 +0,0 @@ -package txmgr_test - -import ( - "errors" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/lib/txmgr" -) - -var ( - testHash = common.HexToHash("0x01") //nolint:gochecknoglobals // This is okay used for testing -) - -const testSafeAbortNonceTooLowCount = 3 - -func newSendState() *txmgr.SendState { - return newSendStateWithTimeout(time.Hour, time.Now) -} - -func newSendStateWithTimeout(t time.Duration, now func() time.Time) *txmgr.SendState { - return txmgr.NewSendStateWithNow(testSafeAbortNonceTooLowCount, t, now) -} - -func processNSendErrors(sendState *txmgr.SendState, err error) { - for range testSafeAbortNonceTooLowCount { - sendState.ProcessSendError(err) - } -} - -// TestSendStateNoAbortAfterInit asserts that the default SendState won't -// trigger an abort even after the safe abort interval has elapsed. -func TestSendStateNoAbortAfterInit(t *testing.T) { - t.Parallel() - sendState := newSendState() - require.False(t, sendState.ShouldAbortImmediately()) - require.False(t, sendState.IsWaitingForConfirmation()) -} - -// TestSendStateNoAbortAfterProcessNilError asserts that nil errors are not -// considered for abort status. -func TestSendStateNoAbortAfterProcessNilError(t *testing.T) { - t.Parallel() - sendState := newSendState() - - processNSendErrors(sendState, nil) - require.False(t, sendState.ShouldAbortImmediately()) -} - -// TestSendStateNoAbortAfterProcessOtherError asserts that non-nil errors other -// than ErrNonceTooLow are not considered for abort status. -func TestSendStateNoAbortAfterProcessOtherError(t *testing.T) { - t.Parallel() - sendState := newSendState() - - otherError := errors.New("other error") - processNSendErrors(sendState, otherError) - require.False(t, sendState.ShouldAbortImmediately()) -} - -// TestSendStateAbortSafelyAfterNonceTooLowButNoTxMined asserts that we will -// abort after the safe abort interval has elapsed if we haven't mined a tx. -func TestSendStateAbortSafelyAfterNonceTooLowButNoTxMined(t *testing.T) { - t.Parallel() - sendState := newSendState() - - sendState.ProcessSendError(core.ErrNonceTooLow) - require.False(t, sendState.ShouldAbortImmediately()) - sendState.ProcessSendError(core.ErrNonceTooLow) - require.False(t, sendState.ShouldAbortImmediately()) - sendState.ProcessSendError(core.ErrNonceTooLow) - require.True(t, sendState.ShouldAbortImmediately()) -} - -// TestSendStateMiningTxCancelsAbort asserts that a tx getting mined after -// processing ErrNonceTooLow takes precedence and doesn't cause an abort. -func TestSendStateMiningTxCancelsAbort(t *testing.T) { - t.Parallel() - sendState := newSendState() - - sendState.ProcessSendError(core.ErrNonceTooLow) - sendState.ProcessSendError(core.ErrNonceTooLow) - sendState.TxMined(testHash) - require.False(t, sendState.ShouldAbortImmediately()) - sendState.ProcessSendError(core.ErrNonceTooLow) - require.False(t, sendState.ShouldAbortImmediately()) -} - -// TestSendStateReorgingTxResetsAbort asserts that unmining a tx does not -// consider ErrNonceTooLow's prior to being mined when determining whether -// to abort. -func TestSendStateReorgingTxResetsAbort(t *testing.T) { - t.Parallel() - sendState := newSendState() - - sendState.ProcessSendError(core.ErrNonceTooLow) - sendState.ProcessSendError(core.ErrNonceTooLow) - sendState.TxMined(testHash) - sendState.TxNotMined(testHash) - sendState.ProcessSendError(core.ErrNonceTooLow) - require.False(t, sendState.ShouldAbortImmediately()) -} - -// TestSendStateNoAbortEvenIfNonceTooLowAfterTxMined asserts that we will not -// abort if we continue to get ErrNonceTooLow after a tx has been mined. -// -// NOTE: This is the most crucial role of the SendState, as we _expect_ to get -// ErrNonceTooLow failures after one of our txs has been mined, but that -// shouldn't cause us to not continue waiting for confirmations. -func TestSendStateNoAbortEvenIfNonceTooLowAfterTxMined(t *testing.T) { - t.Parallel() - sendState := newSendState() - - sendState.TxMined(testHash) - processNSendErrors( - sendState, core.ErrNonceTooLow, - ) - require.False(t, sendState.ShouldAbortImmediately()) -} - -// TestSendStateSafeAbortIfNonceTooLowPersistsAfterUnmine asserts that we will -// correctly abort if we continue to get ErrNonceTooLow after a tx is unmined -// but not remined. -func TestSendStateSafeAbortIfNonceTooLowPersistsAfterUnmine(t *testing.T) { - t.Parallel() - sendState := newSendState() - - sendState.TxMined(testHash) - sendState.TxNotMined(testHash) - sendState.ProcessSendError(core.ErrNonceTooLow) - sendState.ProcessSendError(core.ErrNonceTooLow) - require.False(t, sendState.ShouldAbortImmediately()) - sendState.ProcessSendError(core.ErrNonceTooLow) - require.True(t, sendState.ShouldAbortImmediately()) -} - -// TestSendStateSafeAbortWhileCallingNotMinedOnUnminedTx asserts that we will -// correctly abort if we continue to call TxNotMined on txns that haven't been -// mined. -func TestSendStateSafeAbortWhileCallingNotMinedOnUnminedTx(t *testing.T) { - t.Parallel() - sendState := newSendState() - - processNSendErrors( - sendState, core.ErrNonceTooLow, - ) - sendState.TxNotMined(testHash) - require.True(t, sendState.ShouldAbortImmediately()) -} - -// TestSendStateIsWaitingForConfirmationAfterTxMined asserts that we are waiting -// for confirmation after a tx is mined. -func TestSendStateIsWaitingForConfirmationAfterTxMined(t *testing.T) { - t.Parallel() - sendState := newSendState() - - testHash2 := common.HexToHash("0x02") - - sendState.TxMined(testHash) - require.True(t, sendState.IsWaitingForConfirmation()) - sendState.TxMined(testHash2) - require.True(t, sendState.IsWaitingForConfirmation()) -} - -// TestSendStateIsNotWaitingForConfirmationAfterTxUnmined asserts that we are -// not waiting for confirmation after a tx is mined then unmined. -func TestSendStateIsNotWaitingForConfirmationAfterTxUnmined(t *testing.T) { - t.Parallel() - sendState := newSendState() - - sendState.TxMined(testHash) - sendState.TxNotMined(testHash) - require.False(t, sendState.IsWaitingForConfirmation()) -} - -func stepClock(step time.Duration) func() time.Time { - i := 0 - return func() time.Time { - var start time.Time - i += 1 - - return start.Add(time.Duration(i) * step) - } -} - -// TestSendStateTimeoutAbort ensure that this will abort if it passes the tx pool timeout -// when no successful transactions have been recorded. -func TestSendStateTimeoutAbort(t *testing.T) { - t.Parallel() - sendState := newSendStateWithTimeout(10*time.Millisecond, stepClock(20*time.Millisecond)) - require.True(t, sendState.ShouldAbortImmediately(), "Should abort after timing out") -} - -// TestSendStateNoTimeoutAbortIfPublishedTx ensure that this will not abort if there is -// a successful transaction doSend. -func TestSendStateNoTimeoutAbortIfPublishedTx(t *testing.T) { - t.Parallel() - sendState := newSendStateWithTimeout(10*time.Millisecond, stepClock(20*time.Millisecond)) - sendState.ProcessSendError(nil) - require.False(t, sendState.ShouldAbortImmediately(), "Should not abort if published transaction successfully") -} diff --git a/lib/txmgr/txmgr.go b/lib/txmgr/txmgr.go deleted file mode 100644 index 8fa8bc3f..00000000 --- a/lib/txmgr/txmgr.go +++ /dev/null @@ -1,652 +0,0 @@ -package txmgr - -import ( - "context" - "log/slog" - "math/big" - "strings" - "sync" - "time" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/txpool" - "github.com/ethereum/go-ethereum/core/types" - - "github.com/piplabs/story/lib/errors" - "github.com/piplabs/story/lib/ethclient" - "github.com/piplabs/story/lib/expbackoff" - "github.com/piplabs/story/lib/log" -) - -const ( - // PriceBump geth requires a minimum fee bump of 10% for regular tx resubmission. - PriceBump int64 = 10 -) - -// TxManager is an interface that allows callers to reliably publish txs, -// bumping the gas price if needed, and obtain the receipt of the resulting tx. -type TxManager interface { - // Send is used to create & doSend a transaction. It will handle increasing - // the gas price & ensuring that the transaction remains in the transaction pool. - // It can be stopped by canceling the provided context; however, the transaction - // may be included on L1 even if the context is canceled. - // - // NOTE: Send can be called concurrently, the nonce will be managed internally. - Send(ctx context.Context, candidate TxCandidate) (*types.Transaction, *types.Receipt, error) - - // From returns the sending address associated with the instance of the transaction manager. - // It is static for a single instance of a TxManager. - From() common.Address - - // ReserveNextNonce returns the next available nonce and increments the available nonce. - ReserveNextNonce(ctx context.Context) (uint64, error) -} - -// simple is a implementation of TxManager that performs linear fee -// bumping of a tx until it confirms. -type simple struct { - cfg Config // embed the config directly - chainName string - chainID *big.Int - - backend ethclient.Client - - nonce *uint64 // nil == unset, 0 == unused account - nonceLock sync.Mutex -} - -// NewSimple initializes a new simple with the passed Config. -func NewSimple(chainName string, conf Config) (TxManager, error) { - if err := conf.Check(); err != nil { - return nil, errors.Wrap(err, "invalid config") - } - - return &simple{ - chainID: conf.ChainID, - chainName: chainName, - cfg: conf, - backend: conf.Backend, - }, nil -} - -func (m *simple) ReserveNextNonce(ctx context.Context) (uint64, error) { - m.nonceLock.Lock() - defer m.nonceLock.Unlock() - - if m.nonce == nil { - nonce, err := m.backend.NonceAt(ctx, m.cfg.From, nil) - if err != nil { - return 0, errors.Wrap(err, "failed to get nonce") - } - m.nonce = &nonce - } - - defer func() { - *m.nonce++ - }() - - return *m.nonce, nil -} - -func (m *simple) From() common.Address { - return m.cfg.From -} - -// txFields returns a logger with the transaction hash and nonce fields set. -func txFields(tx *types.Transaction, logGas bool) []any { - fields := []any{ - slog.Int64("nonce", int64(tx.Nonce())), - slog.String("tx", tx.Hash().String()), - } - if logGas { - fields = append(fields, - slog.String("gas_tip_cap", tx.GasTipCap().String()), - slog.String("gas_fee_cap", tx.GasFeeCap().String()), - slog.Int64("gas_limit", int64(tx.Gas())), - ) - } - - return fields -} - -// TxCandidate is a transaction candidate that can be submitted to ask the -// [TxManager] to construct a transaction with gas price bounds. -type TxCandidate struct { - // TxData is the transaction calldata to be used in the constructed tx. - TxData []byte - // To is the recipient of the constructed tx. Nil means contract creation. - To *common.Address - // GasLimit is the gas limit to be used in the constructed tx. - GasLimit uint64 - // Value is the value to be used in the constructed tx. - Value *big.Int - // Nonce to use for the transaction. If nil, the current nonce is used. - Nonce *uint64 -} - -// Send is used to publish a transaction with incrementally higher gas prices -// until the transaction eventually confirms. This method blocks until an -// invocation of sendTx returns (called with differing gas prices). The method -// may be canceled using the passed context. -// -// The transaction manager handles all signing. If and only if the gas limit is 0, the -// transaction manager will do a gas estimation. -// -// NOTE: Send can be called concurrently, the nonce will be managed internally. -func (m *simple) Send(ctx context.Context, candidate TxCandidate) (*types.Transaction, *types.Receipt, error) { - tx, rec, err := m.doSend(ctx, candidate) - if err != nil { - m.resetNonce() - return nil, nil, err - } - - return tx, rec, nil -} - -// doSend performs the actual transaction creation and sending. -func (m *simple) doSend(ctx context.Context, candidate TxCandidate) (*types.Transaction, *types.Receipt, error) { - ctx, cancel := maybeSetTimeout(ctx, m.cfg.TxSendTimeout) - defer cancel() - - backoff := expbackoff.New(ctx, expbackoff.WithFastConfig()) - for { - if ctx.Err() != nil { - return nil, nil, errors.Wrap(ctx.Err(), "send timeout") - } - - // Set a candidate nonce if not already set - if candidate.Nonce == nil { - nonce, err := m.ReserveNextNonce(ctx) - if err != nil { - log.Warn(ctx, "Failed to reserve nonce (will retry)", err) - backoff() - - continue - } - candidate.Nonce = &nonce - } - - // Create the initial transaction - tx, err := m.craftTx(ctx, candidate) - if err != nil { - log.Warn(ctx, "Failed to create transaction (will retry)", err) - backoff() - - continue - } - - // Send it (note this has internal retries bumping fees) - rec, err := m.sendTx(ctx, tx) - if err != nil { - return nil, nil, errors.Wrap(err, "send tx") - } - - return tx, rec, nil - } -} - -// craftTx creates the signed transaction -// It queries L1 for the current fee market conditions as well as for the nonce. -// NOTE: This method SHOULD NOT publish the resulting transaction. -// NOTE: If the [TxCandidate.GasLimit] is non-zero, it will be used as the transaction's gas. -// NOTE: Otherwise, the [simple] will query the specified backend for an estimate. -func (m *simple) craftTx(ctx context.Context, candidate TxCandidate) (*types.Transaction, error) { - if candidate.Nonce == nil { - return nil, errors.New("invalid nil nonce") - } - - gasTipCap, baseFee, err := m.suggestGasPriceCaps(ctx) - if err != nil { - return nil, errors.Wrap(err, "failed to get gas price info") - } - gasFeeCap := calcGasFeeCap(baseFee, gasTipCap) - - gasLimit := candidate.GasLimit - - // If the gas limit is set, we can use that as the gas - if gasLimit == 0 { - // Calculate the intrinsic gas for the transaction - gas, err := m.backend.EstimateGas(ctx, ethereum.CallMsg{ - From: m.cfg.From, - To: candidate.To, - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - Data: candidate.TxData, - Value: candidate.Value, - }) - if err != nil { - return nil, errors.Wrap(err, "failed to estimate gas") - } - gasLimit = gas - } - - txMessage := &types.DynamicFeeTx{ - ChainID: m.chainID, - To: candidate.To, - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - Value: candidate.Value, - Data: candidate.TxData, - Gas: gasLimit, - Nonce: *candidate.Nonce, - } - - ctx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout) - defer cancel() - - return m.cfg.Signer(ctx, m.cfg.From, types.NewTx(txMessage)) -} - -// resetNonce resets the internal nonce tracking. This is called if any pending doSend -// returns an error. -func (m *simple) resetNonce() { - m.nonceLock.Lock() - defer m.nonceLock.Unlock() - m.nonce = nil -} - -// sendTx submits the same transaction several times with increasing gas prices as necessary. -// It waits for the transaction to be confirmed on chain. -func (m *simple) sendTx(ctx context.Context, tx *types.Transaction) (*types.Receipt, error) { - var wg sync.WaitGroup - defer wg.Wait() - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - sendState := NewSendState(m.cfg.SafeAbortNonceTooLowCount, m.cfg.TxNotInMempoolTimeout) - receiptChan := make(chan *types.Receipt, 1) - publishAndWait := func(tx *types.Transaction, bumpFees bool) *types.Transaction { - wg.Add(1) - tx, published := m.publishTx(ctx, tx, sendState, bumpFees) - if published { - go func() { - defer wg.Done() - m.waitForTx(ctx, tx, sendState, receiptChan) - }() - } else { - wg.Done() - } - - return tx - } - - // Immediately publish a transaction before starting the resubmission loop - tx = publishAndWait(tx, false) - - ticker := time.NewTicker(m.cfg.ResubmissionTimeout) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - // Don't resubmit a transaction if it has been mined, but we are waiting for the conf depth. - if sendState.IsWaitingForConfirmation() { - continue - } - // If we see lots of unrecoverable errors (and no pending transactions) abort sending the transaction. - if sendState.ShouldAbortImmediately() { - return nil, errors.New("aborted transaction sending", txFields(tx, false)...) - } - tx = publishAndWait(tx, true) - - case <-ctx.Done(): - return nil, errors.Wrap(ctx.Err(), "timeout") - - case receipt := <-receiptChan: - return receipt, nil - } - } -} - -// publishTx publishes the transaction to the transaction pool. If it receives any underpriced errors -// it will bump the fees and retry. -// Returns the latest fee bumped tx, and a boolean indicating whether the tx was sent or not. -func (m *simple) publishTx(ctx context.Context, tx *types.Transaction, sendState *SendState, - bumpFeesImmediately bool) (*types.Transaction, bool) { - for { - if ctx.Err() != nil { - return tx, false - } - - if bumpFeesImmediately { - newTx, err := m.increaseGasPrice(ctx, tx) - if err != nil { - log.Info(ctx, "Unable to increase gas", err) - return tx, false - } - tx = newTx - sendState.bumpCount++ - } - bumpFeesImmediately = true // bump fees next loop - - if sendState.IsWaitingForConfirmation() { - // there is a chance the previous tx goes into "waiting for confirmation" state - // during the increaseGasPrice call; continue waiting rather than resubmit the tx - return tx, false - } - - cCtx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout) - err := m.backend.SendTransaction(cCtx, tx) - cancel() - sendState.ProcessSendError(err) - - if err == nil { - return tx, true - } - - switch { - case errStringMatch(err, core.ErrNonceTooLow): - log.Warn(ctx, "Nonce too low", err) - case errStringMatch(err, context.Canceled) || errStringMatch(err, context.DeadlineExceeded): - log.Warn(ctx, "Transaction doSend canceled", err) - case errStringMatch(err, txpool.ErrAlreadyKnown): - log.Warn(ctx, "Resubmitted already known transaction", err) - case errStringMatch(err, txpool.ErrReplaceUnderpriced): - log.Warn(ctx, "Transaction replacement is underpriced", err) - continue // retry with fee bump - case errStringMatch(err, txpool.ErrUnderpriced): - log.Warn(ctx, "Transaction is underpriced", err) - continue // retry with fee bump - default: - log.Warn(ctx, "Unknown error publishing transaction", err) - } - - // on non-underpriced error return immediately; will retry on next resubmission timeout - return tx, false - } -} - -// waitForTx calls waitMined, and then sends the receipt to receiptChan in a non-blocking way if a receipt is found -// for the transaction. It should be called in a separate goroutine. -func (m *simple) waitForTx(ctx context.Context, tx *types.Transaction, sendState *SendState, receiptChan chan *types.Receipt) { - // Poll for the transaction to be ready & then doSend the result to receiptChan - receipt, err := m.waitMined(ctx, tx, sendState) - if ctx.Err() != nil { - return - } else if err != nil { - // this will happen if the tx was successfully replaced by a tx with bumped fees - log.Warn(ctx, "Transaction receipt not mined, probably replaced", err) - return - } - - select { - case receiptChan <- receipt: - default: - } -} - -// waitMined waits for the transaction to be mined or for the context to be canceled. -func (m *simple) waitMined(ctx context.Context, tx *types.Transaction, - sendState *SendState) (*types.Receipt, error) { - txHash := tx.Hash() - const logFreqFactor = 10 // Log every 10th attempt - attempt := 1 - - queryTicker := time.NewTicker(m.cfg.ReceiptQueryInterval) - defer queryTicker.Stop() - for { - select { - case <-ctx.Done(): - return nil, errors.Wrap(ctx.Err(), "context canceled") - case <-queryTicker.C: - receipt, ok, err := m.queryReceipt(ctx, txHash, sendState) - if err != nil { - return nil, err - } else if !ok && attempt%logFreqFactor == 0 { - log.Warn(ctx, "Transaction not yet mined", nil, "attempt", attempt) - } else if ok { - return receipt, nil - } - - attempt++ - } - } -} - -// queryReceipt queries for the receipt and returns the receipt if it has passed the confirmation depth. -func (m *simple) queryReceipt(ctx context.Context, txHash common.Hash, - sendState *SendState) (*types.Receipt, bool, error) { - ctx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout) - defer cancel() - receipt, err := m.backend.TransactionReceipt(ctx, txHash) - if err != nil { - if strings.Contains(err.Error(), "transaction indexing is in progress") { - return nil, false, nil // Just back off here - } else if errors.Is(err, ethereum.NotFound) || strings.Contains(err.Error(), ethereum.NotFound.Error()) { - sendState.TxNotMined(txHash) - return nil, false, nil - } - - return nil, false, err - } - - // Receipt is confirmed to be valid from this point on - sendState.TxMined(txHash) - - txHeight := receipt.BlockNumber.Uint64() - tip, err := m.backend.HeaderByNumber(ctx, nil) - if err != nil { - return nil, false, err - } - - // The transaction is considered confirmed when - // txHeight+numConfirmations-1 <= tipHeight. Note that the -1 is - // needed to account for the fact that confirmations have an - // inherent off-by-one, i.e. when using 1 confirmation the - // transaction should be confirmed when txHeight is equal to - // tipHeight. The equation is rewritten in this form to avoid - // underflow'm. - tipHeight := tip.Number.Uint64() - if txHeight+m.cfg.NumConfirmations <= tipHeight+1 { - return receipt, true, nil - } - - return nil, false, nil -} - -// increaseGasPrice returns a new transaction that is equivalent to the input transaction but with -// higher fees that should satisfy geth's tx replacement rules. It also computes an updated gas -// limit estimate. To avoid runaway price increases, fees are capped at a `feeLimitMultiplier` -// multiple of the suggested values. -func (m *simple) increaseGasPrice(ctx context.Context, tx *types.Transaction) (*types.Transaction, error) { - log.Debug(ctx, "Bumping gas price") - tip, baseFee, err := m.suggestGasPriceCaps(ctx) - if err != nil { - return nil, errors.Wrap(err, "failed to get gas price info", txFields(tx, true)...) - } - bumpedTip, bumpedFee := updateFees(ctx, tx.GasTipCap(), tx.GasFeeCap(), tip, baseFee) - - if err := m.checkLimits(tip, baseFee, bumpedTip, bumpedFee); err != nil { - return nil, err - } - - // Re-estimate gaslimit in case things have changed or a previous gaslimit estimate was wrong - gas, err := m.backend.EstimateGas(ctx, ethereum.CallMsg{ - From: m.cfg.From, - To: tx.To(), - GasTipCap: bumpedTip, - GasFeeCap: bumpedFee, - Data: tx.Data(), - Value: tx.Value(), - }) - if err != nil { - // If this is a transaction resubmission, we sometimes see this outcome because the - // original tx can get included in a block just before the above call. In this case the - // error is due to the tx reverting with message "block number must be equal to next - // expected block number" - log.Warn(ctx, "Failed to re-estimate gas", err, - "gas_limit", tx.Gas(), - "gas_fee_cap", bumpedFee, - "gas_tip_cap", bumpedTip, - ) - // just log and carry on - gas = tx.Gas() - } - if tx.Gas() != gas { - log.Debug(ctx, "Re-estimated gas differs", - "old_gas", tx.Gas(), - "new_gas", gas, - "gas_fee_cap", bumpedFee, - "gas_tip_cap", bumpedTip, - ) - } - - newTx := types.NewTx(&types.DynamicFeeTx{ - ChainID: tx.ChainId(), - Nonce: tx.Nonce(), - To: tx.To(), - GasTipCap: bumpedTip, - GasFeeCap: bumpedFee, - Value: tx.Value(), - Data: tx.Data(), - Gas: gas, - }) - - ctx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout) - defer cancel() - signedTx, err := m.cfg.Signer(ctx, m.cfg.From, newTx) - if err != nil { - return nil, err - } - - return signedTx, nil -} - -// suggestGasPriceCaps suggests what the new tip, base fee, and blob base fee should be based on -// the current L1 conditions. blobfee will be nil if 4844 is not yet active. -func (m *simple) suggestGasPriceCaps(ctx context.Context) (*big.Int, *big.Int, error) { - cCtx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout) - defer cancel() - tip, err := m.backend.SuggestGasTipCap(cCtx) - if err != nil { - return nil, nil, errors.Wrap(err, "failed to fetch the suggested gas tip cap") - } else if tip == nil { - return nil, nil, errors.New("the suggested tip was nil") - } - cCtx, cancel = context.WithTimeout(ctx, m.cfg.NetworkTimeout) - defer cancel() - head, err := m.backend.HeaderByNumber(cCtx, nil) - if err != nil { - return nil, nil, errors.Wrap(err, "failed to fetch the suggested base fee") - } else if head.BaseFee == nil { - return nil, nil, errors.New("txmgr does not support pre-london blocks that do not have a base fee") - } - - baseFee := head.BaseFee - - // Enforce minimum base fee and tip cap - if minTipCap := m.cfg.MinTipCap; minTipCap != nil && tip.Cmp(minTipCap) == -1 { - log.Debug(ctx, "Enforcing min tip cap", "min_tip_cap", m.cfg.MinTipCap, "orig_tip_cap", tip) - tip = new(big.Int).Set(m.cfg.MinTipCap) - } - if minBaseFee := m.cfg.MinBaseFee; minBaseFee != nil && baseFee.Cmp(minBaseFee) == -1 { - log.Debug(ctx, "Enforcing min base fee", "min_base_fee", m.cfg.MinBaseFee, "orig_base_fee", baseFee) - baseFee = new(big.Int).Set(m.cfg.MinBaseFee) - } - - return tip, baseFee, nil -} - -// checkLimits checks that the tip and baseFee have not increased by more than the configured multipliers -// if FeeLimitThreshold is specified in config, any increase which stays under the threshold are allowed. -func (m *simple) checkLimits(tip, baseFee, bumpedTip, bumpedFee *big.Int) error { - threshold := m.cfg.FeeLimitThreshold - limit := big.NewInt(int64(m.cfg.FeeLimitMultiplier)) - maxTip := new(big.Int).Mul(tip, limit) - maxFee := calcGasFeeCap(new(big.Int).Mul(baseFee, limit), maxTip) - var errs error - // generic check function to check tip and fee, and build up an error - check := func(v, maxValue *big.Int, name string) { - // if threshold is specified and the value is under the threshold, no need to check the max - if threshold != nil && threshold.Cmp(v) > 0 { - return - } - // if the value is over the max, add an error message - if v.Cmp(maxValue) > 0 { - errs = errors.New("bumped cap is over multiple of the suggested value", name, v, limit) - } - } - check(bumpedTip, maxTip, "tip") - check(bumpedFee, maxFee, "fee") - - return errs -} - -// calcThresholdValue returns ceil(x * priceBumpPercent / 100) for non-blob txs, or -// It guarantees that x is increased by at least 1. -func calcThresholdValue(x *big.Int) *big.Int { - threshold := new(big.Int) - ninetyNine := big.NewInt(99) - oneHundred := big.NewInt(100) - priceBumpPercent := big.NewInt(100 + PriceBump) - threshold.Set(priceBumpPercent) - - return threshold.Mul(threshold, x).Add(threshold, ninetyNine).Div(threshold, oneHundred) -} - -// updateFees takes an old transaction's tip & fee cap plus a new tip & base fee, and returns -// a suggested tip and fee cap such that: -// -// (a) each satisfies geth's required tx-replacement fee bumps, and -// (b) gasTipCap is no less than new tip, and -// (c) gasFeeCap is no less than calcGasFee(newBaseFee, newTip) -func updateFees(ctx context.Context, oldTip, oldFeeCap, newTip, newBaseFee *big.Int) (*big.Int, *big.Int) { - newFeeCap := calcGasFeeCap(newBaseFee, newTip) - log.Debug(ctx, "Updating fees", "old_gas_tip_cap", oldTip, "old_gas_fee_cap", oldFeeCap, - "new_gas_tip_cap", newTip, "new_gas_fee_cap", newFeeCap, "new_base_fee", newBaseFee) - thresholdTip := calcThresholdValue(oldTip) - thresholdFeeCap := calcThresholdValue(oldFeeCap) - if newTip.Cmp(thresholdTip) >= 0 && newFeeCap.Cmp(thresholdFeeCap) >= 0 { - log.Debug(ctx, "Using new tip and feecap") - return newTip, newFeeCap - } else if newTip.Cmp(thresholdTip) >= 0 && newFeeCap.Cmp(thresholdFeeCap) < 0 { - // Tip has gone up, but base fee is flat or down. - // TODO(CLI-3714): Do we need to recalculate the FC here? - log.Debug(ctx, "Using new tip and threshold feecap") - return newTip, thresholdFeeCap - } else if newTip.Cmp(thresholdTip) < 0 && newFeeCap.Cmp(thresholdFeeCap) >= 0 { - // Base fee has gone up, but the tip hasn't. Recalculate the feecap because if the tip went up a lot - // not enough of the feecap may be dedicated to paying the base fee. - log.Debug(ctx, "Using threshold tip and recalculated feecap") - return thresholdTip, calcGasFeeCap(newBaseFee, thresholdTip) - } - - log.Debug(ctx, "Using threshold tip and threshold feecap") - - return thresholdTip, thresholdFeeCap -} - -// calcGasFeeCap deterministically computes the recommended gas fee cap given -// the base fee and gasTipCap. The resulting gasFeeCap is equal to: -// -// gasTipCap + 2*baseFee. -func calcGasFeeCap(baseFee, gasTipCap *big.Int) *big.Int { - return new(big.Int).Add( - gasTipCap, - new(big.Int).Mul(baseFee, big.NewInt(2)), - ) -} - -// errStringMatch returns true if err.Error() is a substring in target.Error() or if both are nil. -// It can accept nil errors without issue. -func errStringMatch(err, target error) bool { - if err == nil && target == nil { - return true - } else if err == nil || target == nil { - return false - } - - return strings.Contains(err.Error(), target.Error()) -} - -// maybeSetTimeout returns a copy of the context with the timeout set if timeout is not zero. -// If the timeout is zero, it doesn't set it and just returns the context. -func maybeSetTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { - if timeout == 0 { - return ctx, func() {} - } - - return context.WithTimeout(ctx, timeout) -} diff --git a/lib/txmgr/txmgr_internal_test.go b/lib/txmgr/txmgr_internal_test.go deleted file mode 100644 index 3e127330..00000000 --- a/lib/txmgr/txmgr_internal_test.go +++ /dev/null @@ -1,1171 +0,0 @@ -package txmgr - -import ( - "context" - "errors" - "math/big" - "strconv" - "sync" - "testing" - "time" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/params" - "github.com/stretchr/testify/require" - - "github.com/piplabs/story/lib/ethclient" -) - -const ( - startingNonce uint64 = 1 // we pick something other than 0 so we can confirm nonces are getting set properly -) - -type sendTransactionFunc func(ctx context.Context, tx *types.Transaction) error - -func testSendState() *SendState { - return NewSendState(100, time.Hour) -} - -// testHarness houses the necessary resources to test the simple. -type testHarness struct { - cfg Config - mgr *simple - backend *mockBackend - gasPricer *gasPricer -} - -// newTestHarnessWithConfig initializes a testHarness with a specific -// configuration. -func newTestHarnessWithConfig(_ *testing.T, cfg Config) *testHarness { - g := newGasPricer(3) - backend := newMockBackend(g) - cfg.Backend = backend - mgr := &simple{ - chainID: cfg.ChainID, - chainName: "TEST", - cfg: cfg, - backend: cfg.Backend, - } - - return &testHarness{ - cfg: cfg, - mgr: mgr, - backend: backend, - gasPricer: g, - } -} - -// newTestHarness initializes a testHarness with a default configuration that is -// suitable for most tests. -func newTestHarness(t *testing.T) *testHarness { - t.Helper() - return newTestHarnessWithConfig(t, configWithNumConfs(1)) -} - -// createTxCandidate creates a mock [TxCandidate]. -func (h testHarness) createTxCandidate() TxCandidate { - inbox := common.HexToAddress("0x42000000000000000000000000000000000000ff") - return TxCandidate{ - To: &inbox, - TxData: []byte{0x00, 0x01, 0x02}, - GasLimit: uint64(1337), - } -} - -func configWithNumConfs(numConfirmations uint64) Config { - return Config{ - ResubmissionTimeout: time.Second, - ReceiptQueryInterval: 50 * time.Millisecond, - NumConfirmations: numConfirmations, - SafeAbortNonceTooLowCount: 3, - FeeLimitMultiplier: 5, - TxNotInMempoolTimeout: 1 * time.Hour, - Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) { - return tx, nil - }, - From: common.Address{}, - } -} - -type gasPricer struct { - epoch int64 - mineAtEpoch int64 - baseGasTipFee *big.Int - baseBaseFee *big.Int - excessBlobGas uint64 - err error - mu sync.RWMutex -} - -func newGasPricer(mineAtEpoch int64) *gasPricer { - return &gasPricer{ - mineAtEpoch: mineAtEpoch, - baseGasTipFee: big.NewInt(5), - baseBaseFee: big.NewInt(7), - // Simulate 100 excess blobs, which results in a blobBaseFee of 50 wei. This default means - // blob txs will be subject to the geth minimum blobgas fee of 1 gwei. - excessBlobGas: 100 * (params.BlobTxBlobGasPerBlob), - } -} - -func (g *gasPricer) getEpoch() int64 { - g.mu.RLock() - defer g.mu.RUnlock() - - return g.epoch -} - -func (g *gasPricer) expGasFeeCap() *big.Int { - _, gasFeeCap := g.feesForEpoch(g.mineAtEpoch) - return gasFeeCap -} - -func (g *gasPricer) shouldMine(gasFeeCap *big.Int) bool { - return g.expGasFeeCap().Cmp(gasFeeCap) <= 0 -} - -func (g *gasPricer) feesForEpoch(epoch int64) (*big.Int, *big.Int) { - e := big.NewInt(epoch) - epochBaseFee := new(big.Int).Mul(g.baseBaseFee, e) - epochGasTipCap := new(big.Int).Mul(g.baseGasTipFee, e) - epochGasFeeCap := calcGasFeeCap(epochBaseFee, epochGasTipCap) - - return epochGasTipCap, epochGasFeeCap -} - -func (g *gasPricer) baseFee() *big.Int { - return new(big.Int).Mul(g.baseBaseFee, big.NewInt(g.getEpoch())) -} - -func (g *gasPricer) sample() (*big.Int, *big.Int) { - g.mu.Lock() - defer g.mu.Unlock() - - g.epoch++ - epochGasTipCap, epochGasFeeCap := g.feesForEpoch(g.epoch) - - return epochGasTipCap, epochGasFeeCap -} - -type minedTxInfo struct { - gasFeeCap *big.Int - blobFeeCap *big.Int - blockNumber uint64 -} - -// mockBackend implements ReceiptSource that tracks mined transactions -// along with the gas price used. -type mockBackend struct { - ethclient.Client - mu sync.RWMutex - - g *gasPricer - send sendTransactionFunc - - // blockHeight tracks the current height of the chain. - blockHeight uint64 - - // minedTxs maps the hash of a mined transaction to its details. - minedTxs map[common.Hash]minedTxInfo -} - -// newMockBackend initializes a new mockBackend. -func newMockBackend(g *gasPricer) *mockBackend { - return &mockBackend{ - g: g, - minedTxs: make(map[common.Hash]minedTxInfo), - } -} - -// setTxSender sets the implementation for the sendTransactionFunction. -func (b *mockBackend) setTxSender(s sendTransactionFunc) { - b.send = s -} - -// mine records a (txHash, gasFeeCap) as confirmed. Subsequent calls to -// TransactionReceipt with a matching txHash will result in a non-nil receipt. -// If a nil txHash is supplied this has the effect of mining an empty block. -func (b *mockBackend) mine(txHash *common.Hash, gasFeeCap *big.Int) { - b.mu.Lock() - defer b.mu.Unlock() - - b.blockHeight++ - if txHash != nil { - b.minedTxs[*txHash] = minedTxInfo{ - gasFeeCap: gasFeeCap, - blockNumber: b.blockHeight, - } - } -} - -// BlockNumber returns the most recent block number. -func (b *mockBackend) BlockNumber(ctx context.Context) (uint64, error) { - b.mu.RLock() - defer b.mu.RUnlock() - - return b.blockHeight, nil -} - -func (b *mockBackend) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { - b.mu.RLock() - defer b.mu.RUnlock() - - num := big.NewInt(int64(b.blockHeight)) - if number != nil { - num.Set(number) - } - - bg := b.g.excessBlobGas + uint64(b.g.getEpoch()) - - return &types.Header{ - Number: num, - BaseFee: b.g.baseFee(), - ExcessBlobGas: &bg, - }, nil -} - -func (b *mockBackend) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { - if b.g.err != nil { - return 0, b.g.err - } - if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 { - return 0, core.ErrTipAboveFeeCap - } - - return b.g.baseFee().Uint64(), nil -} - -func (b *mockBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { - tip, _ := b.g.sample() - return tip, nil -} - -func (b *mockBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { - if b.send == nil { - panic("set sender function was not set") - } - - return b.send(ctx, tx) -} - -func (b *mockBackend) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { - return startingNonce, nil -} - -func (b *mockBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { - return startingNonce, nil -} - -func (*mockBackend) ChainID(ctx context.Context) (*big.Int, error) { - return big.NewInt(1), nil -} - -// TransactionReceipt queries the mockBackend for a mined txHash. If none is found, nil is returned -// for both return values. Otherwise, it returns a receipt containing the txHash, the gasFeeCap -// used in GasUsed, and the blobFeeCap in CumuluativeGasUsed to make the values accessible from our -// test framework. -func (b *mockBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { - b.mu.RLock() - defer b.mu.RUnlock() - - txInfo, ok := b.minedTxs[txHash] - if !ok { - return nil, errors.New("not found") - } - - // Return the gas fee cap for the transaction in the GasUsed field so that - // we can assert the proper tx confirmed in our tests. - var blobFeeCap uint64 - if txInfo.blobFeeCap != nil { - blobFeeCap = txInfo.blobFeeCap.Uint64() - } - - return &types.Receipt{ - TxHash: txHash, - GasUsed: txInfo.gasFeeCap.Uint64(), - CumulativeGasUsed: blobFeeCap, - BlockNumber: big.NewInt(int64(txInfo.blockNumber)), - }, nil -} - -func (b *mockBackend) Close() { -} - -// TestTxMgrConfirmAtMinGasPrice asserts that Send returns the min gas price tx -// if the tx is mined instantly. -func TestTxMgrConfirmAtMinGasPrice(t *testing.T) { - t.Parallel() - - h := newTestHarness(t) - - gasPricer := newGasPricer(1) - - gasTipCap, gasFeeCap := gasPricer.sample() - tx := types.NewTx(&types.DynamicFeeTx{ - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - }) - - sendTx := func(ctx context.Context, tx *types.Transaction) error { - if gasPricer.shouldMine(tx.GasFeeCap()) { - txHash := tx.Hash() - h.backend.mine(&txHash, tx.GasFeeCap()) - } - - return nil - } - h.backend.setTxSender(sendTx) - - ctx, cancel := context.WithTimeout(context.Background(), 250*time.Millisecond) - defer cancel() - receipt, err := h.mgr.sendTx(ctx, tx) - require.NoError(t, err) - require.NotNil(t, receipt) - require.Equal(t, gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed) -} - -// TestTxMgrNeverConfirmCancel asserts that a Send can be canceled even if no -// transaction is mined. This is done to ensure the tx mgr can properly -// abort on shutdown, even if a txn is in the process of being published. -func TestTxMgrNeverConfirmCancel(t *testing.T) { - // t.SkipNow() - t.Parallel() - - h := newTestHarness(t) - - gasTipCap, gasFeeCap := h.gasPricer.sample() - tx := types.NewTx(&types.DynamicFeeTx{ - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - }) - sendTx := func(ctx context.Context, tx *types.Transaction) error { - // Don't publish tx to backend, simulating never being mined. - return nil - } - h.backend.setTxSender(sendTx) - - ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) - defer cancel() - - receipt, err := h.mgr.sendTx(ctx, tx) - require.Error(t, err) - require.Nil(t, receipt) -} - -// TestTxMgrConfirmsAtMaxGasPrice asserts that Send properly returns the max gas -// price receipt if none of the lower gas price txs were mined. -func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) { - t.Parallel() - - h := newTestHarness(t) - - gasTipCap, gasFeeCap := h.gasPricer.sample() - tx := types.NewTx(&types.DynamicFeeTx{ - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - }) - sendTx := func(ctx context.Context, tx *types.Transaction) error { - if h.gasPricer.shouldMine(tx.GasFeeCap()) { - txHash := tx.Hash() - h.backend.mine(&txHash, tx.GasFeeCap()) - } - - return nil - } - h.backend.setTxSender(sendTx) - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - receipt, err := h.mgr.sendTx(ctx, tx) - require.NoError(t, err) - require.NotNil(t, receipt) - require.Equal(t, h.gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed) -} - -// errRPCFailure is a sentinel error used in testing to fail publications. -var errRPCFailure = errors.New("rpc failure") - -// TestTxMgrBlocksOnFailingRpcCalls asserts that if all the publication -// attempts fail due to rpc failures, that the tx manager will return -// ErrPublishTimeout. -func TestTxMgrBlocksOnFailingRpcCalls(t *testing.T) { - t.Parallel() - h := newTestHarness(t) - gasTipCap, gasFeeCap := h.gasPricer.sample() - orig := types.NewTx(&types.DynamicFeeTx{ - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - }) - errFirst := true - var sent *types.Transaction - sendTx := func(ctx context.Context, tx *types.Transaction) error { - if errFirst { - errFirst = false - return errRPCFailure - } - sent = tx - hash := tx.Hash() - h.backend.mine(&hash, tx.GasFeeCap()) - - return nil - } - h.backend.setTxSender(sendTx) - receipt, err := h.mgr.sendTx(context.Background(), orig) - require.NoError(t, err) - require.NotNil(t, receipt) - require.Equal(t, receipt.TxHash, sent.Hash()) - require.GreaterOrEqual(t, sent.GasTipCap().Uint64(), orig.GasTipCap().Uint64()) -} - -// TestTxMgr_CraftTx ensures that the tx manager will create transactions as expected. -func TestTxMgr_CraftTx(t *testing.T) { - t.Parallel() - h := newTestHarness(t) - candidate := h.createTxCandidate() - candidate.Nonce = uint64Ptr(startingNonce) - - // Craft the transaction. - gasTipCap, gasFeeCap := h.gasPricer.feesForEpoch(h.gasPricer.getEpoch() + 1) - tx, err := h.mgr.craftTx(context.Background(), candidate) - require.NoError(t, err) - require.NotNil(t, tx) - require.Equal(t, byte(types.DynamicFeeTxType), tx.Type()) - - // Validate the gas tip cap and fee cap. - require.Equal(t, gasTipCap, tx.GasTipCap()) - require.Equal(t, gasFeeCap, tx.GasFeeCap()) - - // Craft transaction uses candidate nonce. - require.EqualValues(t, startingNonce, tx.Nonce()) - - // Check that the gas was set using the gas limit. - require.Equal(t, candidate.GasLimit, tx.Gas()) -} - -// TestTxMgr_EstimateGas ensures that the tx manager will estimate -// the gas when candidate gas limit is zero in [craftTx]. -func TestTxMgr_EstimateGas(t *testing.T) { - t.Parallel() - h := newTestHarness(t) - candidate := h.createTxCandidate() - candidate.Nonce = uint64Ptr(startingNonce) - - // Set the gas limit to zero to trigger gas estimation. - candidate.GasLimit = 0 - - // Gas estimate - gasEstimate := h.gasPricer.baseBaseFee.Uint64() - - // Craft the transaction. - tx, err := h.mgr.craftTx(context.Background(), candidate) - require.NoError(t, err) - require.NotNil(t, tx) - - // Check that the gas was estimated correctly. - require.Equal(t, gasEstimate, tx.Gas()) -} - -func TestTxMgr_EstimateGasFails(t *testing.T) { - t.Parallel() - h := newTestHarness(t) - candidate := h.createTxCandidate() - - // Set the gas limit to zero to trigger gas estimation. - candidate.GasLimit = 0 - candidate.Nonce = uint64Ptr(startingNonce) - - // Craft a successful transaction. - tx, err := h.mgr.craftTx(context.Background(), candidate) - require.NoError(t, err) - require.EqualValues(t, startingNonce, tx.Nonce()) - - // Mock gas estimation failure. - h.gasPricer.err = errors.New("execution error") - _, err = h.mgr.craftTx(context.Background(), candidate) - require.ErrorContains(t, err, "failed to estimate gas") - - // Ensure successful craft uses the correct nonce - h.gasPricer.err = nil - tx, err = h.mgr.craftTx(context.Background(), candidate) - require.NoError(t, err) - require.EqualValues(t, startingNonce, tx.Nonce()) -} - -func TestTxMgr_SigningFails(t *testing.T) { - t.Parallel() - errorSigning := false - cfg := configWithNumConfs(1) - cfg.Signer = func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) { - if errorSigning { - return nil, errors.New("signer error") - } else { - return tx, nil - } - } - h := newTestHarnessWithConfig(t, cfg) - candidate := h.createTxCandidate() - - const nonce uint64 = 99 - candidate.Nonce = uint64Ptr(nonce) - // Set the gas limit to zero to trigger gas estimation. - candidate.GasLimit = 0 - - // Craft a successful transaction. - tx, err := h.mgr.craftTx(context.Background(), candidate) - require.NoError(t, err) - require.Equal(t, nonce, tx.Nonce()) - - // Mock signer failure. - errorSigning = true - _, err = h.mgr.craftTx(context.Background(), candidate) - require.ErrorContains(t, err, "signer error") - - // Ensure successful craft uses the correct nonce - errorSigning = false - tx, err = h.mgr.craftTx(context.Background(), candidate) - require.NoError(t, err) - require.Equal(t, nonce, tx.Nonce()) -} - -// TestTxMgrOnlyOnePublicationSucceeds asserts that the tx manager will return a -// receipt so long as at least one of the publications is able to succeed with a -// simulated rpc failure. -func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) { - t.Parallel() - - h := newTestHarness(t) - - gasTipCap, gasFeeCap := h.gasPricer.sample() - tx := types.NewTx(&types.DynamicFeeTx{ - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - }) - - sendTx := func(ctx context.Context, tx *types.Transaction) error { - // Fail all but the final attempt. - if !h.gasPricer.shouldMine(tx.GasFeeCap()) { - return errRPCFailure - } - - txHash := tx.Hash() - h.backend.mine(&txHash, tx.GasFeeCap()) - - return nil - } - h.backend.setTxSender(sendTx) - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - receipt, err := h.mgr.sendTx(ctx, tx) - require.NoError(t, err) - - require.NotNil(t, receipt) - require.Equal(t, h.gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed) -} - -// TestTxMgrConfirmsMinGasPriceAfterBumping delays the mining of the initial tx -// with the minimum gas price, and asserts that its receipt is returned even -// though if the gas price has been bumped in other goroutines. -func TestTxMgrConfirmsMinGasPriceAfterBumping(t *testing.T) { - t.Parallel() - - h := newTestHarness(t) - - gasTipCap, gasFeeCap := h.gasPricer.sample() - tx := types.NewTx(&types.DynamicFeeTx{ - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - }) - - sendTx := func(ctx context.Context, tx *types.Transaction) error { - // Delay mining the tx with the min gas price. - if h.gasPricer.shouldMine(tx.GasFeeCap()) { - time.AfterFunc(1*time.Second, func() { - txHash := tx.Hash() - h.backend.mine(&txHash, tx.GasFeeCap()) - }) - } - - return nil - } - h.backend.setTxSender(sendTx) - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - receipt, err := h.mgr.sendTx(ctx, tx) - require.NoError(t, err) - require.NotNil(t, receipt) - require.Equal(t, h.gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed) -} - -// TestTxMgrDoesntAbortNonceTooLowAfterMiningTx. -func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) { - t.Parallel() - - h := newTestHarnessWithConfig(t, configWithNumConfs(2)) - - gasTipCap, gasFeeCap := h.gasPricer.sample() - tx := types.NewTx(&types.DynamicFeeTx{ - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - }) - - sendTx := func(ctx context.Context, tx *types.Transaction) error { - switch { - // If the txn's gas fee cap is less than the one we expect to mine, - // accept the txn to the mempool. - case tx.GasFeeCap().Cmp(h.gasPricer.expGasFeeCap()) < 0: - return nil - - // Accept and mine the actual txn we expect to confirm. - case h.gasPricer.shouldMine(tx.GasFeeCap()): - txHash := tx.Hash() - h.backend.mine(&txHash, tx.GasFeeCap()) - time.AfterFunc(1*time.Second, func() { - h.backend.mine(nil, nil) - }) - - return nil - - // For gas prices greater than our expected, return ErrNonceTooLow since - // the prior txn confirmed and will invalidate subsequent publications. - default: - return core.ErrNonceTooLow - } - } - h.backend.setTxSender(sendTx) - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - receipt, err := h.mgr.sendTx(ctx, tx) - require.NoError(t, err) - require.NotNil(t, receipt) - require.Equal(t, h.gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed) -} - -// TestWaitMinedReturnsReceiptOnFirstSuccess insta-mines a transaction and -// asserts that waitMined returns the appropriate receipt. -func TestWaitMinedReturnsReceiptOnFirstSuccess(t *testing.T) { - t.Parallel() - - h := newTestHarnessWithConfig(t, configWithNumConfs(1)) - - // Create a tx and mine it immediately using the default backend. - tx := types.NewTx(&types.LegacyTx{}) - txHash := tx.Hash() - h.backend.mine(&txHash, new(big.Int)) - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - receipt, err := h.mgr.waitMined(ctx, tx, testSendState()) - require.NoError(t, err) - require.NotNil(t, receipt) - require.Equal(t, receipt.TxHash, txHash) -} - -// TestWaitMinedCanBeCanceled ensures that waitMined exits of the passed context -// is canceled before a receipt is found. -func TestWaitMinedCanBeCanceled(t *testing.T) { - t.Parallel() - - h := newTestHarness(t) - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) - defer cancel() - - // Create an unimined tx. - tx := types.NewTx(&types.LegacyTx{}) - - receipt, err := h.mgr.waitMined(ctx, tx, NewSendState(10, time.Hour)) - require.Error(t, err) - require.Nil(t, receipt) -} - -// TestWaitMinedMultipleConfs asserts that waitMined will properly wait for more -// than one confirmation. -func TestWaitMinedMultipleConfs(t *testing.T) { - t.Parallel() - - const numConfs = 2 - - h := newTestHarnessWithConfig(t, configWithNumConfs(numConfs)) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - - // Create an unimined tx. - tx := types.NewTx(&types.LegacyTx{}) - txHash := tx.Hash() - h.backend.mine(&txHash, new(big.Int)) - - receipt, err := h.mgr.waitMined(ctx, tx, NewSendState(10, time.Hour)) - require.Error(t, err) - require.Nil(t, receipt) - - ctx, cancel = context.WithTimeout(context.Background(), time.Second) - defer cancel() - - // Mine an empty block, tx should now be confirmed. - h.backend.mine(nil, nil) - receipt, err = h.mgr.waitMined(ctx, tx, NewSendState(10, time.Hour)) - require.NoError(t, err) - require.NotNil(t, receipt) - require.Equal(t, txHash, receipt.TxHash) -} - -// failingBackend implements ReceiptSource, returning a failure on the -// first call but a success on the second call. This allows us to test that the -// inner loop of waitMined properly handles this case. -type failingBackend struct { - ethclient.Client - returnSuccessBlockNumber bool - returnSuccessHeader bool - returnSuccessReceipt bool - baseFee, gasTip *big.Int - excessBlobGas *uint64 -} - -// BlockNumber for the failingBackend returns errRPCFailure on the first -// invocation and a fixed block height on subsequent calls. -func (b *failingBackend) BlockNumber(_ context.Context) (uint64, error) { - if !b.returnSuccessBlockNumber { - b.returnSuccessBlockNumber = true - return 0, errRPCFailure - } - - return 1, nil -} - -// TransactionReceipt for the failingBackend returns errRPCFailure on the first -// invocation, and a receipt containing the passed TxHash on the second. -func (b *failingBackend) TransactionReceipt( - _ context.Context, txHash common.Hash, -) (*types.Receipt, error) { - if !b.returnSuccessReceipt { - b.returnSuccessReceipt = true - return nil, ethereum.NotFound - } - - return &types.Receipt{ - TxHash: txHash, - BlockNumber: big.NewInt(1), - }, nil -} - -func (b *failingBackend) HeaderByNumber(_ context.Context, _ *big.Int) (*types.Header, error) { - return &types.Header{ - Number: big.NewInt(1), - BaseFee: b.baseFee, - ExcessBlobGas: b.excessBlobGas, - }, nil -} - -func (b *failingBackend) SuggestGasTipCap(_ context.Context) (*big.Int, error) { - return b.gasTip, nil -} - -func (b *failingBackend) EstimateGas(_ context.Context, msg ethereum.CallMsg) (uint64, error) { - return b.baseFee.Uint64(), nil -} - -// TestWaitMinedReturnsReceiptAfterNotFound asserts that waitMined is able to -// recover from failed calls to the backend. It uses the failedBackend to -// simulate an rpc call failure, followed by the successful return of a receipt. -func TestWaitMinedReturnsReceiptAfterNotFound(t *testing.T) { - t.Parallel() - - var borkedBackend failingBackend - - mgr := &simple{ - cfg: Config{ - ResubmissionTimeout: time.Second, - ReceiptQueryInterval: 50 * time.Millisecond, - NumConfirmations: 1, - SafeAbortNonceTooLowCount: 3, - }, - chainName: "TEST", - backend: &borkedBackend, - } - - // Don't mine the tx with the default backend. The failingBackend will - // return the txHash on the second call. - tx := types.NewTx(&types.LegacyTx{}) - txHash := tx.Hash() - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - receipt, err := mgr.waitMined(ctx, tx, testSendState()) - require.NoError(t, err) - require.NotNil(t, receipt) - require.Equal(t, receipt.TxHash, txHash) -} - -func doGasPriceIncrease(_ *testing.T, txTipCap, txFeeCap, newTip, - newBaseFee int64) (*types.Transaction, *types.Transaction, error) { - borkedBackend := failingBackend{ - gasTip: big.NewInt(newTip), - baseFee: big.NewInt(newBaseFee), - returnSuccessHeader: true, - } - - mgr := &simple{ - cfg: Config{ - ResubmissionTimeout: time.Second, - ReceiptQueryInterval: 50 * time.Millisecond, - NumConfirmations: 1, - SafeAbortNonceTooLowCount: 3, - FeeLimitMultiplier: 5, - Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) { - return tx, nil - }, - From: common.Address{}, - }, - chainName: "TEST", - backend: &borkedBackend, - } - - tx := types.NewTx(&types.DynamicFeeTx{ - GasTipCap: big.NewInt(txTipCap), - GasFeeCap: big.NewInt(txFeeCap), - }) - newTx, err := mgr.increaseGasPrice(context.Background(), tx) - - return tx, newTx, err -} - -func TestIncreaseGasPrice(t *testing.T) { - t.Parallel() - require.Equal(t, int64(10), PriceBump, "test must be updated if priceBump is adjusted") - tests := []struct { - name string - run func(t *testing.T) - }{ - { - name: "bump at least 1", - run: func(t *testing.T) { - t.Helper() - tx, newTx, err := doGasPriceIncrease(t, 1, 3, 1, 1) - require.Greater(t, newTx.GasFeeCap().Uint64(), tx.GasFeeCap().Uint64(), "new tx fee cap must be larger") - require.Greater(t, newTx.GasTipCap().Uint64(), tx.GasTipCap().Uint64(), "new tx tip must be larger") - require.NoError(t, err) - }, - }, - { - name: "enforces min bump", - run: func(t *testing.T) { - t.Helper() - tx, newTx, err := doGasPriceIncrease(t, 100, 1000, 101, 460) - require.Greater(t, newTx.GasFeeCap().Uint64(), tx.GasFeeCap().Uint64(), "new tx fee cap must be larger") - require.Greater(t, newTx.GasTipCap().Uint64(), tx.GasTipCap().Uint64(), "new tx tip must be larger") - require.NoError(t, err) - }, - }, - { - name: "enforces min bump on only tip increase", - run: func(t *testing.T) { - t.Helper() - tx, newTx, err := doGasPriceIncrease(t, 100, 1000, 101, 440) - require.Greater(t, newTx.GasFeeCap().Uint64(), tx.GasFeeCap().Uint64(), "new tx fee cap must be larger") - require.Greater(t, newTx.GasTipCap().Uint64(), tx.GasTipCap().Uint64(), "new tx tip must be larger") - require.NoError(t, err) - }, - }, - { - name: "enforces min bump on only base fee increase", - run: func(t *testing.T) { - t.Helper() - tx, newTx, err := doGasPriceIncrease(t, 100, 1000, 99, 460) - require.Greater(t, newTx.GasFeeCap().Uint64(), tx.GasFeeCap().Uint64(), "new tx fee cap must be larger") - require.Greater(t, newTx.GasTipCap().Uint64(), tx.GasTipCap().Uint64(), "new tx tip must be larger") - require.NoError(t, err) - }, - }, - { - name: "uses L1 values when larger", - run: func(t *testing.T) { - t.Helper() - _, newTx, err := doGasPriceIncrease(t, 10, 100, 50, 200) - require.Equal(t, newTx.GasFeeCap().Uint64(), big.NewInt(450).Uint64(), "new tx fee cap must be equal L1") - require.Equal(t, newTx.GasTipCap().Uint64(), big.NewInt(50).Uint64(), "new tx tip must be equal L1") - require.NoError(t, err) - }, - }, - { - name: "uses L1 tip when larger and threshold FC", - run: func(t *testing.T) { - t.Helper() - _, newTx, err := doGasPriceIncrease(t, 100, 2200, 120, 1050) - require.Equal(t, newTx.GasTipCap().Uint64(), big.NewInt(120).Uint64(), "new tx tip must be equal L1") - require.Equal(t, newTx.GasFeeCap().Uint64(), big.NewInt(2420).Uint64(), - "new tx fee cap must be equal to the threshold value") - require.NoError(t, err) - }, - }, - { - name: "bumped fee above multiplier limit", - run: func(t *testing.T) { - t.Helper() - _, _, err := doGasPriceIncrease(t, 1, 9999, 1, 1) - require.ErrorContains(t, err, "bumped cap") - require.NotContains(t, err.Error(), "tip cap") - }, - }, - { - name: "bumped tip above multiplier limit", - run: func(t *testing.T) { - t.Helper() - _, _, err := doGasPriceIncrease(t, 9999, 0, 0, 9999) - require.ErrorContains(t, err, "bumped cap") - require.NotContains(t, err.Error(), "fee cap") - }, - }, - { - name: "bumped fee and tip above multiplier limit", - run: func(t *testing.T) { - t.Helper() - _, _, err := doGasPriceIncrease(t, 9999, 9999, 1, 1) - require.ErrorContains(t, err, "bumped cap") - }, - }, - { - name: "uses L1 FC when larger and threshold tip", - run: func(t *testing.T) { - t.Helper() - _, newTx, err := doGasPriceIncrease(t, 100, 2200, 100, 2000) - require.Equal(t, big.NewInt(110).Uint64(), - newTx.GasTipCap().Uint64(), "new tx tip must be equal the threshold value") - require.Equal(t, big.NewInt(4110).Uint64(), - newTx.GasFeeCap().Uint64(), "new tx fee cap must be equal L1") - - require.NoError(t, err) - }, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - test.run(t) - }) - } -} - -// TestIncreaseGasPriceLimits asserts that if the L1 base fee & tip remain the -// same, repeated calls to increaseGasPrice eventually hit a limit. -func TestIncreaseGasPriceLimits(t *testing.T) { - t.Parallel() - t.Run("no-threshold", func(t *testing.T) { - t.Parallel() - testIncreaseGasPriceLimit(t, gasPriceLimitTest{ - expTipCap: 46, - expFeeCap: 354, // just below 5*100 - expBlobFeeCap: 4 * params.GWei, - }) - }) - t.Run("with-threshold", func(t *testing.T) { - t.Parallel() - testIncreaseGasPriceLimit(t, gasPriceLimitTest{ - thr: big.NewInt(params.GWei * 10), - expTipCap: 1_293_535_754, - expFeeCap: 9_192_620_686, // just below 10 gwei - expBlobFeeCap: 8 * params.GWei, - }) - }) -} - -type gasPriceLimitTest struct { - thr *big.Int - expTipCap, expFeeCap int64 - expBlobFeeCap int64 -} - -// testIncreaseGasPriceLimit runs a gas bumping test that increases the gas price until it hits an error. -// It starts with a tx that has a tip cap of 10 wei and fee cap of 100 wei. -func testIncreaseGasPriceLimit(t *testing.T, lt gasPriceLimitTest) { - t.Helper() - - borkedTip := int64(10) - borkedFee := int64(45) - // simulate 100 excess blobs which yields a 50 wei blob base fee - borkedExcessBlobGas := uint64(100 * params.BlobTxBlobGasPerBlob) - borkedBackend := failingBackend{ - gasTip: big.NewInt(borkedTip), - baseFee: big.NewInt(borkedFee), - excessBlobGas: &borkedExcessBlobGas, - returnSuccessHeader: true, - } - - mgr := &simple{ - cfg: Config{ - ResubmissionTimeout: time.Second, - ReceiptQueryInterval: 50 * time.Millisecond, - NumConfirmations: 1, - SafeAbortNonceTooLowCount: 3, - FeeLimitMultiplier: 5, - FeeLimitThreshold: lt.thr, - Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) { - return tx, nil - }, - From: common.Address{}, - }, - chainName: "TEST", - backend: &borkedBackend, - } - lastGoodTx := types.NewTx(&types.DynamicFeeTx{ - GasTipCap: big.NewInt(10), - GasFeeCap: big.NewInt(100), - }) - - // Run increaseGasPrice a bunch of times in a row to simulate a very fast resubmit loop to make - // sure it errors out without a runaway fee increase. - ctx := context.Background() - var err error - for { - var tmpTx *types.Transaction - tmpTx, err = mgr.increaseGasPrice(ctx, lastGoodTx) - if err != nil { - break - } - lastGoodTx = tmpTx - } - require.Error(t, err) - - // Confirm that fees only rose until expected threshold - require.Equal(t, lt.expTipCap, lastGoodTx.GasTipCap().Int64()) - require.Equal(t, lt.expFeeCap, lastGoodTx.GasFeeCap().Int64()) -} - -func TestErrStringMatch(t *testing.T) { - t.Parallel() - tests := []struct { - err error - target error - match bool - }{ - {err: nil, target: nil, match: true}, - {err: errors.New("exists"), target: nil, match: false}, - {err: nil, target: errors.New("exists"), match: false}, - {err: errors.New("exact match"), target: errors.New("exact match"), match: true}, - {err: errors.New("partial: match"), target: errors.New("match"), match: true}, - } - - for i, test := range tests { - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - require.Equal(t, test.match, errStringMatch(test.err, test.target)) - }) - } -} - -func TestNonceReset(t *testing.T) { - t.Parallel() - conf := configWithNumConfs(1) - conf.SafeAbortNonceTooLowCount = 1 - h := newTestHarnessWithConfig(t, conf) - - index := -1 - var nonces []uint64 - sendTx := func(ctx context.Context, tx *types.Transaction) error { - index++ - nonces = append(nonces, tx.Nonce()) - // fail every 3rd tx - if index%3 == 0 { - return core.ErrNonceTooLow - } - txHash := tx.Hash() - h.backend.mine(&txHash, tx.GasFeeCap()) - - return nil - } - h.backend.setTxSender(sendTx) - - ctx := context.Background() - for i := range 8 { - _, _, err := h.mgr.Send(ctx, TxCandidate{ - To: &common.Address{}, - }) - // expect every 3rd tx to fail - if i%3 == 0 { - require.Error(t, err) - } else { - require.NoError(t, err) - } - } - - // internal nonce tracking should be reset to startingNonce value every 3rd tx - require.Equal(t, []uint64{1, 1, 2, 3, 1, 2, 3, 1}, nonces) -} - -func TestMinFees(t *testing.T) { - t.Parallel() - for _, tt := range []struct { - desc string - minBaseFee *big.Int - minTipCap *big.Int - expectMinBaseFee bool - expectMinTipCap bool - }{ - { - desc: "no-mins", - }, - { - desc: "high-min-basefee", - minBaseFee: big.NewInt(10_000_000), - expectMinBaseFee: true, - }, - { - desc: "high-min-tipcap", - minTipCap: big.NewInt(1_000_000), - expectMinTipCap: true, - }, - { - desc: "high-mins", - minBaseFee: big.NewInt(10_000_000), - minTipCap: big.NewInt(1_000_000), - expectMinBaseFee: true, - expectMinTipCap: true, - }, - { - desc: "low-min-basefee", - minBaseFee: big.NewInt(1), - }, - { - desc: "low-min-tipcap", - minTipCap: big.NewInt(1), - }, - { - desc: "low-mins", - minBaseFee: big.NewInt(1), - minTipCap: big.NewInt(1), - }, - } { - t.Run(tt.desc, func(t *testing.T) { - t.Parallel() - conf := configWithNumConfs(1) - conf.MinBaseFee = tt.minBaseFee - conf.MinTipCap = tt.minTipCap - h := newTestHarnessWithConfig(t, conf) - - tip, baseFee, err := h.mgr.suggestGasPriceCaps(context.TODO()) - require.NoError(t, err) - - if tt.expectMinBaseFee { - require.Equal(t, tt.minBaseFee, baseFee, "expect suggested base fee to equal MinBaseFee") - } else { - require.Equal(t, h.gasPricer.baseBaseFee, baseFee, "expect suggested base fee to equal mock base fee") - } - - if tt.expectMinTipCap { - require.Equal(t, tt.minTipCap, tip, "expect suggested tip to equal MinTipCap") - } else { - require.Equal(t, h.gasPricer.baseGasTipFee, tip, "expect suggested tip to equal mock tip") - } - }) - } -} - -func uint64Ptr(i uint64) *uint64 { - return &i -}