Coder Social home page Coder Social logo

starknet-rs's Introduction

Logo

starknet-rs

Complete Starknet library in Rust

starknet-version-v0.13.0 jsonrpc-spec-v0.7.1 linting-badge crates-badge

Note that starknet-rs is still experimental. Breaking changes will be made before the first stable release. The library is also NOT audited or reviewed for security at the moment. Use at your own risk.

The underlying cryptography library starknet-crypto does NOT provide constant-time guarantees.

Adding starknet-rs to your project

To use the crate from crates.io, add the following to your Cargo.toml file:

[dependencies]
starknet = "0.11.0"

Note that the crates.io version might be outdated. You may want to use the library directly from GitHub for all the latest features and fixes:

[dependencies]
starknet = { git = "https://github.com/xJonathanLEI/starknet-rs" }

Features

  • Sequencer gateway / feeder gateway client
  • Full node JSON-RPC API client
  • Smart contract deployment
  • Signer for using IAccount account contracts
  • Strongly-typed smart contract binding code generation from ABI
  • Ledger hardware wallet support

Crates

This workspace contains the following crates:

  • starknet: Re-export of other sub-crates (recommended)
  • starknet-core: Core data structures for interacting with Starknet
  • starknet-providers: Abstraction and implementation of clients for interacting with Starknet nodes and sequencers
  • starknet-contract: Types for deploying and interacting with Starknet smart contracts
  • starknet-crypto: Low-level cryptography utilities for Starknet
  • starknet-signers: Starknet signer implementations
  • starknet-accounts: Types for handling Starknet account abstraction
  • starknet-curve: Starknet curve operations
  • starknet-macros: Useful macros for using the starknet crates

WebAssembly

starknet-rs can be used as a WebAssembly module. Check out this example.

Using starknet-rs from C++

starknet-rs can be used as a dynamic or static library from C++. Check out this example.

Performance

Benchmark results for native and WebAssembly targets are available for these crates:

For instructions on running the benchmark yourself, check here.

Example

Examples can be found in the examples folder:

  1. Get the latest block from alpha-sepolia testnet

  2. Deploy contract to alpha-sepolia testnet via UDC

  3. Mint yourself 1,000 TST tokens on alpha-sepolia

    Make sure your account has some L2 Sepolia ETH to pay for the transaction fee.

  4. Declare Cairo 1 contract on alpha-sepolia testnet

    Make sure your account has some L2 Sepolia ETH to pay for the transaction fee.

  5. Declare legacy Cairo 0 contract on alpha-sepolia testnet

    Make sure your account has some L2 Sepolia ETH to pay for the transaction fee.

  6. Query the latest block number with JSON-RPC

  7. Call a contract view function

  8. Deploy an Argent X account to a pre-funded address

  9. Inspect public key with Ledger

  10. Deploy an OpenZeppelin account with Ledger

  11. Parsing a JSON-RPC request on the server side

  12. Inspecting a erased provider-specific error type

License

Licensed under either of

at your option.

starknet-rs's People

Contributors

apoorvsadana avatar clint419 avatar dependabot[bot] avatar exp-table avatar fracek avatar glihm avatar greged93 avatar jbcaron avatar jsdanielh avatar kariy avatar milancermak avatar ofux avatar omahs avatar omerfirmak avatar oppen avatar ponderingdemocritus avatar rhobro avatar tarrencev avatar tcoratger avatar tdelabro avatar xjonathanlei avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

starknet-rs's Issues

Run tests on OS X and Windows

Since we're building a library, we want to make sure it is available on all mainstream dev platforms. We're currently running the test pipeline only on Linux. Add OS X and Windows too.

Replace `ff` with another library

Follow-up issue of #41. We can't publish anything to crates.io at the moment due to the ff issue.

Equilibrium opened zkcrypto/ff#72 and zkcrypto/ff#73, but unfortunately the repo seems to be inactive.

Given the situation, we should probably start experimenting with replacing ff with an alternative library. Doing so will introduce a breaking change to starknet-crypto but not any other crates.

Release `starknet-crypto` - v0.1.0?

With #38, #39, and #40 merged, we've implemented all high-level crypto functions necessary for building a signer, which means the underlying starknet-crypto library is up to its job. Shall we release a v0.1.0 to crates.io?

@milancermak What do you think?

Duplicate InvokeFunction types

Looks like starknet_core::types::transaction_request::InvokeFunctionTransaction is literally the same as starknet_core::types::call_contract::CallContractResult. We should remove the latter.

Add buffer to `max_fee` from fee estimate

Quoting StarkWare:

Hey guys! quick note about fees following some trouble we had with rejected transactions.

Currently the fee charged is determined by the marginal L1 cost (proof + on chain data), and is thus measured in gas. The
estimate_fee endpoint returns the price in wei, using the current gas price.

If no buffer is used between the result of estimate_fee and max_fee used for the transaction, then a negligible change in gas prices can cause the transaction to be rejected, as the estimated fee at the time of sequencer execution will be higher than max_fee. We suggest, in the different libraries, to set max_fee=1.1*estimated_fee. Nothing special about 10% (good magic number), the difference depends on the average time a transaction lives in the "mempool", and the expected change in gas price during this interval.

We're also considering adding some leeway in the sequencer itself in 0.8.2, i.e. having him accept transactions with |max_fee-estimated_fee| < epsilon (can be multiplicative).

Replace `H256` and `U256` with `UnsignedFieldElement`

For convenience, the library currently uses H256 and U256 from the ethereum_types crate to represent hashes and integers on StarkNet. However, the range of these hashes and integers are actually smaller than 256 bits. Exposing public interfaces with H256 or U256 is thus misleading.

We should implement the StarkHash and StarkInteger types (names open to discussion) to replace the existing Ethereum types used, probably as wrappers around H256 and U256, respectively, with range checks.

TODOs:

  • add UnsignedFieldElement type as wrapper around U256
  • replace U256 and H256 with UnsignedFieldElement in value objects and providers
  • replace U256 and H256 with UnsignedFieldElement in crypto functions
  • fix outdated examples in README

Add support for full node JSON-RPC

pathfinder just made the first public release. We need to add support for the JSON-RPC APIs formally defined here.

Update: I will consider the support to be done once the list of methods implemented by pathfinder is covered, since we don't really have a good way to test the others.

Note: While starknet_getCode is implemented by pathfinder, we will not support this method since it's already been removed from the spec.

  • starknet_getBlockByHash
  • starknet_getBlockByNumber
  • starknet_getStorageAt
  • starknet_getTransactionByHash
  • starknet_getTransactionByBlockHashAndIndex
  • starknet_getTransactionByBlockNumberAndIndex
  • starknet_getTransactionReceipt
  • starknet_getBlockTransactionCountByHash
  • starknet_getBlockTransactionCountByNumber
  • starknet_call
  • starknet_blockNumber
  • starknet_chainId
  • starknet_syncing
  • starknet_getEvents
  • starknet_addInvokeTransaction
  • starknet_addDeployTransaction
  • starknet_addDeclareTransaction

JSON RPC batching

JSON RPC supports batching multiple requests into one (batching). The performance gain is quite high, for example it takes around 80-100 ms to fetch a single block, but ~200ms to fetch 25 with batching.

Type `ProviderError` is not public

In starknet-providers there is a ProviderError type being defined:

#[derive(Debug, Error)]
pub enum ProviderError {
    #[error(transparent)]
    ReqwestError(#[from] ReqwestError),
    #[error(transparent)]
    Serialization(SerdeJsonError),
    #[error("Deserialization error: {err}, Response: {text}")]
    Deserialization { err: SerdeJsonError, text: String },
    #[error(transparent)]
    StarknetError(StarknetError),
}

It is not reexporte in lib.rs

#![doc = include_str!("../README.md")]

mod provider;
pub use provider::Provider;

mod sequencer_gateway;
pub use sequencer_gateway::SequencerGatewayProvider;

pub mod jsonrpc;

So I cannot use it in my code:

	provider
		.call_contract(
			InvokeFunctionTransactionRequest {
				contract_address,
				entry_point_selector: selector!("get_user_information_from_github_handle"),
				calldata,
				signature: Default::default(),
				max_fee: Default::default(),
			},
			BlockId::Latest,
		)
		.await

The return type of this expression is Result<CallContractResult, ProviderError> but I cannot do anything with it because it is not exported.
I cannot wrap it in my own error type, neither can I write a From implementation, nor return it directly.

What should be done ?
Probably export it too, or make the submodules public instead of keeping them private

Use builder pattern for Account transactions

Follow up issue on #98. Instead of hard-coding a fee estimation process, we should adapt the builder pattern to allow users to specify transaction fees.

Such a pattern should also be applied to nonce fetching. Currently execute() forces callers to supply a nonce, meaning it's the caller's responsibility to call get_nonce() before calling execute(). It makes the Account trait hard to use.

Implementation of `compute_hash_on_elements` for stark orders

Hi there,

I am trying out using starknet as part of signing transactions for the dYdX exchange. As part of the hashing, I have a series of pedersen hashes like the following:

pedersen(
    &pedersen(
        &pedersen(
            &pedersen(
                &asset_id_sell.to_fe(),
                &asset_id_buy.to_fe(),
            ),
            &self.message.asset_id_fee.to_fe(),
        ),
        &part_1,
    ),
    &part_2,
)

However, I found starknet::core::crypto::compute_hash_on_elements which seemed to do the same job? When I tried it, it didn't seem to produce valid signatures for the orders on the exchange. Upon reading the implementation, it seems to do the same job all the way until the end when it seems to further hash it again with the data length:

let data_len = FieldElement::from(data.len());
pedersen_hash(&current_hash, &data_len)

dYdX uses stark exchange and it seems to work without the final hashing with data length but not with. Does this mean that the implementation of compute_hash_on_elements is wrong or that the implementation of starkex is wrong on dYdX's side? I'd like to put all of this into my future PR, so just confirming.

Cannot deploy the new OpenZeppelin account contract

Tried using starknet-rs to deploy the latest account contract from OpenZeppelin.

The transaction is not working:

{
  "status": "REJECTED",
  "transaction_failure_reason": {
    "tx_id": 1256114,
    "code": "TRANSACTION_FAILED",
    "error_message": "Error at pc=0:45:\nGot an exception while executing a hint.\nCairo traceback (most recent call last):\nUnknown location (pc=0:509)\nUnknown location (pc=0:499)\nUnknown location (pc=0:280)\nUnknown location (pc=0:242)\nUnknown location (pc=0:219)\n\nTraceback (most recent call last):\n  File \"<hint6>\", line 5, in <module>\nAssertionError: normalize_address() cannot be used with the current constants."
  },
  "transaction": {
    "contract_address": "0x615161661cdbea1c112383ce703d33299bef0e796903b88ec5601010ee22091",
    "contract_address_salt": "0x6c8035ea0cc6d4b0d933969db1367d7380adcaa462184b814f2def2bd9adf5",
    "constructor_calldata": [
      "1"
    ],
    "transaction_hash": "0x232db7959b3ccc22b6e1321fc412025a7d9733b90db809c29e47601b5ef5e3f",
    "type": "DEPLOY"
  }
}

Unify field element types

Follow-up issue of this comment.

We now have 2 field element types thanks to the work done in #13 and #33. There are a few reasons for unifying them:

We should do this through a multi-step process:

  • replace ff with ark-ff in starknet-crypto to make sure crypto primitives are properly upheld
  • refactor the field element type from starknet-crypto into a new sub-crate starknet-ff
  • replace UnsignedFieldElement in starknet-core with the new type from starknet-ff

Use only a single TransactionStatus enum

We currently have TransactionStatus and TransactionStatusType which are almost the same. The only difference is that the former also wraps a block hash with two of its variants. TransactionStatus is used only in a single place, as the response type for get_transaction_status.

In my opinion, this is quite confusing. Both types do pretty much the same job. I'm suggesting to introduce a different type to replace the current TransactionStatus, something like:

pub struct ShortTransactionStatus { // name is of course open for discussion
    pub block_hash: Option<H256>,
    pub tx_status: TransactionStatusType
}

It would be used as the return type for get_transaction_status. That way, we could remove TransactionStatus and also TransactionBlockHash, because that would just be "merged" into this new struct and have a cleaner API.

Update for new account signature scheme

I just realized that OpenZeppelin changed how signatures are validated a while ago. I wonder if there is even a standard for account signatures, as it looks like Argent X is still using the old hashing.

Not sure what's the appropriate action on our side. If there's a standard, we should probably stick to the standard. Otherwise we will have to build different Account implementations to cater for each major implementation.

Creating this issue to track progress. Will have to gather more info before concrete actions.

Match response object type names with `cairo-lang`

With the release of StarkNet v0.7.1, I noticed that a new file response_objects.py has been added to cairo-lang which contains gateway response object type definitions. While they're still using JsonObject for most requests, some of them are now typed and thus have official names.

We should probably update our code to match with their names, for the sake of making it easier for people familiar with cairo-lang to start using our library.

Full documentation coverage on public items

There's currently very little documentation. We need to:

  • achieve full doc coverage on all public items on all crates
  • add #![deny(missing_docs)] to all crates to enforce documentation

Add support for StarkNet v0.8.0

OK so as we're wrapping up our work on supporting #62, StarkNet v0.8.0 is already around the corner... Creating this issue to track the progress on supporting v0.8.0.

Can barely keep up :(

TODOs:

  • resolve breaking changes
  • add support for new gateway APIs
  • add support for paying transaction fees

why is there not getter for fields in `SingleOwnerAccount` ?

I have a function that call a specific smart contract, I want to pass a SingleOwnerAccount as argument.

fn call_contract(
	account: SingleOwnerAccount<SequencerGatewayProvider, LocalWallet>
) {
      let contract_address = <some_address>
      let result = account
		.execute(&[Call {
			to: contract_address,
			selector: get_selector_from_name("mint").unwrap(),
			calldata: vec![
				account.address(),
				FieldElement::from_dec_str("1000000000000000000000").unwrap(),
				FieldElement::ZERO,
			],
		}])
		.send()
		.await
		.unwrap();
}

I want to use miner_account.address() as the first argument of the calldata.
Why is there no getters implemented onto SingleOwnerAccount ?

Is it by design ? Or should I do a PR adding those getter methods ?

call contract `view` methods

How do I make a call to a view (read only) method of a contract?

I feel I have to use Provider::call_contract, but it require signature and max_fee as the two last arguments, which doesn't make sense when calling a view.
Should I still use call_contract with and empty Vec as signature and 0 as max_fee, or is there a better way to do this ?

`get_selector_from_name` argument lifetime

I'm using get_selector_from_name and, at the moment, the signature is the following:

pub fn get_selector_from_name(func_name: &str) -> Result<FieldElement, NonAsciiNameError>;

Which is equivalent to the following:

pub fn get_selector_from_name<'a>(func_name: &'a str) -> Result<FieldElement, NonAsciiNameError<'a>>;

This means that the function is unusable with the ? operator since the borrowed argument will not live long enough (unless func_name is 'static, but that's not usually the case).

Workaround

For now I just map_err to fix the lifetime requirements.

Proposed solution

Either have the error own func_name or just return the error type without func_name. I believe the latter approach is the most common, e.g. the url crate parse errors don't contain a copy or reference to the url that caused the error.

Implement wrapper around the standard `IAccount` interface

As set out in our feature roadmap, we need to support the IAccount interface for using account abstraction.

TODOs:

  • add Signer trait as abstraction over SigningKey and other future implementations
  • add Account trait which models IAccount, and an implementation for the single-owner variant
  • add example to README and mark feature as done :)))

Set up folder for examples

As we're adding more functionalities to the library, it becomes necessary to set up an example folder for show-casing some of them, instead of stuff them into README.

Add support for StarkNet v0.8.1

StarkNet 0.8.1 is about to release. Creating this issue to track integration progress.

See comments below. It looks like we only need to resolve API changes for this version.

Add support for StarkNet v0.9.0

Creating this issue to track integration progress of StarkNet v0.9.0.

TODOs:

  • resolve breaking changes
  • add support for new gateway APIs
  • add support for new transaction type declare
  • declare v0.9.0 support on README

Add support for StarkNet v0.7.1

StarkNet v0.7.1 is around the corner and I'm creating this issue to track support for this version.

TODOs:

  • support the new account contract
  • support new feeder gateway APIs get_state_update and get_full_contract

Bug: incorrect JSON serialization on `FieldElement`

So when working on #62 I found out about a critical bug on the FieldElement type that's basically breaking all contract deployments and calls. This code doesn't work:

let num = FieldElement::from_hex_be("0x1").unwrap();
let text = serde_json::to_string(&num).unwrap();
assert_eq!(text, "\"1\"");

Fails with:

thread 'tests::test_json_ser' panicked at 'assertion failed: `(left == right)`
  left: `"\"0\""`,
 right: `"\"1\""`', starknet-ff/src/lib.rs:450:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

WebAssembly support

Currently cargo build --target wasm32-unknown-unknown fails on the repo. This issue tracks the progress of making starknet-rs work in WebAssembly.

Support smart contract deployment

Now that there's full sequencer gateway API coverage, smart contract deployment can be built. This issue tracks the progress of contract deployment support.

TODOs:

  • figure out what the program field in deployment requests represents
  • contract artifact parsing and program compression
  • add type for deploying artifact through starknet_providers::Provider

Implement RFC 6979 for deterministic `k` generation

After #33 is merged, we'll need to implement RFC 6979 before being able to build high-level crypto functions into starknet-core.

Technically, we don't have to use RFC 6979 to generate valid signatures given strong enough entropy. But we should still use it for the sake of behaving the same as other libraries and generating deterministic signatures.

DX improvements

While playing around with the lib, I came across a couple of peculiarities listed below. I think tackling them would result in a better developer experience. I'd like to know your opinion @xJonathanLEI, if you think they are worth dealing with.

1) Print out a hash representation of UnsignedFieldElement (where it makes sense?):

Take the example from #52 of dbg!(result); This prints out something like this:

[src/main.rs:59] result = AddTransactionResult {
    code: TransactionReceived,
    transaction_hash: UnsignedFieldElement {
        inner: 2728449641502938754811783899429488071781078345184259994040934169808967186824,
    },
    address: None,
}

The issue is I can't copy-paste the transaction hash to Voyager easily, I have to convert it to its hex representation first. Same applies to e.g. block_hash in various types. Is there a good solution for this?

2) Build an abstraction over AcceptedOnL1 / AcceptedOnL2.

The idea is to prevent code like this:

  let tx = provider.get_transaction_status(tx_id).await.unwrap();

    if let TransactionStatus::AcceptedOnL2(b) = tx {
        dbg!(b.block_hash);
    } else if let TransactionStatus::AcceptedOnL1(b) = tx {
        dbg!(b.block_hash);
    }

since, normally, a TX gets accepted on L1 eventually, but I might not care when the transition happens. This might be potentially solved by resolving #9.

3) Add From/TryFrom to streamline UnsignedFieldElement conversion

What do you think about implementing From/TryFrom traits to enable UnsignedFieldElement::from("0xc0ffee"), replacing from_str and from_hex_str? An added benefit is less need for unwraping. Similarly could be done to replace try_from_bytes_be though I don't think that's going to be used so often.

4) Use BlockId::Latest instead of None

A bunch of provider functions take a block_identifier: Option<BlockId> argument. Setting it to None implies the latest block (however, only on the gateway level). This results in, subjectively, ugly code:

account.get_nonce(Some(BlockId::Number(56000))).await.unwrap();
account.get_nonce(None).await.unwrap();

For example in Python, I'd define the get_nonce function with a default argument def get_nonce(block_id=None) so one could do:

account.get_nonce(56000)
account.get_nonce()

Is there a way to achieve something similar?

What I thought about is to replace the None option with a BlockId::Latest enum field. That way, we could remove the Optionality of the block_identifier and the intention would be clear and explicit: account.get_nonce(BlockId::Latest).await.unwrap(). WDYT?

TODOs:

  • Print out a hash representation of UnsignedFieldElement
  • Use a non-optional BlockId in Provider trait
  • Have only from_hex_str and from_dec_str to convert strings to UnsignedFieldElement

Switch to JSON-RPC as de facto provider implementation

Full node fee estimation has just been implemented by pathfinder: eqlabs/pathfinder#388

Now the full node JSON-RPC implementation has everything we need to implement a transaction sender. We should probably move away from the sequencer gateway.

Only one problem: due to the lack of a decentralized mempool, currently all the write methods are just proxied to the sequencer gateway. Therefore, if a full node is used by many people at the same time, you're more likely to get rate limited than just going to the sequencer yourself.

Should we wait for the mempool be become available (which will take some very long time I guess)? Or do we just do it anyways? Or do we take a hybird approach, where all the write methods are sent directly to the sequencer (probably with an option to go thru JSON-RPC instead anyways)?

Write tests and set up CI pipeline for running them

There's currently zero test written for the project, making it hard to contribute without breaking stuff. We need to write tests and run them on master and PRs. After this is done, all new PRs that add/change Rust code must be accompanied with corresponding test cases.

TODOs:

  • add basic tests
  • set up CI for running tests
  • set up CI for audit-check, cargo-udeps, and cargo-deny

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.