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 positions list #16

Merged
merged 5 commits into from
Jan 4, 2024
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
- uses: actions/checkout@v3
- uses: software-mansion/setup-scarb@v1
with:
scarb-version: "0.7.0"
scarb-version: "2.4.1"
- run: scarb fmt --check
- run: scarb build
- run: scarb test
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
target
.env
accounts
accounts/*.json
14 changes: 14 additions & 0 deletions Scarb.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "openzeppelin"
version = "0.8.0"
source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.8.0#c23e8e96de60e6e3159b1ff8591a1187269c0eb7"

[[package]]
name = "swappy"
version = "0.0.1"
dependencies = [
"openzeppelin",
]
2 changes: 1 addition & 1 deletion Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ version = "0.0.1"

[dependencies]
starknet = ">=2.2.0"
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.7.0-rc.0" }
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.8.0" }

[[target.starknet-contract]]

Expand Down
55 changes: 55 additions & 0 deletions accounts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Create a new signer
```
starkli signer keystore new accounts/keystore.json
export STARKNET_KEYSTORE=accounts/keystore.json
```

# Create the deployer account
```
starkli account oz init accounts/deployer.json
starkli account deploy accounts/deployer.json
```

# Declare Swappy Account Contract
```
starkli declare target/dev/swappy_Account.contract_class.json --account accounts/deployer.json
# 0x03dd8bbd25d0807d830cc78f3aef5e759c186c8f91efe1fcf3531132fdcade4b
```

# Create a swappy account
```
starkli account oz init accounts/swappy.json
```
> update class_hash
```
starkli account deploy accounts/swappy.json
```

# Deploy Position Manager Contract
```
starkli declare target/dev/swappy_PositionManager.contract_class.json --account accounts/deployer.json
# 0x0303b3c6adfb5015a09bce62b23428bb3e3b4bca3726a27919ae78763b5e993a

starkli deploy 0x0303b3c6adfb5015a09bce62b23428bb3e3b4bca3726a27919ae78763b5e993a 0x0 --account accounts/deployer.json
# 0x0097ab8a6dc7760a687caaffa7101611b20babda533ce40b3cac94fb1926355e
```

# Get last position ID
```
starkli call 0x0097ab8a6dc7760a687caaffa7101611b20babda533ce40b3cac94fb1926355e get_last_id
```

# create position
```
starkli invoke 0x0097ab8a6dc7760a687caaffa7101611b20babda533ce40b3cac94fb1926355e create_position 0x1 0x2 u256:1 u256:1 --account accounts/swappy.json
```

# Pause position
```
starkli invoke 0x0097ab8a6dc7760a687caaffa7101611b20babda533ce40b3cac94fb1926355e pause_position [POSITION_ID] --account accounts/swappy.json
```

# Resume position
```
starkli invoke 0x0097ab8a6dc7760a687caaffa7101611b20babda533ce40b3cac94fb1926355e resume_position [POSITION_ID] --account accounts/swappy.json
```
264 changes: 35 additions & 229 deletions src/account/account.cairo
Original file line number Diff line number Diff line change
@@ -1,244 +1,50 @@
use array::ArrayTrait;
use array::SpanTrait;
use option::OptionTrait;
use serde::Serde;
use starknet::ContractAddress;
use starknet::account::Call;

const TRANSACTION_VERSION: felt252 = 1;

// 2**128 + TRANSACTION_VERSION
const QUERY_VERSION: felt252 = 340282366920938463463374607431768211457;

trait PublicKeyTrait<TState> {
fn set_public_key(ref self: TState, new_public_key: felt252);
fn get_public_key(self: @TState) -> felt252;
}

trait PublicKeyCamelTrait<TState> {
fn setPublicKey(ref self: TState, newPublicKey: felt252);
fn getPublicKey(self: @TState) -> felt252;
}

#[starknet::contract]
mod Account {
use array::ArrayTrait;
use array::SpanTrait;
use box::BoxTrait;
use ecdsa::check_ecdsa_signature;

use openzeppelin::account::interface;
use openzeppelin::introspection::interface::ISRC5;
use openzeppelin::introspection::interface::ISRC5Camel;
use openzeppelin::introspection::src5::SRC5;
use option::OptionTrait;
use starknet::get_caller_address;
use starknet::get_contract_address;
use starknet::get_tx_info;

use super::Call;
use super::QUERY_VERSION;
use super::TRANSACTION_VERSION;
use zeroable::Zeroable;

use swappy::account::error::AccountError;
use openzeppelin::account::AccountComponent;
use openzeppelin::introspection::src5::SRC5Component;

component!(path: AccountComponent, storage: account, event: AccountEvent);
component!(path: SRC5Component, storage: src5, event: SRC5Event);

// Account
#[abi(embed_v0)]
impl SRC6Impl = AccountComponent::SRC6Impl<ContractState>;
#[abi(embed_v0)]
impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl<ContractState>;
#[abi(embed_v0)]
impl PublicKeyImpl = AccountComponent::PublicKeyImpl<ContractState>;
#[abi(embed_v0)]
impl PublicKeyCamelImpl = AccountComponent::PublicKeyCamelImpl<ContractState>;
#[abi(embed_v0)]
impl DeclarerImpl = AccountComponent::DeclarerImpl<ContractState>;
#[abi(embed_v0)]
impl DeployableImpl = AccountComponent::DeployableImpl<ContractState>;

impl AccountInternalImpl = AccountComponent::InternalImpl<ContractState>;

// SRC5
#[abi(embed_v0)]
impl SRC5Impl = SRC5Component::SRC5Impl<ContractState>;

#[storage]
struct Storage {
public_key: felt252
#[substorage(v0)]
account: AccountComponent::Storage,
#[substorage(v0)]
src5: SRC5Component::Storage
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
OwnerAdded: OwnerAdded,
OwnerRemoved: OwnerRemoved,
}

#[derive(Drop, starknet::Event)]
struct OwnerAdded {
new_owner_guid: felt252
}

#[derive(Drop, starknet::Event)]
struct OwnerRemoved {
removed_owner_guid: felt252
#[flat]
AccountEvent: AccountComponent::Event,
#[flat]
SRC5Event: SRC5Component::Event
}

#[constructor]
fn constructor(ref self: ContractState, _public_key: felt252) {
self.initializer(_public_key);
}

//
// External
//

#[external(v0)]
impl SRC6Impl of interface::ISRC6<ContractState> {
fn __execute__(self: @ContractState, mut calls: Array<Call>) -> Array<Span<felt252>> {
// Avoid calls from other contracts
let sender = get_caller_address();
assert(sender.is_zero(), AccountError::INVALID_CALLER);

// Check tx version
let tx_info = get_tx_info().unbox();
let version = tx_info.version;
if version != TRANSACTION_VERSION {
assert(version == QUERY_VERSION, AccountError::INVALID_TX_VERSION);
}

_execute_calls(calls)
}

fn __validate__(self: @ContractState, mut calls: Array<Call>) -> felt252 {
self.validate_transaction()
}

fn is_valid_signature(
self: @ContractState, hash: felt252, signature: Array<felt252>
) -> felt252 {
if self._is_valid_signature(hash, signature.span()) {
starknet::VALIDATED
} else {
0
}
}
}

#[external(v0)]
impl SRC6CamelOnlyImpl of interface::ISRC6CamelOnly<ContractState> {
fn isValidSignature(
self: @ContractState, hash: felt252, signature: Array<felt252>
) -> felt252 {
SRC6Impl::is_valid_signature(self, hash, signature)
}
}

#[external(v0)]
impl DeclarerImpl of interface::IDeclarer<ContractState> {
fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 {
self.validate_transaction()
}
}

#[external(v0)]
impl SRC5Impl of ISRC5<ContractState> {
fn supports_interface(self: @ContractState, interface_id: felt252) -> bool {
let unsafe_state = SRC5::unsafe_new_contract_state();
SRC5::SRC5Impl::supports_interface(@unsafe_state, interface_id)
}
}

#[external(v0)]
impl SRC5CamelImpl of ISRC5Camel<ContractState> {
fn supportsInterface(self: @ContractState, interfaceId: felt252) -> bool {
let unsafe_state = SRC5::unsafe_new_contract_state();
SRC5::SRC5CamelImpl::supportsInterface(@unsafe_state, interfaceId)
}
}

#[external(v0)]
impl PublicKeyImpl of super::PublicKeyTrait<ContractState> {
fn get_public_key(self: @ContractState) -> felt252 {
self.public_key.read()
}

fn set_public_key(ref self: ContractState, new_public_key: felt252) {
assert_only_self();
self.emit(OwnerRemoved { removed_owner_guid: self.public_key.read() });
self._set_public_key(new_public_key);
}
}

#[external(v0)]
impl PublicKeyCamelImpl of super::PublicKeyCamelTrait<ContractState> {
fn getPublicKey(self: @ContractState) -> felt252 {
self.public_key.read()
}

fn setPublicKey(ref self: ContractState, newPublicKey: felt252) {
PublicKeyImpl::set_public_key(ref self, newPublicKey);
}
}

#[external(v0)]
fn __validate_deploy__(
self: @ContractState,
class_hash: felt252,
contract_address_salt: felt252,
_public_key: felt252
) -> felt252 {
self.validate_transaction()
}

//
// Internal
//

#[generate_trait]
impl InternalImpl of InternalTrait {
fn initializer(ref self: ContractState, _public_key: felt252) {
let mut unsafe_state = SRC5::unsafe_new_contract_state();
SRC5::InternalImpl::register_interface(ref unsafe_state, interface::ISRC6_ID);
self._set_public_key(_public_key);
}

fn validate_transaction(self: @ContractState) -> felt252 {
let tx_info = get_tx_info().unbox();
let tx_hash = tx_info.transaction_hash;
let signature = tx_info.signature;
assert(self._is_valid_signature(tx_hash, signature), AccountError::INVALID_SIGNATURE);
starknet::VALIDATED
}

fn _set_public_key(ref self: ContractState, new_public_key: felt252) {
self.public_key.write(new_public_key);
self.emit(OwnerAdded { new_owner_guid: new_public_key });
}

fn _is_valid_signature(
self: @ContractState, hash: felt252, signature: Span<felt252>
) -> bool {
let valid_length = signature.len() == 2_u32;

if valid_length {
check_ecdsa_signature(
hash, self.public_key.read(), *signature.at(0_u32), *signature.at(1_u32)
)
} else {
false
}
}
}

#[internal]
fn assert_only_self() {
let caller = get_caller_address();
let self = get_contract_address();
assert(self == caller, AccountError::UNAUTHORIZED);
}

#[internal]
fn _execute_calls(mut calls: Array<Call>) -> Array<Span<felt252>> {
let mut res = ArrayTrait::new();
loop {
match calls.pop_front() {
Option::Some(call) => {
let _res = _execute_single_call(call);
res.append(_res);
},
Option::None(_) => {
break ();
},
};
};
res
}

#[internal]
fn _execute_single_call(call: Call) -> Span<felt252> {
let Call{to, selector, calldata } = call;
starknet::call_contract_syscall(to, selector, calldata.span()).unwrap()
fn constructor(ref self: ContractState, public_key: felt252) {
self.account.initializer(public_key);
}
}
10 changes: 5 additions & 5 deletions src/account/error.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mod AccountError {
const INVALID_CALLER: felt252 = 'Account: invalid caller';
const INVALID_TX_VERSION: felt252 = 'Account: invalid tx version';
const INVALID_SIGNATURE: felt252 = 'Account: invalid signature';
const UNAUTHORIZED: felt252 = 'Account: unauthorized';
mod AccountError { // const INVALID_CALLER: felt252 = 'Account: invalid caller';
// const INVALID_TX_VERSION: felt252 = 'Account: invalid tx version';
// const INVALID_SIGNATURE: felt252 = 'Account: invalid signature';
// const UNAUTHORIZED: felt252 = 'Account: unauthorized';
// const POSITION_NOT_FOUND: felt252 = 'Account: position not found';
}
Loading