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

audit: NM audit fixes +ISolverHooks refactor #5

Merged
merged 15 commits into from
Jul 11, 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
10 changes: 5 additions & 5 deletions docs/1-technical-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,11 @@ fn deposit(
) -> (u256, u256, u256);
```

Withdrawals can be made either by calling `withdraw_at_ratio()` to withdraw from a public vault, or `withdraw()` to withdraw an arbitrary amounts from a private vault (available to the vault owner only).
Withdrawals can be made either by calling `withdraw_public()` to withdraw from a public vault, or `withdraw_private()` to withdraw an arbitrary amounts from a private vault (available to the vault owner only).

```rust
// Burn pool shares and withdraw funds from market.
// Called for public vaults. For private vaults, use `withdraw_amount`.
// Called for public vaults. For private vaults, use `withdraw_private`.
//
// # Arguments
// * `market_id` - market id
Expand All @@ -157,12 +157,12 @@ Withdrawals can be made either by calling `withdraw_at_ratio()` to withdraw from
// # Returns
// * `base_amount` - base asset withdrawn
// * `quote_amount` - quote asset withdrawn
fn withdraw_at_ratio(
fn withdraw_public(
ref self: TContractState, market_id: felt252, shares: u256
) -> (u256, u256);

// Withdraw exact token amounts from market.
// Called for private vaults. For public vaults, use `withdraw_at_ratio`.
// Called for private vaults. For public vaults, use `withdraw_public`.
//
// # Arguments
// * `market_id` - market id
Expand All @@ -172,7 +172,7 @@ fn withdraw_at_ratio(
// # Returns
// * `base_amount` - base asset withdrawn
// * `quote_amount` - quote asset withdrawn
fn withdraw(
fn withdraw_private(
ref self: TContractState, market_id: felt252, base_amount: u256, quote_amount: u256
) -> (u256, u256);
```
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/contracts/mocks/mock_solver.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,14 @@ pub mod MockSolver {
}

// Callback function to execute any state updates after a swap is completed.
// This fn should only be callable by the solver contract.
//
// # Arguments
// * `market_id` - market id
// * `swap_params` - swap parameters
fn after_swap(ref self: ContractState, market_id: felt252, swap_params: SwapParams) {}
fn after_swap(ref self: ContractState, market_id: felt252, swap_params: SwapParams) {
assert(self.solver.unlocked.read(), 'NotSolver');
}
}

#[abi(embed_v0)]
Expand Down
26 changes: 19 additions & 7 deletions packages/core/src/contracts/solver.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub mod SolverComponent {
withdraw_fees: LegacyMap::<ContractAddress, u256>,
// vault token class hash
vault_token_class: ClassHash,
// reentrancy guard (unlocked for hook calls)
unlocked: bool,
}

////////////////////////////////
Expand Down Expand Up @@ -377,6 +379,7 @@ pub mod SolverComponent {
// Check params.
assert(market_info.base_token != contract_address_const::<0x0>(), 'BaseTokenNull');
assert(market_info.quote_token != contract_address_const::<0x0>(), 'QuoteTokenNull');
assert(market_info.base_token != market_info.quote_token, 'SameToken');
assert(market_info.owner != contract_address_const::<0x0>(), 'OwnerNull');

// Set market info.
Expand All @@ -387,7 +390,7 @@ pub mod SolverComponent {
if market_info.is_public {
let vault_token_addr = self._deploy_vault_token(market_info);
vault_token = Option::Some(vault_token_addr);
let mut state: MarketState = self.market_state.read(market_id);
let mut state: MarketState = Default::default();
state.vault_token = vault_token_addr;
self.market_state.write(market_id, state);
}
Expand Down Expand Up @@ -426,6 +429,12 @@ pub mod SolverComponent {
fn swap(
ref self: ComponentState<TContractState>, market_id: felt252, swap_params: SwapParams,
) -> (u256, u256) {
// Run validity checks.
let state: MarketState = self.market_state.read(market_id);
let market_info: MarketInfo = self.market_info.read(market_id);
assert(market_info.base_token != contract_address_const::<0x0>(), 'MarketNull');
assert(!state.is_paused, 'Paused');

// Get amounts.
let solver_hooks = ISolverHooksDispatcher { contract_address: get_contract_address() };
let (amount_in, amount_out) = solver_hooks.quote(market_id, swap_params);
Expand Down Expand Up @@ -469,7 +478,9 @@ pub mod SolverComponent {
self.market_state.write(market_id, state);

// Execute after swap hook.
self.unlocked.write(true);
solver_hooks.after_swap(market_id, swap_params);
self.unlocked.write(false);

// Emit events.
self
Expand Down Expand Up @@ -754,7 +765,7 @@ pub mod SolverComponent {
}

// Burn pool shares and withdraw funds from market.
// Called for public vaults. For private vaults, use `withdraw_amount`.
// Called for public vaults. For private vaults, use `withdraw_private`.
//
// # Arguments
// * `market_id` - market id
Expand All @@ -763,7 +774,7 @@ pub mod SolverComponent {
// # Returns
// * `base_amount` - base asset withdrawn
// * `quote_amount` - quote asset withdrawn
fn withdraw_at_ratio(
fn withdraw_public(
ref self: ComponentState<TContractState>, market_id: felt252, shares: u256
) -> (u256, u256) {
// Fetch state.
Expand All @@ -773,7 +784,7 @@ pub mod SolverComponent {
// Run checks.
assert(market_info.base_token != contract_address_const::<0x0>(), 'MarketNull');
assert(shares != 0, 'SharesZero');
assert(market_info.is_public, 'UseWithdraw');
assert(market_info.is_public, 'UseWithdrawPrivate');
let vault_token = ERC20ABIDispatcher { contract_address: state.vault_token };
let caller = get_caller_address();
assert(shares <= vault_token.balanceOf(caller), 'InsuffShares');
Expand All @@ -795,7 +806,7 @@ pub mod SolverComponent {
}

// Withdraw exact token amounts from market.
// Called for private vaults. For public vaults, use `withdraw_at_ratio`.
// Called for private vaults. For public vaults, use `withdraw_public`.
//
// # Arguments
// * `market_id` - market id
Expand All @@ -805,7 +816,7 @@ pub mod SolverComponent {
// # Returns
// * `base_amount` - base asset withdrawn
// * `quote_amount` - quote asset withdrawn
fn withdraw(
fn withdraw_private(
ref self: ComponentState<TContractState>,
market_id: felt252,
base_amount: u256,
Expand All @@ -817,7 +828,8 @@ pub mod SolverComponent {

// Run checks.
assert(market_info.base_token != contract_address_const::<0x0>(), 'MarketNull');
assert(!market_info.is_public, 'UseWithdrawAtRatio');
assert(!market_info.is_public, 'UseWithdrawPublic');
self.assert_market_owner(market_id);

// Cap withdraw amount at available. Commit state changes.
let base_withdraw = min(base_amount, state.base_reserves);
Expand Down
12 changes: 5 additions & 7 deletions packages/core/src/interfaces/ISolver.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ pub trait ISolver<TContractState> {
) -> (u256, u256, u256);

// Burn pool shares and withdraw funds from market.
// Called for public vaults. For private vaults, use `withdraw_amount`.
// Called for public vaults. For private vaults, use `withdraw_private`.
//
// # Arguments
// * `market_id` - market id
Expand All @@ -187,12 +187,10 @@ pub trait ISolver<TContractState> {
// # Returns
// * `base_amount` - base asset withdrawn
// * `quote_amount` - quote asset withdrawn
fn withdraw_at_ratio(
ref self: TContractState, market_id: felt252, shares: u256
) -> (u256, u256);
fn withdraw_public(ref self: TContractState, market_id: felt252, shares: u256) -> (u256, u256);

// Withdraw exact token amounts from market.
// Called for private vaults. For public vaults, use `withdraw_at_ratio`.
// Called for private vaults. For public vaults, use `withdraw_public`.
//
// # Arguments
// * `market_id` - market id
Expand All @@ -202,7 +200,7 @@ pub trait ISolver<TContractState> {
// # Returns
// * `base_amount` - base asset withdrawn
// * `quote_amount` - quote asset withdrawn
fn withdraw(
fn withdraw_private(
ref self: TContractState, market_id: felt252, base_amount: u256, quote_amount: u256
) -> (u256, u256);

Expand Down Expand Up @@ -263,7 +261,7 @@ pub trait ISolver<TContractState> {
fn upgrade(ref self: TContractState, new_class_hash: ClassHash);
}

// Solvers must implement the `ISolverHooks` interface, which returns the quote for a swap.
// Solvers must implement the `ISolverHooks` interface.
#[starknet::interface]
pub trait ISolverHooks<TContractState> {
// Obtain quote for swap through a market.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ fn test_erc20_bytearray() {
symbol.serialize(ref calldata);
let contract_address = erc20_class.deploy(@calldata).unwrap();
let result = erc20_versioned_call::get_symbol(contract_address);
println!("result(bytearray): {}", result);
assert(result == "MOCK", 'Symbol (byte array)');
}

Expand All @@ -38,6 +37,5 @@ fn test_erc20_felt252() {
symbol.serialize(ref calldata);
let contract_address = erc20_class.deploy(@calldata).unwrap();
let result = erc20_versioned_call::get_symbol(contract_address);
println!("result(felt252): {}", result);
assert(result == "MOCK", 'Symbol (felt252)');
}
19 changes: 19 additions & 0 deletions packages/core/src/tests/solver/test_create_market.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,25 @@ fn test_create_market_with_null_quote_token_fails() {
solver.create_market(market_info);
}

#[test]
#[should_panic(expected: ('SameToken',))]
fn test_create_market_with_same_token_fails() {
let (base_token, _quote_token, _vault_token_class, solver, _market_id, _vault_token_opt) =
before(
true
);

// Create market.
start_prank(CheatTarget::One(solver.contract_address), owner());
let market_info = MarketInfo {
base_token: base_token.contract_address,
quote_token: base_token.contract_address,
owner: alice(),
is_public: true,
};
solver.create_market(market_info);
}

#[test]
#[should_panic(expected: ('OwnerNull',))]
fn test_create_market_with_null_owner_fails() {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/tests/solver/test_pause.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fn test_pause_allows_withdraws() {

// Withdraw.
start_prank(CheatTarget::One(solver.contract_address), alice());
solver.withdraw_at_ratio(market_id, shares);
solver.withdraw_public(market_id, shares);
}

#[test]
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/tests/solver/test_swap.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -428,3 +428,24 @@ fn test_swap_fails_if_swap_sell_below_threshold_amount() {
};
solver.swap(market_id, params);
}

#[test]
#[should_panic(expected: ('NotSolver',))]
fn test_after_swap_fails_for_non_solver_caller() {
let (_base_token, _quote_token, _vault_token_class, solver, market_id, _vault_token_opt) =
before(
false
);

// Call after swap.
start_prank(CheatTarget::One(solver.contract_address), alice());
let solver_hooks = ISolverHooksDispatcher { contract_address: solver.contract_address };
let params = SwapParams {
is_buy: true,
amount: to_e18(10),
exact_input: true,
threshold_sqrt_price: Option::None(()),
threshold_amount: Option::None(()),
};
solver_hooks.after_swap(market_id, params);
}
Loading
Loading