{"id":27026941,"url":"https://github.com/sajjon/gastimator","last_synced_at":"2025-04-04T23:16:32.862Z","repository":{"id":285398632,"uuid":"957857413","full_name":"Sajjon/gastimator","owner":"Sajjon","description":"Gas Estimator for Ethereum running in as a REST server","archived":false,"fork":false,"pushed_at":"2025-03-31T13:58:08.000Z","size":1531,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-31T14:50:47.552Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Sajjon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-03-31T08:51:27.000Z","updated_at":"2025-03-31T13:58:12.000Z","dependencies_parsed_at":"2025-03-31T14:51:19.875Z","dependency_job_id":"ace10648-074c-48e8-baf7-6df1e8be3731","html_url":"https://github.com/Sajjon/gastimator","commit_stats":null,"previous_names":["sajjon/gastimator"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2Fgastimator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2Fgastimator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2Fgastimator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2Fgastimator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Sajjon","download_url":"https://codeload.github.com/Sajjon/gastimator/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247261591,"owners_count":20910108,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-04-04T23:16:32.245Z","updated_at":"2025-04-04T23:16:32.856Z","avatar_url":"https://github.com/Sajjon.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003e [!TIP]\n\u003e I'm happy with the code quality, tooling, testing and documentation of this\n\u003e solution, however, I'm not happy with the precision, or rather lack of, when\n\u003e it comes to estimates. Especially estimating TX making smart contract calls,\n\u003e was hard, and the estimates are sometimes completely off. I hope you judge not\n\u003e only the gas estimate values, but also the code, tooling, testing and doc.\n\u003e\n\u003e Thank you for looking at my solution!\n\n# Interpretation of Assignment\n\nI've interpret the assignment such that you care about the amount of gas used\nfor a particular Ethereum transaction. Not the amount of Eth paid in fees. So my\nsolution is gas price agnostic, meaning it is does not use any [EIP-1559][eip15519]\nfields - `max_priority_fee_per_gas` / `max_fee_per_gas` - as those are not relevant\nfor gas usage estimation.\n\nFurthermore I've interpreted the assignemnt such that I'm allowed to use [\nRPC method `eth_estimateGas` with the Alchemy API][alchemy] and that I'm allowed to\nuse [`revm`][revm] for estimates too.\n\n# Setup\n\nTo setup development see [SETUP.md](SETUP.md)\n\n# Design\n\nI've split the solution into three crates:\n\n-   `gastimate` - the binary (tiny crate)\n-   `gastimator-rest` - a REST server (small crate)\n-   `gastimator` - a library, with the logic and models (medium sized crate)\n\n## CLI\n\n`gastimate` uses [`clap`][clap] to start the server in `gastimator-rest` using `async fn run(config: \u0026Config)`.\n\n## REST Server\n\n`gastimator-rest` uses [`axum`][axum] to spin up a REST server, and uses `gastimator`.\n\n## Logic\n\n`gastimator` has two key components:\n\n-   **Local** transaction simulation with gas estimation using [`revm`][revm]\n-   **Remote** gas estimation using [Alchemy's RPC method `eth_estimateGas`][alchemy]\n\nI'm using [`reqwest`][reqwest] to build a small RPC client consuming the Alchemy API.\n\nIf both a _local_ and _remote_ estimate was successfully obtained, the max value of the two\nis returned as the estimate.\n\n### Request\n\nYou can send requests to this software, the `gastimate` binary, using two different\nformats. Either send a `Transaction` objects, or send a `RawTransaction` with an\n[`rlp` (Recursive Length Prefix, a binary encoding)][rlp] hex string, see below:\n\n#### Transaction Request Model\n\n```rust\nstruct Transaction {\n    nonce: Option\u003cu64\u003e,\n    from: Option\u003cAddress\u003e,\n    to: TxKind,\n    value: U256,\n    gas_limit: Option\u003cGas\u003e,\n    input: Bytes,\n}\n```\n\n#### `rlp`\n\nYou can find the RLP [by navigating to a TX on `Etherscan`][etherscan] and then clicking\nthe list button on the right hand side and then `\"Get Raw Tx Hex\"`, [see screenshot](.github/etherscan_get_rlp.png). The pass the value into:\n\n```rust\nstruct RawTransaction {\n    rlp: Bytes\n}\n```\n\n### `gas_limit`\n\n`gas_limit` you typically don't need to or ought to specify. The purpose of this\nsoftware is to provide you with an estimate.\n\nIf you do provide a value, and if it is too low, e.g. `10` for a simple ETH transfer,\nwhich requires `21,000` gas, `gastimator` will return an error:\n`GasExceedsLimit { estimated_cost: Some(Gas(21000)), gas_limit: Gas(10) }`\n\n### Caching\n\nIf **both** `nonce` and `from` is set I will try to read a previous gas estimate from\na cache, since same value of `(nonce, from)` tuple ought to mean it is the same\ntransaction.\n\nI use a newtype around [`dashmap`][dashmap] for cache, which is fast concurrent map in Rust,\nessentially a drop-in-replacement for `RwLock\u003cHashMap\u003c_, _\u003e\u003e`.\n\nBut I use the `Transaction` in **its entirety** as a cache key, meaning\nif you for example send a similar transaction but other value of `gas_limit` it will\nbe a cache miss. I do not cache transaction which lacks either `nonce` or `from`.\n\n## Code Style\n\nThe code style uses builder pattern with [`derive_builder` crate](https://crates.io/crates/derive_builder) ensuring code is easy to review on Github (does not rely on\nIDE rendered labels, which are omitted from source code) which also performs `Into`\nconversion. Fields are read by reference using [`getset` crate](https://crates.io/crates/getset).\n\n## Safety \u0026 Security\n\nAll cargo (supported) commands should be run using `--locked` flag, ensuring that\nthe lock file is indeed used, thus protecting us from supply chain attacks.\n\nSecrets are read using [`direnv`](https://direnv.net/) and not put in plaintext (duh!).\n\n## Testing\n\nTesting is done with integration tests (see [`integration.rs`](./crates/rest-server/tests/integration.rs)) and unit tests, some unit test use [`insta` crate](https://crates.io/crates/insta) for\nJSON snapshot tests.\n\n# Requirements\n\n\u003e [!IMPORTANT]\n\u003e You MUST have an Alchemy API key, either [create an account](https://auth.alchemy.com/signup) (there is a FREE plan)\n\u003e or ask Alex Cyon for his.\n\u003e Either export the key as an environment variable named `ALCHEMY_API_KEY` (see [SETUP](SETUP.md))\n\u003e or pass it as an argument like so `cargo run --release --locked -- --key \u003cKEY_HERE\u003e`\n\n## Make commands\n\nIf you have exported `ALCHEMY_API_KEY` (either direclly in your shell or inside an `.envrc.secret` (gitignored)) you can use these `make` commands.\n\n### Run\n\n```sh\nmake run\n```\n\nand then in another shell, you can send a request to the locally running server, typically (using `rlp`):\n\n```sh\ncurl http://0.0.0.0:3000/rlp -X POST \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"rlp\": \"02f902db01820168841dcd6500843d831e6783027a6d9466a9893cc07d91d95644aedd05d03f95e1dba8af8803bbae1324948000b9026424856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000003bbae1324948000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000096cdea52111684fd74ec6cdf31dd97f395737a5d00000000000000000000000000000000000000000000000003bbae132494800000000000000000000000000000000000000000000000001e28ba62f4e8c66e7b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000aff507ac29b8cea2fb10d2ad14408c2d79a35adc001a0ff907e592e412943d4f7136aeb55f9fcf701ef295c7cd620be07ffe037de5b58a05889a72e62156c986ccc657bcdba1f91c481e817ee607408320d312416fa3a67\"}'\n```\n\nAnd you should see something like:\n\n```sh\n{\"gas_usage\":{\"estimate\":{\"kind\":{\"contract_call\":{\"with_native_token_transfer\":true}},\"gas\":147649}},\"time_elapsed_in_millis\":133}\n```\n\nwhere `147649` is the estimated gas usage.\n\nOr alternatively if you wanna pass the transaction using individual JSON fields:\n\n```sh\ncurl http://0.0.0.0:3000/tx -X POST \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"nonce\": 360,\n  \"to\": \"0x66a9893cc07d91d95644aedd05d03f95e1dba8af\",\n  \"value\": \"0x3bbae1324948000\",\n  \"input\": \"0x24856bc30000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000020b080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000003bbae1324948000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000096cdea52111684fd74ec6cdf31dd97f395737a5d00000000000000000000000000000000000000000000000003bbae132494800000000000000000000000000000000000000000000000001e28ba62f4e8c66e7b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000aff507ac29b8cea2fb10d2ad14408c2d79a35ad\"\n}'\n```\n\nOr a simple ETH transfer (and `value` as decimal):\n\n```sh\ncurl http://0.0.0.0:3000/tx -X POST \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"to\": \"0x11a9893cc07d91d95644aedd05d03f95e1dbaccd\",\n  \"value\": \"123456789\"\n}'\n```\n\n\u003e [!NOTE]\n\u003e Note the different endpoints `/rlp` vs `/tx`\n\u003e Also make sure you use the correct port if you specified another\n\u003e port using `--port` flag.\n\n\u003e [!NOTE]\n\u003e Note that `value` is in wei, not in full Eth.\n\n#### Gas limit\n\nIf you set a too low gas limit on your transaction, e.g something less than `21_000` for\na simple ETH transfer:\n\n```sh\n$ curl http://0.0.0.0:3000/tx -X POST \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"to\": \"0x11a9893cc07d91d95644aedd05d03f95e1dbaccd\",\n  \"value\": \"123456789\", \"gas_limit\": 1\n}'\n```\n\nthen error `GasExceedsLimit` is returned:\n\n```\nGasExceedsLimit { estimated_cost: Some(Gas(21000)), gas_limit: Gas(1) }\n```\n\n### Test\n\nYou _MUST_ export `ALCHEMY_API_KEY` variable to run **integration** tests:\n\n```sh\nmake itest\n```\n\nYou can run unit tests without `ALCHEMY_API_KEY`:\n\n```sh\nmake utest\n```\n\n## Run by passing `ALCHEMY_API_KEY` as argument\n\n```sh\ncargo run --release --locked -- --key \u003cALCHEMY_API_KEY\u003e\n```\n\n# Help\n\nSee help, for which arguments to pass:\n\n```sh\nmake help\n```\n\n[clap]: https://crates.io/crates/clap\n[axum]: https://crates.io/crates/axum\n[revm]: https://crates.io/crates/revm\n[reqwest]: https://crates.io/crates/reqwest\n[alchemy]: https://docs.alchemy.com/reference/eth-estimategas\n[etherscan]: https://etherscan.io/tx/0x6e9710bc55d7498934c22e9accad4c11810f6e86f51e1d6def3d750026cae1ab\n[rlp]: https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/\n[dashmap]: https://crates.io/crates/dashmap\n[eip15519]: https://eips.ethereum.org/EIPS/eip-1559\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsajjon%2Fgastimator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsajjon%2Fgastimator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsajjon%2Fgastimator/lists"}