Skip to content

Commit

Permalink
feat(sdk): add sdk base create listing, offer & cancel order (#203)
Browse files Browse the repository at this point in the history
## Description

# New JS SDK Library for ArkChain Orderbook Interaction

This pull request introduces a new SDK library specifically designed for
interacting with the ArkChain orderbook. This library is a comprehensive
toolset enabling users to effectively engage with ArkChain's
functionalities. The following outlines the key features and components
included in this initial release:

## Features
- **Account Creation (Burner Wallet):** Facilitates the creation of new
accounts, functioning as burner wallets for transactions.
- **Listing Creation:** Provides the ability to create listings on the
ArkChain orderbook.
- **Offer Creation:** Enables the creation of offers within the
orderbook environment.
- **Order Cancellation:** Offers the functionality to cancel existing
orders.
- **Fulfilling Listings:** Introduces the capability to fulfill active
listings in the orderbook.
- **Fulfilling Offers:** Allows users to fulfill existing offers within
the orderbook.

## Example Scripts
- A set of example scripts are included to demonstrate practical
applications of the SDK functions, such as creating accounts, listings,
offers, and more.

## Testing
- Comprehensive tests using Chai and Mocha have been added to validate
the interaction with the ArkChain. These tests cover all the core
features of the SDK, ensuring reliability and performance.

This SDK represents a foundational step in streamlining and simplifying
interactions with the ArkChain orderbook, providing developers with the
tools necessary for efficient and effective blockchain engagements.


## What type of PR is this? (check all applicable)

- [x] 🍕 Feature (`feat:`)

## Related Tickets & Documents

<!-- 
Please use this format to link related issues: Fixes #<issue_number>
More info:
https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->

## Added tests?

- [x] 👍 yes
- [ ] 🙅 no, because they aren't needed
- [ ] 🙋 no, because I need help

## Added to documentation?

- [ ] 📜 README.md
- [ ] 📓 Documentation
- [ ] 🙅 no documentation needed

## [optional] Are there any post-deployment tasks we need to perform?

<!-- Describe any additional tasks, if any, and provide steps. -->

## [optional] What gif best describes this PR or how it makes you feel?

<!-- Share a fun gif related to your PR! -->

### PR Title and Description Guidelines:

- Ensure your PR title follows semantic versioning standards. This helps
automate releases and changelogs.
- Use types like `feat:`, `fix:`, `chore:`, `BREAKING CHANGE:` etc. in
your PR title.
- Your PR title will be used as a commit message when merging. Make sure
it adheres to [Conventional Commits
standards](https://www.conventionalcommits.org/).

## Closing Issues

<!-- 
Use keywords to close related issues. This ensures that the associated
issues will automatically close when the PR is merged.

- `Fixes #123` will close issue 123 when the PR is merged.
- `Closes #123` will also close issue 123 when the PR is merged.
- `Resolves #123` will also close issue 123 when the PR is merged.

You can also use multiple keywords in one comment:
- `Fixes #123, Resolves #456`

More info:
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue
-->

---------

Co-authored-by: Rémi Roycourt <[email protected]>
  • Loading branch information
kwiss and remiroyc authored Dec 8, 2023
1 parent 20ee40a commit 296b1d9
Show file tree
Hide file tree
Showing 80 changed files with 6,827 additions and 96 deletions.
10 changes: 10 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// This configuration only applies to the package manager root.
/** @type {import("eslint").Linter.Config} */
module.exports = {
ignorePatterns: ["apps/**", "packages/**"],
extends: ["@repo/eslint-config/library.js"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: true,
},
};
45 changes: 45 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,48 @@ crates/ark-metadata/images/**/*

# Prisma
*/dev.db

.dump

# dependencies
node_modules
.pnp
.pnp.js

# testing
coverage

# next.js
.next/
out/
next-env.d.ts

# expo
.expo/
dist/

# production
build

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env
.env*.local

# typescript
*.tsbuildinfo

# turbo
.turbo

# db
dbdata/

# misc
.DS_Store
*.pem
Empty file added .npmrc
Empty file.
13 changes: 10 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
{
"rust-analyzer.linkedProjects": ["./Cargo.toml"],
"rust-analyzer.linkedProjects": [
"./Cargo.toml"
],
"[cairo]": {
"editor.formatOnSave": false
},
"editor.formatOnSave": true
}
"editor.formatOnSave": true,
"eslint.workingDirectories": [
{
"mode": "auto"
}
]
}
3 changes: 3 additions & 0 deletions crates/ark-contracts/arkchain/Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ version = 1
[[package]]
name = "ark_common"
version = "0.1.0"
dependencies = [
"snforge_std",
]

[[package]]
name = "arkchain"
Expand Down
2 changes: 1 addition & 1 deletion crates/ark-contracts/arkchain/src/order/order_v1.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl OrderTraitOrderV1 of OrderTrait<OrderV1> {
return Result::Err(OrderValidationError::EndDateInThePast);
}

// End date -> block_ts + 30 days.
// End date -> start_date + 30 days.
let max_end_date = *self.start_date + (30 * 24 * 60 * 60);
if end_date > max_end_date {
return Result::Err(OrderValidationError::EndDateTooFar);
Expand Down
34 changes: 27 additions & 7 deletions crates/ark-contracts/arkchain/src/orderbook.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use ark_common::protocol::order_types::{FulfillInfo, OrderType, CancelInfo, OrderStatus};
use ark_common::crypto::signer::{SignInfo, Signer, SignerValidator};
use arkchain::order::order_v1::OrderV1;

use debug::PrintTrait;
/// Orderbook trait to define operations on orderbooks.
#[starknet::interface]
trait Orderbook<T> {
Expand Down Expand Up @@ -68,6 +68,12 @@ trait Orderbook<T> {
/// * `order_hash` - The order hash of order.
fn get_order(self: @T, order_hash: felt252) -> OrderV1;

/// Retrieves the order signer using its hash.
///
/// # Arguments
/// * `order_hash` - The order hash of order.
fn get_order_signer(self: @T, order_hash: felt252) -> felt252;

/// Retrieves the order hash using its token hash.
///
/// # Arguments
Expand Down Expand Up @@ -115,6 +121,7 @@ mod orderbook_errors {
/// StarkNet smart contract module for an order book.
#[starknet::contract]
mod orderbook {
use core::debug::PrintTrait;
use ark_common::crypto::signer::{SignInfo, Signer, SignerTrait, SignerValidator};
use ark_common::protocol::order_types::{
OrderStatus, OrderTrait, OrderType, CancelInfo, FulfillInfo, ExecutionValidationInfo
Expand Down Expand Up @@ -303,6 +310,16 @@ mod orderbook {
order.unwrap()
}

/// Retrieves the order signer using its hash.
/// # View
fn get_order_signer(self: @ContractState, order_hash: felt252) -> felt252 {
let order_signer = self.order_signers.read(order_hash);
if (order_signer.is_zero()) {
panic_with_felt252(orderbook_errors::ORDER_NOT_FOUND);
}
order_signer
}

/// Retrieves the order hash using its token hash.
/// # View
fn get_order_hash(self: @ContractState, token_hash: felt252) -> felt252 {
Expand All @@ -328,6 +345,7 @@ mod orderbook {
fn create_order(ref self: ContractState, order: OrderV1, signer: Signer) {
let order_hash = order.compute_order_hash();
let user_pubkey = SignerValidator::verify(order_hash, signer);

let block_ts = starknet::get_block_timestamp();
let validation = order.validate_common_data(block_ts);
if validation.is_err() {
Expand All @@ -347,19 +365,22 @@ mod orderbook {
OrderType::CollectionOffer => {
self._create_collection_offer(order, order_type, order_hash);
},
}
};
self.order_signers.write(order_hash, user_pubkey);
}

fn cancel_order(ref self: ContractState, cancel_info: CancelInfo, signer: Signer) {
let mut generated_signer = signer.clone();
generated_signer.set_public_key(self.order_signers.read(cancel_info.order_hash));
SignerValidator::verify(cancel_info.order_hash, generated_signer);
let original_signer_public_key = self.order_signers.read(cancel_info.order_hash);
let mut canceller_signer = signer.clone();
canceller_signer.set_public_key(original_signer_public_key);
let cancel_info_hash = serialized_hash(cancel_info);
SignerValidator::verify(cancel_info_hash, canceller_signer);

let order_hash = cancel_info.order_hash;
let order_option = order_read::<OrderV1>(order_hash);
assert(order_option.is_some(), orderbook_errors::ORDER_NOT_FOUND);
let order = order_option.unwrap();
assert(order.offerer == cancel_info.canceller, 'not the same offerrer'); // TODO
assert(order.offerer == cancel_info.canceller, 'not the same offerrer');
let status = match order_status_read(order_hash) {
Option::Some(s) => s,
Option::None => panic_with_felt252(orderbook_errors::ORDER_NOT_FOUND),
Expand All @@ -372,7 +393,6 @@ mod orderbook {
let (auction_order_hash, auction_end_date, auction_offer_count) = self
.auctions
.read(auction_token_hash);

assert(
block_ts <= auction_end_date, orderbook_errors::ORDER_AUCTION_IS_EXPIRED
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use snforge_std::{
EventAssertions, Event, SpyOn, test_address,
signature::{StarkCurveKeyPair, StarkCurveKeyPairTrait, Verifier}
};
use super::super::common::setup::{setup_auction_order, sign_mock, setup_orders, setup_offer};

use super::super::common::setup::{
setup_auction_order, setup_listing, sign_mock, setup_orders, setup_offer
};

#[test]
fn test_cancel_auction() {
Expand All @@ -38,7 +41,9 @@ fn test_cancel_auction() {
token_id: auction_listing_order.token_id,
};

dispatcher.cancel_order(cancel_info, signer: signer);
let cancel_info_hash = serialized_hash(cancel_info);
let canceller_signer = sign_mock(cancel_info_hash, Option::None);
dispatcher.cancel_order(cancel_info, signer: canceller_signer);
}

#[test]
Expand Down Expand Up @@ -68,8 +73,10 @@ fn test_cancel_non_existing_order() {
token_id: Option::Some(1),
};

let cancel_info_hash = serialized_hash(cancel_info);
let canceller_signer = sign_mock(cancel_info_hash, Option::None);
let dispatcher = OrderbookDispatcher { contract_address };
dispatcher.cancel_order(cancel_info, signer: signer);
dispatcher.cancel_order(cancel_info, signer: canceller_signer);
}

#[test]
Expand Down Expand Up @@ -104,9 +111,8 @@ fn test_invalid_cancel_auction_order() {
let order_hash = auction_listing_order.compute_order_hash();

let cancel_info_hash = serialized_hash(cancel_info);
let fulfill_signer = sign_mock(cancel_info_hash, Option::None);
let cancel_signer = sign_mock(order_hash, Option::None);
dispatcher.cancel_order(cancel_info, signer: cancel_signer);
let canceller_signer = sign_mock(cancel_info_hash, Option::None);
dispatcher.cancel_order(cancel_info, signer: canceller_signer);
}

#[test]
Expand Down Expand Up @@ -150,9 +156,7 @@ fn test_cancel_auction_during_the_extended_time() {
};

let cancel_info_hash = serialized_hash(cancel_info);
let fulfill_signer = sign_mock(cancel_info_hash, Option::None);
let cancel_signer = sign_mock(order_hash, Option::None);
let canceller_signer = sign_mock(cancel_info_hash, Option::None);

dispatcher.cancel_order(cancel_info, signer: auction_listing_signer);
dispatcher.cancel_order(cancel_info, signer: canceller_signer);
}

Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ fn test_create_listing_order() {
assert(order_status == OrderStatus::Open.into(), 'Order status is not open');
}


#[test]
fn test_auction_order_with_extended_time_order() {
let start_date = 1699556828;
Expand Down
8 changes: 8 additions & 0 deletions crates/ark-contracts/common/Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,11 @@ version = 1
[[package]]
name = "ark_common"
version = "0.1.0"
dependencies = [
"snforge_std",
]

[[package]]
name = "snforge_std"
version = "0.1.0"
source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.9.1#da085bd11e1b151d0592f43917136560d9b70d37"
1 change: 1 addition & 0 deletions crates/ark-contracts/common/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ version = "0.1.0"

[dependencies]
starknet = "2.3.1"
snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.9.1" }

[lib]
1 change: 0 additions & 1 deletion crates/ark-contracts/common/src/protocol/order_types.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ struct CancelInfo {
token_id: Option<u256>,
}


/// The info related to the fulfill of an order.
#[derive(starknet::Store, Serde, Copy, Drop)]
struct FulfillInfo {
Expand Down
32 changes: 32 additions & 0 deletions crates/ark-contracts/common/tests/test_hash.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use core::debug::PrintTrait;
use ark_common::crypto::hash::serialized_hash;
use ark_common::crypto::signer;
use core::option::OptionTrait;
use ark_common::crypto::signer::{Signer, SignInfo};
use snforge_std::signature::{
StarkCurveKeyPair, StarkCurveKeyPairTrait, Signer as SNSigner, Verifier
};

fn sign_mock(message_hash: felt252, pk: Option<felt252>) -> Signer {
let private_key: felt252 = 0x1234567890987654321;
if pk.is_some() {
let private_key = pk.unwrap();
}
let mut key_pair = StarkCurveKeyPairTrait::from_private_key(private_key);
let (r, s) = key_pair.sign(message_hash).unwrap();

key_pair.public_key.print();
r.print();
s.print();
Signer::WEIERSTRESS_STARKNET(
SignInfo { user_pubkey: key_pair.public_key, user_sig_r: r, user_sig_s: s, }
)
}

#[test]
fn test_create_listing() {
let order_hash = 1187808578384236024063928606418651083953200617486980640350664202139369612009;
let poseidon_hash = serialized_hash(order_hash);
poseidon_hash.print();
let signer = sign_mock(poseidon_hash, Option::None);
}
24 changes: 14 additions & 10 deletions crates/solis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ There is a `args.rs` and `solis_args.rs` files. Those are related to `clap` argu
## How to run Solis

To run, Solis requires some important information:

1. A messaging configuration to interact with Starknet. You can choose between `messaging.json` and `messaging.local.json`. Those files contain all the configuration required by Katana to send/receive messages to/from a Starknet sequencer. This Starknet sequencer only required the `appchain_messaging` contract to be deployed. No other modifications.
As this configuration also contains the RPC URL of the Starknet node, Solis will then be able to call some functions on the node to verify the orders content.

Expand All @@ -39,31 +40,34 @@ As you can see, the orderbook address must be known before Solis starts. To ensu

## Solis default

* port: default port is `7777`, to refer to the Everai collection with 7777 Everais. So, in order to run a Starknet node with Katana, you can keep using the default port `5050` (be sure this Katana node is up and running before starting Solis).
- port: default port is `7777`, to refer to the Everai collection with 7777 Everais. So, in order to run a Starknet node with Katana, you can keep using the default port `5050` (be sure this Katana node is up and running before starting Solis).

* fees: by default, fees are currently disabled on Solis.
- fees: by default, fees are currently disabled on Solis.

* pre-funded accounts: dev accounts are for now limited to 2, and the seed used is the same as Katana (to ease the re-use of Starkli built-in accounts like `katana-0` and `katana-1`).
- pre-funded accounts: dev accounts are for now limited to 2, and the seed used is the same as Katana (to ease the re-use of Starkli built-in accounts like `katana-0` and `katana-1`).

## Work locally with Katana as Starknet node

1. First, opens a terminal and start Katana:

```bash
dojoup -v nightly
katana
```
2. Deploy the `appchain_messaging` contract on Katana:
```bash
make -C crates/ark-contracts/starknet/ setup_messaging_katana
```
3. Start Solis with `messaging.local.json`, already configured with the deployed `appchain_messaging` address:

(be aware if you have a previous version of Katana installed, you may have to remove the `~/.katana` folder)

2. Start Solis with `messaging.local.json`, already configured with the deployed `appchain_messaging` address:

```bash
cargo run -p solis -- \
--messaging crates/solis/messaging.local.json \
--orderbook-address 0x024df499c7b1b14c0e52ea237e26a7401ef70507cf72eaef105316dfb5a207a7 \
--executor-address 0x1234
```
4. Deploy the `orderbook` contract on Katana with a pre-defined salt to have the same address as the one used to start Solis.

3. Deploy the `contracts` contract on Katana:

```bash
make -C crates/ark-contracts/arkchain/ setup_orderbook
node scripts/deployer/index.js
```
14 changes: 7 additions & 7 deletions crates/solis/messaging.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"chain": "starknet",
"rpc_url": "http://127.0.0.1:5050",
"contract_address": "0x046c0ea3fb2ad27053e8af3c8cfab38a51afb9fe90fcab1f75446bd41f7d3796",
"sender_address": "0x5686a647a9cdd63ade617e0baf3b364856b813b508f03903eb58a7e622d5855",
"private_key": "0x33003003001800009900180300d206308b0070db00121318d17b5e6262150b",
"interval": 2,
"from_block": 0
"chain": "starknet",
"rpc_url": "http://127.0.0.1:5050",
"contract_address": "0x046c0ea3fb2ad27053e8af3c8cfab38a51afb9fe90fcab1f75446bd41f7d3796",
"sender_address": "0x5686a647a9cdd63ade617e0baf3b364856b813b508f03903eb58a7e622d5855",
"private_key": "0x33003003001800009900180300d206308b0070db00121318d17b5e6262150b",
"interval": 2,
"from_block": 0
}
Loading

0 comments on commit 296b1d9

Please sign in to comment.