Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add abigen! macro #475

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
865007c
add cairo_types and abi parser
glihm Sep 24, 2023
5c994fa
add abigen
glihm Sep 24, 2023
e80a0ee
fix test abi parser
glihm Sep 24, 2023
5c9628e
wip on examples
glihm Sep 24, 2023
d1fefcd
cargo fmt
glihm Sep 24, 2023
f94c1c5
fix some use to don't clash with main imports
glihm Sep 24, 2023
fa5b9b8
fix imports
glihm Sep 24, 2023
2942185
cargo fmt
glihm Sep 24, 2023
b16941c
change print location for example
glihm Sep 24, 2023
9ec158e
clippy revision
glihm Sep 25, 2023
00474cc
fix typo for abigen function test
glihm Sep 25, 2023
a26aeb2
add ptr_arg ignore clippy
glihm Sep 25, 2023
bb97020
fix missing macro in expansion
glihm Sep 26, 2023
f328b29
add support for interfaces
glihm Sep 27, 2023
f79f9e6
fix tuple parsing with nested generic types
glihm Oct 8, 2023
d4291db
rename macro variables to avoid conflict with struct/enum members/var…
glihm Oct 8, 2023
85a5034
add bool as enum already managed by built-ins
glihm Oct 8, 2023
60b79ce
make block_id customizable for functionc all
glihm Oct 8, 2023
ca6d5f0
add thiserror for cairo error
glihm Oct 8, 2023
06e1d3a
rework expansion for contract to use ConnectedAccount
glihm Oct 10, 2023
b95b065
merge main
glihm Oct 10, 2023
2264a15
update README.md
glihm Oct 10, 2023
e32d98a
run prettier
glihm Oct 10, 2023
272310d
add missing path for Prodiver
glihm Oct 11, 2023
e735878
add convertion for basic starknet type to FieldElement
glihm Oct 11, 2023
339348f
derive more attribute for starknet base types
glihm Oct 11, 2023
d9c2e11
rename calldata in macro to avoid naming conflicts
glihm Oct 11, 2023
a190edd
add getter for call_block_id
glihm Oct 11, 2023
e35c40d
fix tests
glihm Oct 11, 2023
bfbcb5e
take mut ref instead of ownership to change call_block_id
glihm Oct 11, 2023
a322893
Merge branch 'master' into abigen
glihm Oct 26, 2023
1a39dea
fix: autogen contract takes now ConntectAccount instead of a reference
glihm Nov 10, 2023
6aabafb
merge main
glihm Nov 10, 2023
a28d1b9
fix: adjust to provider change in 0.7.0
glihm Nov 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
799 changes: 435 additions & 364 deletions Cargo.lock

Large diffs are not rendered by default.

82 changes: 82 additions & 0 deletions examples/abigen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use starknet::{
// Note here, we import an ABI type. This applies for
// ContractAddress, ClassHash, EthAddress only.
accounts::{ExecutionEncoding, SingleOwnerAccount},
contract::abi::ContractAddress,
core::{chain_id, types::FieldElement},
macros::abigen,
providers::SequencerGatewayProvider,
signers::{LocalWallet, SigningKey},
};

// Generate the bindings for the contract and also includes
// all the structs and enums present in the ABI with the exact
// same name.
abigen!(TokenContract, "./examples/contracts_abis/mini_erc20.json");

#[tokio::main]
async fn main() {
let provider = SequencerGatewayProvider::starknet_alpha_goerli();
println!("provider {:?}", provider);
let eth_goerli_token_address = FieldElement::from_hex_be(
"0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
)
.unwrap();

// If you only plan to call views functions, you can use the `Reader`, which
// only requires a provider along with your contract address.
let token_contract = TokenContractReader::new(eth_goerli_token_address, &provider);

// To call a view, there is no need to initialize an account. You can directly
// use the name of the method in the ABI to realize the call.
let balance: u256 = token_contract
.balanceOf(&ContractAddress(
FieldElement::from_hex_be("YOUR_HEX_CONTRACT_ADDRESS_HERE").unwrap(),
))
.await
.expect("Call to get balance failed");

println!("Your ETH (goerli) balance: {:?}", balance);

// For the inputs / outputs of the ABI functions, all the types are
// defined where the abigen! macro is expanded. Note that `u256`
// for the balance were already in the scope as it's generated from
// the ABI.

// If you want to do some invoke for external functions, you must use an account.
let signer = LocalWallet::from(SigningKey::from_secret_scalar(
FieldElement::from_hex_be("YOUR_PRIVATE_KEY_IN_HEX_HERE").unwrap(),
));
let address = FieldElement::from_hex_be("YOUR_ACCOUNT_CONTRACT_ADDRESS_IN_HEX_HERE").unwrap();
let account = SingleOwnerAccount::new(
provider,
signer,
address,
chain_id::TESTNET,
ExecutionEncoding::Legacy,
);

// The `TokenContract` also contains a reader field that you can use if you need both
// to call external and views with the same instance.
let token_contract = TokenContract::new(eth_goerli_token_address, &account);

// Example here of querying again the balance, using the internal reader of the
// contract setup with an account.
token_contract
.reader()
.balanceOf(&ContractAddress(
FieldElement::from_hex_be("YOUR_HEX_CONTRACT_ADDRESS_HERE").unwrap(),
))
.await
.expect("Call to get balance failed");

let _ = token_contract
.approve(
&ContractAddress(FieldElement::from_hex_be("SPENDER_ADDRESS_HEX").unwrap()),
&u256 {
low: 10000,
high: 0,
},
)
.await;
}
92 changes: 92 additions & 0 deletions examples/abigen_events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use starknet::{
accounts::{ExecutionEncoding, SingleOwnerAccount},
core::types::{BlockId, BlockTag, EventFilter, FieldElement},
macros::{abigen, felt},
providers::{jsonrpc::HttpTransport, JsonRpcClient, Provider},
signers::{LocalWallet, SigningKey},
};

use std::sync::Arc;
use url::Url;

// All the events are always grouped in one enun called `Event`
// in the ABI.
abigen!(Contract, "./examples/contracts_abis/events.json");

#[tokio::main]
async fn main() {
let rpc_url = Url::parse("http://0.0.0.0:5050").unwrap();
let provider = Arc::new(JsonRpcClient::new(HttpTransport::new(rpc_url.clone())));

let signer = LocalWallet::from(SigningKey::from_secret_scalar(
FieldElement::from_hex_be("0x1800000000300000180000000000030000000000003006001800006600")
.unwrap(),
));
let address = FieldElement::from_hex_be(
"0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973",
)
.unwrap();
let account = SingleOwnerAccount::new(
provider.clone(),
signer,
address,
felt!("0x4b4154414e41"), // KATANA
ExecutionEncoding::Legacy,
);

let contract_address = FieldElement::from_hex_be("YOUR_CONTRACT_ADDRESS_HEX").unwrap();

let event_contract = Contract::new(contract_address, &account);

// Let emits some events by calling two externals.
event_contract
.emit_a(&FieldElement::ONE, &vec![felt!("0xff"), felt!("0xf1")])
.await
.expect("Emit a invoke failed");

event_contract
.emit_b(&felt!("0x1234"))
.await
.expect("Emit b invoke failed");

// Fetch events with some filters with a chunck size of 100 without continuation
// token.
// This will not work on the gateway, you need to use JsonRPC node.
let event_page = provider
.get_events(
EventFilter {
from_block: Some(BlockId::Number(0)),
to_block: Some(BlockId::Tag(BlockTag::Latest)),
address: None,
keys: None,
},
None,
100,
)
.await
.expect("Fetch events failed");

for e in event_page.events {
// abigen! macro generate for you the `TryFrom<EmittedEvent` for the
// `Event` enum.
let my_event: Event = match e.try_into() {
Ok(ev) => ev,
Err(_s) => {
// An event from other contracts, ignore.
continue;
}
};

// This way, the deserialization of the event
// is automatically done based on the variant
// from the event keys and data.
match my_event {
Event::MyEventA(_a) => {
// do stuff with a.header and a.value.
}
Event::MyEventB(_b) => {
// do stuff with b.value.
}
};
}
}
86 changes: 86 additions & 0 deletions examples/contracts_abis/events.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
[
{
"type": "struct",
"name": "core::array::Span::<core::felt252>",
"members": [
{
"name": "snapshot",
"type": "@core::array::Array::<core::felt252>"
}
]
},
{
"type": "function",
"name": "emit_a",
"inputs": [
{
"name": "header",
"type": "core::felt252"
},
{
"name": "value",
"type": "core::array::Span::<core::felt252>"
}
],
"outputs": [],
"state_mutability": "external"
},
{
"type": "function",
"name": "emit_b",
"inputs": [
{
"name": "value",
"type": "core::felt252"
}
],
"outputs": [],
"state_mutability": "external"
},
{
"type": "event",
"name": "contracts::event::event::MyEventA",
"kind": "struct",
"members": [
{
"name": "header",
"type": "core::felt252",
"kind": "key"
},
{
"name": "value",
"type": "core::array::Span::<core::felt252>",
"kind": "data"
}
]
},
{
"type": "event",
"name": "contracts::event::event::MyEventB",
"kind": "struct",
"members": [
{
"name": "value",
"type": "core::felt252",
"kind": "data"
}
]
},
{
"type": "event",
"name": "contracts::event::event::Event",
"kind": "enum",
"variants": [
{
"name": "MyEventA",
"type": "contracts::event::event::MyEventA",
"kind": "nested"
},
{
"name": "MyEventB",
"type": "contracts::event::event::MyEventB",
"kind": "nested"
}
]
}
]
72 changes: 72 additions & 0 deletions examples/contracts_abis/mini_erc20.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
[
{
"type": "struct",
"name": "core::integer::u256",
"members": [
{
"name": "low",
"type": "core::integer::u128"
},
{
"name": "high",
"type": "core::integer::u128"
}
]
},
{
"type": "function",
"name": "balanceOf",
"inputs": [
{
"name": "address",
"type": "core::starknet::contract_address::ContractAddress"
}
],
"outputs": [
{
"type": "core::integer::u256"
}
],
"state_mutability": "view"
},
{
"type": "enum",
"name": "core::bool",
"variants": [
{
"name": "False",
"type": "()"
},
{
"name": "True",
"type": "()"
}
]
},
{
"type": "function",
"name": "approve",
"inputs": [
{
"name": "spender",
"type": "core::starknet::contract_address::ContractAddress"
},
{
"name": "value",
"type": "core::integer::u256"
}
],
"outputs": [
{
"type": "core::bool"
}
],
"state_mutability": "external"
},
{
"type": "event",
"name": "contracts::basic::basic::Event",
"kind": "enum",
"variants": []
}
]
31 changes: 31 additions & 0 deletions starknet-contract/src/abi/cairo_types/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::abi::cairo_types::CairoType;

use starknet_core::types::FieldElement;

/// Cairo types result.
pub type Result<T> = core::result::Result<T, Error>;

/// A cairo type error.
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum Error {
#[error("Invalid type found {0:?}.")]
InvalidTypeString(String),
#[error("Error during serialization {0:?}.")]
Serialize(String),
#[error("Error during deserialization {0:?}.")]
Deserialize(String),
}

impl CairoType for Error {
type RustType = Self;

fn serialize(_rust: &Self::RustType) -> Vec<FieldElement> {
vec![]
}

fn deserialize(_felts: &[FieldElement], _offset: usize) -> Result<Self::RustType> {
Ok(Error::Deserialize(
"Error cairotype deserialized?".to_string(),
))
}
}
Loading
Loading