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

Add the starkli interaction process using katana as the local node #175

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions rust-book-old/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
- [events](./toolchain/sozo/world-commands/events.md)
- [auth](./toolchain/sozo/world-commands/auth.md)
- [Katana](./toolchain/katana/overview.md)
- [interact](./toolchain/katana/interact.md)
- [Reference](./toolchain/katana/reference.md)
- [Torii](./toolchain/torii/overview.md)
- [Reference](./toolchain/torii/reference.md)
Expand Down
321 changes: 321 additions & 0 deletions rust-book-old/src/toolchain/katana/interact.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
### install starkli

```console
curl https://get.starkli.sh | sh
## open a new terminal
starkliup
JameWade marked this conversation as resolved.
Show resolved Hide resolved
```

You can check your installation by running `starkli --version`,then you will get the starkli version.

### install katana

```console
git clone https://github.com/dojoengine/dojo
cd dojo
cargo install --path ./crates/katana --locked --force
JameWade marked this conversation as resolved.
Show resolved Hide resolved
```

You can check your installation by running `katana --version`,then you will get the katana version.

### Deploy and interact with contracts

#### run katana node

```console
katana --accounts 3 --seed 0 --gas-price 250
```

After starting the node, the accounts you need will be automatically generated and deployed. We need to import the pre-deployed accounts into starkli for use.

#### Configure starkli account

1. Configure the signer, execute the following command, and then enter the private key. This private key is the private key of the account generated by kanata.
JameWade marked this conversation as resolved.
Show resolved Hide resolved

```console
starkli signer keystore from-key ~/.starkli-wallets/deployer/account0_keystore.json
```

2. Configure account description and create a new json file

```console
touch ~/.starkli-wallets/deployer/account0_account.json
```

3. Copy the following content into the json file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's nice to use account file for now, due to starkli incompatibility.
Once we're again compatible with starkli, we should also show to users that they can use --katana0 account for instance.


```console
{
"version": 1,
"variant": {
"type": "open_zeppelin",
"version": 1,
"public_key": "<SMART_WALLET_PUBLIC_KEY>"
JameWade marked this conversation as resolved.
Show resolved Hide resolved
},
"deployment": {
"status": "deployed",
"class_hash": "<SMART_WALLET_CLASS_HASH>",
"address": "<SMART_WALLET_ADDRESS>"
}
}
```

Change `<>` and its contents to those provided by katana, including `public_key`, `class_hash`, and `address`.Among them, `class_hash` needs to be obtained with the following command. Please replace `<SMART_WALLET_ADDRESS>` with address.

```console
starkli class-hash-at <SMART_WALLET_ADDRESS> --rpc http://0.0.0.0:5050
```

In order to facilitate subsequent contract deployment and invocation, three accounts are deployed directly here. The process is the same.we will get 6 json files.

#### Contract Deployment

1. Create a Vote project

```console
## create a new project
scarb new vote
## Add contract dependencies to scarb.toml
[dependencies]
starknet = "2.1.0"
JameWade marked this conversation as resolved.
Show resolved Hide resolved
[[target.starknet-contract]]
casm = true
```

Copy the vote contract to lib.cairo

```console
/// @dev Core Library Imports for the Traits outside the Starknet Contract
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if a simpler contract can be more understandable for new comers.

use starknet::ContractAddress;

/// @dev Trait defining the functions that can be implemented or called by the Starknet Contract
#[starknet::interface]
trait VoteTrait<T> {
/// @dev Function that returns the current vote status
fn get_vote_status(self: @T) -> (u8, u8, u8, u8);
/// @dev Function that checks if the user at the specified address is allowed to vote
fn voter_can_vote(self: @T, user_address: ContractAddress) -> bool;
/// @dev Function that checks if the specified address is registered as a voter
fn is_voter_registered(self: @T, address: ContractAddress) -> bool;
/// @dev Function that allows a user to vote
fn vote(ref self: T, vote: u8);
}

/// @dev Starknet Contract allowing three registered voters to vote on a proposal
#[starknet::contract]
mod Vote {
use starknet::ContractAddress;
use starknet::get_caller_address;

const YES: u8 = 1_u8;
const NO: u8 = 0_u8;

/// @dev Structure that stores vote counts and voter states
#[storage]
struct Storage {
yes_votes: u8,
no_votes: u8,
can_vote: LegacyMap::<ContractAddress, bool>,
registered_voter: LegacyMap::<ContractAddress, bool>,
}

/// @dev Contract constructor initializing the contract with a list of registered voters and 0 vote count
#[constructor]
fn constructor(
ref self: ContractState,
voter_1: ContractAddress,
voter_2: ContractAddress,
voter_3: ContractAddress
) {
// Register all voters by calling the _register_voters function
self._register_voters(voter_1, voter_2, voter_3);

// Initialize the vote count to 0
self.yes_votes.write(0_u8);
self.no_votes.write(0_u8);
}

/// @dev Event that gets emitted when a vote is cast
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
VoteCast: VoteCast,
UnauthorizedAttempt: UnauthorizedAttempt,
}

/// @dev Represents a vote that was cast
#[derive(Drop, starknet::Event)]
struct VoteCast {
voter: ContractAddress,
vote: u8,
}

/// @dev Represents an unauthorized attempt to vote
#[derive(Drop, starknet::Event)]
struct UnauthorizedAttempt {
unauthorized_address: ContractAddress,
}

/// @dev Implementation of VoteTrait for ContractState
#[external(v0)]
impl VoteImpl of super::VoteTrait<ContractState> {
/// @dev Returns the voting results
fn get_vote_status(self: @ContractState) -> (u8, u8, u8, u8) {
let (n_yes, n_no) = self._get_voting_result();
let (yes_percentage, no_percentage) = self._get_voting_result_in_percentage();
(n_yes, n_no, yes_percentage, no_percentage)
}

/// @dev Check whether a voter is allowed to vote
fn voter_can_vote(self: @ContractState, user_address: ContractAddress) -> bool {
self.can_vote.read(user_address)
}

/// @dev Check whether an address is registered as a voter
fn is_voter_registered(self: @ContractState, address: ContractAddress) -> bool {
self.registered_voter.read(address)
}

/// @dev Submit a vote
fn vote(ref self: ContractState, vote: u8) {
assert(vote == NO || vote == YES, 'VOTE_0_OR_1');
let caller: ContractAddress = get_caller_address();
self._assert_allowed(caller);
self.can_vote.write(caller, false);

if (vote == NO) {
self.no_votes.write(self.no_votes.read() + 1_u8);
}
if (vote == YES) {
self.yes_votes.write(self.yes_votes.read() + 1_u8);
}

self.emit(VoteCast { voter: caller, vote: vote, });
}
}

/// @dev Internal Functions implementation for the Vote contract
#[generate_trait]
impl InternalFunctions of InternalFunctionsTrait {
/// @dev Registers the voters and initializes their voting status to true (can vote)
fn _register_voters(
ref self: ContractState,
voter_1: ContractAddress,
voter_2: ContractAddress,
voter_3: ContractAddress
) {
self.registered_voter.write(voter_1, true);
self.can_vote.write(voter_1, true);

self.registered_voter.write(voter_2, true);
self.can_vote.write(voter_2, true);

self.registered_voter.write(voter_3, true);
self.can_vote.write(voter_3, true);
}
}

/// @dev Asserts implementation for the Vote contract
#[generate_trait]
impl AssertsImpl of AssertsTrait {
// @dev Internal function that checks if an address is allowed to vote
fn _assert_allowed(ref self: ContractState, address: ContractAddress) {
let is_voter: bool = self.registered_voter.read((address));
let can_vote: bool = self.can_vote.read((address));

if (can_vote == false) {
self.emit(UnauthorizedAttempt { unauthorized_address: address, });
}

assert(is_voter == true, 'USER_NOT_REGISTERED');
assert(can_vote == true, 'USER_ALREADY_VOTED');
}
}

/// @dev Implement the VotingResultTrait for the Vote contract
#[generate_trait]
impl VoteResultFunctionsImpl of VoteResultFunctionsTrait {
// @dev Internal function to get the voting results (yes and no vote counts)
fn _get_voting_result(self: @ContractState) -> (u8, u8) {
let n_yes: u8 = self.yes_votes.read();
let n_no: u8 = self.no_votes.read();

(n_yes, n_no)
}

// @dev Internal function to calculate the voting results in percentage
fn _get_voting_result_in_percentage(self: @ContractState) -> (u8, u8) {
let n_yes: u8 = self.yes_votes.read();
let n_no: u8 = self.no_votes.read();

let total_votes: u8 = n_yes + n_no;

if (total_votes == 0_u8) {
return (0, 0);
}
let yes_percentage: u8 = (n_yes * 100_u8) / (total_votes);
let no_percentage: u8 = (n_no * 100_u8) / (total_votes);

(yes_percentage, no_percentage)
}
}
}
```

2. Compile contract

```console
scarb build
```

3. Declare contract

```console
starkli declare target/dev/test_Vote.sierra.json --compiler-version 2.1.0 --rpc http://0.0.0.0:5050 --account ~/.starkli-wallets/deployer/account0_account.json --keystore ~/.starkli-wallets/deployer/account0_keystore.json
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a suggestion, we may show to the user how to use environment variables. This can make the commands way shorter, and educate the user on good practices.

```

4. Deploy contract

```console
## starkli deploy <class_hash_of_the_contract_to_be_deployed> <voter_0_address> <voter_1_address> <voter_2_address> --rpc http://0.0.0.0:5050 --account ~/.starkli-wallets/deployer/account0_account.json --keystore ~/.starkli-wallets/deployer/account0_keystore.json
## There are four hexadecimal numbers in total. The first one is the class_hash of the contract, and the next three are the vote account addresses, which are the three account addresses in the previous account configuration.
## Below is the command I ran,you need to change it to your address
starkli deploy 0x043e965f6c644b15c0c46d5615c22cf26746edc2547f482ae5ab517d3dffdf37 0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 0x5686a647a9cdd63ade617e0baf3b364856b813b508f03903eb58a7e622d5855 0x765149d6bc63271df7b0316537888b81aa021523f9516a05306f10fd36914da --rpc http://0.0.0.0:5050 --account ~/.starkli-wallets/deployer/account0_account.json --keystore ~/.starkli-wallets/deployer/account0_keystore.json
```

5. Call contract [only read state]

```console
## The first parameter is the contract address, the second parameter is the function to be called, and the third parameter is the function parameter. Here I pass in a voting address.
starkli call 0x050ffb64b3042bf91422dfa1453b3cdeaf4af7eca8562b499fc49017d41b85ec voter_can_vote 0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973 --rpc http://0.0.0.0:5050
```

6. Invoke contract [can write state]

```console
##The first parameter is the contract address, the second parameter is the function to be invoked, and the third parameter is the function parameter
##Voting Yes
starkli invoke 0x050ffb64b3042bf91422dfa1453b3cdeaf4af7eca8562b499fc49017d41b85ec vote 1 --rpc http://0.0.0.0:5050 --account ~/.starkli-wallets/deployer/account0_account.json --keystore ~/.starkli-wallets/deployer/account0_keystore.json

##Voting No
starkli invoke 0x050ffb64b3042bf91422dfa1453b3cdeaf4af7eca8562b499fc49017d41b85ec vote 0 --rpc http://0.0.0.0:5050 --account ~/.starkli-wallets/deployer/account1_account.json --keystore ~/.starkli-wallets/deployer/account1_keystore.json

##The same signer cannot vote repeatedly. If the same signer votes repeatedly, Katana will report an error.
called contract (0x0517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973):
Error at pc=0:81:
Got an exception while executing a hint: Custom Hint Error: Execution failed. Failure reason: \"USER_ALREADY_VOTED\".
Cairo traceback (most recent call last):
Unknown location (pc=0:731)
Unknown location (pc=0:677)
Unknown location (pc=0:291)
Unknown location (pc=0:314)
```

7. Query transaction

```console
### starkli transaction <TRANSACTION_HASH> --rpc http://0.0.0.0:5050
starkli transaction 0x0499bca29c88798ea70233c41d1d17621f49a2afb1f4ed902ca2eecf4a19e951 --rpc http://0.0.0.0:5050
```

All the above interaction processes can be seen on the katana client. Pay attention to the status changes of katana at each step.