From cf5473ee319bf76f05659a88cc2f2fc8bdaf2ad6 Mon Sep 17 00:00:00 2001 From: henryf10h Date: Mon, 27 May 2024 22:05:56 -0400 Subject: [PATCH] fee_on_transfer_added --- .gitignore | 5 + .snfoundry_cache/.prev_tests_failed | 0 README.md | 3 +- Scarb.lock | 14 + Scarb.toml | 18 + contracts/Factory.cairo | 294 ------ contracts/FactoryProxy.cairo | 104 -- contracts/Pair.cairo | 947 ----------------- contracts/PairProxy.cairo | 104 -- contracts/Router.cairo | 665 ------------ contracts/RouterProxy.cairo | 103 -- contracts/test/FactoryV2.cairo | 300 ------ contracts/test/FlashSwapTest.cairo | 73 -- contracts/test/PairV2.cairo | 954 ------------------ contracts/test/RouterV2.cairo | 671 ------------ contracts/utils/Multicall.cairo | 59 -- contracts/utils/math.cairo | 84 -- lib/cairo_contracts | 1 - protostar.toml | 14 - public/gas_comp_1.png | Bin 0 -> 9780 bytes src/contracts/FactoryC1.cairo | 257 +++++ src/contracts/PairC1.cairo | 690 +++++++++++++ src/contracts/RouterC1V2.cairo | 580 +++++++++++ src/interfaces/ownable_interface.cairo | 17 + src/lib.cairo | 28 + src/tests/test_add_remove_liquidity.cairo | 1 + .../test_add_remove_liquidity.cairo | 344 +++++++ src/tests/test_create_pair.cairo | 1 + .../test_create_pair/test_create_pair.cairo | 100 ++ src/tests/test_deployment.cairo | 1 + .../test_deployment/test_deployment.cairo | 109 ++ src/tests/test_flash_swap.cairo | 1 + .../test_flash_swap/test_flash_swap.cairo | 533 ++++++++++ src/tests/test_protocol_fees.cairo | 1 + .../test_protocol_fees.cairo | 190 ++++ src/tests/test_swap.cairo | 1 + src/tests/test_swap/test_swap.cairo | 476 +++++++++ src/tests/test_updates.cairo | 1 + src/tests/test_updates/test_updates.cairo | 82 ++ src/tests/utils.cairo | 1 + src/tests/utils/utils.cairo | 36 + src/utils/FlashSwapTest.cairo | 76 ++ src/utils/erc20.cairo | 180 ++++ src/utils/ownable.cairo | 130 +++ src/utils/reflect.cairo | 459 +++++++++ tests/test_add_remove_liquidity.cairo | 660 ------------ tests/test_create_pair.cairo | 153 --- tests/test_deployment.cairo | 169 ---- tests/test_flash_swap.cairo | 522 ---------- tests/test_multicall.cairo | 152 --- tests/test_protocol_fee.cairo | 314 ------ tests/test_swap.cairo | 635 ------------ tests/test_updates.cairo | 228 ----- tests/test_upgrades.cairo | 201 ---- 54 files changed, 4334 insertions(+), 7408 deletions(-) create mode 100644 .snfoundry_cache/.prev_tests_failed create mode 100644 Scarb.lock create mode 100644 Scarb.toml delete mode 100644 contracts/Factory.cairo delete mode 100644 contracts/FactoryProxy.cairo delete mode 100644 contracts/Pair.cairo delete mode 100644 contracts/PairProxy.cairo delete mode 100644 contracts/Router.cairo delete mode 100644 contracts/RouterProxy.cairo delete mode 100644 contracts/test/FactoryV2.cairo delete mode 100644 contracts/test/FlashSwapTest.cairo delete mode 100644 contracts/test/PairV2.cairo delete mode 100644 contracts/test/RouterV2.cairo delete mode 100644 contracts/utils/Multicall.cairo delete mode 100644 contracts/utils/math.cairo delete mode 160000 lib/cairo_contracts delete mode 100644 protostar.toml create mode 100644 public/gas_comp_1.png create mode 100644 src/contracts/FactoryC1.cairo create mode 100644 src/contracts/PairC1.cairo create mode 100644 src/contracts/RouterC1V2.cairo create mode 100644 src/interfaces/ownable_interface.cairo create mode 100644 src/lib.cairo create mode 100644 src/tests/test_add_remove_liquidity.cairo create mode 100644 src/tests/test_add_remove_liquidity/test_add_remove_liquidity.cairo create mode 100644 src/tests/test_create_pair.cairo create mode 100644 src/tests/test_create_pair/test_create_pair.cairo create mode 100644 src/tests/test_deployment.cairo create mode 100644 src/tests/test_deployment/test_deployment.cairo create mode 100644 src/tests/test_flash_swap.cairo create mode 100644 src/tests/test_flash_swap/test_flash_swap.cairo create mode 100644 src/tests/test_protocol_fees.cairo create mode 100644 src/tests/test_protocol_fees/test_protocol_fees.cairo create mode 100644 src/tests/test_swap.cairo create mode 100644 src/tests/test_swap/test_swap.cairo create mode 100644 src/tests/test_updates.cairo create mode 100644 src/tests/test_updates/test_updates.cairo create mode 100644 src/tests/utils.cairo create mode 100644 src/tests/utils/utils.cairo create mode 100644 src/utils/FlashSwapTest.cairo create mode 100644 src/utils/erc20.cairo create mode 100644 src/utils/ownable.cairo create mode 100644 src/utils/reflect.cairo delete mode 100644 tests/test_add_remove_liquidity.cairo delete mode 100644 tests/test_create_pair.cairo delete mode 100644 tests/test_deployment.cairo delete mode 100644 tests/test_flash_swap.cairo delete mode 100644 tests/test_multicall.cairo delete mode 100644 tests/test_protocol_fee.cairo delete mode 100644 tests/test_swap.cairo delete mode 100644 tests/test_updates.cairo delete mode 100644 tests/test_upgrades.cairo diff --git a/.gitignore b/.gitignore index de41d4d..f36c972 100644 --- a/.gitignore +++ b/.gitignore @@ -136,5 +136,10 @@ dmypy.json node.json +<<<<<<< HEAD +======= +.vscode/ + +>>>>>>> fee_on_transfer_added/fee_on_transfer_added 127.0.0.1.accounts.json 127.0.0.1.deployments.txt \ No newline at end of file diff --git a/.snfoundry_cache/.prev_tests_failed b/.snfoundry_cache/.prev_tests_failed new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index 0b6c073..43e2203 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD # JediSwap Clone of Uniswap V2 to Cairo. AMM for StarkNet. @@ -67,4 +68,4 @@ starknet-devnet Run script by specifying the path to the script file. Example: ``` python scripts/deploy.py local -``` \ No newline at end of file +``` diff --git a/Scarb.lock b/Scarb.lock new file mode 100644 index 0000000..c4d2c21 --- /dev/null +++ b/Scarb.lock @@ -0,0 +1,14 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "jediswap" +version = "0.1.0" +dependencies = [ + "snforge_std", +] + +[[package]] +name = "snforge_std" +version = "0.23.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.23.0#f2bff8f796763ada77fe6033ec1b034ceee22abd" diff --git a/Scarb.toml b/Scarb.toml new file mode 100644 index 0000000..6bc780b --- /dev/null +++ b/Scarb.toml @@ -0,0 +1,18 @@ +[package] +name = "jediswap" +version = "0.1.0" +edition = "2023_01" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +starknet = ">=2.2.0" +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.23.0" } + +[tool.snforge] +exit_first = true + +[[target.starknet-contract]] +allowed-libfuncs-list.name = "experimental" +sierra = true +casm = true \ No newline at end of file diff --git a/contracts/Factory.cairo b/contracts/Factory.cairo deleted file mode 100644 index c7ae1cf..0000000 --- a/contracts/Factory.cairo +++ /dev/null @@ -1,294 +0,0 @@ -%lang starknet - -// @title JediSwap V2 Factory -// @author Mesh Finance -// @license MIT -// @notice Factory to create and register new pairs - -from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin -from starkware.starknet.common.syscalls import get_caller_address, deploy, get_contract_address -from starkware.cairo.common.uint256 import Uint256 -from starkware.cairo.common.hash import hash2 -from starkware.cairo.common.math import assert_not_zero, assert_not_equal -from starkware.cairo.common.alloc import alloc -from starkware.cairo.common.math_cmp import is_le, is_le_felt -from openzeppelin.upgrades.library import Proxy - -// -// Storage -// - -// @dev Address of fee recipient -@storage_var -func _fee_to() -> (address: felt) { -} - -// @dev Address allowed to change feeTo. -@storage_var -func _fee_to_setter() -> (address: felt) { -} - -// @dev Array of all pairs -@storage_var -func _all_pairs(index: felt) -> (address: felt) { -} - -// @dev Pair address for pair of `token0` and `token1` -@storage_var -func _pair(token0: felt, token1: felt) -> (pair: felt) { -} - -// @dev Total pairs -@storage_var -func _num_of_pairs() -> (num: felt) { -} - -@storage_var -func _pair_proxy_contract_class_hash() -> (class_hash: felt) { -} - -@storage_var -func _pair_contract_class_hash() -> (class_hash: felt) { -} - -// @dev Emitted each time a pair is created via createPair -// token0 is guaranteed to be strictly less than token1 by sort order. - -@event -func PairCreated(token0: felt, token1: felt, pair: felt, total_pairs: felt) { -} - -// -// Constructor -// - -// @notice Contract constructor -// @param fee_to_setter Fee Recipient Setter -@external -func initializer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - pair_proxy_contract_class_hash: felt, pair_contract_class_hash: felt, fee_to_setter: felt -) { - - with_attr error_message("Factory::constructor::Fee Recipient Setter can not be zero") { - assert_not_zero(fee_to_setter); - } - - with_attr error_message("Factory::constructor::Pair Proxy Contract Class Hash can not be zero") { - assert_not_zero(pair_proxy_contract_class_hash); - } - - with_attr error_message("Factory::constructor::Pair Contract Class Hash can not be zero") { - assert_not_zero(pair_contract_class_hash); - } - - _fee_to_setter.write(fee_to_setter); - _pair_proxy_contract_class_hash.write(pair_proxy_contract_class_hash); - _pair_contract_class_hash.write(pair_contract_class_hash); - _num_of_pairs.write(0); - Proxy.initializer(fee_to_setter); - return (); -} - -// -// Getters -// - -// @notice Get pair address for the pair of `token0` and `token1` -// @param token0 Address of token0 -// @param token1 Address of token1 -// @return pair Address of the pair -@view -func get_pair{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - token0: felt, token1: felt -) -> (pair: felt) { - let (pair_0_1) = _pair.read(token0, token1); - if (pair_0_1 == 0) { - let (pair_1_0) = _pair.read(token1, token0); - return (pair=pair_1_0); - } else { - return (pair=pair_0_1); - } -} - -// @notice Get all the pairs registered -// @return all_pairs_len Length of `all_pairs` array -// @return all_pairs Array of addresses of the registered pairs -@view -func get_all_pairs{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - all_pairs_len: felt, all_pairs: felt* -) { - alloc_locals; - let (num_pairs) = _num_of_pairs.read(); - let (local all_pairs: felt*) = alloc(); - let (all_pairs_end: felt*) = _build_all_pairs_array(0, num_pairs, all_pairs); - return (num_pairs, all_pairs); -} - -// @notice Get the number of pairs -// @return num_of_pairs -@view -func get_num_of_pairs{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - num_of_pairs: felt -) { - let (num_of_pairs) = _num_of_pairs.read(); - return (num_of_pairs,); -} - -// @notice Get fee recipient address -// @return address -@view -func get_fee_to{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - address: felt -) { - return _fee_to.read(); -} - -// @notice Get the address allowed to change fee_to. -// @return address -@view -func get_fee_to_setter{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - address: felt -) { - return _fee_to_setter.read(); -} - -// @notice Get the class hash of the Pair contract which is deployed for each pair. -// @return class_hash -@view -func get_pair_contract_class_hash{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - ) -> (class_hash: felt) { - return _pair_proxy_contract_class_hash.read(); -} - -// -// Setters -// - -// @notice Create pair of `tokenA` and `tokenB` with deterministic address using deploy -// @dev tokens are sorted before creating pair. We deploy PairProxy. -// @param tokenA Address of tokenA -// @param tokenB Address of tokenB -// @return pair Address of the created pair -@external -func create_pair{ - syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, bitwise_ptr: BitwiseBuiltin*, range_check_ptr -}(tokenA: felt, tokenB: felt) -> (pair: felt) { - alloc_locals; - with_attr error_message("Factory::create_pair::tokenA and tokenB must be non zero") { - assert_not_zero(tokenA); - assert_not_zero(tokenB); - } - - with_attr error_message("Factory::create_pair::tokenA and tokenB must be different") { - assert_not_equal(tokenA, tokenB); - } - - let (existing_pair) = get_pair(tokenA, tokenB); - with_attr error_message("Factory::create_pair::pair already exists for tokenA and tokenB") { - assert existing_pair = 0; - } - let (pair_proxy_class_hash: felt) = _pair_proxy_contract_class_hash.read(); - let (pair_implementation_class_hash: felt) = _pair_contract_class_hash.read(); - - let (token0, token1) = _sort_tokens(tokenA, tokenB); - - let (contract_address: felt) = get_contract_address(); - - tempvar pedersen_ptr = pedersen_ptr; - - let (salt) = hash2{hash_ptr=pedersen_ptr}(token0, token1); - - let (fee_to_setter) = get_fee_to_setter(); - - let constructor_calldata: felt* = alloc(); - - assert [constructor_calldata] = pair_implementation_class_hash; - assert [constructor_calldata + 1] = token0; - assert [constructor_calldata + 2] = token1; - assert [constructor_calldata + 3] = fee_to_setter; - - let (pair: felt) = deploy( - class_hash=pair_proxy_class_hash, - contract_address_salt=salt, - constructor_calldata_size=4, - constructor_calldata=constructor_calldata, - deploy_from_zero=0, - ); - - _pair.write(token0, token1, pair); - let (num_pairs) = _num_of_pairs.read(); - _all_pairs.write(num_pairs, pair); - _num_of_pairs.write(num_pairs + 1); - PairCreated.emit(token0=token0, token1=token1, pair=pair, total_pairs=num_pairs + 1); - - return (pair=pair); -} - -// @notice Change fee recipient to `new_fee_to` -// @dev Only fee_to_setter can change -// @param fee_to Address of new fee recipient -@external -func set_fee_to{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(new_fee_to: felt) { - let (sender) = get_caller_address(); - let (fee_to_setter) = get_fee_to_setter(); - with_attr error_message("Factory::set_fee_to::Caller must be fee to setter") { - assert sender = fee_to_setter; - } - _fee_to.write(new_fee_to); - return (); -} - -// @notice Change fee setter to `fee_to_setter` -// @dev Only fee_to_setter can change -// @param fee_to_setter Address of new fee setter -@external -func set_fee_to_setter{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - new_fee_to_setter: felt -) { - let (sender) = get_caller_address(); - let (fee_to_setter) = get_fee_to_setter(); - with_attr error_message("Factory::set_fee_to_setter::Caller must be fee to setter") { - assert sender = fee_to_setter; - } - with_attr error_message("Factory::set_fee_to_setter::new_fee_to_setter must be non zero") { - assert_not_zero(new_fee_to_setter); - } - _fee_to_setter.write(new_fee_to_setter); - return (); -} - -// -// Internals LIBRARY -// - -func _build_all_pairs_array{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - current_index: felt, num_pairs: felt, all_pairs: felt* -) -> (all_pairs: felt*) { - alloc_locals; - if (current_index == num_pairs) { - return (all_pairs,); - } - let (current_pair) = _all_pairs.read(current_index); - assert [all_pairs] = current_pair; - return _build_all_pairs_array(current_index + 1, num_pairs, all_pairs + 1); -} - -func _sort_tokens{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - tokenA: felt, tokenB: felt -) -> (token0: felt, token1: felt) { - alloc_locals; - local token0; - local token1; - assert_not_equal(tokenA, tokenB); - let is_tokenA_less_than_tokenB = is_le_felt(tokenA, tokenB); - if (is_tokenA_less_than_tokenB == 1) { - assert token0 = tokenA; - assert token1 = tokenB; - } else { - assert token0 = tokenB; - assert token1 = tokenA; - } - - assert_not_zero(token0); - return (token0, token1); -} diff --git a/contracts/FactoryProxy.cairo b/contracts/FactoryProxy.cairo deleted file mode 100644 index 2448916..0000000 --- a/contracts/FactoryProxy.cairo +++ /dev/null @@ -1,104 +0,0 @@ -%lang starknet - -// @title JediSwap V2 Factory Proxy -// @author Mesh Finance -// @license MIT -// @notice Upgradeable proxy for Factory.cairo - -from starkware.cairo.common.cairo_builtins import HashBuiltin -from starkware.starknet.common.syscalls import library_call, library_call_l1_handler -from starkware.cairo.common.alloc import alloc -from openzeppelin.upgrades.library import Proxy - -// -// Constructor -// - -@constructor -func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - implementation_hash: felt, pair_proxy_contract_class_hash:felt, pair_contract_class_hash: felt, fee_to_setter: felt -) { - - Proxy._set_implementation_hash(implementation_hash); - - let calldata: felt* = alloc(); - assert [calldata] = pair_proxy_contract_class_hash; - assert [calldata + 1] = pair_contract_class_hash; - assert [calldata + 2] = fee_to_setter; - library_call( - class_hash=implementation_hash, - function_selector=1295919550572838631247819983596733806859788957403169325509326258146877103642, // initializer - calldata_size=3, - calldata=calldata, - ); - return (); -} - -// -// Fallback functions -// - -@external -@raw_input -@raw_output -func __default__{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - selector: felt, calldata_size: felt, calldata: felt* -) -> (retdata_size: felt, retdata: felt*) { - let (class_hash) = Proxy.get_implementation_hash(); - - let (retdata_size: felt, retdata: felt*) = library_call( - class_hash=class_hash, - function_selector=selector, - calldata_size=calldata_size, - calldata=calldata, - ); - return (retdata_size, retdata); -} - -@l1_handler -@raw_input -func __l1_default__{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - selector: felt, calldata_size: felt, calldata: felt* -) { - let (class_hash) = Proxy.get_implementation_hash(); - - library_call_l1_handler( - class_hash=class_hash, - function_selector=selector, - calldata_size=calldata_size, - calldata=calldata, - ); - return (); -} - -@external -func upgrade{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - new_implementation: felt -) { - Proxy.assert_only_admin(); - Proxy._set_implementation_hash(new_implementation); - return (); -} - -@external -func set_admin{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - new_admin: felt -) { - Proxy.assert_only_admin(); - Proxy._set_admin(new_admin); - return (); -} - -@view -func get_admin{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - admin: felt -) { - return Proxy.get_admin(); -} - -@view -func get_implementation_hash{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - implementation: felt -) { - return Proxy.get_implementation_hash(); -} diff --git a/contracts/Pair.cairo b/contracts/Pair.cairo deleted file mode 100644 index 46d8b0b..0000000 --- a/contracts/Pair.cairo +++ /dev/null @@ -1,947 +0,0 @@ -%lang starknet - -// @title JediSwap Pair -// @author Mesh Finance -// @license MIT -// @notice Low level pair contract -// @dev Based on the Uniswap V2 pair -// https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol -// Also an ERC20 token - -from starkware.cairo.common.cairo_builtins import HashBuiltin -from starkware.cairo.common.bool import TRUE, FALSE -from starkware.starknet.common.syscalls import ( - get_caller_address, - get_contract_address, - get_block_timestamp, -) -from starkware.cairo.common.math import ( - assert_not_zero, - assert_in_range, - assert_le, - assert_not_equal, -) -from starkware.cairo.common.math_cmp import is_not_zero, is_le -from starkware.cairo.common.uint256 import ( - Uint256, - uint256_le, - uint256_lt, - uint256_check, - uint256_eq, - uint256_sqrt, - uint256_unsigned_div_rem, -) -from starkware.cairo.common.alloc import alloc -from openzeppelin.token.erc20.library import ERC20 -from contracts.utils.math import ( - uint256_checked_add, - uint256_checked_sub_lt, - uint256_checked_sub_le, - uint256_checked_mul, - uint256_felt_checked_mul, -) -from openzeppelin.upgrades.library import Proxy - -const MINIMUM_LIQUIDITY = 1000; -const BURN_ADDRESS = 1; - -// -// Interfaces -// -@contract_interface -namespace IERC20 { - func balanceOf(account: felt) -> (balance: Uint256) { - } - - func transfer(recipient: felt, amount: Uint256) -> (success: felt) { - } - - func transferFrom(sender: felt, recipient: felt, amount: Uint256) -> (success: felt) { - } -} - -@contract_interface -namespace IFactory { - func get_fee_to() -> (address: felt) { - } -} - -@contract_interface -namespace IJediSwapCallee { - func jediswap_call( - sender: felt, amount0Out: Uint256, amount1Out: Uint256, data_len: felt, data: felt* - ) { - } -} - -// -// Storage Pair -// - -// @dev token0 address -@storage_var -func _token0() -> (address: felt) { -} - -// @dev token1 address -@storage_var -func _token1() -> (address: felt) { -} - -// @dev reserve for token0 -@storage_var -func _reserve0() -> (res: Uint256) { -} - -// @dev reserve for token1 -@storage_var -func _reserve1() -> (res: Uint256) { -} - -// @dev block timestamp for last update -@storage_var -func _block_timestamp_last() -> (ts: felt) { -} - -// @dev cumulative price for token0 on last update -@storage_var -func _price_0_cumulative_last() -> (res: Uint256) { -} - -// @dev cumulative price for token1 on last update -@storage_var -func _price_1_cumulative_last() -> (res: Uint256) { -} - -// @dev reserve0 * reserve1, as of immediately after the most recent liquidity event -@storage_var -func _klast() -> (res: Uint256) { -} - -// @dev Boolean to check reentrancy -@storage_var -func _locked() -> (res: felt) { -} - -// @dev Factory contract address -@storage_var -func _factory() -> (address: felt) { -} - -// @notice An event emitted whenever token is transferred. -@event -func Transfer(from_address: felt, to_address: felt, amount: Uint256) { -} - -// @notice An event emitted whenever allowances is updated -@event -func Approval(owner: felt, spender: felt, amount: Uint256) { -} - -// @notice An event emitted whenever mint() is called. -@event -func Mint(sender: felt, amount0: Uint256, amount1: Uint256) { -} - -// @notice An event emitted whenever burn() is called. -@event -func Burn(sender: felt, amount0: Uint256, amount1: Uint256, to: felt) { -} - -// @notice An event emitted whenever swap() is called. -@event -func Swap( - sender: felt, - amount0In: Uint256, - amount1In: Uint256, - amount0Out: Uint256, - amount1Out: Uint256, - to: felt, -) { -} - -// @notice An event emitted whenever _update() is called. -@event -func Sync(reserve0: Uint256, reserve1: Uint256) { -} - -// -// Constructor -// - -// @notice Contract constructor -// @param name Name of the pair token -// @param symbol Symbol of the pair token -// @param token0 Address of token0 -// @param token1 Address of token1 -@external -func initializer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - token0: felt, token1: felt, proxy_admin: felt -) { - with_attr error_message("Pair::constructor::all arguments must be non zero") { - assert_not_zero(token0); - assert_not_zero(token1); - } - ERC20.initializer('JediSwap Pair', 'JEDI-P', 18); - _locked.write(0); - _token0.write(token0); - _token1.write(token1); - let (factory) = get_caller_address(); - _factory.write(factory); - Proxy.initializer(proxy_admin); - return (); -} - -// -// Getters ERC20 -// - -// @notice Name of the token -// @return name -@view -func name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (name: felt) { - let (name) = ERC20.name(); - return (name,); -} - -// @notice Symbol of the token -// @return symbol -@view -func symbol{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (symbol: felt) { - let (symbol) = ERC20.symbol(); - return (symbol,); -} - -// @notice Total Supply of the token -// @return totalSupply -@view -func totalSupply{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - totalSupply: Uint256 -) { - let (totalSupply: Uint256) = ERC20.total_supply(); - return (totalSupply,); -} - -// @notice Decimals of the token -// @return decimals -@view -func decimals{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - decimals: felt -) { - let (decimals) = ERC20.decimals(); - return (decimals,); -} - -// @notice Balance of `account` -// @param account Account address whose balance is fetched -// @return balance Balance of `account` -@view -func balanceOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(account: felt) -> ( - balance: Uint256 -) { - let (balance: Uint256) = ERC20.balance_of(account); - return (balance,); -} - -// @notice Allowance which `spender` can spend on behalf of `owner` -// @param owner Account address whose tokens are spent -// @param spender Account address which can spend the tokens -// @return remaining -@view -func allowance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - owner: felt, spender: felt -) -> (remaining: Uint256) { - let (remaining: Uint256) = ERC20.allowance(owner, spender); - return (remaining,); -} - -// -// Getters Pair -// - -// @notice token0 address -// @return address -@view -func token0{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (address: felt) { - let (address) = _token0.read(); - return (address,); -} - -// @notice token1 address -// @return address -@view -func token1{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (address: felt) { - let (address) = _token1.read(); - return (address,); -} - -// @notice Current reserves for tokens in the pair -// @return reserve0 reserve for token0 -// @return reserve1 reserve for token1 -// @return block_timestamp_last block timestamp for last update -@view -func get_reserves{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - reserve0: Uint256, reserve1: Uint256, block_timestamp_last: felt -) { - return _get_reserves(); -} - -// @notice cumulative price for token0 on last update -// @return res -@view -func price_0_cumulative_last{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - res: Uint256 -) { - let (res) = _price_0_cumulative_last.read(); - return (res,); -} - -// @notice cumulative price for token1 on last update -// @return res -@view -func price_1_cumulative_last{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - res: Uint256 -) { - let (res) = _price_1_cumulative_last.read(); - return (res,); -} - -// @notice reserve0 * reserve1, as of immediately after the most recent liquidity event -// @return res -@view -func klast{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (res: Uint256) { - let (res) = _klast.read(); - return (res,); -} - -// -// Externals ERC20 -// - -// @notice Transfer `amount` tokens from `caller` to `recipient` -// @param recipient Account address to which tokens are transferred -// @param amount Amount of tokens to transfer -// @return success 0 or 1 -@external -func transfer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - recipient: felt, amount: Uint256 -) -> (success: felt) { - ERC20.transfer(recipient, amount); - return (TRUE,); -} - -// @notice Transfer `amount` tokens from `sender` to `recipient` -// @dev Checks for allowance. -// @param sender Account address from which tokens are transferred -// @param recipient Account address to which tokens are transferred -// @param amount Amount of tokens to transfer -// @return success 0 or 1 -@external -func transferFrom{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - sender: felt, recipient: felt, amount: Uint256 -) -> (success: felt) { - ERC20.transfer_from(sender, recipient, amount); - return (TRUE,); -} - -// @notice Approve `spender` to transfer `amount` tokens on behalf of `caller` -// @param spender The address which will spend the funds -// @param amount The amount of tokens to be spent -// @return success 0 or 1 -@external -func approve{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - spender: felt, amount: Uint256 -) -> (success: felt) { - ERC20.approve(spender, amount); - return (TRUE,); -} - -// @notice Increase allowance of `spender` to transfer `added_value` more tokens on behalf of `caller` -// @param spender The address which will spend the funds -// @param added_value The increased amount of tokens to be spent -// @return success 0 or 1 -@external -func increaseAllowance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - spender: felt, added_value: Uint256 -) -> (success: felt) { - ERC20.increase_allowance(spender, added_value); - return (TRUE,); -} - -// @notice Decrease allowance of `spender` to transfer `subtracted_value` less tokens on behalf of `caller` -// @param spender The address which will spend the funds -// @param subtracted_value The decreased amount of tokens to be spent -// @return success 0 or 1 -@external -func decreaseAllowance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - spender: felt, subtracted_value: Uint256 -) -> (success: felt) { - ERC20.decrease_allowance(spender, subtracted_value); - return (TRUE,); -} - -// -// Externals Pair -// - -// @notice Mint tokens and assign them to `to` -// @dev This low-level function should be called from a contract which performs important safety checks -// @param to The account that will receive the created tokens -// @return liquidity New tokens created -@external -func mint{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(to: felt) -> ( - liquidity: Uint256 -) { - alloc_locals; - _check_and_lock(); - let (local reserve0: Uint256, local reserve1: Uint256, _) = _get_reserves(); - let (self_address) = get_contract_address(); - let (token0) = _token0.read(); - let (local balance0: Uint256) = IERC20.balanceOf(contract_address=token0, account=self_address); - let (token1) = _token1.read(); - let (local balance1: Uint256) = IERC20.balanceOf(contract_address=token1, account=self_address); - - let (local amount0: Uint256) = uint256_checked_sub_lt(balance0, reserve0); - let (local amount1: Uint256) = uint256_checked_sub_lt(balance1, reserve1); - - let (fee_on) = _mint_protocol_fee(reserve0, reserve1); - - let (local _total_supply: Uint256) = totalSupply(); - let (is_total_supply_equal_to_zero) = uint256_eq(_total_supply, Uint256(0, 0)); - - local liquidity: Uint256; - - if (is_total_supply_equal_to_zero == 1) { - let (amount0_mul_amount1: Uint256) = uint256_checked_mul(amount0, amount1); - - let (mul_sqrt: Uint256) = uint256_sqrt(amount0_mul_amount1); - - // local mul_sqrt: Uint256 - // assert mul_sqrt = amount0 - - let (initial_liquidity: Uint256) = uint256_checked_sub_lt( - mul_sqrt, Uint256(MINIMUM_LIQUIDITY, 0) - ); - assert liquidity = initial_liquidity; - ERC20._mint(BURN_ADDRESS, Uint256(MINIMUM_LIQUIDITY, 0)); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - let (amount0_mul_total_supply: Uint256) = uint256_checked_mul(amount0, _total_supply); - let (liquidity0: Uint256, _) = uint256_unsigned_div_rem(amount0_mul_total_supply, reserve0); - - let (amount1_mul_total_supply: Uint256) = uint256_checked_mul(amount1, _total_supply); - let (liquidity1: Uint256, _) = uint256_unsigned_div_rem(amount1_mul_total_supply, reserve1); - - let (is_liquidity0_less_than_liquidity1) = uint256_lt(liquidity0, liquidity1); - if (is_liquidity0_less_than_liquidity1 == 1) { - assert liquidity = liquidity0; - } else { - assert liquidity = liquidity1; - } - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - local syscall_ptr: felt* = syscall_ptr; - local pedersen_ptr: HashBuiltin* = pedersen_ptr; - - let (is_liquidity_greater_than_zero) = uint256_lt(Uint256(0, 0), liquidity); - with_attr error_message("Pair::mint::insufficient liquidity minted") { - assert is_liquidity_greater_than_zero = 1; - } - - ERC20._mint(to, liquidity); - - _update(balance0, balance1, reserve0, reserve1); - - if (fee_on == 1) { - let (klast: Uint256) = uint256_checked_mul(balance0, balance1); - _klast.write(klast); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - let (caller) = get_caller_address(); - - Mint.emit(sender=caller, amount0=amount0, amount1=amount1); - - _unlock(); - return (liquidity,); -} - -// @notice Burn tokens belonging to `to` -// @dev This low-level function should be called from a contract which performs important safety checks -// @param to The account that will receive the created tokens -// @return amount0 Amount of token0 received -// @return amount1 Amount of token1 received -@external -func burn{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(to: felt) -> ( - amount0: Uint256, amount1: Uint256 -) { - alloc_locals; - _check_and_lock(); - let (local reserve0: Uint256, local reserve1: Uint256, _) = _get_reserves(); - - let (self_address) = get_contract_address(); - let (token0) = _token0.read(); - let (local balance0: Uint256) = IERC20.balanceOf(contract_address=token0, account=self_address); - let (token1) = _token1.read(); - let (local balance1: Uint256) = IERC20.balanceOf(contract_address=token1, account=self_address); - - let (local liquidity: Uint256) = balanceOf(self_address); - - let (fee_on) = _mint_protocol_fee(reserve0, reserve1); - - let (local _total_supply: Uint256) = totalSupply(); - let (is_total_supply_greater_than_zero) = uint256_lt(Uint256(0, 0), _total_supply); - - assert is_total_supply_greater_than_zero = 1; - - let (liquidity_mul_balance0: Uint256) = uint256_checked_mul(liquidity, balance0); - let (local amount0: Uint256, _) = uint256_unsigned_div_rem( - liquidity_mul_balance0, _total_supply - ); - let (is_amount0_greater_than_zero) = uint256_lt(Uint256(0, 0), amount0); - - let (liquidity_mul_balance1: Uint256) = uint256_checked_mul(liquidity, balance1); - let (local amount1: Uint256, _) = uint256_unsigned_div_rem( - liquidity_mul_balance1, _total_supply - ); - let (is_amount1_greater_than_zero) = uint256_lt(Uint256(0, 0), amount1); - - with_attr error_message("Pair::burn::insufficient liquidity burned") { - assert is_amount0_greater_than_zero = 1; - assert is_amount1_greater_than_zero = 1; - } - - ERC20._burn(self_address, liquidity); - - IERC20.transfer(contract_address=token0, recipient=to, amount=amount0); - IERC20.transfer(contract_address=token1, recipient=to, amount=amount1); - - let (local final_balance0: Uint256) = IERC20.balanceOf( - contract_address=token0, account=self_address - ); - let (local final_balance1: Uint256) = IERC20.balanceOf( - contract_address=token1, account=self_address - ); - - _update(final_balance0, final_balance1, reserve0, reserve1); - - if (fee_on == 1) { - let (klast: Uint256) = uint256_checked_mul(final_balance0, final_balance1); - _klast.write(klast); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - let (caller) = get_caller_address(); - - Burn.emit(sender=caller, amount0=amount0, amount1=amount1, to=to); - - _unlock(); - return (amount0, amount1); -} - -// @notice Swaps from one token to another -// @dev This low-level function should be called from a contract which performs important safety checks -// @param amount0Out Amount of token0 received -// @param amount1Out Amount of token1 received -// @param to The account that will receive the tokens -@external -func swap{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amount0Out: Uint256, amount1Out: Uint256, to: felt, data_len: felt, data: felt* -) { - alloc_locals; - _check_and_lock(); - local sufficient_output_amount; - let (local is_amount0out_greater_than_zero) = uint256_lt(Uint256(0, 0), amount0Out); - let (local is_amount1out_greater_than_zero) = uint256_lt(Uint256(0, 0), amount1Out); - if (is_amount0out_greater_than_zero == 1) { - assert sufficient_output_amount = 1; - } else { - if (is_amount1out_greater_than_zero == 1) { - assert sufficient_output_amount = 1; - } else { - assert sufficient_output_amount = 0; - } - } - with_attr error_message("Pair::swap::insufficient output amount") { - assert sufficient_output_amount = 1; - } - - let (local reserve0: Uint256, local reserve1: Uint256, _) = _get_reserves(); - let (is_amount0out_lesser_than_reserve0) = uint256_lt(amount0Out, reserve0); - let (is_amount1out_lesser_than_reserve0) = uint256_lt(amount1Out, reserve1); - with_attr error_message("Pair::swap::insufficient liquidity") { - assert is_amount0out_lesser_than_reserve0 = 1; - assert is_amount1out_lesser_than_reserve0 = 1; - } - - let (local token0) = _token0.read(); - let (local token1) = _token1.read(); - with_attr error_message("Pair::swap::invalid to") { - assert_not_equal(token0, to); - assert_not_equal(token1, to); - } - - let (self_address) = get_contract_address(); - - if (is_amount0out_greater_than_zero == 1) { - IERC20.transfer(contract_address=token0, recipient=to, amount=amount0Out); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - if (is_amount1out_greater_than_zero == 1) { - IERC20.transfer(contract_address=token1, recipient=to, amount=amount1Out); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - local syscall_ptr: felt* = syscall_ptr; - local pedersen_ptr: HashBuiltin* = pedersen_ptr; - - let (caller_address) = get_caller_address(); - - let data_len_greater_than_zero = is_le(1, data_len); - if (data_len_greater_than_zero == 1) { - IJediSwapCallee.jediswap_call( - contract_address=to, - sender=caller_address, - amount0Out=amount0Out, - amount1Out=amount1Out, - data_len=data_len, - data=data, - ); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - let (local balance0: Uint256) = IERC20.balanceOf(contract_address=token0, account=self_address); - let (local balance1: Uint256) = IERC20.balanceOf(contract_address=token1, account=self_address); - - let (local expected_balance0: Uint256) = uint256_checked_sub_le(reserve0, amount0Out); - let (local expected_balance1: Uint256) = uint256_checked_sub_le(reserve1, amount1Out); - - local sufficient_input_amount; - let (local is_balance0_greater_than_expected_balance0) = uint256_lt( - expected_balance0, balance0 - ); - let (local is_balance1_greater_than_expected_balance1) = uint256_lt( - expected_balance1, balance1 - ); - if (is_balance0_greater_than_expected_balance0 == 1) { - assert sufficient_input_amount = 1; - } else { - if (is_balance1_greater_than_expected_balance1 == 1) { - assert sufficient_input_amount = 1; - } else { - assert sufficient_input_amount = 0; - } - } - with_attr error_message("Pair::swap::insufficient input amount") { - assert sufficient_input_amount = 1; - } - - let (local amount0In: Uint256) = uint256_checked_sub_le(balance0, expected_balance0); - let (local amount1In: Uint256) = uint256_checked_sub_le(balance1, expected_balance1); - - let (balance0_mul_1000: Uint256) = uint256_felt_checked_mul(balance0, 1000); - let (amount0In_mul_3: Uint256) = uint256_felt_checked_mul(amount0In, 3); - let (local balance0Adjusted: Uint256) = uint256_checked_sub_lt( - balance0_mul_1000, amount0In_mul_3 - ); - - let (balance1_mul_1000: Uint256) = uint256_felt_checked_mul(balance1, 1000); - let (amount1In_mul_3: Uint256) = uint256_felt_checked_mul(amount1In, 3); - let (local balance1Adjusted: Uint256) = uint256_checked_sub_lt( - balance1_mul_1000, amount1In_mul_3 - ); - - let (balance0Adjusted_mul_balance1Adjusted: Uint256) = uint256_checked_mul( - balance0Adjusted, balance1Adjusted - ); - - let (reserve0_mul_reserve1: Uint256) = uint256_checked_mul(reserve0, reserve1); - - let (reserve0_mul_reserve1_mul_multiplier: Uint256) = uint256_felt_checked_mul( - reserve0_mul_reserve1, 1000000 - ); - - let (is_balance_adjusted_mul_greater_than_equal_final_reserve_mul) = uint256_le( - reserve0_mul_reserve1_mul_multiplier, balance0Adjusted_mul_balance1Adjusted - ); - with_attr error_message("Pair::swap::invariant K") { - assert is_balance_adjusted_mul_greater_than_equal_final_reserve_mul = 1; - } - - _update(balance0, balance1, reserve0, reserve1); - - let (caller) = get_caller_address(); - - Swap.emit( - sender=caller, - amount0In=amount0In, - amount1In=amount1In, - amount0Out=amount0Out, - amount1Out=amount1Out, - to=to, - ); - - _unlock(); - - return (); -} - -// @notice force balances to match reserves -// @param to The account that will receive the balance tokens -@external -func skim{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(to: felt) { - alloc_locals; - _check_and_lock(); - let (local reserve0: Uint256, local reserve1: Uint256, _) = _get_reserves(); - - let (self_address) = get_contract_address(); - let (token0) = _token0.read(); - let (local balance0: Uint256) = IERC20.balanceOf(contract_address=token0, account=self_address); - let (token1) = _token1.read(); - let (local balance1: Uint256) = IERC20.balanceOf(contract_address=token1, account=self_address); - - let (local amount0: Uint256) = uint256_checked_sub_lt(balance0, reserve0); - let (local amount1: Uint256) = uint256_checked_sub_lt(balance1, reserve1); - - IERC20.transfer(contract_address=token0, recipient=to, amount=amount0); - IERC20.transfer(contract_address=token1, recipient=to, amount=amount1); - - _unlock(); - - return (); -} - -// @notice Force reserves to match balances -@external -func sync{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { - alloc_locals; - _check_and_lock(); - - let (self_address) = get_contract_address(); - let (token0) = _token0.read(); - let (local balance0: Uint256) = IERC20.balanceOf(contract_address=token0, account=self_address); - let (token1) = _token1.read(); - let (local balance1: Uint256) = IERC20.balanceOf(contract_address=token1, account=self_address); - - let (local reserve0: Uint256) = _reserve0.read(); - let (local reserve1: Uint256) = _reserve1.read(); - - _update(balance0, balance1, reserve0, reserve1); - - _unlock(); - - return (); -} - -// -// Internals Pair -// - -// @dev Check if the entry is not locked, and lock it -func _check_and_lock{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { - let (locked) = _locked.read(); - with_attr error_message("Pair::_check_and_lock::locked") { - assert locked = 0; - } - _locked.write(1); - return (); -} - -// @dev Unlock the entry -func _unlock{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { - let (locked) = _locked.read(); - with_attr error_message("Pair::_unlock::not locked") { - assert locked = 1; - } - _locked.write(0); - return (); -} - -// @dev If fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k) -func _mint_protocol_fee{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - reserve0: Uint256, reserve1: Uint256 -) -> (fee_on: felt) { - alloc_locals; - let (local factory) = _factory.read(); - let (local fee_to) = IFactory.get_fee_to(contract_address=factory); - local fee_on = is_not_zero(fee_to); - - let (local klast: Uint256) = _klast.read(); - let (local is_klast_equal_to_zero) = uint256_eq(klast, Uint256(0, 0)); - - if (fee_on == 1) { - if (is_klast_equal_to_zero == 0) { - let (reserve0_mul_reserve1: Uint256) = uint256_checked_mul(reserve0, reserve1); - let (local rootk: Uint256) = uint256_sqrt(reserve0_mul_reserve1); - let (local rootklast: Uint256) = uint256_sqrt(klast); - let (is_rootk_greater_than_rootklast) = uint256_lt(rootklast, rootk); - if (is_rootk_greater_than_rootklast == 1) { - let (local rootkdiff: Uint256) = uint256_checked_sub_le(rootk, rootklast); - let (local _total_supply: Uint256) = totalSupply(); - let (numerator: Uint256) = uint256_checked_mul(rootkdiff, _total_supply); - let (rootk_mul_5: Uint256) = uint256_felt_checked_mul(rootk, 5); - let (local denominator: Uint256) = uint256_checked_add(rootk_mul_5, rootklast); - let (liquidity: Uint256, _) = uint256_unsigned_div_rem(numerator, denominator); - let (is_liquidity_greater_than_zero) = uint256_lt(Uint256(0, 0), liquidity); - if (is_liquidity_greater_than_zero == 1) { - ERC20._mint(fee_to, liquidity); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - } else { - if (is_klast_equal_to_zero == 0) { - _klast.write(Uint256(0, 0)); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - } - return (fee_on,); -} - -func _get_reserves{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - reserve0: Uint256, reserve1: Uint256, block_timestamp_last: felt -) { - let (reserve0) = _reserve0.read(); - let (reserve1) = _reserve1.read(); - let (block_timestamp_last) = _block_timestamp_last.read(); - return (reserve0, reserve1, block_timestamp_last); -} - -// @dev Update reserves and, on the first call per block, price accumulators -func _update{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - balance0: Uint256, balance1: Uint256, reserve0: Uint256, reserve1: Uint256 -) { - alloc_locals; - with_attr error_message("Pair::_update::overflow") { - assert balance0.high = 0; - assert balance1.high = 0; - } - let (block_timestamp) = get_block_timestamp(); - let (block_timestamp_last) = _block_timestamp_last.read(); - let is_block_timestamp_greater_than_equal_to_last = is_le( - block_timestamp_last, block_timestamp - ); - if (is_block_timestamp_greater_than_equal_to_last == 1) { - let is_block_timestamp_not_equal_to_last = is_not_zero( - block_timestamp - block_timestamp_last - ); - if (is_block_timestamp_not_equal_to_last == 1) { - let (is_reserve0_equal_to_zero) = uint256_eq(reserve0, Uint256(0, 0)); - if (is_reserve0_equal_to_zero == 0) { - let (is_reserve1_equal_to_zero) = uint256_eq(reserve1, Uint256(0, 0)); - if (is_reserve1_equal_to_zero == 0) { - let (price_0_cumulative_last) = _price_0_cumulative_last.read(); - let (reserve1by0: Uint256, _) = uint256_unsigned_div_rem(reserve1, reserve0); - let (reserve1by0_mul_time: Uint256) = uint256_felt_checked_mul( - reserve1by0, block_timestamp - block_timestamp_last - ); - let (new_price_0_cumulative: Uint256) = uint256_checked_add( - price_0_cumulative_last, reserve1by0_mul_time - ); - _price_0_cumulative_last.write(new_price_0_cumulative); - - let (price_1_cumulative_last) = _price_1_cumulative_last.read(); - let (reserve0by1: Uint256, _) = uint256_unsigned_div_rem(reserve0, reserve1); - let (reserve0by1_mul_time: Uint256) = uint256_felt_checked_mul( - reserve0by1, block_timestamp - block_timestamp_last - ); - let (new_price_1_cumulative: Uint256) = uint256_checked_add( - price_1_cumulative_last, reserve0by1_mul_time - ); - _price_1_cumulative_last.write(new_price_1_cumulative); - - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - _reserve0.write(balance0); - _reserve1.write(balance1); - _block_timestamp_last.write(block_timestamp); - - Sync.emit(reserve0=balance0, reserve1=balance1); - return (); -} diff --git a/contracts/PairProxy.cairo b/contracts/PairProxy.cairo deleted file mode 100644 index 4b6fd04..0000000 --- a/contracts/PairProxy.cairo +++ /dev/null @@ -1,104 +0,0 @@ -%lang starknet - -// @title JediSwap V2 Pair Proxy -// @author Mesh Finance -// @license MIT -// @notice Upgradeable proxy for Pair.cairo - -from starkware.cairo.common.cairo_builtins import HashBuiltin -from starkware.starknet.common.syscalls import library_call, library_call_l1_handler -from starkware.cairo.common.alloc import alloc -from openzeppelin.upgrades.library import Proxy - -// -// Constructor -// - -@constructor -func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - implementation_hash: felt, token0: felt, token1: felt, proxy_admin: felt -) { - - Proxy._set_implementation_hash(implementation_hash); - - let calldata: felt* = alloc(); - assert [calldata] = token0; - assert [calldata + 1] = token1; - assert [calldata + 2] = proxy_admin; - library_call( - class_hash=implementation_hash, - function_selector=1295919550572838631247819983596733806859788957403169325509326258146877103642, // initializer - calldata_size=3, - calldata=calldata, - ); - return (); -} - -// -// Fallback functions -// - -@external -@raw_input -@raw_output -func __default__{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - selector: felt, calldata_size: felt, calldata: felt* -) -> (retdata_size: felt, retdata: felt*) { - let (class_hash) = Proxy.get_implementation_hash(); - - let (retdata_size: felt, retdata: felt*) = library_call( - class_hash=class_hash, - function_selector=selector, - calldata_size=calldata_size, - calldata=calldata, - ); - return (retdata_size, retdata); -} - -@l1_handler -@raw_input -func __l1_default__{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - selector: felt, calldata_size: felt, calldata: felt* -) { - let (class_hash) = Proxy.get_implementation_hash(); - - library_call_l1_handler( - class_hash=class_hash, - function_selector=selector, - calldata_size=calldata_size, - calldata=calldata, - ); - return (); -} - -@external -func upgrade{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - new_implementation: felt -) { - Proxy.assert_only_admin(); - Proxy._set_implementation_hash(new_implementation); - return (); -} - -@external -func set_admin{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - new_admin: felt -) { - Proxy.assert_only_admin(); - Proxy._set_admin(new_admin); - return (); -} - -@view -func get_admin{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - admin: felt -) { - return Proxy.get_admin(); -} - -@view -func get_implementation_hash{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - implementation: felt -) { - return Proxy.get_implementation_hash(); -} diff --git a/contracts/Router.cairo b/contracts/Router.cairo deleted file mode 100644 index e13bf6e..0000000 --- a/contracts/Router.cairo +++ /dev/null @@ -1,665 +0,0 @@ -%lang starknet - -// @title JediSwap router for stateless execution of swaps -// @author Mesh Finance -// @license MIT -// @dev Based on the Uniswap V2 Router -// https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Router02.sol - -from starkware.cairo.common.cairo_builtins import HashBuiltin -from starkware.starknet.common.syscalls import get_caller_address, get_block_timestamp -from starkware.cairo.common.math import assert_le, assert_not_zero, assert_not_equal -from starkware.cairo.common.math_cmp import is_le, is_le_felt -from starkware.cairo.common.uint256 import ( - Uint256, - uint256_eq, - uint256_le, - uint256_lt, - uint256_unsigned_div_rem, -) -from starkware.cairo.common.alloc import alloc -from contracts.utils.math import ( - uint256_checked_add, - uint256_checked_sub_lt, - uint256_checked_mul, - uint256_felt_checked_mul, -) -from openzeppelin.upgrades.library import Proxy - -// -// Interfaces -// -@contract_interface -namespace IERC20 { - func transferFrom(sender: felt, recipient: felt, amount: Uint256) -> (success: felt) { - } -} - -@contract_interface -namespace IPair { - func get_reserves() -> (reserve0: Uint256, reserve1: Uint256, block_timestamp_last: felt) { - } - - func mint(to: felt) -> (liquidity: Uint256) { - } - - func burn(to: felt) -> (amount0: Uint256, amount1: Uint256) { - } - - func swap(amount0Out: Uint256, amount1Out: Uint256, to: felt, data_len: felt) { - } -} - -@contract_interface -namespace IFactory { - func get_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func create_pair(token0: felt, token1: felt) -> (pair: felt) { - } -} - -// -// Storage -// - -// @dev Factory contract address -@storage_var -func _factory() -> (address: felt) { -} - -// -// Constructor -// - -// @notice Contract constructor -// @param factory Address of factory contract -@external -func initializer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(factory: felt, proxy_admin: felt) { - with_attr error_message("Router::constructor::factory can not be zero") { - assert_not_zero(factory); - } - _factory.write(factory); - Proxy.initializer(proxy_admin); - return (); -} - -// -// Getters -// - -// @notice factory address -// @return address -@view -func factory{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (address: felt) { - let (address) = _factory.read(); - return (address,); -} - -// @notice Sort tokens `tokenA` and `tokenB` by address -// @param tokenA Address of tokenA -// @param tokenB Address of tokenB -// @return token0 First token -// @return token1 Second token -@view -func sort_tokens{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - tokenA: felt, tokenB: felt -) -> (token0: felt, token1: felt) { - return _sort_tokens(tokenA, tokenB); -} - -// @notice Given some amount of an asset and pair reserves, returns an equivalent amount of the other asset -// @param amountA Amount of tokenA -// @param reserveA Reserves for tokenA -// @param reserveB Reserves for tokenB -// @return amountB Amount of tokenB -@view -func quote{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountA: Uint256, reserveA: Uint256, reserveB: Uint256 -) -> (amountB: Uint256) { - return _quote(amountA, reserveA, reserveB); -} - -// @notice Given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset -// @param amountIn Input Amount -// @param reserveIn Reserves for input token -// @param reserveOut Reserves for output token -// @return amountOut Maximum output amount -@view -func get_amount_out{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountIn: Uint256, reserveIn: Uint256, reserveOut: Uint256 -) -> (amountOut: Uint256) { - return _get_amount_out(amountIn, reserveIn, reserveOut); -} - -// @notice Given an output amount of an asset and pair reserves, returns a required input amount of the other asset -// @param amountOut Output Amount -// @param reserveIn Reserves for input token -// @param reserveOut Reserves for output token -// @return amountIn Required input amount -@view -func get_amount_in{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountOut: Uint256, reserveIn: Uint256, reserveOut: Uint256 -) -> (amountIn: Uint256) { - return _get_amount_in(amountOut, reserveIn, reserveOut); -} - -// @notice Performs chained get_amount_out calculations on any number of pairs -// @param amountIn Input Amount -// @param path_len Length of path array -// @param path Array of pair addresses through which swaps are chained -// @return amounts_len Required output amount array's length -// @return amounts Required output amount array -@view -func get_amounts_out{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountIn: Uint256, path_len: felt, path: felt* -) -> (amounts_len: felt, amounts: Uint256*) { - alloc_locals; - let (local factory) = _factory.read(); - let (local amounts: Uint256*) = _get_amounts_out(factory, amountIn, path_len, path); - return (path_len, amounts); -} - -// @notice Performs chained get_amount_in calculations on any number of pairs -// @param amountOut Output Amount -// @param path_len Length of path array -// @param path Array of pair addresses through which swaps are chained -// @return amounts_len Required input amount array's length -// @return amounts Required input amount array -@view -func get_amounts_in{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountOut: Uint256, path_len: felt, path: felt* -) -> (amounts_len: felt, amounts: Uint256*) { - alloc_locals; - let (local factory) = _factory.read(); - let (local amounts: Uint256*) = _get_amounts_in(factory, amountOut, path_len, path); - return (path_len, amounts); -} - -// -// Externals -// - -// @notice Add liquidity to a pool -// @dev `caller` should have already given the router an allowance of at least amountADesired/amountBDesired on tokenA/tokenB -// @param tokenA Address of tokenA -// @param tokenB Address of tokenB -// @param amountADesired The amount of tokenA to add as liquidity -// @param amountBDesired The amount of tokenB to add as liquidity -// @param amountAMin Bounds the extent to which the B/A price can go up before the transaction reverts. Must be <= amountADesired -// @param amountBMin Bounds the extent to which the A/B price can go up before the transaction reverts. Must be <= amountBDesired -// @param to Recipient of liquidity tokens -// @param deadline Timestamp after which the transaction will revert -// @return amountA The amount of tokenA sent to the pool -// @return amountB The amount of tokenB sent to the pool -// @return liquidity The amount of liquidity tokens minted -@external -func add_liquidity{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - tokenA: felt, - tokenB: felt, - amountADesired: Uint256, - amountBDesired: Uint256, - amountAMin: Uint256, - amountBMin: Uint256, - to: felt, - deadline: felt, -) -> (amountA: Uint256, amountB: Uint256, liquidity: Uint256) { - alloc_locals; - _ensure_deadline(deadline); - let (local factory) = _factory.read(); - let (local amountA: Uint256, local amountB: Uint256) = _add_liquidity( - tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin - ); - let (local pair) = _pair_for(factory, tokenA, tokenB); - let (sender) = get_caller_address(); - IERC20.transferFrom(contract_address=tokenA, sender=sender, recipient=pair, amount=amountA); - IERC20.transferFrom(contract_address=tokenB, sender=sender, recipient=pair, amount=amountB); - let (local liquidity: Uint256) = IPair.mint(contract_address=pair, to=to); - return (amountA, amountB, liquidity); -} - -// @notice Remove liquidity from a pool -// @dev `caller` should have already given the router an allowance of at least liquidity on the pool -// @param tokenA Address of tokenA -// @param tokenB Address of tokenB -// @param tokenB Address of tokenB -// @param liquidity The amount of liquidity tokens to remove -// @param amountAMin The minimum amount of tokenA that must be received for the transaction not to revert -// @param amountBMin The minimum amount of tokenB that must be received for the transaction not to revert -// @param to Recipient of the underlying tokens -// @param deadline Timestamp after which the transaction will revert -// @return amountA The amount of tokenA received -// @return amountB The amount of tokenA received -@external -func remove_liquidity{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - tokenA: felt, - tokenB: felt, - liquidity: Uint256, - amountAMin: Uint256, - amountBMin: Uint256, - to: felt, - deadline: felt, -) -> (amountA: Uint256, amountB: Uint256) { - alloc_locals; - _ensure_deadline(deadline); - let (local factory) = _factory.read(); - let (local pair) = _pair_for(factory, tokenA, tokenB); - let (sender) = get_caller_address(); - IERC20.transferFrom(contract_address=pair, sender=sender, recipient=pair, amount=liquidity); - let (local amount0: Uint256, local amount1: Uint256) = IPair.burn(contract_address=pair, to=to); - let (local token0, _) = _sort_tokens(tokenA, tokenB); - local amountA: Uint256; - local amountB: Uint256; - if (tokenA == token0) { - assert amountA = amount0; - assert amountB = amount1; - } else { - assert amountA = amount1; - assert amountB = amount0; - } - - let (is_amountA_greater_than_equal_amountAMin) = uint256_le(amountAMin, amountA); - with_attr error_message("Router::remove_liquidity::insufficient A amount") { - assert is_amountA_greater_than_equal_amountAMin = 1; - } - let (is_amountB_greater_than_equal_amountBMin) = uint256_le(amountBMin, amountB); - with_attr error_message("Router::remove_liquidity::insufficient B amount") { - assert is_amountB_greater_than_equal_amountBMin = 1; - } - - return (amountA, amountB); -} - -// @notice Swaps an exact amount of input tokens for as many output tokens as possible, along the route determined by the path -// @dev `caller` should have already given the router an allowance of at least amountIn on the input token -// @param amountIn The amount of input tokens to send -// @param amountOutMin The minimum amount of output tokens that must be received for the transaction not to revert -// @param path_len Length of path array -// @param path Array of pair addresses through which swaps are chained -// @param to Recipient of the output tokens -// @param deadline Timestamp after which the transaction will revert -// @return amounts_len Length of amounts array -// @return amounts The input token amount and all subsequent output token amounts -@external -func swap_exact_tokens_for_tokens{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountIn: Uint256, amountOutMin: Uint256, path_len: felt, path: felt*, to: felt, deadline: felt -) -> (amounts_len: felt, amounts: Uint256*) { - alloc_locals; - _ensure_deadline(deadline); - let (local factory) = _factory.read(); - let (local amounts: Uint256*) = _get_amounts_out(factory, amountIn, path_len, path); - let (is_amount_last_greater_than_equal_amountOutMin) = uint256_le( - amountOutMin, [amounts + (path_len - 1) * Uint256.SIZE] - ); - with_attr error_message("Router::swap_exact_tokens_for_tokens::insufficient output amount") { - assert is_amount_last_greater_than_equal_amountOutMin = 1; - } - let (local pair) = _pair_for(factory, [path], [path + 1]); - let (sender) = get_caller_address(); - IERC20.transferFrom(contract_address=[path], sender=sender, recipient=pair, amount=[amounts]); - _swap(0, path_len, amounts, path, to); - return (path_len, amounts); -} - -// @notice Receive an exact amount of output tokens for as few input tokens as possible, along the route determined by the path -// @dev `caller` should have already given the router an allowance of at least amountInMax on the input token -// @param amountOut The amount of output tokens to receive -// @param amountInMax The maximum amount of input tokens that can be required before the transaction reverts -// @param path_len Length of path array -// @param path Array of pair addresses through which swaps are chained -// @param to Recipient of the output tokens -// @param deadline Timestamp after which the transaction will revert -// @return amounts_len Length of amounts array -// @return amounts The input token amount and all subsequent output token amounts -@external -func swap_tokens_for_exact_tokens{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountOut: Uint256, amountInMax: Uint256, path_len: felt, path: felt*, to: felt, deadline: felt -) -> (amounts_len: felt, amounts: Uint256*) { - alloc_locals; - _ensure_deadline(deadline); - let (local factory) = _factory.read(); - let (local amounts: Uint256*) = _get_amounts_in(factory, amountOut, path_len, path); - let (is_amount_first_less_than_equal_amountInMax) = uint256_le([amounts], amountInMax); - with_attr error_message("Router::swap_tokens_for_exact_tokens::excessive input amount") { - assert is_amount_first_less_than_equal_amountInMax = 1; - } - let (local pair) = _pair_for(factory, [path], [path + 1]); - let (sender) = get_caller_address(); - IERC20.transferFrom(contract_address=[path], sender=sender, recipient=pair, amount=[amounts]); - _swap(0, path_len, amounts, path, to); - return (path_len, amounts); -} - -// -// Internals -// - -func _ensure_deadline{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - deadline: felt -) { - let (block_timestamp) = get_block_timestamp(); - with_attr error_message("Router::_ensure_deadline::expired") { - assert_le(block_timestamp, deadline); - } - return (); -} - -func _add_liquidity{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - tokenA: felt, - tokenB: felt, - amountADesired: Uint256, - amountBDesired: Uint256, - amountAMin: Uint256, - amountBMin: Uint256, -) -> (amountA: Uint256, amountB: Uint256) { - alloc_locals; - let (local factory) = _factory.read(); - let (local pair) = IFactory.get_pair(contract_address=factory, token0=tokenA, token1=tokenB); - - if (pair == 0) { - let (new_pair) = IFactory.create_pair( - contract_address=factory, token0=tokenA, token1=tokenB - ); - } - - let (local reserveA: Uint256, local reserveB: Uint256) = _get_reserves(factory, tokenA, tokenB); - let (reserveA_mul_reserveB: Uint256) = uint256_checked_mul(reserveA, reserveB); - let (is_reserveA_mul_reserveB_equal_to_zero) = uint256_eq(reserveA_mul_reserveB, Uint256(0, 0)); - - if (is_reserveA_mul_reserveB_equal_to_zero == 1) { - return (amountADesired, amountBDesired); - } else { - let (local amountBOptimal: Uint256) = _quote(amountADesired, reserveA, reserveB); - let (is_amountBOptimal_less_than_equal_amountBDesired) = uint256_le( - amountBOptimal, amountBDesired - ); - if (is_amountBOptimal_less_than_equal_amountBDesired == 1) { - let (is_amountBOptimal_greater_than_equal_amountBMin) = uint256_le( - amountBMin, amountBOptimal - ); - with_attr error_message("Router::_add_liquidity::insufficient B amount") { - assert is_amountBOptimal_greater_than_equal_amountBMin = 1; - } - return (amountADesired, amountBOptimal); - } else { - let (local amountAOptimal: Uint256) = _quote(amountBDesired, reserveB, reserveA); - let (is_amountAOptimal_less_than_equal_amountADesired) = uint256_le( - amountAOptimal, amountADesired - ); - assert is_amountAOptimal_less_than_equal_amountADesired = 1; - let (is_amountAOptimal_greater_than_equal_amountAMin) = uint256_le( - amountAMin, amountAOptimal - ); - with_attr error_message("Router::_add_liquidity::insufficient A amount") { - assert is_amountAOptimal_greater_than_equal_amountAMin = 1; - } - return (amountAOptimal, amountBDesired); - } - } -} - -func _swap{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - current_index: felt, amounts_len: felt, amounts: Uint256*, path: felt*, _to: felt -) { - alloc_locals; - let (local factory) = _factory.read(); - if (current_index == amounts_len - 1) { - return (); - } - let (local token0, _) = _sort_tokens([path], [path + 1]); - local amount0Out: Uint256; - local amount1Out: Uint256; - if ([path] == token0) { - assert amount0Out = Uint256(0, 0); - assert amount1Out = [amounts + Uint256.SIZE]; - } else { - assert amount0Out = [amounts + Uint256.SIZE]; - assert amount1Out = Uint256(0, 0); - } - local to; - let is_current_index_less_than_len_2 = is_le(current_index, amounts_len - 3); - if (is_current_index_less_than_len_2 == 1) { - let (local pair) = _pair_for(factory, [path + 1], [path + 2]); - assert to = pair; - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - assert to = _to; - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - let (local pair) = _pair_for(factory, [path], [path + 1]); - IPair.swap( - contract_address=pair, amount0Out=amount0Out, amount1Out=amount1Out, to=to, data_len=0 - ); - return _swap(current_index + 1, amounts_len, amounts + Uint256.SIZE, path + 1, _to); -} - -func _sort_tokens{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - tokenA: felt, tokenB: felt -) -> (token0: felt, token1: felt) { - alloc_locals; - local token0; - local token1; - assert_not_equal(tokenA, tokenB); - let is_tokenA_less_than_tokenB = is_le_felt(tokenA, tokenB); - if (is_tokenA_less_than_tokenB == 1) { - assert token0 = tokenA; - assert token1 = tokenB; - } else { - assert token0 = tokenB; - assert token1 = tokenA; - } - - assert_not_zero(token0); - return (token0, token1); -} - -func _pair_for{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - factory: felt, tokenA: felt, tokenB: felt -) -> (pair: felt) { - alloc_locals; - let (local token0, local token1) = _sort_tokens(tokenA, tokenB); - let (local pair) = IFactory.get_pair(contract_address=factory, token0=token0, token1=token1); - return (pair,); -} - -func _get_reserves{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - factory: felt, tokenA: felt, tokenB: felt -) -> (reserveA: Uint256, reserveB: Uint256) { - alloc_locals; - let (local token0, _) = _sort_tokens(tokenA, tokenB); - let (local pair) = _pair_for(factory, tokenA, tokenB); - let (local reserve0: Uint256, local reserve1: Uint256, _) = IPair.get_reserves( - contract_address=pair - ); - if (tokenA == token0) { - return (reserve0, reserve1); - } else { - return (reserve1, reserve0); - } -} - -// -// Internals LIBRARY -// - -func _quote{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountA: Uint256, reserveA: Uint256, reserveB: Uint256 -) -> (amountB: Uint256) { - alloc_locals; - let (is_amountA_greater_than_zero) = uint256_lt(Uint256(0, 0), amountA); - with_attr error_message("Router::_quote::insufficient amount") { - assert is_amountA_greater_than_zero = 1; - } - let (is_reserveA_greater_than_zero) = uint256_lt(Uint256(0, 0), reserveA); - let (is_reserveB_greater_than_zero) = uint256_lt(Uint256(0, 0), reserveB); - with_attr error_message("Router::_quote::insufficient liquidity") { - assert is_reserveA_greater_than_zero = 1; - assert is_reserveB_greater_than_zero = 1; - } - - let (amountA_mul_reserveB: Uint256) = uint256_checked_mul(amountA, reserveB); - let (amountB: Uint256, _) = uint256_unsigned_div_rem(amountA_mul_reserveB, reserveA); - return (amountB,); -} - -func _get_amount_out{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountIn: Uint256, reserveIn: Uint256, reserveOut: Uint256 -) -> (amountOut: Uint256) { - alloc_locals; - let (is_amountIn_greater_than_zero) = uint256_lt(Uint256(0, 0), amountIn); - with_attr error_message("Router::_get_amount_out::insufficient input amount") { - assert is_amountIn_greater_than_zero = 1; - } - let (is_reserveIn_greater_than_zero) = uint256_lt(Uint256(0, 0), reserveIn); - let (is_reserveOut_greater_than_zero) = uint256_lt(Uint256(0, 0), reserveOut); - with_attr error_message("Router::_get_amount_out::insufficient liquidity") { - assert is_reserveIn_greater_than_zero = 1; - assert is_reserveOut_greater_than_zero = 1; - } - - let (amountIn_with_fee: Uint256) = uint256_felt_checked_mul(amountIn, 997); - let (numerator: Uint256) = uint256_checked_mul(amountIn_with_fee, reserveOut); - let (reserveIn_mul_1000: Uint256) = uint256_felt_checked_mul(reserveIn, 1000); - let (local denominator: Uint256) = uint256_checked_add(reserveIn_mul_1000, amountIn_with_fee); - - let (amountOut: Uint256, _) = uint256_unsigned_div_rem(numerator, denominator); - return (amountOut,); -} - -func _get_amount_in{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountOut: Uint256, reserveIn: Uint256, reserveOut: Uint256 -) -> (amountIn: Uint256) { - alloc_locals; - let (is_amountOut_greater_than_zero) = uint256_lt(Uint256(0, 0), amountOut); - with_attr error_message("Router::_get_amount_in::insufficient output amount") { - assert is_amountOut_greater_than_zero = 1; - } - let (is_reserveIn_greater_than_zero) = uint256_lt(Uint256(0, 0), reserveIn); - let (is_reserveOut_greater_than_zero) = uint256_lt(Uint256(0, 0), reserveOut); - with_attr error_message("Router::_get_amount_in::insufficient liquidity") { - assert is_reserveIn_greater_than_zero = 1; - assert is_reserveOut_greater_than_zero = 1; - } - - let (amountOut_mul_reserveIn: Uint256) = uint256_checked_mul(amountOut, reserveIn); - let (numerator: Uint256) = uint256_felt_checked_mul(amountOut_mul_reserveIn, 1000); - let (denominator_0: Uint256) = uint256_checked_sub_lt(reserveOut, amountOut); - let (denominator: Uint256) = uint256_felt_checked_mul(denominator_0, 997); - - let (amountIn_0: Uint256, _) = uint256_unsigned_div_rem(numerator, denominator); - let (local amountIn: Uint256) = uint256_checked_add(amountIn_0, Uint256(1, 0)); - - return (amountIn,); -} - -func _get_amounts_out{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - factory: felt, amountIn: Uint256, path_len: felt, path: felt* -) -> (amounts: Uint256*) { - alloc_locals; - with_attr error_message("Router::_get_amounts_out::invalid path") { - assert_le(2, path_len); - } - let (local amounts_start: Uint256*) = alloc(); - let (amounts_end: Uint256*) = _build_amounts_out( - factory, amountIn, 0, path_len, path, amounts_start - ); - - return (amounts_start,); -} - -func _build_amounts_out{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - factory: felt, - amountIn: Uint256, - current_index: felt, - path_len: felt, - path: felt*, - amounts: Uint256*, -) -> (amounts: Uint256*) { - alloc_locals; - if (current_index == path_len) { - return (amounts,); - } - - if (current_index == 0) { - assert [amounts] = amountIn; - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - let (local reserveIn: Uint256, local reserveOut: Uint256) = _get_reserves( - factory, [path - 1], [path] - ); - let (local amountOut: Uint256) = _get_amount_out( - [amounts - Uint256.SIZE], reserveIn, reserveOut - ); - assert [amounts] = amountOut; - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - return _build_amounts_out( - factory, amountIn, current_index + 1, path_len, path + 1, amounts + Uint256.SIZE - ); -} - -func _get_amounts_in{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - factory: felt, amountOut: Uint256, path_len: felt, path: felt* -) -> (amounts: Uint256*) { - alloc_locals; - with_attr error_message("Router::_get_amounts_in::invalid path") { - assert_le(2, path_len); - } - let (local amounts_start: Uint256*) = alloc(); - let (amounts_start_temp: Uint256*) = _build_amounts_in( - factory, - amountOut, - path_len - 1, - path_len, - path + (path_len - 1), - amounts_start + (path_len - 1) * Uint256.SIZE, - ); - - return (amounts_start,); -} - -func _build_amounts_in{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - factory: felt, - amountOut: Uint256, - current_index: felt, - path_len: felt, - path: felt*, - amounts: Uint256*, -) -> (amounts: Uint256*) { - alloc_locals; - - if (current_index == path_len - 1) { - assert [amounts] = amountOut; - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - let (local reserveIn: Uint256, local reserveOut: Uint256) = _get_reserves( - factory, [path], [path + 1] - ); - let (local amountIn: Uint256) = _get_amount_in( - [amounts + Uint256.SIZE], reserveIn, reserveOut - ); - assert [amounts] = amountIn; - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - if (current_index == 0) { - return (amounts,); - } - - return _build_amounts_in( - factory, amountOut, current_index - 1, path_len, path - 1, amounts - Uint256.SIZE - ); -} diff --git a/contracts/RouterProxy.cairo b/contracts/RouterProxy.cairo deleted file mode 100644 index fd1e40b..0000000 --- a/contracts/RouterProxy.cairo +++ /dev/null @@ -1,103 +0,0 @@ -%lang starknet - -// @title JediSwap V2 Router Proxy -// @author Mesh Finance -// @license MIT -// @notice Upgradeable proxy for Router.cairo - -from starkware.cairo.common.cairo_builtins import HashBuiltin -from starkware.starknet.common.syscalls import library_call, library_call_l1_handler -from starkware.cairo.common.alloc import alloc -from openzeppelin.upgrades.library import Proxy - -// -// Constructor -// - -@constructor -func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - implementation_hash: felt, factory: felt, proxy_admin: felt -) { - - Proxy._set_implementation_hash(implementation_hash); - - let calldata: felt* = alloc(); - assert [calldata] = factory; - assert [calldata + 1] = proxy_admin; - library_call( - class_hash=implementation_hash, - function_selector=1295919550572838631247819983596733806859788957403169325509326258146877103642, // initializer - calldata_size=2, - calldata=calldata, - ); - return (); -} - -// -// Fallback functions -// - -@external -@raw_input -@raw_output -func __default__{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - selector: felt, calldata_size: felt, calldata: felt* -) -> (retdata_size: felt, retdata: felt*) { - let (class_hash) = Proxy.get_implementation_hash(); - - let (retdata_size: felt, retdata: felt*) = library_call( - class_hash=class_hash, - function_selector=selector, - calldata_size=calldata_size, - calldata=calldata, - ); - return (retdata_size, retdata); -} - -@l1_handler -@raw_input -func __l1_default__{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - selector: felt, calldata_size: felt, calldata: felt* -) { - let (class_hash) = Proxy.get_implementation_hash(); - - library_call_l1_handler( - class_hash=class_hash, - function_selector=selector, - calldata_size=calldata_size, - calldata=calldata, - ); - return (); -} - -@external -func upgrade{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - new_implementation: felt -) { - Proxy.assert_only_admin(); - Proxy._set_implementation_hash(new_implementation); - return (); -} - -@external -func set_admin{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - new_admin: felt -) { - Proxy.assert_only_admin(); - Proxy._set_admin(new_admin); - return (); -} - -@view -func get_admin{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - admin: felt -) { - return Proxy.get_admin(); -} - -@view -func get_implementation_hash{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - implementation: felt -) { - return Proxy.get_implementation_hash(); -} diff --git a/contracts/test/FactoryV2.cairo b/contracts/test/FactoryV2.cairo deleted file mode 100644 index 6273e79..0000000 --- a/contracts/test/FactoryV2.cairo +++ /dev/null @@ -1,300 +0,0 @@ -%lang starknet - -// @title JediSwap V2 Factory -// @author Mesh Finance -// @license MIT -// @notice Factory to create and register new pairs - -from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin -from starkware.starknet.common.syscalls import get_caller_address, deploy, get_contract_address -from starkware.cairo.common.uint256 import Uint256 -from starkware.cairo.common.hash import hash2 -from starkware.cairo.common.math import assert_not_zero, assert_not_equal -from starkware.cairo.common.alloc import alloc -from starkware.cairo.common.math_cmp import is_le, is_le_felt -from openzeppelin.upgrades.library import Proxy - -// -// Storage -// - -// @dev Address of fee recipient -@storage_var -func _fee_to() -> (address: felt) { -} - -// @dev Address allowed to change feeTo. -@storage_var -func _fee_to_setter() -> (address: felt) { -} - -// @dev Array of all pairs -@storage_var -func _all_pairs(index: felt) -> (address: felt) { -} - -// @dev Pair address for pair of `token0` and `token1` -@storage_var -func _pair(token0: felt, token1: felt) -> (pair: felt) { -} - -// @dev Total pairs -@storage_var -func _num_of_pairs() -> (num: felt) { -} - -@storage_var -func _pair_proxy_contract_class_hash() -> (class_hash: felt) { -} - -@storage_var -func _pair_contract_class_hash() -> (class_hash: felt) { -} - -// @dev Emitted each time a pair is created via createPair -// token0 is guaranteed to be strictly less than token1 by sort order. - -@event -func PairCreated(token0: felt, token1: felt, pair: felt, total_pairs: felt) { -} - -// -// Constructor -// - -// @notice Contract constructor -// @param fee_to_setter Fee Recipient Setter -@external -func initializer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - pair_proxy_contract_class_hash: felt, pair_contract_class_hash: felt, fee_to_setter: felt -) { - - with_attr error_message("Factory::constructor::Fee Recipient Setter can not be zero") { - assert_not_zero(fee_to_setter); - } - - with_attr error_message("Factory::constructor::Pair Proxy Contract Class Hash can not be zero") { - assert_not_zero(pair_proxy_contract_class_hash); - } - - with_attr error_message("Factory::constructor::Pair Contract Class Hash can not be zero") { - assert_not_zero(pair_contract_class_hash); - } - - _fee_to_setter.write(fee_to_setter); - _pair_proxy_contract_class_hash.write(pair_proxy_contract_class_hash); - _pair_contract_class_hash.write(pair_contract_class_hash); - _num_of_pairs.write(0); - Proxy.initializer(fee_to_setter); - return (); -} - -// -// Getters -// - -// @notice Get pair address for the pair of `token0` and `token1` -// @param token0 Address of token0 -// @param token1 Address of token1 -// @return pair Address of the pair -@view -func get_pair{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - token0: felt, token1: felt -) -> (pair: felt) { - let (pair_0_1) = _pair.read(token0, token1); - if (pair_0_1 == 0) { - let (pair_1_0) = _pair.read(token1, token0); - return (pair=pair_1_0); - } else { - return (pair=pair_0_1); - } -} - -// @notice Get all the pairs registered -// @return all_pairs_len Length of `all_pairs` array -// @return all_pairs Array of addresses of the registered pairs -@view -func get_all_pairs{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - all_pairs_len: felt, all_pairs: felt* -) { - alloc_locals; - let (num_pairs) = _num_of_pairs.read(); - let (local all_pairs: felt*) = alloc(); - let (all_pairs_end: felt*) = _build_all_pairs_array(0, num_pairs, all_pairs); - return (num_pairs, all_pairs); -} - -// @notice Get the number of pairs -// @return num_of_pairs -@view -func get_num_of_pairs{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - num_of_pairs: felt -) { - let (num_of_pairs) = _num_of_pairs.read(); - return (num_of_pairs,); -} - -// @notice Get fee recipient address -// @return address -@view -func get_fee_to{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - address: felt -) { - return _fee_to.read(); -} - -// @notice Get the address allowed to change fee_to. -// @return address -@view -func get_fee_to_setter{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - address: felt -) { - return _fee_to_setter.read(); -} - -// @notice Get the class hash of the Pair contract which is deployed for each pair. -// @return class_hash -@view -func get_pair_contract_class_hash{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - ) -> (class_hash: felt) { - return _pair_proxy_contract_class_hash.read(); -} - -@view -func test_v2_contract{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - ) -> (success: felt) { - return (success=1); -} - -// -// Setters -// - -// @notice Create pair of `tokenA` and `tokenB` with deterministic address using deploy -// @dev tokens are sorted before creating pair. We deploy PairProxy. -// @param tokenA Address of tokenA -// @param tokenB Address of tokenB -// @return pair Address of the created pair -@external -func create_pair{ - syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, bitwise_ptr: BitwiseBuiltin*, range_check_ptr -}(tokenA: felt, tokenB: felt) -> (pair: felt) { - alloc_locals; - with_attr error_message("Factory::create_pair::tokenA and tokenB must be non zero") { - assert_not_zero(tokenA); - assert_not_zero(tokenB); - } - - with_attr error_message("Factory::create_pair::tokenA and tokenB must be different") { - assert_not_equal(tokenA, tokenB); - } - - let (existing_pair) = get_pair(tokenA, tokenB); - with_attr error_message("Factory::create_pair::pair already exists for tokenA and tokenB") { - assert existing_pair = 0; - } - let (pair_proxy_class_hash: felt) = _pair_proxy_contract_class_hash.read(); - let (pair_implementation_class_hash: felt) = _pair_contract_class_hash.read(); - - let (token0, token1) = _sort_tokens(tokenA, tokenB); - - let (contract_address: felt) = get_contract_address(); - - tempvar pedersen_ptr = pedersen_ptr; - - let (salt) = hash2{hash_ptr=pedersen_ptr}(token0, token1); - - let (fee_to_setter) = get_fee_to_setter(); - - let constructor_calldata: felt* = alloc(); - - assert [constructor_calldata] = pair_implementation_class_hash; - assert [constructor_calldata + 1] = token0; - assert [constructor_calldata + 2] = token1; - assert [constructor_calldata + 3] = fee_to_setter; - - let (pair: felt) = deploy( - class_hash=pair_proxy_class_hash, - contract_address_salt=salt, - constructor_calldata_size=4, - constructor_calldata=constructor_calldata, - deploy_from_zero=0, - ); - - _pair.write(token0, token1, pair); - let (num_pairs) = _num_of_pairs.read(); - _all_pairs.write(num_pairs, pair); - _num_of_pairs.write(num_pairs + 1); - PairCreated.emit(token0=token0, token1=token1, pair=pair, total_pairs=num_pairs + 1); - - return (pair=pair); -} - -// @notice Change fee recipient to `new_fee_to` -// @dev Only fee_to_setter can change -// @param fee_to Address of new fee recipient -@external -func set_fee_to{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(new_fee_to: felt) { - let (sender) = get_caller_address(); - let (fee_to_setter) = get_fee_to_setter(); - with_attr error_message("Factory::set_fee_to::Caller must be fee to setter") { - assert sender = fee_to_setter; - } - _fee_to.write(new_fee_to); - return (); -} - -// @notice Change fee setter to `fee_to_setter` -// @dev Only fee_to_setter can change -// @param fee_to_setter Address of new fee setter -@external -func set_fee_to_setter{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - new_fee_to_setter: felt -) { - let (sender) = get_caller_address(); - let (fee_to_setter) = get_fee_to_setter(); - with_attr error_message("Factory::set_fee_to_setter::Caller must be fee to setter") { - assert sender = fee_to_setter; - } - with_attr error_message("Factory::set_fee_to_setter::new_fee_to_setter must be non zero") { - assert_not_zero(new_fee_to_setter); - } - _fee_to_setter.write(new_fee_to_setter); - return (); -} - -// -// Internals LIBRARY -// - -func _build_all_pairs_array{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - current_index: felt, num_pairs: felt, all_pairs: felt* -) -> (all_pairs: felt*) { - alloc_locals; - if (current_index == num_pairs) { - return (all_pairs,); - } - let (current_pair) = _all_pairs.read(current_index); - assert [all_pairs] = current_pair; - return _build_all_pairs_array(current_index + 1, num_pairs, all_pairs + 1); -} - -func _sort_tokens{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - tokenA: felt, tokenB: felt -) -> (token0: felt, token1: felt) { - alloc_locals; - local token0; - local token1; - assert_not_equal(tokenA, tokenB); - let is_tokenA_less_than_tokenB = is_le_felt(tokenA, tokenB); - if (is_tokenA_less_than_tokenB == 1) { - assert token0 = tokenA; - assert token1 = tokenB; - } else { - assert token0 = tokenB; - assert token1 = tokenA; - } - - assert_not_zero(token0); - return (token0, token1); -} diff --git a/contracts/test/FlashSwapTest.cairo b/contracts/test/FlashSwapTest.cairo deleted file mode 100644 index 1fb4e86..0000000 --- a/contracts/test/FlashSwapTest.cairo +++ /dev/null @@ -1,73 +0,0 @@ -%lang starknet - -from starkware.cairo.common.cairo_builtins import HashBuiltin -from starkware.starknet.common.syscalls import get_caller_address, get_contract_address -from starkware.cairo.common.math import assert_not_zero -from starkware.cairo.common.uint256 import Uint256 - -// -// Interfaces -// -@contract_interface -namespace IERC20 { - func balanceOf(account: felt) -> (balance: Uint256) { - } - - func transfer(recipient: felt, amount: Uint256) -> (success: felt) { - } -} - -@contract_interface -namespace IPair { - func token0() -> (address: felt) { - } - - func token1() -> (address: felt) { - } -} - -@contract_interface -namespace IFactory { - func get_pair(token0: felt, token1: felt) -> (pair: felt) { - } -} - -@storage_var -func _factory() -> (address: felt) { -} - -@constructor -func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(factory: felt) { - with_attr error_message("Router::constructor::factory can not be zero") { - assert_not_zero(factory); - } - _factory.write(factory); - return (); -} - -@external -func jediswap_call{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - sender: felt, amount0Out: Uint256, amount1Out: Uint256, data_len: felt, data: felt* -) { - alloc_locals; - - let (caller) = get_caller_address(); - - let (local token0) = IPair.token0(contract_address=caller); - let (local token1) = IPair.token1(contract_address=caller); - let (local factory) = _factory.read(); - let (local pair) = IFactory.get_pair(contract_address=factory, token0=token0, token1=token1); - - with_attr error_message("FlashSwapTest::jediswap_call::Only valid pair can call") { - assert pair = caller; - } - - let (self_address) = get_contract_address(); - let (local balance0: Uint256) = IERC20.balanceOf(contract_address=token0, account=self_address); - let (local balance1: Uint256) = IERC20.balanceOf(contract_address=token1, account=self_address); - - IERC20.transfer(contract_address=token0, recipient=caller, amount=balance0); - IERC20.transfer(contract_address=token1, recipient=caller, amount=balance1); - - return (); -} diff --git a/contracts/test/PairV2.cairo b/contracts/test/PairV2.cairo deleted file mode 100644 index 7ca2db0..0000000 --- a/contracts/test/PairV2.cairo +++ /dev/null @@ -1,954 +0,0 @@ -%lang starknet - -// @title JediSwap Pair -// @author Mesh Finance -// @license MIT -// @notice Low level pair contract -// @dev Based on the Uniswap V2 pair -// https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol -// Also an ERC20 token - -from starkware.cairo.common.cairo_builtins import HashBuiltin -from starkware.cairo.common.bool import TRUE, FALSE -from starkware.starknet.common.syscalls import ( - get_caller_address, - get_contract_address, - get_block_timestamp, -) -from starkware.cairo.common.math import ( - assert_not_zero, - assert_in_range, - assert_le, - assert_not_equal, -) -from starkware.cairo.common.math_cmp import is_not_zero, is_le -from starkware.cairo.common.uint256 import ( - Uint256, - uint256_le, - uint256_lt, - uint256_check, - uint256_eq, - uint256_sqrt, - uint256_unsigned_div_rem, -) -from starkware.cairo.common.alloc import alloc -from openzeppelin.token.erc20.library import ERC20 -from contracts.utils.math import ( - uint256_checked_add, - uint256_checked_sub_lt, - uint256_checked_sub_le, - uint256_checked_mul, - uint256_felt_checked_mul, -) -from openzeppelin.upgrades.library import Proxy - -const MINIMUM_LIQUIDITY = 1000; -const BURN_ADDRESS = 1; - -// -// Interfaces -// -@contract_interface -namespace IERC20 { - func balanceOf(account: felt) -> (balance: Uint256) { - } - - func transfer(recipient: felt, amount: Uint256) -> (success: felt) { - } - - func transferFrom(sender: felt, recipient: felt, amount: Uint256) -> (success: felt) { - } -} - -@contract_interface -namespace IFactory { - func get_fee_to() -> (address: felt) { - } -} - -@contract_interface -namespace IJediSwapCallee { - func jediswap_call( - sender: felt, amount0Out: Uint256, amount1Out: Uint256, data_len: felt, data: felt* - ) { - } -} - -// -// Storage Pair -// - -// @dev token0 address -@storage_var -func _token0() -> (address: felt) { -} - -// @dev token1 address -@storage_var -func _token1() -> (address: felt) { -} - -// @dev reserve for token0 -@storage_var -func _reserve0() -> (res: Uint256) { -} - -// @dev reserve for token1 -@storage_var -func _reserve1() -> (res: Uint256) { -} - -// @dev block timestamp for last update -@storage_var -func _block_timestamp_last() -> (ts: felt) { -} - -// @dev cumulative price for token0 on last update -@storage_var -func _price_0_cumulative_last() -> (res: Uint256) { -} - -// @dev cumulative price for token1 on last update -@storage_var -func _price_1_cumulative_last() -> (res: Uint256) { -} - -// @dev reserve0 * reserve1, as of immediately after the most recent liquidity event -@storage_var -func _klast() -> (res: Uint256) { -} - -// @dev Boolean to check reentrancy -@storage_var -func _locked() -> (res: felt) { -} - -// @dev Factory contract address -@storage_var -func _factory() -> (address: felt) { -} - -// @notice An event emitted whenever token is transferred. -@event -func Transfer(from_address: felt, to_address: felt, amount: Uint256) { -} - -// @notice An event emitted whenever allowances is updated -@event -func Approval(owner: felt, spender: felt, amount: Uint256) { -} - -// @notice An event emitted whenever mint() is called. -@event -func Mint(sender: felt, amount0: Uint256, amount1: Uint256) { -} - -// @notice An event emitted whenever burn() is called. -@event -func Burn(sender: felt, amount0: Uint256, amount1: Uint256, to: felt) { -} - -// @notice An event emitted whenever swap() is called. -@event -func Swap( - sender: felt, - amount0In: Uint256, - amount1In: Uint256, - amount0Out: Uint256, - amount1Out: Uint256, - to: felt, -) { -} - -// @notice An event emitted whenever _update() is called. -@event -func Sync(reserve0: Uint256, reserve1: Uint256) { -} - -// -// Constructor -// - -// @notice Contract constructor -// @param name Name of the pair token -// @param symbol Symbol of the pair token -// @param token0 Address of token0 -// @param token1 Address of token1 -@external -func initializer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - token0: felt, token1: felt, proxy_admin: felt -) { - with_attr error_message("Pair::constructor::all arguments must be non zero") { - assert_not_zero(token0); - assert_not_zero(token1); - } - ERC20.initializer('JediSwap Pair', 'JEDI-P', 18); - _locked.write(0); - _token0.write(token0); - _token1.write(token1); - let (factory) = get_caller_address(); - _factory.write(factory); - Proxy.initializer(proxy_admin); - return (); -} - -// -// Getters ERC20 -// - -// @notice Name of the token -// @return name -@view -func name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (name: felt) { - let (name) = ERC20.name(); - return (name,); -} - -// @notice Symbol of the token -// @return symbol -@view -func symbol{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (symbol: felt) { - let (symbol) = ERC20.symbol(); - return (symbol,); -} - -// @notice Total Supply of the token -// @return totalSupply -@view -func totalSupply{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - totalSupply: Uint256 -) { - let (totalSupply: Uint256) = ERC20.total_supply(); - return (totalSupply,); -} - -// @notice Decimals of the token -// @return decimals -@view -func decimals{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - decimals: felt -) { - let (decimals) = ERC20.decimals(); - return (decimals,); -} - -// @notice Balance of `account` -// @param account Account address whose balance is fetched -// @return balance Balance of `account` -@view -func balanceOf{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(account: felt) -> ( - balance: Uint256 -) { - let (balance: Uint256) = ERC20.balance_of(account); - return (balance,); -} - -// @notice Allowance which `spender` can spend on behalf of `owner` -// @param owner Account address whose tokens are spent -// @param spender Account address which can spend the tokens -// @return remaining -@view -func allowance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - owner: felt, spender: felt -) -> (remaining: Uint256) { - let (remaining: Uint256) = ERC20.allowance(owner, spender); - return (remaining,); -} - -// -// Getters Pair -// - -// @notice token0 address -// @return address -@view -func token0{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (address: felt) { - let (address) = _token0.read(); - return (address,); -} - -// @notice token1 address -// @return address -@view -func token1{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (address: felt) { - let (address) = _token1.read(); - return (address,); -} - -// @notice Current reserves for tokens in the pair -// @return reserve0 reserve for token0 -// @return reserve1 reserve for token1 -// @return block_timestamp_last block timestamp for last update -@view -func get_reserves{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - reserve0: Uint256, reserve1: Uint256, block_timestamp_last: felt -) { - return _get_reserves(); -} - -// @notice cumulative price for token0 on last update -// @return res -@view -func price_0_cumulative_last{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - res: Uint256 -) { - let (res) = _price_0_cumulative_last.read(); - return (res,); -} - -// @notice cumulative price for token1 on last update -// @return res -@view -func price_1_cumulative_last{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - res: Uint256 -) { - let (res) = _price_1_cumulative_last.read(); - return (res,); -} - -// @notice reserve0 * reserve1, as of immediately after the most recent liquidity event -// @return res -@view -func klast{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (res: Uint256) { - let (res) = _klast.read(); - return (res,); -} - -@view -func test_v2_contract{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - success: felt -) { - return (success=1); -} - -// -// Externals ERC20 -// - -// @notice Transfer `amount` tokens from `caller` to `recipient` -// @param recipient Account address to which tokens are transferred -// @param amount Amount of tokens to transfer -// @return success 0 or 1 -@external -func transfer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - recipient: felt, amount: Uint256 -) -> (success: felt) { - ERC20.transfer(recipient, amount); - return (TRUE,); -} - -// @notice Transfer `amount` tokens from `sender` to `recipient` -// @dev Checks for allowance. -// @param sender Account address from which tokens are transferred -// @param recipient Account address to which tokens are transferred -// @param amount Amount of tokens to transfer -// @return success 0 or 1 -@external -func transferFrom{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - sender: felt, recipient: felt, amount: Uint256 -) -> (success: felt) { - ERC20.transfer_from(sender, recipient, amount); - return (TRUE,); -} - -// @notice Approve `spender` to transfer `amount` tokens on behalf of `caller` -// @param spender The address which will spend the funds -// @param amount The amount of tokens to be spent -// @return success 0 or 1 -@external -func approve{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - spender: felt, amount: Uint256 -) -> (success: felt) { - ERC20.approve(spender, amount); - return (TRUE,); -} - -// @notice Increase allowance of `spender` to transfer `added_value` more tokens on behalf of `caller` -// @param spender The address which will spend the funds -// @param added_value The increased amount of tokens to be spent -// @return success 0 or 1 -@external -func increaseAllowance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - spender: felt, added_value: Uint256 -) -> (success: felt) { - ERC20.increase_allowance(spender, added_value); - return (TRUE,); -} - -// @notice Decrease allowance of `spender` to transfer `subtracted_value` less tokens on behalf of `caller` -// @param spender The address which will spend the funds -// @param subtracted_value The decreased amount of tokens to be spent -// @return success 0 or 1 -@external -func decreaseAllowance{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - spender: felt, subtracted_value: Uint256 -) -> (success: felt) { - ERC20.decrease_allowance(spender, subtracted_value); - return (TRUE,); -} - -// -// Externals Pair -// - -// @notice Mint tokens and assign them to `to` -// @dev This low-level function should be called from a contract which performs important safety checks -// @param to The account that will receive the created tokens -// @return liquidity New tokens created -@external -func mint{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(to: felt) -> ( - liquidity: Uint256 -) { - alloc_locals; - _check_and_lock(); - let (local reserve0: Uint256, local reserve1: Uint256, _) = _get_reserves(); - let (self_address) = get_contract_address(); - let (token0) = _token0.read(); - let (local balance0: Uint256) = IERC20.balanceOf(contract_address=token0, account=self_address); - let (token1) = _token1.read(); - let (local balance1: Uint256) = IERC20.balanceOf(contract_address=token1, account=self_address); - - let (local amount0: Uint256) = uint256_checked_sub_lt(balance0, reserve0); - let (local amount1: Uint256) = uint256_checked_sub_lt(balance1, reserve1); - - let (fee_on) = _mint_protocol_fee(reserve0, reserve1); - - let (local _total_supply: Uint256) = totalSupply(); - let (is_total_supply_equal_to_zero) = uint256_eq(_total_supply, Uint256(0, 0)); - - local liquidity: Uint256; - - if (is_total_supply_equal_to_zero == 1) { - let (amount0_mul_amount1: Uint256) = uint256_checked_mul(amount0, amount1); - - let (mul_sqrt: Uint256) = uint256_sqrt(amount0_mul_amount1); - - // local mul_sqrt: Uint256 - // assert mul_sqrt = amount0 - - let (initial_liquidity: Uint256) = uint256_checked_sub_lt( - mul_sqrt, Uint256(MINIMUM_LIQUIDITY, 0) - ); - assert liquidity = initial_liquidity; - ERC20._mint(BURN_ADDRESS, Uint256(MINIMUM_LIQUIDITY, 0)); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - let (amount0_mul_total_supply: Uint256) = uint256_checked_mul(amount0, _total_supply); - let (liquidity0: Uint256, _) = uint256_unsigned_div_rem(amount0_mul_total_supply, reserve0); - - let (amount1_mul_total_supply: Uint256) = uint256_checked_mul(amount1, _total_supply); - let (liquidity1: Uint256, _) = uint256_unsigned_div_rem(amount1_mul_total_supply, reserve1); - - let (is_liquidity0_less_than_liquidity1) = uint256_lt(liquidity0, liquidity1); - if (is_liquidity0_less_than_liquidity1 == 1) { - assert liquidity = liquidity0; - } else { - assert liquidity = liquidity1; - } - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - local syscall_ptr: felt* = syscall_ptr; - local pedersen_ptr: HashBuiltin* = pedersen_ptr; - - let (is_liquidity_greater_than_zero) = uint256_lt(Uint256(0, 0), liquidity); - with_attr error_message("Pair::mint::insufficient liquidity minted") { - assert is_liquidity_greater_than_zero = 1; - } - - ERC20._mint(to, liquidity); - - _update(balance0, balance1, reserve0, reserve1); - - if (fee_on == 1) { - let (klast: Uint256) = uint256_checked_mul(balance0, balance1); - _klast.write(klast); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - let (caller) = get_caller_address(); - - Mint.emit(sender=caller, amount0=amount0, amount1=amount1); - - _unlock(); - return (liquidity,); -} - -// @notice Burn tokens belonging to `to` -// @dev This low-level function should be called from a contract which performs important safety checks -// @param to The account that will receive the created tokens -// @return amount0 Amount of token0 received -// @return amount1 Amount of token1 received -@external -func burn{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(to: felt) -> ( - amount0: Uint256, amount1: Uint256 -) { - alloc_locals; - _check_and_lock(); - let (local reserve0: Uint256, local reserve1: Uint256, _) = _get_reserves(); - - let (self_address) = get_contract_address(); - let (token0) = _token0.read(); - let (local balance0: Uint256) = IERC20.balanceOf(contract_address=token0, account=self_address); - let (token1) = _token1.read(); - let (local balance1: Uint256) = IERC20.balanceOf(contract_address=token1, account=self_address); - - let (local liquidity: Uint256) = balanceOf(self_address); - - let (fee_on) = _mint_protocol_fee(reserve0, reserve1); - - let (local _total_supply: Uint256) = totalSupply(); - let (is_total_supply_greater_than_zero) = uint256_lt(Uint256(0, 0), _total_supply); - - assert is_total_supply_greater_than_zero = 1; - - let (liquidity_mul_balance0: Uint256) = uint256_checked_mul(liquidity, balance0); - let (local amount0: Uint256, _) = uint256_unsigned_div_rem( - liquidity_mul_balance0, _total_supply - ); - let (is_amount0_greater_than_zero) = uint256_lt(Uint256(0, 0), amount0); - - let (liquidity_mul_balance1: Uint256) = uint256_checked_mul(liquidity, balance1); - let (local amount1: Uint256, _) = uint256_unsigned_div_rem( - liquidity_mul_balance1, _total_supply - ); - let (is_amount1_greater_than_zero) = uint256_lt(Uint256(0, 0), amount1); - - with_attr error_message("Pair::burn::insufficient liquidity burned") { - assert is_amount0_greater_than_zero = 1; - assert is_amount1_greater_than_zero = 1; - } - - ERC20._burn(self_address, liquidity); - - IERC20.transfer(contract_address=token0, recipient=to, amount=amount0); - IERC20.transfer(contract_address=token1, recipient=to, amount=amount1); - - let (local final_balance0: Uint256) = IERC20.balanceOf( - contract_address=token0, account=self_address - ); - let (local final_balance1: Uint256) = IERC20.balanceOf( - contract_address=token1, account=self_address - ); - - _update(final_balance0, final_balance1, reserve0, reserve1); - - if (fee_on == 1) { - let (klast: Uint256) = uint256_checked_mul(final_balance0, final_balance1); - _klast.write(klast); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - let (caller) = get_caller_address(); - - Burn.emit(sender=caller, amount0=amount0, amount1=amount1, to=to); - - _unlock(); - return (amount0, amount1); -} - -// @notice Swaps from one token to another -// @dev This low-level function should be called from a contract which performs important safety checks -// @param amount0Out Amount of token0 received -// @param amount1Out Amount of token1 received -// @param to The account that will receive the tokens -@external -func swap{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amount0Out: Uint256, amount1Out: Uint256, to: felt, data_len: felt, data: felt* -) { - alloc_locals; - _check_and_lock(); - local sufficient_output_amount; - let (local is_amount0out_greater_than_zero) = uint256_lt(Uint256(0, 0), amount0Out); - let (local is_amount1out_greater_than_zero) = uint256_lt(Uint256(0, 0), amount1Out); - if (is_amount0out_greater_than_zero == 1) { - assert sufficient_output_amount = 1; - } else { - if (is_amount1out_greater_than_zero == 1) { - assert sufficient_output_amount = 1; - } else { - assert sufficient_output_amount = 0; - } - } - with_attr error_message("Pair::swap::insufficient output amount") { - assert sufficient_output_amount = 1; - } - - let (local reserve0: Uint256, local reserve1: Uint256, _) = _get_reserves(); - let (is_amount0out_lesser_than_reserve0) = uint256_lt(amount0Out, reserve0); - let (is_amount1out_lesser_than_reserve0) = uint256_lt(amount1Out, reserve1); - with_attr error_message("Pair::swap::insufficient liquidity") { - assert is_amount0out_lesser_than_reserve0 = 1; - assert is_amount1out_lesser_than_reserve0 = 1; - } - - let (local token0) = _token0.read(); - let (local token1) = _token1.read(); - with_attr error_message("Pair::swap::invalid to") { - assert_not_equal(token0, to); - assert_not_equal(token1, to); - } - - let (self_address) = get_contract_address(); - - if (is_amount0out_greater_than_zero == 1) { - IERC20.transfer(contract_address=token0, recipient=to, amount=amount0Out); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - if (is_amount1out_greater_than_zero == 1) { - IERC20.transfer(contract_address=token1, recipient=to, amount=amount1Out); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - local syscall_ptr: felt* = syscall_ptr; - local pedersen_ptr: HashBuiltin* = pedersen_ptr; - - let (caller_address) = get_caller_address(); - - let data_len_greater_than_zero = is_le(1, data_len); - if (data_len_greater_than_zero == 1) { - IJediSwapCallee.jediswap_call( - contract_address=to, - sender=caller_address, - amount0Out=amount0Out, - amount1Out=amount1Out, - data_len=data_len, - data=data, - ); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - let (local balance0: Uint256) = IERC20.balanceOf(contract_address=token0, account=self_address); - let (local balance1: Uint256) = IERC20.balanceOf(contract_address=token1, account=self_address); - - let (local expected_balance0: Uint256) = uint256_checked_sub_le(reserve0, amount0Out); - let (local expected_balance1: Uint256) = uint256_checked_sub_le(reserve1, amount1Out); - - local sufficient_input_amount; - let (local is_balance0_greater_than_expected_balance0) = uint256_lt( - expected_balance0, balance0 - ); - let (local is_balance1_greater_than_expected_balance1) = uint256_lt( - expected_balance1, balance1 - ); - if (is_balance0_greater_than_expected_balance0 == 1) { - assert sufficient_input_amount = 1; - } else { - if (is_balance1_greater_than_expected_balance1 == 1) { - assert sufficient_input_amount = 1; - } else { - assert sufficient_input_amount = 0; - } - } - with_attr error_message("Pair::swap::insufficient input amount") { - assert sufficient_input_amount = 1; - } - - let (local amount0In: Uint256) = uint256_checked_sub_le(balance0, expected_balance0); - let (local amount1In: Uint256) = uint256_checked_sub_le(balance1, expected_balance1); - - let (balance0_mul_1000: Uint256) = uint256_felt_checked_mul(balance0, 1000); - let (amount0In_mul_3: Uint256) = uint256_felt_checked_mul(amount0In, 3); - let (local balance0Adjusted: Uint256) = uint256_checked_sub_lt( - balance0_mul_1000, amount0In_mul_3 - ); - - let (balance1_mul_1000: Uint256) = uint256_felt_checked_mul(balance1, 1000); - let (amount1In_mul_3: Uint256) = uint256_felt_checked_mul(amount1In, 3); - let (local balance1Adjusted: Uint256) = uint256_checked_sub_lt( - balance1_mul_1000, amount1In_mul_3 - ); - - let (balance0Adjusted_mul_balance1Adjusted: Uint256) = uint256_checked_mul( - balance0Adjusted, balance1Adjusted - ); - - let (reserve0_mul_reserve1: Uint256) = uint256_checked_mul(reserve0, reserve1); - - let (reserve0_mul_reserve1_mul_multiplier: Uint256) = uint256_felt_checked_mul( - reserve0_mul_reserve1, 1000000 - ); - - let (is_balance_adjusted_mul_greater_than_equal_final_reserve_mul) = uint256_le( - reserve0_mul_reserve1_mul_multiplier, balance0Adjusted_mul_balance1Adjusted - ); - with_attr error_message("Pair::swap::invariant K") { - assert is_balance_adjusted_mul_greater_than_equal_final_reserve_mul = 1; - } - - _update(balance0, balance1, reserve0, reserve1); - - let (caller) = get_caller_address(); - - Swap.emit( - sender=caller, - amount0In=amount0In, - amount1In=amount1In, - amount0Out=amount0Out, - amount1Out=amount1Out, - to=to, - ); - - _unlock(); - - return (); -} - -// @notice force balances to match reserves -// @param to The account that will receive the balance tokens -@external -func skim{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(to: felt) { - alloc_locals; - _check_and_lock(); - let (local reserve0: Uint256, local reserve1: Uint256, _) = _get_reserves(); - - let (self_address) = get_contract_address(); - let (token0) = _token0.read(); - let (local balance0: Uint256) = IERC20.balanceOf(contract_address=token0, account=self_address); - let (token1) = _token1.read(); - let (local balance1: Uint256) = IERC20.balanceOf(contract_address=token1, account=self_address); - - let (local amount0: Uint256) = uint256_checked_sub_lt(balance0, reserve0); - let (local amount1: Uint256) = uint256_checked_sub_lt(balance1, reserve1); - - IERC20.transfer(contract_address=token0, recipient=to, amount=amount0); - IERC20.transfer(contract_address=token1, recipient=to, amount=amount1); - - _unlock(); - - return (); -} - -// @notice Force reserves to match balances -@external -func sync{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { - alloc_locals; - _check_and_lock(); - - let (self_address) = get_contract_address(); - let (token0) = _token0.read(); - let (local balance0: Uint256) = IERC20.balanceOf(contract_address=token0, account=self_address); - let (token1) = _token1.read(); - let (local balance1: Uint256) = IERC20.balanceOf(contract_address=token1, account=self_address); - - let (local reserve0: Uint256) = _reserve0.read(); - let (local reserve1: Uint256) = _reserve1.read(); - - _update(balance0, balance1, reserve0, reserve1); - - _unlock(); - - return (); -} - -// -// Internals Pair -// - -// @dev Check if the entry is not locked, and lock it -func _check_and_lock{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { - let (locked) = _locked.read(); - with_attr error_message("Pair::_check_and_lock::locked") { - assert locked = 0; - } - _locked.write(1); - return (); -} - -// @dev Unlock the entry -func _unlock{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { - let (locked) = _locked.read(); - with_attr error_message("Pair::_unlock::not locked") { - assert locked = 1; - } - _locked.write(0); - return (); -} - -// @dev If fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k) -func _mint_protocol_fee{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - reserve0: Uint256, reserve1: Uint256 -) -> (fee_on: felt) { - alloc_locals; - let (local factory) = _factory.read(); - let (local fee_to) = IFactory.get_fee_to(contract_address=factory); - local fee_on = is_not_zero(fee_to); - - let (local klast: Uint256) = _klast.read(); - let (local is_klast_equal_to_zero) = uint256_eq(klast, Uint256(0, 0)); - - if (fee_on == 1) { - if (is_klast_equal_to_zero == 0) { - let (reserve0_mul_reserve1: Uint256) = uint256_checked_mul(reserve0, reserve1); - let (local rootk: Uint256) = uint256_sqrt(reserve0_mul_reserve1); - let (local rootklast: Uint256) = uint256_sqrt(klast); - let (is_rootk_greater_than_rootklast) = uint256_lt(rootklast, rootk); - if (is_rootk_greater_than_rootklast == 1) { - let (local rootkdiff: Uint256) = uint256_checked_sub_le(rootk, rootklast); - let (local _total_supply: Uint256) = totalSupply(); - let (numerator: Uint256) = uint256_checked_mul(rootkdiff, _total_supply); - let (rootk_mul_5: Uint256) = uint256_felt_checked_mul(rootk, 5); - let (local denominator: Uint256) = uint256_checked_add(rootk_mul_5, rootklast); - let (liquidity: Uint256, _) = uint256_unsigned_div_rem(numerator, denominator); - let (is_liquidity_greater_than_zero) = uint256_lt(Uint256(0, 0), liquidity); - if (is_liquidity_greater_than_zero == 1) { - ERC20._mint(fee_to, liquidity); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - } else { - if (is_klast_equal_to_zero == 0) { - _klast.write(Uint256(0, 0)); - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - } - return (fee_on,); -} - -func _get_reserves{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> ( - reserve0: Uint256, reserve1: Uint256, block_timestamp_last: felt -) { - let (reserve0) = _reserve0.read(); - let (reserve1) = _reserve1.read(); - let (block_timestamp_last) = _block_timestamp_last.read(); - return (reserve0, reserve1, block_timestamp_last); -} - -// @dev Update reserves and, on the first call per block, price accumulators -func _update{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - balance0: Uint256, balance1: Uint256, reserve0: Uint256, reserve1: Uint256 -) { - alloc_locals; - with_attr error_message("Pair::_update::overflow") { - assert balance0.high = 0; - assert balance1.high = 0; - } - let (block_timestamp) = get_block_timestamp(); - let (block_timestamp_last) = _block_timestamp_last.read(); - let is_block_timestamp_greater_than_equal_to_last = is_le( - block_timestamp_last, block_timestamp - ); - if (is_block_timestamp_greater_than_equal_to_last == 1) { - let is_block_timestamp_not_equal_to_last = is_not_zero( - block_timestamp - block_timestamp_last - ); - if (is_block_timestamp_not_equal_to_last == 1) { - let (is_reserve0_equal_to_zero) = uint256_eq(reserve0, Uint256(0, 0)); - if (is_reserve0_equal_to_zero == 0) { - let (is_reserve1_equal_to_zero) = uint256_eq(reserve1, Uint256(0, 0)); - if (is_reserve1_equal_to_zero == 0) { - let (price_0_cumulative_last) = _price_0_cumulative_last.read(); - let (reserve1by0: Uint256, _) = uint256_unsigned_div_rem(reserve1, reserve0); - let (reserve1by0_mul_time: Uint256) = uint256_felt_checked_mul( - reserve1by0, block_timestamp - block_timestamp_last - ); - let (new_price_0_cumulative: Uint256) = uint256_checked_add( - price_0_cumulative_last, reserve1by0_mul_time - ); - _price_0_cumulative_last.write(new_price_0_cumulative); - - let (price_1_cumulative_last) = _price_1_cumulative_last.read(); - let (reserve0by1: Uint256, _) = uint256_unsigned_div_rem(reserve0, reserve1); - let (reserve0by1_mul_time: Uint256) = uint256_felt_checked_mul( - reserve0by1, block_timestamp - block_timestamp_last - ); - let (new_price_1_cumulative: Uint256) = uint256_checked_add( - price_1_cumulative_last, reserve0by1_mul_time - ); - _price_1_cumulative_last.write(new_price_1_cumulative); - - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - _reserve0.write(balance0); - _reserve1.write(balance1); - _block_timestamp_last.write(block_timestamp); - - Sync.emit(reserve0=balance0, reserve1=balance1); - return (); -} diff --git a/contracts/test/RouterV2.cairo b/contracts/test/RouterV2.cairo deleted file mode 100644 index b948822..0000000 --- a/contracts/test/RouterV2.cairo +++ /dev/null @@ -1,671 +0,0 @@ -%lang starknet - -// @title JediSwap router for stateless execution of swaps -// @author Mesh Finance -// @license MIT -// @dev Based on the Uniswap V2 Router -// https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Router02.sol - -from starkware.cairo.common.cairo_builtins import HashBuiltin -from starkware.starknet.common.syscalls import get_caller_address, get_block_timestamp -from starkware.cairo.common.math import assert_le, assert_not_zero, assert_not_equal -from starkware.cairo.common.math_cmp import is_le, is_le_felt -from starkware.cairo.common.uint256 import ( - Uint256, - uint256_eq, - uint256_le, - uint256_lt, - uint256_unsigned_div_rem, -) -from starkware.cairo.common.alloc import alloc -from contracts.utils.math import ( - uint256_checked_add, - uint256_checked_sub_lt, - uint256_checked_mul, - uint256_felt_checked_mul, -) -from openzeppelin.upgrades.library import Proxy - -// -// Interfaces -// -@contract_interface -namespace IERC20 { - func transferFrom(sender: felt, recipient: felt, amount: Uint256) -> (success: felt) { - } -} - -@contract_interface -namespace IPair { - func get_reserves() -> (reserve0: Uint256, reserve1: Uint256, block_timestamp_last: felt) { - } - - func mint(to: felt) -> (liquidity: Uint256) { - } - - func burn(to: felt) -> (amount0: Uint256, amount1: Uint256) { - } - - func swap(amount0Out: Uint256, amount1Out: Uint256, to: felt, data_len: felt) { - } -} - -@contract_interface -namespace IFactory { - func get_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func create_pair(token0: felt, token1: felt) -> (pair: felt) { - } -} - -// -// Storage -// - -// @dev Factory contract address -@storage_var -func _factory() -> (address: felt) { -} - -// -// Constructor -// - -// @notice Contract constructor -// @param factory Address of factory contract -@external -func initializer{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(factory: felt, proxy_admin: felt) { - with_attr error_message("Router::constructor::factory can not be zero") { - assert_not_zero(factory); - } - _factory.write(factory); - Proxy.initializer(proxy_admin); - return (); -} - -// -// Getters -// - -// @notice factory address -// @return address -@view -func factory{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() -> (address: felt) { - let (address) = _factory.read(); - return (address,); -} - -// @notice Sort tokens `tokenA` and `tokenB` by address -// @param tokenA Address of tokenA -// @param tokenB Address of tokenB -// @return token0 First token -// @return token1 Second token -@view -func sort_tokens{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - tokenA: felt, tokenB: felt -) -> (token0: felt, token1: felt) { - return _sort_tokens(tokenA, tokenB); -} - -// @notice Given some amount of an asset and pair reserves, returns an equivalent amount of the other asset -// @param amountA Amount of tokenA -// @param reserveA Reserves for tokenA -// @param reserveB Reserves for tokenB -// @return amountB Amount of tokenB -@view -func quote{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountA: Uint256, reserveA: Uint256, reserveB: Uint256 -) -> (amountB: Uint256) { - return _quote(amountA, reserveA, reserveB); -} - -// @notice Given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset -// @param amountIn Input Amount -// @param reserveIn Reserves for input token -// @param reserveOut Reserves for output token -// @return amountOut Maximum output amount -@view -func get_amount_out{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountIn: Uint256, reserveIn: Uint256, reserveOut: Uint256 -) -> (amountOut: Uint256) { - return _get_amount_out(amountIn, reserveIn, reserveOut); -} - -// @notice Given an output amount of an asset and pair reserves, returns a required input amount of the other asset -// @param amountOut Output Amount -// @param reserveIn Reserves for input token -// @param reserveOut Reserves for output token -// @return amountIn Required input amount -@view -func get_amount_in{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountOut: Uint256, reserveIn: Uint256, reserveOut: Uint256 -) -> (amountIn: Uint256) { - return _get_amount_in(amountOut, reserveIn, reserveOut); -} - -// @notice Performs chained get_amount_out calculations on any number of pairs -// @param amountIn Input Amount -// @param path_len Length of path array -// @param path Array of pair addresses through which swaps are chained -// @return amounts_len Required output amount array's length -// @return amounts Required output amount array -@view -func get_amounts_out{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountIn: Uint256, path_len: felt, path: felt* -) -> (amounts_len: felt, amounts: Uint256*) { - alloc_locals; - let (local factory) = _factory.read(); - let (local amounts: Uint256*) = _get_amounts_out(factory, amountIn, path_len, path); - return (path_len, amounts); -} - -// @notice Performs chained get_amount_in calculations on any number of pairs -// @param amountOut Output Amount -// @param path_len Length of path array -// @param path Array of pair addresses through which swaps are chained -// @return amounts_len Required input amount array's length -// @return amounts Required input amount array -@view -func get_amounts_in{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountOut: Uint256, path_len: felt, path: felt* -) -> (amounts_len: felt, amounts: Uint256*) { - alloc_locals; - let (local factory) = _factory.read(); - let (local amounts: Uint256*) = _get_amounts_in(factory, amountOut, path_len, path); - return (path_len, amounts); -} - -@view -func test_v2_contract{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - ) -> (success: felt) { - return (success=1); -} - -// -// Externals -// - -// @notice Add liquidity to a pool -// @dev `caller` should have already given the router an allowance of at least amountADesired/amountBDesired on tokenA/tokenB -// @param tokenA Address of tokenA -// @param tokenB Address of tokenB -// @param amountADesired The amount of tokenA to add as liquidity -// @param amountBDesired The amount of tokenB to add as liquidity -// @param amountAMin Bounds the extent to which the B/A price can go up before the transaction reverts. Must be <= amountADesired -// @param amountBMin Bounds the extent to which the A/B price can go up before the transaction reverts. Must be <= amountBDesired -// @param to Recipient of liquidity tokens -// @param deadline Timestamp after which the transaction will revert -// @return amountA The amount of tokenA sent to the pool -// @return amountB The amount of tokenB sent to the pool -// @return liquidity The amount of liquidity tokens minted -@external -func add_liquidity{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - tokenA: felt, - tokenB: felt, - amountADesired: Uint256, - amountBDesired: Uint256, - amountAMin: Uint256, - amountBMin: Uint256, - to: felt, - deadline: felt, -) -> (amountA: Uint256, amountB: Uint256, liquidity: Uint256) { - alloc_locals; - _ensure_deadline(deadline); - let (local factory) = _factory.read(); - let (local amountA: Uint256, local amountB: Uint256) = _add_liquidity( - tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin - ); - let (local pair) = _pair_for(factory, tokenA, tokenB); - let (sender) = get_caller_address(); - IERC20.transferFrom(contract_address=tokenA, sender=sender, recipient=pair, amount=amountA); - IERC20.transferFrom(contract_address=tokenB, sender=sender, recipient=pair, amount=amountB); - let (local liquidity: Uint256) = IPair.mint(contract_address=pair, to=to); - return (amountA, amountB, liquidity); -} - -// @notice Remove liquidity from a pool -// @dev `caller` should have already given the router an allowance of at least liquidity on the pool -// @param tokenA Address of tokenA -// @param tokenB Address of tokenB -// @param tokenB Address of tokenB -// @param liquidity The amount of liquidity tokens to remove -// @param amountAMin The minimum amount of tokenA that must be received for the transaction not to revert -// @param amountBMin The minimum amount of tokenB that must be received for the transaction not to revert -// @param to Recipient of the underlying tokens -// @param deadline Timestamp after which the transaction will revert -// @return amountA The amount of tokenA received -// @return amountB The amount of tokenA received -@external -func remove_liquidity{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - tokenA: felt, - tokenB: felt, - liquidity: Uint256, - amountAMin: Uint256, - amountBMin: Uint256, - to: felt, - deadline: felt, -) -> (amountA: Uint256, amountB: Uint256) { - alloc_locals; - _ensure_deadline(deadline); - let (local factory) = _factory.read(); - let (local pair) = _pair_for(factory, tokenA, tokenB); - let (sender) = get_caller_address(); - IERC20.transferFrom(contract_address=pair, sender=sender, recipient=pair, amount=liquidity); - let (local amount0: Uint256, local amount1: Uint256) = IPair.burn(contract_address=pair, to=to); - let (local token0, _) = _sort_tokens(tokenA, tokenB); - local amountA: Uint256; - local amountB: Uint256; - if (tokenA == token0) { - assert amountA = amount0; - assert amountB = amount1; - } else { - assert amountA = amount1; - assert amountB = amount0; - } - - let (is_amountA_greater_than_equal_amountAMin) = uint256_le(amountAMin, amountA); - with_attr error_message("Router::remove_liquidity::insufficient A amount") { - assert is_amountA_greater_than_equal_amountAMin = 1; - } - let (is_amountB_greater_than_equal_amountBMin) = uint256_le(amountBMin, amountB); - with_attr error_message("Router::remove_liquidity::insufficient B amount") { - assert is_amountB_greater_than_equal_amountBMin = 1; - } - - return (amountA, amountB); -} - -// @notice Swaps an exact amount of input tokens for as many output tokens as possible, along the route determined by the path -// @dev `caller` should have already given the router an allowance of at least amountIn on the input token -// @param amountIn The amount of input tokens to send -// @param amountOutMin The minimum amount of output tokens that must be received for the transaction not to revert -// @param path_len Length of path array -// @param path Array of pair addresses through which swaps are chained -// @param to Recipient of the output tokens -// @param deadline Timestamp after which the transaction will revert -// @return amounts_len Length of amounts array -// @return amounts The input token amount and all subsequent output token amounts -@external -func swap_exact_tokens_for_tokens{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountIn: Uint256, amountOutMin: Uint256, path_len: felt, path: felt*, to: felt, deadline: felt -) -> (amounts_len: felt, amounts: Uint256*) { - alloc_locals; - _ensure_deadline(deadline); - let (local factory) = _factory.read(); - let (local amounts: Uint256*) = _get_amounts_out(factory, amountIn, path_len, path); - let (is_amount_last_greater_than_equal_amountOutMin) = uint256_le( - amountOutMin, [amounts + (path_len - 1) * Uint256.SIZE] - ); - with_attr error_message("Router::swap_exact_tokens_for_tokens::insufficient output amount") { - assert is_amount_last_greater_than_equal_amountOutMin = 1; - } - let (local pair) = _pair_for(factory, [path], [path + 1]); - let (sender) = get_caller_address(); - IERC20.transferFrom(contract_address=[path], sender=sender, recipient=pair, amount=[amounts]); - _swap(0, path_len, amounts, path, to); - return (path_len, amounts); -} - -// @notice Receive an exact amount of output tokens for as few input tokens as possible, along the route determined by the path -// @dev `caller` should have already given the router an allowance of at least amountInMax on the input token -// @param amountOut The amount of output tokens to receive -// @param amountInMax The maximum amount of input tokens that can be required before the transaction reverts -// @param path_len Length of path array -// @param path Array of pair addresses through which swaps are chained -// @param to Recipient of the output tokens -// @param deadline Timestamp after which the transaction will revert -// @return amounts_len Length of amounts array -// @return amounts The input token amount and all subsequent output token amounts -@external -func swap_tokens_for_exact_tokens{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountOut: Uint256, amountInMax: Uint256, path_len: felt, path: felt*, to: felt, deadline: felt -) -> (amounts_len: felt, amounts: Uint256*) { - alloc_locals; - _ensure_deadline(deadline); - let (local factory) = _factory.read(); - let (local amounts: Uint256*) = _get_amounts_in(factory, amountOut, path_len, path); - let (is_amount_first_less_than_equal_amountInMax) = uint256_le([amounts], amountInMax); - with_attr error_message("Router::swap_tokens_for_exact_tokens::excessive input amount") { - assert is_amount_first_less_than_equal_amountInMax = 1; - } - let (local pair) = _pair_for(factory, [path], [path + 1]); - let (sender) = get_caller_address(); - IERC20.transferFrom(contract_address=[path], sender=sender, recipient=pair, amount=[amounts]); - _swap(0, path_len, amounts, path, to); - return (path_len, amounts); -} - -// -// Internals -// - -func _ensure_deadline{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - deadline: felt -) { - let (block_timestamp) = get_block_timestamp(); - with_attr error_message("Router::_ensure_deadline::expired") { - assert_le(block_timestamp, deadline); - } - return (); -} - -func _add_liquidity{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - tokenA: felt, - tokenB: felt, - amountADesired: Uint256, - amountBDesired: Uint256, - amountAMin: Uint256, - amountBMin: Uint256, -) -> (amountA: Uint256, amountB: Uint256) { - alloc_locals; - let (local factory) = _factory.read(); - let (local pair) = IFactory.get_pair(contract_address=factory, token0=tokenA, token1=tokenB); - - if (pair == 0) { - let (new_pair) = IFactory.create_pair( - contract_address=factory, token0=tokenA, token1=tokenB - ); - } - - let (local reserveA: Uint256, local reserveB: Uint256) = _get_reserves(factory, tokenA, tokenB); - let (reserveA_mul_reserveB: Uint256) = uint256_checked_mul(reserveA, reserveB); - let (is_reserveA_mul_reserveB_equal_to_zero) = uint256_eq(reserveA_mul_reserveB, Uint256(0, 0)); - - if (is_reserveA_mul_reserveB_equal_to_zero == 1) { - return (amountADesired, amountBDesired); - } else { - let (local amountBOptimal: Uint256) = _quote(amountADesired, reserveA, reserveB); - let (is_amountBOptimal_less_than_equal_amountBDesired) = uint256_le( - amountBOptimal, amountBDesired - ); - if (is_amountBOptimal_less_than_equal_amountBDesired == 1) { - let (is_amountBOptimal_greater_than_equal_amountBMin) = uint256_le( - amountBMin, amountBOptimal - ); - with_attr error_message("Router::_add_liquidity::insufficient B amount") { - assert is_amountBOptimal_greater_than_equal_amountBMin = 1; - } - return (amountADesired, amountBOptimal); - } else { - let (local amountAOptimal: Uint256) = _quote(amountBDesired, reserveB, reserveA); - let (is_amountAOptimal_less_than_equal_amountADesired) = uint256_le( - amountAOptimal, amountADesired - ); - assert is_amountAOptimal_less_than_equal_amountADesired = 1; - let (is_amountAOptimal_greater_than_equal_amountAMin) = uint256_le( - amountAMin, amountAOptimal - ); - with_attr error_message("Router::_add_liquidity::insufficient A amount") { - assert is_amountAOptimal_greater_than_equal_amountAMin = 1; - } - return (amountAOptimal, amountBDesired); - } - } -} - -func _swap{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - current_index: felt, amounts_len: felt, amounts: Uint256*, path: felt*, _to: felt -) { - alloc_locals; - let (local factory) = _factory.read(); - if (current_index == amounts_len - 1) { - return (); - } - let (local token0, _) = _sort_tokens([path], [path + 1]); - local amount0Out: Uint256; - local amount1Out: Uint256; - if ([path] == token0) { - assert amount0Out = Uint256(0, 0); - assert amount1Out = [amounts + Uint256.SIZE]; - } else { - assert amount0Out = [amounts + Uint256.SIZE]; - assert amount1Out = Uint256(0, 0); - } - local to; - let is_current_index_less_than_len_2 = is_le(current_index, amounts_len - 3); - if (is_current_index_less_than_len_2 == 1) { - let (local pair) = _pair_for(factory, [path + 1], [path + 2]); - assert to = pair; - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - assert to = _to; - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - let (local pair) = _pair_for(factory, [path], [path + 1]); - IPair.swap( - contract_address=pair, amount0Out=amount0Out, amount1Out=amount1Out, to=to, data_len=0 - ); - return _swap(current_index + 1, amounts_len, amounts + Uint256.SIZE, path + 1, _to); -} - -func _sort_tokens{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - tokenA: felt, tokenB: felt -) -> (token0: felt, token1: felt) { - alloc_locals; - local token0; - local token1; - assert_not_equal(tokenA, tokenB); - let is_tokenA_less_than_tokenB = is_le_felt(tokenA, tokenB); - if (is_tokenA_less_than_tokenB == 1) { - assert token0 = tokenA; - assert token1 = tokenB; - } else { - assert token0 = tokenB; - assert token1 = tokenA; - } - - assert_not_zero(token0); - return (token0, token1); -} - -func _pair_for{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - factory: felt, tokenA: felt, tokenB: felt -) -> (pair: felt) { - alloc_locals; - let (local token0, local token1) = _sort_tokens(tokenA, tokenB); - let (local pair) = IFactory.get_pair(contract_address=factory, token0=token0, token1=token1); - return (pair,); -} - -func _get_reserves{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - factory: felt, tokenA: felt, tokenB: felt -) -> (reserveA: Uint256, reserveB: Uint256) { - alloc_locals; - let (local token0, _) = _sort_tokens(tokenA, tokenB); - let (local pair) = _pair_for(factory, tokenA, tokenB); - let (local reserve0: Uint256, local reserve1: Uint256, _) = IPair.get_reserves( - contract_address=pair - ); - if (tokenA == token0) { - return (reserve0, reserve1); - } else { - return (reserve1, reserve0); - } -} - -// -// Internals LIBRARY -// - -func _quote{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountA: Uint256, reserveA: Uint256, reserveB: Uint256 -) -> (amountB: Uint256) { - alloc_locals; - let (is_amountA_greater_than_zero) = uint256_lt(Uint256(0, 0), amountA); - with_attr error_message("Router::_quote::insufficient amount") { - assert is_amountA_greater_than_zero = 1; - } - let (is_reserveA_greater_than_zero) = uint256_lt(Uint256(0, 0), reserveA); - let (is_reserveB_greater_than_zero) = uint256_lt(Uint256(0, 0), reserveB); - with_attr error_message("Router::_quote::insufficient liquidity") { - assert is_reserveA_greater_than_zero = 1; - assert is_reserveB_greater_than_zero = 1; - } - - let (amountA_mul_reserveB: Uint256) = uint256_checked_mul(amountA, reserveB); - let (amountB: Uint256, _) = uint256_unsigned_div_rem(amountA_mul_reserveB, reserveA); - return (amountB,); -} - -func _get_amount_out{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountIn: Uint256, reserveIn: Uint256, reserveOut: Uint256 -) -> (amountOut: Uint256) { - alloc_locals; - let (is_amountIn_greater_than_zero) = uint256_lt(Uint256(0, 0), amountIn); - with_attr error_message("Router::_get_amount_out::insufficient input amount") { - assert is_amountIn_greater_than_zero = 1; - } - let (is_reserveIn_greater_than_zero) = uint256_lt(Uint256(0, 0), reserveIn); - let (is_reserveOut_greater_than_zero) = uint256_lt(Uint256(0, 0), reserveOut); - with_attr error_message("Router::_get_amount_out::insufficient liquidity") { - assert is_reserveIn_greater_than_zero = 1; - assert is_reserveOut_greater_than_zero = 1; - } - - let (amountIn_with_fee: Uint256) = uint256_felt_checked_mul(amountIn, 997); - let (numerator: Uint256) = uint256_checked_mul(amountIn_with_fee, reserveOut); - let (reserveIn_mul_1000: Uint256) = uint256_felt_checked_mul(reserveIn, 1000); - let (local denominator: Uint256) = uint256_checked_add(reserveIn_mul_1000, amountIn_with_fee); - - let (amountOut: Uint256, _) = uint256_unsigned_div_rem(numerator, denominator); - return (amountOut,); -} - -func _get_amount_in{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - amountOut: Uint256, reserveIn: Uint256, reserveOut: Uint256 -) -> (amountIn: Uint256) { - alloc_locals; - let (is_amountOut_greater_than_zero) = uint256_lt(Uint256(0, 0), amountOut); - with_attr error_message("Router::_get_amount_in::insufficient output amount") { - assert is_amountOut_greater_than_zero = 1; - } - let (is_reserveIn_greater_than_zero) = uint256_lt(Uint256(0, 0), reserveIn); - let (is_reserveOut_greater_than_zero) = uint256_lt(Uint256(0, 0), reserveOut); - with_attr error_message("Router::_get_amount_in::insufficient liquidity") { - assert is_reserveIn_greater_than_zero = 1; - assert is_reserveOut_greater_than_zero = 1; - } - - let (amountOut_mul_reserveIn: Uint256) = uint256_checked_mul(amountOut, reserveIn); - let (numerator: Uint256) = uint256_felt_checked_mul(amountOut_mul_reserveIn, 1000); - let (denominator_0: Uint256) = uint256_checked_sub_lt(reserveOut, amountOut); - let (denominator: Uint256) = uint256_felt_checked_mul(denominator_0, 997); - - let (amountIn_0: Uint256, _) = uint256_unsigned_div_rem(numerator, denominator); - let (local amountIn: Uint256) = uint256_checked_add(amountIn_0, Uint256(1, 0)); - - return (amountIn,); -} - -func _get_amounts_out{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - factory: felt, amountIn: Uint256, path_len: felt, path: felt* -) -> (amounts: Uint256*) { - alloc_locals; - with_attr error_message("Router::_get_amounts_out::invalid path") { - assert_le(2, path_len); - } - let (local amounts_start: Uint256*) = alloc(); - let (amounts_end: Uint256*) = _build_amounts_out( - factory, amountIn, 0, path_len, path, amounts_start - ); - - return (amounts_start,); -} - -func _build_amounts_out{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - factory: felt, - amountIn: Uint256, - current_index: felt, - path_len: felt, - path: felt*, - amounts: Uint256*, -) -> (amounts: Uint256*) { - alloc_locals; - if (current_index == path_len) { - return (amounts,); - } - - if (current_index == 0) { - assert [amounts] = amountIn; - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - let (local reserveIn: Uint256, local reserveOut: Uint256) = _get_reserves( - factory, [path - 1], [path] - ); - let (local amountOut: Uint256) = _get_amount_out( - [amounts - Uint256.SIZE], reserveIn, reserveOut - ); - assert [amounts] = amountOut; - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - return _build_amounts_out( - factory, amountIn, current_index + 1, path_len, path + 1, amounts + Uint256.SIZE - ); -} - -func _get_amounts_in{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - factory: felt, amountOut: Uint256, path_len: felt, path: felt* -) -> (amounts: Uint256*) { - alloc_locals; - with_attr error_message("Router::_get_amounts_in::invalid path") { - assert_le(2, path_len); - } - let (local amounts_start: Uint256*) = alloc(); - let (amounts_start_temp: Uint256*) = _build_amounts_in( - factory, - amountOut, - path_len - 1, - path_len, - path + (path_len - 1), - amounts_start + (path_len - 1) * Uint256.SIZE, - ); - - return (amounts_start,); -} - -func _build_amounts_in{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - factory: felt, - amountOut: Uint256, - current_index: felt, - path_len: felt, - path: felt*, - amounts: Uint256*, -) -> (amounts: Uint256*) { - alloc_locals; - - if (current_index == path_len - 1) { - assert [amounts] = amountOut; - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } else { - let (local reserveIn: Uint256, local reserveOut: Uint256) = _get_reserves( - factory, [path], [path + 1] - ); - let (local amountIn: Uint256) = _get_amount_in( - [amounts + Uint256.SIZE], reserveIn, reserveOut - ); - assert [amounts] = amountIn; - tempvar syscall_ptr = syscall_ptr; - tempvar pedersen_ptr = pedersen_ptr; - tempvar range_check_ptr = range_check_ptr; - } - - if (current_index == 0) { - return (amounts,); - } - - return _build_amounts_in( - factory, amountOut, current_index - 1, path_len, path - 1, amounts - Uint256.SIZE - ); -} diff --git a/contracts/utils/Multicall.cairo b/contracts/utils/Multicall.cairo deleted file mode 100644 index 124c802..0000000 --- a/contracts/utils/Multicall.cairo +++ /dev/null @@ -1,59 +0,0 @@ -%lang starknet - -from starkware.starknet.common.syscalls import call_contract, get_block_number, get_block_timestamp -from starkware.cairo.common.alloc import alloc -from starkware.cairo.common.memcpy import memcpy - -// //////////////////////////////////////////////////////////////////////// -// The Multicall contract can call an array of view methods on different -// contracts and return the aggregate response as an array. -// E.g. -// Input: [to_1, selector_1, data_1_len, data_1, ..., to_N, selector_N, data_N_len, data_N] -// Output: [result_1 + .... + result_N] -// //////////////////////////////////////////////////////////////////////// - -@view -func aggregate{syscall_ptr: felt*, range_check_ptr}(calls_len: felt, calls: felt*) -> ( - block_number: felt, result_len: felt, result: felt* -) { - alloc_locals; - - let (result: felt*) = alloc(); - let (result_len) = call_loop(calls_len=calls_len, calls=calls, result=result); - let (block_number) = get_block_number(); - - return (block_number=block_number, result_len=result_len, result=result); -} - -func call_loop{syscall_ptr: felt*, range_check_ptr}( - calls_len: felt, calls: felt*, result: felt* -) -> (result_len: felt) { - if (calls_len == 0) { - return (0,); - } - alloc_locals; - - let response = call_contract( - contract_address=[calls], - function_selector=[calls + 1], - calldata_size=[calls + 2], - calldata=&[calls + 3], - ); - - memcpy(result, response.retdata, response.retdata_size); - - let (len) = call_loop( - calls_len=calls_len - (3 + [calls + 2]), - calls=calls + (3 + [calls + 2]), - result=result + response.retdata_size, - ); - return (len + response.retdata_size,); -} - -@view -func get_current_block_timestamp{syscall_ptr : felt*, range_check_ptr}() -> ( - block_timestamp : felt) { - let (block_timestamp) = get_block_timestamp(); - - return (block_timestamp=block_timestamp); -} \ No newline at end of file diff --git a/contracts/utils/math.cairo b/contracts/utils/math.cairo deleted file mode 100644 index c439aa8..0000000 --- a/contracts/utils/math.cairo +++ /dev/null @@ -1,84 +0,0 @@ -%lang starknet - -from starkware.cairo.common.cairo_builtins import HashBuiltin, SignatureBuiltin -from starkware.cairo.common.math import assert_not_zero -from starkware.cairo.common.uint256 import ( - Uint256, - uint256_check, - uint256_eq, - uint256_add, - uint256_sub, - uint256_mul, - uint256_le, - uint256_lt, -) - -// Adds two integers. -// Reverts if the sum overflows. -func uint256_checked_add{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - a: Uint256, b: Uint256 -) -> (c: Uint256) { - uint256_check(a); - uint256_check(b); - let (c: Uint256, is_overflow) = uint256_add(a, b); - assert (is_overflow) = 0; - return (c,); -} - -// Subtracts two integers. -// Reverts if minuend (`b`) is greater than subtrahend (`a`). -func uint256_checked_sub_le{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - a: Uint256, b: Uint256 -) -> (c: Uint256) { - alloc_locals; - uint256_check(a); - uint256_check(b); - let (is_le) = uint256_le(b, a); - assert_not_zero(is_le); - let (c: Uint256) = uint256_sub(a, b); - return (c,); -} - -// Subtracts two integers. -// Reverts if minuend (`b`) is greater than or equal to subtrahend (`a`). -func uint256_checked_sub_lt{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - a: Uint256, b: Uint256 -) -> (c: Uint256) { - alloc_locals; - uint256_check(a); - uint256_check(b); - - let (is_lt) = uint256_lt(b, a); - assert_not_zero(is_lt); - let (c: Uint256) = uint256_sub(a, b); - return (c,); -} - -// Multiplies two integers. -// Reverts if overflows -func uint256_checked_mul{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - a: Uint256, b: Uint256 -) -> (c: Uint256) { - alloc_locals; - uint256_check(a); - uint256_check(b); - - let (c: Uint256, mul_high: Uint256) = uint256_mul(a, b); - let (is_equal_to_zero) = uint256_eq(mul_high, Uint256(0, 0)); - assert is_equal_to_zero = 1; - return (c,); -} - -// Multiplies two integers. -// Reverts if overflows -func uint256_felt_checked_mul{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - a: Uint256, b: felt -) -> (c: Uint256) { - alloc_locals; - uint256_check(a); - - let (c: Uint256, mul_high: Uint256) = uint256_mul(a, Uint256(b, 0)); - let (is_mul_high_equal_to_zero) = uint256_eq(mul_high, Uint256(0, 0)); - assert is_mul_high_equal_to_zero = 1; - return (c,); -} diff --git a/lib/cairo_contracts b/lib/cairo_contracts deleted file mode 160000 index b764660..0000000 --- a/lib/cairo_contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b764660da224ee757b831ea62bf7a4c63ee898fb diff --git a/protostar.toml b/protostar.toml deleted file mode 100644 index e56067f..0000000 --- a/protostar.toml +++ /dev/null @@ -1,14 +0,0 @@ -[project] -protostar-version = "0.9.2" -lib-path = "lib" -cairo-path = ["./lib/cairo_contracts/src"] - -[contracts] -FactoryProxy = ["contracts/FactoryProxy.cairo"] -Factory = ["contracts/Factory.cairo"] -RouterProxy = ["contracts/RouterProxy.cairo"] -Router = ["contracts/Router.cairo"] -PairProxy = ["contracts/PairProxy.cairo"] -Pair = ["contracts/Pair.cairo"] -Multicall = ["contracts/utils/Multicall.cairo"] - diff --git a/public/gas_comp_1.png b/public/gas_comp_1.png new file mode 100644 index 0000000000000000000000000000000000000000..17a0639a6016ad9f169eb99ee55f73abde603199 GIT binary patch literal 9780 zcmch72T)U8yKYd5h{z{=f^^XUB25tK1Vvh;iwXz<=^%t&69NbVVx)$uK%|TG7Nm=m z&?Hi%h29~w&>@r?{Lg>xng5>i&)k_iH1&DFASxmt3~HL`^Y!noQix+>!NPt z4gg$jKL1f*oxfNC0K6Sw z#z7Mm;$Z20ES4mdeI=y@k6kNn1fAB@3s^61d*MBJ5MFm#3{VM2aTo4aJ7*P& z{(au~=B4$C@W^oWCypt=8X*6%N%4mUCK*~N%C4SEBWGzW%Nupiz65xe&*V0kGky6B zr}vIyiB(xyr_Sm(f%}<|ODV6cGYx!fiz`e}b7e z8VgSVgO5%b!5*@TiS(z+Xr>}rirV|(7%$gt-}R7i9@KArCXj(mI`(d=9q0td;E&pc<9u!P6K@i2LxFn(0 z4QSBQp54v_)KZ9*=50Q^mr6M(rWV-Hkj5%yUn6^s=Bof3nnB?WtM2Th~r8{Q{QKjfbBEf3_!`t7t;pDAU=7S}BX47{aFX?dK^!^A zZ$dj$QWFLN{BbN(Yx-0239(Ci8#w`_15&`*#;kLSVn9CbaIq2OpD}xOXn-)uBkc!7 z#aYid;L}o-3I4uU)t1~a2y|0u`5WBA8=PtI1N6Ls7a!jQB0!V7u%R~K_VLI->tssY znF(@4$A5xk`xugIO9)pa#)>yPIJt^l(4I*LOX#?q5z)fS*5Ny%lV=A9%Oqz!Q4w@X z#2wY48_qUPQ)lC^M=NVY{+f3rfr9C6FQR{5MAuUbf-+@!T_b5%zkCSW*Mt3DB0asp zGF0M#)qEM|+~ZUOOTT9v)nhCEVF+V!C8%6lDExGHvN7Ozt+8=qQh+CCD@ilAMS4|x z2)r9<@Iow6+4f``T{d7)wkv$gTDJ=N_6SeeGyiNA1uwvJZ7eGR5d)kFm{l&w$Fd#N za7Fh*ZiV3RRC?C{_L02QUiue{whV;8n=M* z*(N=R>k4QATBB%Q{NFqH+0)UbcBG?cpYLs1iKHsmOOOjAHn^b-$JYe~rTDdpSfw>K zgrrfi2#Tr*M+5Cwqd3^(`WwT&<%Di+xl}>`1;h6}!Ap@)41de{#avv2PL9 zhV$Fc!h{FnrtWVXWcuaIj+m|;hv0biNTE?~aQ~d=Jr-jT1+Tt2&b)5Pv)1>`>=6PY(lkXBgvnne=d7M~dau z0-L{_$37`y$)4Vb6B*d3Z4>kB;1Q-#ZX|7`f-U`xi1I_==vKt0>#tzQ&7_9N zc~d{_$NuJG*XLVrIQr!rx`h2UE5t_bi!jfz(SN_j;Vq}e$KM#cAj}z4vMv9dnI=i< zk$ZQ?N9t0J=;frrHXlYPgPVS$oKbH!pZ2OrTT&6U{&|{hFij#WOH7rp>JkZ`FjW`h zk5~F!37Js|H}Ixlr2&1S0Wn<}=30Z(ZE4_9HJ5S_%sYSA_ArU1W7KxD*G9#{UTr@! z8ExR<^o^BYAiNCqS{DKuV^VixWZfh_m8IqHe%(klHKD?@vQD`i6hbNMOyKLP47JrASQR~<%BGZsWwbcO-#edg=ebreaIv;#zrFZZ; zR?3Z}#@oAHQ-L+E{@5J@D|Wgln&>00ZBe)feBHh2G)&a(Fm9j@uNo5DqJNd zhv-Q`xi+g47D`fn4cD}|6WsD}1-N%LJ>L*-f1AFKU-+g*C=N{i`|?2u?d>CsP4=WQ zdmYh*|GD_sibYX>^RDulRaSAcH0v1^Mc{L;IAukY^ps00Z4t$cC)a@^*TJf6OT*W} z=fbpCurRrjK%aVfD!&)e$`@Vkz8mtLDkpz6c$loLy=B)ymfn?WoBd100KizsQ%30N zTerKrI9WrX@A7Rr!O6G%*X#V;$l$yE1OyKi5iD5oeT(2fEU3wTRGWH{)`NRR@PIZ2 z>%mC>hmF1)(EPi!Fo=eZ%s_#*x4DEU1*e*nTtqa$9;ElR2eyLi+?jKYXHKV>+Ct;g zidMm{x;7N4cxR>YwucP90r#qTqZM9Ug$*6Mi)`Ns!tiahq3{UOgUX znOhv^XJ3(3Fcw7@$3v&CdrxU{eN^72G&oh+|KBWgw9&9Ezs5}ux26L2#{O&dvADk4 zxaP)P1(|R@Uoz8#pL{$177CHG>66tA06Xu7{DWZk8}rrM|dqbW4u4q+&LtiJAeD1G+j>UCN+Re>cIdZ?Tjb-DF$SZHchqv9dYozj|{h zY)8;d7{bUiEimu4p1*YDST5MXAZ}$$eX{qhQBrm8ZpZWo?L8=mQsP4=>~`gyPi%q? zt9vz7S-Q2MXg_Zw@H;6(m}@JgVD){C*UpN?PR)JUn!pJw+r+}9TWc|f;_%&~SHWSt`iiDA zb9hpO){zPBAbBHgK=RrqtKR}=Kpf6P9-p7uxLFOnixJ5%l|`4&lxuFrBnK`pt`qbt$p;F1mn!{X1d>itXx_dRx33f(aDTwgW&cq?j!y{hoktrs*N zNp>d8e{8M>tbLOT~a@=kGM2C zwalT?E{}E`@fuWHY43IHgcS|uH+Xv;{o{`Wg){Fzq)7>*ZbtkX)vhoa zM1Pjq`yPwZieqow#u*zW8CFq<(s#dPq)Ip|=9N z{5Q?MlRaYEx)yL`ns)oMafL%vM@hR)edY(oVxq~x6_8-c{QZc?HTk@PST&u*IVOJA zdaSLW=dNs@8gz06h+f-LC8!K|-aNoz2)RvnZ6x!7rxX4;o`(2~a9hedqp5;2mVt;C42 zQ$6rboyZ#TMG>ES)R_OI5zaU*!Op-8}FGbPYxFBz#W36xYo0t zgc?J-Jz?L{me3zo_wQ-THr@nm0{&m{%BCnIU<~{Ji&q#Y+G2+8*R`!XrA_M)U)?dB zhB1VS@b<0=uFs$gzXg1VJv*&E#IIwV4*Qvlg|^vr)*Jis&;C_p5I`EA1W+Aav!pvk zkKr?tyKRwn&GAw}rh*ITDI8s6v+*e{V<|~wIGbbgGsu9jdV@VRQgh~Y@f|-q#-B_k zhb-fberL0@xf_j8ETNHnmPIw z9A3GEEErZWKS?8_{PcJwf1t29rL$#>jNZ?} zNy`LT)z zQC7%X3{pfW$mzmgo6(ukxTP3k1g#^4!Rsz)QuNO?j?8QL-zQ4e)>onxvB$RU@+V4m z|E=~3WT!unUQPsbqaX?Tjv8+`dK~bVcH?Hj4oSn=N zL;u1~w_^&1Yu>?@{d(Tx#GBM!Gg@X=@q6@uRu47#^qO95)uSmX>zjRp2GWKYoVb&H z_Ofws?ugD0h9@jLR+7X9wy5!!EZzm`W@VV#Q>(?EmN*Bvlg?^4%(Attc+^C-?!gP;xK-Yh%&2e?w^QUY8s}(tX#XO#b>Ro=wlXRAYAM z;ag2+#{`jy7P%kqMO-hG;Rmhv2!(Dr^jtZbucdYaVm|RP@ao+mX0!;FzZpuVI?w4* z2bli-;WoJUM0|}8$cSjfS8<<}?ls&hw*q0M-E#^Ke;Q}LuNOR6)X#7mku@_cRo3Qf zc%p_Ctmg=Tm3Dd3qUl-RfCFNk?!yM}%nChRBhH@QNGfywI4sFqo{f-Yp1qG@>AI+j zmI7J0g^v%)UrkEE6a~m97WUBEx(x@1$WQ;Qa+}*wJhKP398Nzz)Xgxqp(>3W5gP?c zh&6q2bH4{B(;ijFdy#5WkO%=@1KWKegQsSA&bQ1sI?mUHhXz}`(Hi(zG|j7ezI4#t-4}e;qQ86UJ&&+2T65M znj!ye%>Vc5JqrV4XLrs@ZvJENsbQ3&Ag1*O?OHkVZvUR)`luA$VesLDPD^+75D>;P zW3BZ*D%IlaZUCZy-<|I?t;4m--O6oC(0-!t8m$~o-+C=@`yLHaan3LMS+XY$@+8|-+bR7>19)?^Nj84`a+;^P@9gNn)|*1X-g*!VRLHV`Upb`YSv z07IvA5y2JTXk@44C2p(-4|-+%EIXzA$;v&T=XBho=xl8sJ!s&-bLS)Y{$M{1Ry{f^ z(3?8(zmsMmqz1cNH%BV;Z+IfT*2Gx(q6@s^^kyEUoikHNxnE9iX)6Dn^!mELU^PH= ziOMs#DBO#c70;}w(T3k(5Pm*lP{R(4#xtaX#EGj&v_>6}n;v7n`^dST6A95_ae6E( zxrQS#N>)0RVBdQO6b#(i%Rb6P+*j%2B`_a@q91B}YUejmJVj{QuwN|hoCa5T5~z7w zh|`ZTa$0l15i%sYV-yXay4d(cFFfSObj%d84%kQkgJ&Cx>89zKiydDi?)eBBpk$tI z{Dw}=GwRsf?V?kAA2o({&1m1fL)%HTSjOQ)$aYCOmIqXRZzbpFMbJ=atC;Y+|H8FT zZsl55CR$|6Ok^fVtrO@UEZ_d%zm(u>xToS<=lnQ8coa?Wbubb2@`SvsW#?o$9x+35 ze%PBC{)H!u^bjQWs<%gk$`sPZ-Wzr}T59%}_cSRV7Uc_~R(p8LimP=;FGuC1Bc-O^+y+wm!v|3eX83G9!iEi04b^hOt# z)*fjFp+f`oU?2@}MKx6bS@B)?hvL&n4m&pCtA_on9HwpldYoGH&n4TIOJ7` z=;o{R)BigK_z^cV*2u5HtI+V7W%b$Yj|s{X#^P@dG#aF+kS)=gfWuQPR%bE*A2}4TueE4@W;ZM-cr!eKBfV>6 z4?)e|LwydHaQYX1s-90~I9TTOSQ$1lcTA@?aqi``57lLhPL9eG-yim$u)0U*NBTZ_ z={epZa=7j=tCa43O)uhioE2OKuhFU>)*QQZP&NKPnvaLS4f2f5IO97?&edU%Qtf-X z|KdFwau)xw9wlhCx*Yk6{}1O;@?V_Cm&e~gS!OW~%dublEu0q{-lyT>ROH%Yp_h-= zTmzsUb1E3w<+^K}Y)>yt>_zl4jEggLHB|a6ANHHg7WgbL-&z4HupBQFM&69-lS~d| zqb&!^AOa4KV7D;RSeT>J;*E&mqIH(L`Pj_ostxN$dpmULo;~1<$4V)nepfFIwc%39 zSnJ|~zb-mSOwDGQUAU(MJ2xk(+@y=i@##~WpY}v0Zm&oRK{{V;Q6nLk3}l6xMaSyz z+8}za1kDqlaFp|glw`V#!mO>oiWy${J2?UIKGp;E6CI1aGL_3L2)9LAvn*(DsKzCrT8zt;$}Nd$1Z&%D?cS-IInA#7Sf_ z>!)VW=d1j5)Yz`Y^fvZR99bI0k9;R|UpP0o!G8E|U z87mcH1)%5@RPfJo*St!?PP009p7dTsfh*UGvtHs8Q7qbY)eZ8)_Oa@#K6DoTL)wE* zX*yl_@i#^4oYhZ!7%f@{dvs)LDI*>^PmzgZ=N7Ae3u1XEmW&m2$c#6R)cHxPGgCrC z=lkQ6^L+bcz-hnwyY1$%t#sw8?#mXO<(3s-JBuK_`Gvrkk~eVW=z^tK!u)JmLvFr- zb9^~;Q`b6HBz+tj`)yqE4)d^b;WAr~-pupVoyC7@KHCw$)BA+=qa*j73S5xz0ChQk zg(5T4Yj_XaJvLi&NVGW2?qp`C*^pb{pK(>6d>t`wDZ)%F$i7z8K0qCdaR$h}a72nR zjvB;*d%hsbqZO0PGUX-1o-J9*@r_PMPnC5MZRE3j97s9w3QPgGxo>zGahbz-3FqZ^ zZ&MfZ%Dle&CIzdo`;e{CP{}Xs)v7L5gEU1rvLtl1m=bUEfD#I~0a46aQzvvFe|SWHC;B(Nm!)l{4#%jfBQ_pyqB(me(hwk4n*YBtYRE)dReK z+y^1Kl{}c|q|iuOBh|@*{f=r5v@#O@`C`xG;AxN@lz&u+pz|w*_=xjA3{+}+L?!cu zFUj(j3J8}El;F!gwl4P8f;9t|;8GsRv6zWn$IU5m@V@c4DkL9etd?AeUX|St7s@=g z9j3<0YdTv^wrO5SciFOW;(db{N4w}Uay7QX%XDoP{?$TVfeSCYlhXUu_|gzW_wYok zu2<{{Ksu0`!%8{mQT;-&z#K8@+s_j?Sj;$|yFJnsz=B2U?^>)1KVL|ty%0@T$(dfV* zo3%mYt(0M1nGs}kw$MatLA~Cf>P@1Cd~%+U4x<3bEugJ~e<%}p2aC*BbUt^4-*SZ= zbpQDBNs{5fgq`_&Mf>RgXtl;Z{PG3NZNBrks=K*Nw9_H>GY^S&96iA{goHp62iH}x zQ~`TK7Brdl(vmxSFwC}W1Y+G!66qMl-+k+axUq=??1fB{dYgkXA(SeCRmQQ@xWE3BY z9kHQze!cg3SP2VT;AAMOTX1ljxoFm4T4UBTbqIo|t>xG49msio;B0b;G$wCuVAakb zBnG>$BR0%6?`j9QJ*dBO`~+FqYRQ~B#t@zt@<3!@+gWFmDd#VD9i5B~BC^duJ!2y( zyEsvq`@!j8+wgs+k^7_mIS4=?uK9=dP9{rroli=#GFGyAa?#YT6ywtp^iaIhf{o;3 zxbWyqWalz?2jZ{C3eG@d9gbEjeQR**Hv-f%cHIDg8`E#V6~o}XPW{x>Z*b_k6S6fc zt^>01tVrkrS30Cmr*5b>)G-rN4f5;Va%8bKRVXef(;KXHf24Z~w%@;|h*R<{^=sMF z_`R=swoG^Y{P>n<$7whgX)2RrQN70U)wcDsK|C{IO@$lE1S$a$PEV>gqpEWf5^ zUbo3#RuI=Qh`Nv5%jyeeB{{KneWgMk6|d&J8tW&AFWkWWL8s*?O%&#m0!J|EE z`OtCPyno(|r-Rh8KNXq^(d>I62k)OIJt$c2xN;Oy8Bm~D-w=VW1j+_*JCO4?KQ5GV zu1bW?6IZ2rD7t?R6Tb~EjP8m6juup@KJe{q?aE4xtFQ6gE+Xs)^vJjyA%smJtqG*YP(r6elK|rOS34pjDQ?L~9L&pi3t` zYh56qf-!#oC<|GUI>NDVr|)?j0z(`+DmTl)Tam1F;&5BJHro?vOiat9aRhne340Vc zqxr2Wsu8H=Mw*G9Mm|p298mdQSOxgB`Fn#;HP&LZiidC2&qRCdbuwSnH``v~Fe$3z z`KZ`*!<5IdlBVz`%QkXiLuP_jF3q@cSZpZ)(rx?Jn&dCk-qqj$og|<{-2CoCG>wZF z$_Iu9tMx5~(nseGLj3%`DyBAgTHZo5*5b&+Ia`uyhrE}pcY;vTptG`VvLpO_%w+o@ z{Qg(UtS0rRcGuKTr!aWr3wY7X93dgOd}O3rv;d#!$y(TX#aDRE%Jzacj_sODYNp|#VS#w0T@pZb@o?+_~7U0w>*Bx{2$BTM+N zrjN#1YjvRg$1+j@RZMxv!_`2bEQj;X{;}I<#piVm*H7tA^YBA!7Xg7XWDqQ$O}S?; zfQ5V75?E2N#17y`!VmsKd7_#V_Xg?5z@T6a%sN$aJ0p5Umt=x|>OGZVlj~HRFw|t& zl6$P=(WSnA{WxRz=8mY_R`sD4Q^tg8m9T@goptJ#!5(YOF|%leL&lgP8BmsMC97Jt zSF@tN1>qQ14z`b%<5_q2%P>xZyV3zKhY_`A?SJ7p&R(=qnvc)`0JB2CUb7hK>8+5X z-?U)tFRC9yGd2E1i_PH!zZzUc>XJ*rHsLo@(*ZdS97F69k4T*IB7{?D>Otau7gOw# z0WrC(pUvx2t`|(_!Yp|+N5d8MjIeLIjPxJ@9rX;ecO~T-9m_p*e80%h+wk{);_l2) b)ESLb?d8eq6k9jXQ5meFtz4w^D(HU!djpv9 literal 0 HcmV?d00001 diff --git a/src/contracts/FactoryC1.cairo b/src/contracts/FactoryC1.cairo new file mode 100644 index 0000000..c71019a --- /dev/null +++ b/src/contracts/FactoryC1.cairo @@ -0,0 +1,257 @@ +// @title JediSwap Factory Cairo 1.0 +// @author Mesh Finance +// @license MIT +// @notice Factory to create and register new pairs + +use starknet::ContractAddress; +use starknet::ClassHash; + +#[starknet::interface] +trait IFactoryC1 { + // view functions + fn get_pair(self: @TContractState, token0: ContractAddress, token1: ContractAddress) -> ContractAddress; + fn get_all_pairs(self: @TContractState) -> (u32, Array::); + fn get_num_of_pairs(self: @TContractState) -> u32; + fn get_fee_to(self: @TContractState) -> ContractAddress; + fn get_fee_to_setter(self: @TContractState) -> ContractAddress; + fn get_pair_contract_class_hash(self: @TContractState) -> ClassHash; + // external functions + fn create_pair(ref self: TContractState, tokenA: ContractAddress, tokenB: ContractAddress) -> ContractAddress; + fn set_fee_to(ref self: TContractState, new_fee_to: ContractAddress); + fn set_fee_to_setter(ref self: TContractState, new_fee_to_setter: ContractAddress); + // fn replace_implementation_class(ref self: TContractState, new_implementation_class: ClassHash); + fn replace_pair_contract_hash(ref self: TContractState, new_pair_contract_class: ClassHash); +} + +#[starknet::contract] +mod FactoryC1 { + use array::ArrayTrait; + use zeroable::Zeroable; + use starknet::{ContractAddress, ClassHash, SyscallResult, SyscallResultTrait, get_caller_address, contract_address_const, contract_address_to_felt252}; + use starknet::class_hash::class_hash_to_felt252; + use integer::u256_from_felt252; + use starknet::syscalls::{deploy_syscall, replace_class_syscall}; + use poseidon::poseidon_hash_span; + + + // + // Storage + // + #[storage] + struct Storage { + _fee_to: ContractAddress, // @dev Address of fee recipient + _fee_to_setter: ContractAddress, // @dev Address allowed to change feeTo. + _all_pairs: LegacyMap::, // @dev Array of all pairs + _pair: LegacyMap::<(ContractAddress, ContractAddress), + ContractAddress>, // @dev Pair address for pair of `token0` and `token1` + _num_of_pairs: u32, // @dev Total pairs + _pair_contract_class_hash: ClassHash, + Proxy_admin: ContractAddress, // @dev Admin contract address, to be used till we finalize Cairo upgrades. + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + PairCreated: PairCreated, + } + + // @dev Emitted each time a pair is created via create_pair + // token0 is guaranteed to be strictly less than token1 by sort order. + #[derive(Drop, starknet::Event)] + struct PairCreated { + token0: ContractAddress, + token1: ContractAddress, + pair: ContractAddress, + total_pairs: u32 + } + + // + // Constructor + // + + // @notice Contract constructor + // @param fee_to_setter Fee Recipient Setter + #[constructor] + fn constructor(ref self: ContractState, pair_contract_class_hash: ClassHash, fee_to_setter: ContractAddress) { + assert(!fee_to_setter.is_zero(), 'can not be zero'); + + assert(!pair_contract_class_hash.is_zero(), 'can not be zero'); + + self._fee_to_setter.write(fee_to_setter); + self._pair_contract_class_hash.write(pair_contract_class_hash); + self._num_of_pairs.write(0); + } + + #[abi(embed_v0)] + impl FactoryC1 of super::IFactoryC1 { + // + // Getters + // + + // @notice Get pair address for the pair of `token0` and `token1` + // @param token0 Address of token0 + // @param token1 Address of token1 + // @return pair Address of the pair + fn get_pair(self: @ContractState, token0: ContractAddress, token1: ContractAddress) -> ContractAddress { + let pair_0_1 = self._pair.read((token0, token1)); + if (pair_0_1 == contract_address_const::<0>()) { + let pair_1_0 = self._pair.read((token1, token0)); + return pair_1_0; + } else { + return pair_0_1; + } + } + + // @notice Get all the pairs registered + // @return all_pairs_len Length of `all_pairs` array + // @return all_pairs Array of addresses of the registered pairs + fn get_all_pairs(self: @ContractState) -> (u32, Array::) { //Array:: + let mut all_pairs_array = ArrayTrait::::new(); + let num_pairs = self._num_of_pairs.read(); + let mut current_index = 0; + loop { + if current_index == num_pairs { + break true; + } + all_pairs_array.append(self._all_pairs.read(current_index)); + current_index += 1; + }; + (num_pairs, all_pairs_array) + } + + // @notice Get the number of pairs + // @return num_of_pairs + fn get_num_of_pairs(self: @ContractState) -> u32 { + self._num_of_pairs.read() + } + + // @notice Get fee recipient address + // @return address + fn get_fee_to(self: @ContractState) -> ContractAddress { + self._fee_to.read() + } + + // @notice Get the address allowed to change fee_to. + // @return address + fn get_fee_to_setter(self: @ContractState) -> ContractAddress { + self._fee_to_setter.read() + } + + // @notice Get the class hash of the Pair contract which is deployed for each pair. + // @return class_hash + fn get_pair_contract_class_hash(self: @ContractState) -> ClassHash { + self._pair_contract_class_hash.read() + } + + // + // Setters + // + + // @notice Create pair of `tokenA` and `tokenB` with deterministic address using deploy + // @dev tokens are sorted before creating pair. + // @param tokenA Address of tokenA + // @param tokenB Address of tokenB + // @return pair Address of the created pair + fn create_pair(ref self: ContractState, tokenA: ContractAddress, tokenB: ContractAddress) -> ContractAddress { + assert(!tokenA.is_zero() & !tokenB.is_zero(), 'must be non zero'); + + assert(tokenA != tokenB, 'must be different'); + + let existing_pair = FactoryC1::get_pair(@self, tokenA, tokenB); + assert(existing_pair.is_zero(), 'pair already exists'); + + let pair_contract_class_hash = self._pair_contract_class_hash.read(); + let (token0, token1) = _sort_tokens(tokenA, tokenB); + let mut hash_data: Array = ArrayTrait::new(); + Serde::serialize(@token0, ref hash_data); + Serde::serialize(@token1, ref hash_data); + let salt = poseidon_hash_span(hash_data.span()); + + let mut constructor_calldata = Default::default(); + Serde::serialize(@token0, ref constructor_calldata); + Serde::serialize(@token1, ref constructor_calldata); + + let syscall_result = deploy_syscall( + pair_contract_class_hash, salt, constructor_calldata.span(), false + ); + let (pair, _) = syscall_result.unwrap_syscall(); + + self._pair.write((token0, token1), pair); + let num_pairs = self._num_of_pairs.read(); + self._all_pairs.write(num_pairs, pair); + self._num_of_pairs.write(num_pairs + 1); + self.emit(PairCreated {token0: token0, token1: token1, pair: pair, total_pairs: num_pairs + 1}); + + pair + } + + // @notice Change fee recipient to `new_fee_to` + // @dev Only fee_to_setter can change + // @param fee_to Address of new fee recipient + fn set_fee_to(ref self: ContractState, new_fee_to: ContractAddress) { + let sender = get_caller_address(); + let fee_to_setter = FactoryC1::get_fee_to_setter(@self); + assert(sender == fee_to_setter, 'must be fee to setter'); + self._fee_to.write(new_fee_to); + } + + // @notice Change fee setter to `fee_to_setter` + // @dev Only fee_to_setter can change + // @param fee_to_setter Address of new fee setter + fn set_fee_to_setter(ref self: ContractState, new_fee_to_setter: ContractAddress) { + let sender = get_caller_address(); + let fee_to_setter = FactoryC1::get_fee_to_setter(@self); + assert(sender == fee_to_setter, 'must be fee to setter'); + assert(!new_fee_to_setter.is_zero(), 'must be non zero'); + self._fee_to_setter.write(new_fee_to_setter); + } + + // @notice This is used upgrade (Will push a upgrade without this to finalize) + // @dev Only Proxy_admin can call + // @param new_implementation_class New implementation hash + // fn replace_implementation_class(ref self: ContractState, new_implementation_class: ClassHash) { + // let sender = get_caller_address(); + // let proxy_admin = self.Proxy_admin.read(); + // assert(sender == proxy_admin, 'must be admin'); + // assert(!new_implementation_class.is_zero(), 'must be non zero'); + // replace_class_syscall(new_implementation_class); + // } + + // @notice This replaces _pair_contract_class_hash used to deploy new pairs + // @dev Only Proxy_admin can call + // @param new_pair_contract_class New _pair_contract_class_hash + fn replace_pair_contract_hash(ref self: ContractState, new_pair_contract_class: ClassHash) { + let sender = get_caller_address(); + let proxy_admin = self.Proxy_admin.read(); + assert(sender == proxy_admin, 'must be admin'); + assert(!new_pair_contract_class.is_zero(), 'must be non zero'); + self._pair_contract_class_hash.write(new_pair_contract_class); + } + } + + // + // Internals LIBRARY + // + + fn _sort_tokens( + tokenA: ContractAddress, tokenB: ContractAddress + ) -> (ContractAddress, ContractAddress) { + assert(tokenA != tokenB, 'must not be identical'); + let mut token0 = contract_address_const::<0>(); + let mut token1 = contract_address_const::<0>(); + if u256_from_felt252( + contract_address_to_felt252(tokenA) + ) < u256_from_felt252( + contract_address_to_felt252(tokenB) + ) { // TODO token comparison directly + token0 = tokenA; + token1 = tokenB; + } else { + token0 = tokenB; + token1 = tokenA; + } + + assert(!token0.is_zero(), 'must be non zero'); + (token0, token1) + } +} \ No newline at end of file diff --git a/src/contracts/PairC1.cairo b/src/contracts/PairC1.cairo new file mode 100644 index 0000000..d5f42cd --- /dev/null +++ b/src/contracts/PairC1.cairo @@ -0,0 +1,690 @@ +// @title JediSwap Pair Cairo 1.0 +// @author Mesh Finance +// @license MIT +// @notice Low level pair contract +// @dev Based on the Uniswap V2 pair +// https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol +// Also an ERC20 token + +use starknet::ContractAddress; +use starknet::ClassHash; +// +// External Interfaces +// +#[starknet::interface] +trait IERC20 { + fn balance_of(self: @T, account: ContractAddress) -> u256; + fn balanceOf(self: @T, account: ContractAddress) -> u256; // TODO Remove after regenesis + fn transfer(ref self: T, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from(ref self: T, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn transferFrom(ref self: T, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; // TODO Remove after regenesis +} + +#[starknet::interface] +trait IFactory { + fn get_fee_to(self: @T) -> ContractAddress; +} + +#[starknet::interface] +trait IJediSwapCallee { + fn jediswap_call(ref self: T, sender: ContractAddress, amount0Out: u256, amount1Out: u256, data: Array:: + ); +} + +// +// Contract Interface +// +#[starknet::interface] +trait IPairC1 { + // view functions + fn name(self: @TContractState) -> felt252; + fn symbol(self: @TContractState) -> felt252; + fn total_supply(self: @TContractState) -> u256; + fn totalSupply(self: @TContractState) -> u256; //TODO Remove after regenesis? + fn decimals(self: @TContractState) -> u8; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn balanceOf(self: @TContractState, account: ContractAddress) -> u256; //TODO Remove after regenesis? + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn token0(self: @TContractState) -> ContractAddress; + fn token1(self: @TContractState) -> ContractAddress; + fn get_reserves(self: @TContractState) -> (u256, u256, u64); + fn price_0_cumulative_last(self: @TContractState) -> u256; + fn price_1_cumulative_last(self: @TContractState) -> u256; + fn klast(self: @TContractState) -> u256; + // external functions + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from(ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) -> bool; + fn transferFrom(ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) -> bool; //TODO Remove after regenesis? + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; + fn increase_allowance(ref self: TContractState, spender: ContractAddress, added_value: u256) -> bool; + fn increaseAllowance(ref self: TContractState, spender: ContractAddress, added_value: u256) -> bool; //TODO Remove after regenesis? + fn decrease_allowance(ref self: TContractState, spender: ContractAddress, subtracted_value: u256) -> bool; + fn decreaseAllowance(ref self: TContractState, spender: ContractAddress, subtracted_value: u256) -> bool; //TODO Remove after regenesis? + fn mint(ref self: TContractState, to: ContractAddress) -> u256; + fn burn(ref self: TContractState, to: ContractAddress) -> (u256, u256); + fn swap(ref self: TContractState, amount0Out: u256, amount1Out: u256, to: ContractAddress, data: Array::); + fn skim(ref self: TContractState, to: ContractAddress); + fn sync(ref self: TContractState); + // fn replace_implementation_class(ref self: TContractState, new_implementation_class: ClassHash); +} + +#[starknet::contract] +mod PairC1 { + use jediswap::utils::erc20::ERC20; + use traits::Into; // TODO remove intos when u256 inferred type is available + use option::OptionTrait; + use array::{ArrayTrait, SpanTrait}; + use result::ResultTrait; + use zeroable::Zeroable; + use starknet::{ContractAddress, ClassHash, SyscallResult, SyscallResultTrait, get_caller_address, get_contract_address, get_block_timestamp, contract_address_const}; + use integer::{u128_try_from_felt252, u256_sqrt, u256_from_felt252}; + use starknet::syscalls::{replace_class_syscall, call_contract_syscall}; + + use super::{ + IERC20Dispatcher, IERC20DispatcherTrait, IFactoryDispatcher, IFactoryDispatcherTrait, IJediSwapCalleeDispatcher, IJediSwapCalleeDispatcherTrait + }; + + // + // Storage Pair + // + #[storage] + struct Storage { + _token0: ContractAddress, // @dev token0 address + _token1: ContractAddress, // @dev token1 address + _reserve0: u256, // @dev reserve for token0 + _reserve1: u256, // @dev reserve for token1 + _block_timestamp_last: u64, // @dev block timestamp for last update + _price_0_cumulative_last: u256, // @dev cumulative price for token0 on last update + _price_1_cumulative_last: u256, // @dev cumulative price for token1 on last update + _klast: u256, // @dev reserve0 * reserve1, as of immediately after the most recent liquidity event + _locked: bool, // @dev Boolean to check reentrancy + _factory: ContractAddress, // @dev Factory contract address + Proxy_admin: ContractAddress, // @dev Admin contract address, to be used till we finalize Cairo upgrades. + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Mint: Mint, + Burn: Burn, + Swap: Swap, + Sync: Sync + } + + // @notice An event emitted whenever mint() is called. + #[derive(Drop, starknet::Event)] + struct Mint { + sender: ContractAddress, + amount0: u256, + amount1: u256 + } + + // @notice An event emitted whenever burn() is called. + #[derive(Drop, starknet::Event)] + struct Burn { + sender: ContractAddress, + amount0: u256, + amount1: u256, + to: ContractAddress + } + + // @notice An event emitted whenever swap() is called. + #[derive(Drop, starknet::Event)] + struct Swap { + sender: ContractAddress, + amount0In: u256, + amount1In: u256, + amount0Out: u256, + amount1Out: u256, + to: ContractAddress + } + + // @notice An event emitted whenever _update() is called. + #[derive(Drop, starknet::Event)] + struct Sync { + reserve0: u256, + reserve1: u256 + } + + // + // Constructor + // + + // @notice Contract constructor + // @param name Name of the pair token + // @param symbol Symbol of the pair token + // @param token0 Address of token0 + // @param token1 Address of token1 + #[constructor] + fn constructor(ref self: ContractState, token0: ContractAddress, token1: ContractAddress) { + assert(!token0.is_zero() & !token1.is_zero(), 'must be non zero'); + let mut erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::InternalImpl::initializer(ref erc20_state, 'JediSwap Pair', 'JEDI-P'); + self._locked.write(false); + self._token0.write(token0); + self._token1.write(token1); + let factory = get_caller_address(); + self._factory.write(factory); + } + + #[abi(embed_v0)] + impl PairC1 of super::IPairC1 { + + // + // Getters ERC20 + // + + // @notice Name of the token + // @return name + fn name(self: @ContractState) -> felt252 { + let erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::name(@erc20_state) + } + + // @notice Symbol of the token + // @return symbol + fn symbol(self: @ContractState) -> felt252 { + let erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::symbol(@erc20_state) + } + + // @notice Total Supply of the token + // @return total supply + fn total_supply(self: @ContractState) -> u256 { + let erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::total_supply(@erc20_state) + } + + // @notice Total Supply of the token + // @return totalSupply + fn totalSupply(self: @ContractState) -> u256 { + let erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::total_supply(@erc20_state) + } + + // @notice Decimals of the token + // @return decimals + fn decimals(self: @ContractState) -> u8 { + let erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::decimals(@erc20_state) + } + + // @notice Balance of `account` + // @param account Account address whose balance is fetched + // @return balance Balance of `account` + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + let erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::balance_of(@erc20_state, account) + } + + // @notice Balance of `account` + // @param account Account address whose balance is fetched + // @return balance Balance of `account` + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + let erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::balance_of(@erc20_state, account) + } + + // @notice Allowance which `spender` can spend on behalf of `owner` + // @param owner Account address whose tokens are spent + // @param spender Account address which can spend the tokens + // @return remaining + fn allowance(self: @ContractState, owner: ContractAddress, spender: ContractAddress) -> u256 { + let erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::allowance(@erc20_state, owner, spender) + } + + // + // Getters Pair + // + + // @notice token0 address + // @return address + fn token0(self: @ContractState) -> ContractAddress { + self._token0.read() + } + + // @notice token1 address + // @return address + fn token1(self: @ContractState) -> ContractAddress { + self._token1.read() + } + + // @notice Current reserves for tokens in the pair + // @return reserve0 reserve for token0 + // @return reserve1 reserve for token1 + // @return block_timestamp_last block timestamp for last update + fn get_reserves(self: @ContractState) -> (u256, u256, u64) { + InternalImpl::_get_reserves(self) + } + + // @notice cumulative price for token0 on last update + // @return res + fn price_0_cumulative_last(self: @ContractState) -> u256 { + self._price_0_cumulative_last.read() + } + + // @notice cumulative price for token1 on last update + // @return res + fn price_1_cumulative_last(self: @ContractState) -> u256 { + self._price_1_cumulative_last.read() + } + + // @notice reserve0 * reserve1, as of immediately after the most recent liquidity event + // @return res + fn klast(self: @ContractState) -> u256 { + self._klast.read() + } + + // + // Externals ERC20 + // + + // @notice Transfer `amount` tokens from `caller` to `recipient` + // @param recipient Account address to which tokens are transferred + // @param amount Amount of tokens to transfer + // @return success 0 or 1 + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { + let mut erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::transfer(ref erc20_state, recipient, amount); + true + } + + // @notice Transfer `amount` tokens from `sender` to `recipient` + // @dev Checks for allowance. + // @param sender Account address from which tokens are transferred + // @param recipient Account address to which tokens are transferred + // @param amount Amount of tokens to transfer + // @return success 0 or 1 + fn transfer_from(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) -> bool { + let mut erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::transfer_from(ref erc20_state, sender, recipient, amount); + true + } + + // @notice Transfer `amount` tokens from `sender` to `recipient` + // @dev Checks for allowance. + // @param sender Account address from which tokens are transferred + // @param recipient Account address to which tokens are transferred + // @param amount Amount of tokens to transfer + // @return success 0 or 1 + fn transferFrom(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) -> bool { + let mut erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::transfer_from(ref erc20_state, sender, recipient, amount); + true + } + + // @notice Approve `spender` to transfer `amount` tokens on behalf of `caller` + // @param spender The address which will spend the funds + // @param amount The amount of tokens to be spent + // @return success 0 or 1 + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { + let mut erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::approve(ref erc20_state, spender, amount); + true + } + + // @notice Increase allowance of `spender` to transfer `added_value` more tokens on behalf of `caller` + // @param spender The address which will spend the funds + // @param added_value The increased amount of tokens to be spent + // @return success 0 or 1 + fn increase_allowance(ref self: ContractState, spender: ContractAddress, added_value: u256) -> bool { + let mut erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::increase_allowance(ref erc20_state, spender, added_value); + true + } + + // @notice Increase allowance of `spender` to transfer `added_value` more tokens on behalf of `caller` + // @param spender The address which will spend the funds + // @param added_value The increased amount of tokens to be spent + // @return success 0 or 1 + fn increaseAllowance(ref self: ContractState, spender: ContractAddress, added_value: u256) -> bool { + let mut erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::increase_allowance(ref erc20_state, spender, added_value); + true + } + + // @notice Decrease allowance of `spender` to transfer `subtracted_value` less tokens on behalf of `caller` + // @param spender The address which will spend the funds + // @param subtracted_value The decreased amount of tokens to be spent + // @return success 0 or 1 + fn decrease_allowance(ref self: ContractState, spender: ContractAddress, subtracted_value: u256) -> bool { + let mut erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::decrease_allowance(ref erc20_state, spender, subtracted_value); + true + } + + // @notice Decrease allowance of `spender` to transfer `subtracted_value` less tokens on behalf of `caller` + // @param spender The address which will spend the funds + // @param subtracted_value The decreased amount of tokens to be spent + // @return success 0 or 1 + fn decreaseAllowance(ref self: ContractState, spender: ContractAddress, subtracted_value: u256) -> bool { + let mut erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::ERC20::decrease_allowance(ref erc20_state, spender, subtracted_value); + true + } + + // + // Externals Pair + // + + // @notice Mint tokens and assign them to `to` + // @dev This low-level function should be called from a contract which performs important safety checks + // @param to The account that will receive the created tokens + // @return liquidity New tokens created + fn mint(ref self: ContractState, to: ContractAddress) -> u256 { + InternalImpl::_check_and_lock(ref self); + let (reserve0, reserve1, _) = InternalImpl::_get_reserves(@self); + let self_address = get_contract_address(); + let token0 = self._token0.read(); + let balance0 = _balance_of_token(token0, self_address); + let token1 = self._token1.read(); + let balance1 = _balance_of_token(token1, self_address); + let amount0 = balance0 - reserve0; + let amount1 = balance1 - reserve1; + let fee_on = InternalImpl::_mint_protocol_fee(ref self, reserve0, reserve1); + let _total_supply = PairC1::total_supply(@self); + let mut liquidity = 0.into(); + if (_total_supply == 0.into()) { + liquidity = u256 { low: u256_sqrt(amount0 * amount1) - 1000.try_into().unwrap(), high: 0 }; + } else { + let liquidity0 = (amount0 * _total_supply) / reserve0; + let liquidity1 = (amount1 * _total_supply) / reserve1; + if liquidity0 < liquidity1 { + liquidity = liquidity0; + } else { + liquidity = liquidity1; + } + } + + assert(liquidity > 0.into(), 'insufficient liquidity minted'); + + let mut erc20_state = ERC20::unsafe_new_contract_state(); + if (_total_supply == 0.into()) { + ERC20::InternalImpl::_mint(ref erc20_state, contract_address_const::<1>(), 1000.into()); + } + ERC20::InternalImpl::_mint(ref erc20_state, to, liquidity); + + InternalImpl::_update(ref self, balance0, balance1, reserve0, reserve1); + + if (fee_on) { + let klast = balance0 * balance1; + self._klast.write(klast); + } + + self.emit(Mint {sender: get_caller_address(), amount0: amount0, amount1: amount1}); // TODO?? sender address instead of caller address + + InternalImpl::_unlock(ref self); + liquidity + } + + // @notice Burn tokens belonging to `to` + // @dev This low-level function should be called from a contract which performs important safety checks + // @param to The account that will receive the created tokens + // @return amount0 Amount of token0 received + // @return amount1 Amount of token1 received + fn burn(ref self: ContractState, to: ContractAddress) -> (u256, u256) { + InternalImpl::_check_and_lock(ref self); + let (reserve0, reserve1, _) = InternalImpl::_get_reserves(@self); + let self_address = get_contract_address(); + let token0 = self._token0.read(); + let mut balance0 = _balance_of_token(token0, self_address); + let token1 = self._token1.read(); + let mut balance1 = _balance_of_token(token1, self_address); + let liquidity = PairC1::balance_of(@self, self_address); + let fee_on = InternalImpl::_mint_protocol_fee(ref self, reserve0, reserve1); + let _total_supply = PairC1::total_supply(@self); + + let amount0 = (liquidity * balance0) / _total_supply; + let amount1 = (liquidity * balance1) / _total_supply; + assert(amount0 > 0.into() && amount1 > 0.into(), 'insufficient liquidity burned'); + + let mut erc20_state = ERC20::unsafe_new_contract_state(); + + ERC20::InternalImpl::_burn(ref erc20_state, self_address, liquidity); + + let token0Dispatcher = IERC20Dispatcher { contract_address: token0 }; + token0Dispatcher.transfer(to, amount0); + let token1Dispatcher = IERC20Dispatcher { contract_address: token1 }; + token1Dispatcher.transfer(to, amount1); + + balance0 = _balance_of_token(token0, self_address); + balance1 = _balance_of_token(token1, self_address); + + InternalImpl::_update(ref self, balance0, balance1, reserve0, reserve1); + + if (fee_on) { + let klast = balance0 * balance1; + self._klast.write(klast); + } + + self.emit(Burn {sender: get_caller_address(), amount0: amount0, amount1: amount1, to: to}); + + InternalImpl::_unlock(ref self); + (amount0, amount1) + } + + // @notice Swaps from one token to another + // @dev This low-level function should be called from a contract which performs important safety checks + // @param amount0Out Amount of token0 received + // @param amount1Out Amount of token1 received + // @param to The account that will receive the tokens + fn swap(ref self: ContractState, amount0Out: u256, amount1Out: u256, to: ContractAddress, data: Array::) { + InternalImpl::_check_and_lock(ref self); + assert(amount0Out > 0.into() || amount1Out > 0.into(), 'insufficient output amount'); + + let (reserve0, reserve1, _) = InternalImpl::_get_reserves(@self); + assert(amount0Out < reserve0 && amount1Out < reserve1, 'insufficient liquidity'); + + let token0 = self._token0.read(); + let token1 = self._token1.read(); + assert(to != token0 && to != token1, 'invalid to'); + + let token0Dispatcher = IERC20Dispatcher { contract_address: token0 }; + let token1Dispatcher = IERC20Dispatcher { contract_address: token1 }; + + if (amount0Out > 0.into()) { + token0Dispatcher.transfer(to, amount0Out); + } + + if (amount1Out > 0.into()) { + token1Dispatcher.transfer(to, amount1Out); + } + + if (data.len() > 0) { + let JediSwapCalleeDispatcher = IJediSwapCalleeDispatcher { contract_address: to }; + JediSwapCalleeDispatcher + .jediswap_call(get_caller_address(), amount0Out, amount1Out, data); + } + + let self_address = get_contract_address(); + let balance0 = _balance_of_token(token0, self_address); + let balance1 = _balance_of_token(token1, self_address); + + let mut amount0In = 0.into(); + + if (balance0 > (reserve0 - amount0Out)) { + amount0In = balance0 - (reserve0 - amount0Out); + } + + let mut amount1In = 0.into(); + + if (balance1 > (reserve1 - amount1Out)) { + amount1In = balance1 - (reserve1 - amount1Out); + } + + assert(amount0In > 0.into() || amount1In > 0.into(), 'insufficient input amount'); + + let balance0Adjusted = (balance0 * 1000.into()) - (amount0In * 3.into()); + let balance1Adjusted = (balance1 * 1000.into()) - (amount1In * 3.into()); + + assert( + balance0Adjusted * balance1Adjusted > reserve0 * reserve1 * 1000000.into(), + 'invariant K' + ); + + InternalImpl::_update(ref self, balance0, balance1, reserve0, reserve1); + + self.emit(Swap {sender: get_caller_address(), amount0In: amount0In, amount1In: amount1In, amount0Out: amount0Out, amount1Out: amount1Out, to: to}); + + InternalImpl::_unlock(ref self); + } + + // @notice force balances to match reserves + // @param to The account that will receive the balance tokens + fn skim(ref self: ContractState, to: ContractAddress) { + InternalImpl::_check_and_lock(ref self); + let (reserve0, reserve1, _) = InternalImpl::_get_reserves(@self); + + let self_address = get_contract_address(); + let token0 = self._token0.read(); + let balance0 = _balance_of_token(token0, self_address); + let token1 = self._token1.read(); + let balance1 = _balance_of_token(token1, self_address); + + let token0Dispatcher = IERC20Dispatcher { contract_address: token0 }; + token0Dispatcher.transfer(to, balance0 - reserve0); + let token1Dispatcher = IERC20Dispatcher { contract_address: token1 }; + token1Dispatcher.transfer(to, balance1 - reserve1); + + InternalImpl::_unlock(ref self); + } + + // @notice Force reserves to match balances + fn sync(ref self: ContractState) { + InternalImpl::_check_and_lock(ref self); + + let self_address = get_contract_address(); + let token0 = self._token0.read(); + let balance0 = _balance_of_token(token0, self_address); + let token1 = self._token1.read(); + let balance1 = _balance_of_token(token1, self_address); + + let (reserve0, reserve1, _) = InternalImpl::_get_reserves(@self); + + InternalImpl::_update(ref self, balance0, balance1, reserve0, reserve1); + + InternalImpl::_unlock(ref self); + } + + // @notice This is used upgrade (Will push a upgrade without this to finalize) + // @dev Only Proxy_admin can call + // @param new_implementation_class New implementation hash + // fn replace_implementation_class(ref self: ContractState, new_implementation_class: ClassHash) { + // let sender = get_caller_address(); + // let proxy_admin = self.Proxy_admin.read(); + // assert(sender == proxy_admin, 'must be admin'); + // assert(!new_implementation_class.is_zero(), 'must be non zero'); + // replace_class_syscall(new_implementation_class); + // } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + + // + // Internals Pair + // + + // @dev Check if the entry is not locked, and lock it + fn _check_and_lock(ref self: ContractState) { + let locked = self._locked.read(); + assert(!locked, 'locked'); + self._locked.write(true); + } + + // @dev Unlock the entry + fn _unlock(ref self: ContractState) { + let locked = self._locked.read(); + assert(locked, 'not locked'); + self._locked.write(false); + } + + // @dev If fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k) + fn _mint_protocol_fee(ref self: ContractState, reserve0: u256, reserve1: u256) -> bool { + let factory = self._factory.read(); + let factoryDispatcher = IFactoryDispatcher { contract_address: factory }; + let fee_to = factoryDispatcher.get_fee_to(); + let fee_on = (fee_to != contract_address_const::<0>()); + + let klast = self._klast.read(); + + if (fee_on) { + if (klast != 0.into()) { + let rootk = u256 { low: u256_sqrt(reserve0 * reserve1), high: 0 }; + let rootklast = u256 { low: u256_sqrt(klast), high: 0 }; + if (rootk > rootklast) { + let numerator = PairC1::total_supply(@self) * (rootk - rootklast); + let denominator = (rootk * 5.into()) + rootklast; + let liquidity = numerator / denominator; + if (liquidity > 0.into()) { + let mut erc20_state = ERC20::unsafe_new_contract_state(); + ERC20::InternalImpl::_mint(ref erc20_state, fee_to, liquidity); + } + } + } + } else { + if (klast != 0.into()) { + self._klast.write(0.into()); + } + } + fee_on + } + + fn _get_reserves(self: @ContractState) -> (u256, u256, u64) { + (self._reserve0.read(), self._reserve1.read(), self._block_timestamp_last.read()) + } + + // @dev Update reserves and, on the first call per block, price accumulators + fn _update(ref self: ContractState, balance0: u256, balance1: u256, reserve0: u256, reserve1: u256) { + assert(balance0.high == 0 && balance1.high == 0, 'overflow'); + let block_timestamp = get_block_timestamp(); + let block_timestamp_last = self._block_timestamp_last.read(); + let time_elapsed = block_timestamp - block_timestamp_last; + let (reserve0, reserve1, _) = InternalImpl::_get_reserves(@self); + if (time_elapsed > 0 && reserve0 != 0.into() && reserve1 != 0.into()) { + let mut price_0_cumulative_last = self._price_0_cumulative_last.read(); + let mut price_1_cumulative_last = self._price_1_cumulative_last.read(); + price_0_cumulative_last += (reserve1 / reserve0) * u256 { + low: u128_try_from_felt252(time_elapsed.into()).unwrap(), high: 0 + }; // TODO official support for casting to u256 + price_1_cumulative_last += (reserve0 / reserve1) * u256 { + low: u128_try_from_felt252(time_elapsed.into()).unwrap(), high: 0 + }; // TODO official support for casting to u256 + self._price_0_cumulative_last.write(price_0_cumulative_last); + self._price_1_cumulative_last.write(price_1_cumulative_last); + } + + self._reserve0.write(balance0); + self._reserve1.write(balance1); + self._block_timestamp_last.write(block_timestamp); + + self.emit(Sync {reserve0: balance0, reserve1: balance1}); + } + } + + // + // Internals LIBRARY + // + + fn _balance_of_token( + token: ContractAddress, account: ContractAddress + ) -> u256 { + // let tokenDispatcher = IERC20Dispatcher { contract_address: token }; + // tokenDispatcher.balance_of(account) + + let mut calldata = Default::default(); + Serde::serialize(@account, ref calldata); + + let selector_for_balance_of = 1516754014369808875012295842270199525215452866521012470027807093200784961331; + let selector_for_balanceOf = 1307730684388977109649524593492043083703013045633289330664425380824804018030; + + let mut result = call_contract_syscall(token, selector_for_balance_of, calldata.span()); + if (result.is_err()) { + result = call_contract_syscall(token, selector_for_balanceOf, calldata.span()); + } + u256_from_felt252(*result.unwrap_syscall().at(0)) + } +} \ No newline at end of file diff --git a/src/contracts/RouterC1V2.cairo b/src/contracts/RouterC1V2.cairo new file mode 100644 index 0000000..55c8915 --- /dev/null +++ b/src/contracts/RouterC1V2.cairo @@ -0,0 +1,580 @@ +// @title JediSwap router for stateless execution of swaps Cairo 1.0 +// @author Mesh Finance +// @license MIT +// @dev Based on the Uniswap V2 Router +// https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Router02.sol + +use starknet::ContractAddress; +use starknet::ClassHash; + +// +// External Interfaces +// +#[starknet::interface] + trait IERC20 { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; + } + +#[starknet::interface] +trait IPair { + fn get_reserves(self: @T) -> (u256, u256, u64); + fn mint(ref self: T, to: ContractAddress) -> u256; + fn burn(ref self: T, to: ContractAddress) -> (u256, u256); + fn swap(ref self: T, amount0Out: u256, amount1Out: u256, to: ContractAddress, data: Array::); +} + +#[starknet::interface] +trait IFactory { + fn get_pair(self: @T, token0: ContractAddress, token1: ContractAddress) -> ContractAddress; + fn create_pair(ref self: T, token0: ContractAddress, token1: ContractAddress) -> ContractAddress; +} + +// +// Contract Interface +// +#[starknet::interface] +trait IRouterC1 { + // view functions + fn factory(self: @TContractState) -> ContractAddress; + fn sort_tokens(self: @TContractState, tokenA: ContractAddress, tokenB: ContractAddress) -> (ContractAddress, ContractAddress); + fn quote(self: @TContractState, amountA: u256, reserveA: u256, reserveB: u256) -> u256; + fn get_amount_out(self: @TContractState, amountIn: u256, reserveIn: u256, reserveOut: u256) -> u256; + fn get_amount_in(self: @TContractState, amountOut: u256, reserveIn: u256, reserveOut: u256) -> u256; + fn get_amounts_out(self: @TContractState, amountIn: u256, path: Array::) -> Array::; + fn get_amounts_in(self: @TContractState, amountOut: u256, path: Array::) -> Array::; + // external functions + fn add_liquidity(ref self: TContractState, tokenA: ContractAddress, tokenB: ContractAddress, amountADesired: u256, amountBDesired: u256, amountAMin: u256, amountBMin: u256, to: ContractAddress, deadline: u64) -> (u256, u256, u256); + fn remove_liquidity(ref self: TContractState, tokenA: ContractAddress, tokenB: ContractAddress, liquidity: u256, amountAMin: u256, amountBMin: u256, to: ContractAddress, deadline: u64) -> (u256, u256); + fn swap_exact_tokens_for_tokens(ref self: TContractState, amountIn: u256, amountOutMin: u256, path: Array::, to: ContractAddress, deadline: u64) -> Array::; + fn swap_tokens_for_exact_tokens(ref self: TContractState, amountOut: u256, amountInMax: u256, path: Array::, to: ContractAddress, deadline: u64) -> Array::; + fn swap_exact_tokens_for_tokens_supporting_fee_on_transfer_tokens(ref self: TContractState, amount_in: u256, amount_out_min: u256, path: Array, to: ContractAddress, deadline: u64); + fn replace_implementation_class(ref self: TContractState, new_implementation_class: ClassHash); +} + +#[starknet::contract] +mod RouterC1 { + use traits::Into; + use array::{ArrayTrait, SpanTrait}; + use result::ResultTrait; + use zeroable::Zeroable; + use starknet::{ContractAddress, ClassHash, SyscallResult, SyscallResultTrait, get_caller_address, get_block_timestamp, contract_address_const, contract_address_to_felt252}; + use integer::u256_from_felt252; + use core::starknet::syscalls::{replace_class_syscall, call_contract_syscall}; + + use super::{ + IERC20Dispatcher, IERC20DispatcherTrait, IPairDispatcher, IPairDispatcherTrait, IFactoryDispatcher, IFactoryDispatcherTrait + }; + + // + // Storage + // + #[storage] + struct Storage { + _factory: ContractAddress, // @dev Factory contract address + Proxy_admin: ContractAddress, // @dev Admin contract address, to be used till we finalize Cairo upgrades. + } + + // + // Constructor + // + + // @notice Contract constructor + // @param factory Address of factory contract + #[constructor] + fn constructor(ref self: ContractState, factory: ContractAddress) { + assert(!factory.is_zero(), 'can not be zero'); + self._factory.write(factory); + } + + #[abi(embed_v0)] + impl RouterC1 of super::IRouterC1 { + // + // Getters + // + + // @notice factory address + // @return address + fn factory(self: @ContractState) -> ContractAddress { + self._factory.read() + } + + // @notice Sort tokens `tokenA` and `tokenB` by address + // @param tokenA Address of tokenA + // @param tokenB Address of tokenB + // @return token0 First token + // @return token1 Second token + fn sort_tokens(self: @ContractState, tokenA: ContractAddress, tokenB: ContractAddress) -> (ContractAddress, ContractAddress) { + _sort_tokens(tokenA, tokenB) + } + + // @notice Given some amount of an asset and pair reserves, returns an equivalent amount of the other asset + // @param amountA Amount of tokenA + // @param reserveA Reserves for tokenA + // @param reserveB Reserves for tokenB + // @return amountB Amount of tokenB + fn quote(self: @ContractState, amountA: u256, reserveA: u256, reserveB: u256) -> u256 { + _quote(amountA, reserveA, reserveB) + } + + // @notice Given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset + // @param amountIn Input Amount + // @param reserveIn Reserves for input token + // @param reserveOut Reserves for output token + // @return amountOut Maximum output amount + fn get_amount_out(self: @ContractState, amountIn: u256, reserveIn: u256, reserveOut: u256) -> u256 { + _get_amount_out(amountIn, reserveIn, reserveOut) + } + + // @notice Given an output amount of an asset and pair reserves, returns a required input amount of the other asset + // @param amountOut Output Amount + // @param reserveIn Reserves for input token + // @param reserveOut Reserves for output token + // @return amountIn Required input amount + fn get_amount_in(self: @ContractState, amountOut: u256, reserveIn: u256, reserveOut: u256) -> u256 { + _get_amount_in(amountOut, reserveIn, reserveOut) + } + + // @notice Performs chained get_amount_out calculations on any number of pairs + // @param amountIn Input Amount + // @param path Array of pair addresses through which swaps are chained + // @return amounts Required output amount array + fn get_amounts_out(self: @ContractState, amountIn: u256, path: Array::) -> Array:: { + let factory = self._factory.read(); + _get_amounts_out(factory, amountIn, path.span()) + } + + // @notice Performs chained get_amount_in calculations on any number of pairs + // @param amountOut Output Amount + // @param path Array of pair addresses through which swaps are chained + // @return amounts Required input amount array + fn get_amounts_in(self: @ContractState, amountOut: u256, path: Array::) -> Array:: { + let factory = self._factory.read(); + _get_amounts_in(factory, amountOut, path.span()) + } + + // + // Externals + // + + // @notice Add liquidity to a pool + // @dev `caller` should have already given the router an allowance of at least amountADesired/amountBDesired on tokenA/tokenB + // @param tokenA Address of tokenA + // @param tokenB Address of tokenB + // @param amountADesired The amount of tokenA to add as liquidity + // @param amountBDesired The amount of tokenB to add as liquidity + // @param amountAMin Bounds the extent to which the B/A price can go up before the transaction reverts. Must be <= amountADesired + // @param amountBMin Bounds the extent to which the A/B price can go up before the transaction reverts. Must be <= amountBDesired + // @param to Recipient of liquidity tokens + // @param deadline Timestamp after which the transaction will revert + // @return amountA The amount of tokenA sent to the pool + // @return amountB The amount of tokenB sent to the pool + // @return liquidity The amount of liquidity tokens minted + fn add_liquidity(ref self: ContractState, + tokenA: ContractAddress, + tokenB: ContractAddress, + amountADesired: u256, + amountBDesired: u256, + amountAMin: u256, + amountBMin: u256, + to: ContractAddress, + deadline: u64 + ) -> (u256, u256, u256) { + _ensure_deadline(deadline); + let (amountA, amountB) = InternalImpl::_add_liquidity(ref self, tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin); + let factory = self._factory.read(); + let pair = _pair_for(factory, tokenA, tokenB); + let sender = get_caller_address(); + _transfer_token(tokenA, sender, pair, amountA); + _transfer_token(tokenB, sender, pair, amountB); + let pairDispatcher = IPairDispatcher { contract_address: pair }; + let liquidity = pairDispatcher.mint(to); + (amountA, amountB, liquidity) + } + + // @notice Remove liquidity from a pool + // @dev `caller` should have already given the router an allowance of at least liquidity on the pool + // @param tokenA Address of tokenA + // @param tokenB Address of tokenB + // @param liquidity The amount of liquidity tokens to remove + // @param amountAMin The minimum amount of tokenA that must be received for the transaction not to revert + // @param amountBMin The minimum amount of tokenB that must be received for the transaction not to revert + // @param to Recipient of the underlying tokens + // @param deadline Timestamp after which the transaction will revert + // @return amountA The amount of tokenA received + // @return amountB The amount of tokenB received + fn remove_liquidity(ref self: ContractState, + tokenA: ContractAddress, + tokenB: ContractAddress, + liquidity: u256, + amountAMin: u256, + amountBMin: u256, + to: ContractAddress, + deadline: u64 + ) -> (u256, u256) { + _ensure_deadline(deadline); + let factory = self._factory.read(); + let pair = _pair_for(factory, tokenA, tokenB); + let sender = get_caller_address(); + _transfer_token(pair, sender, pair, liquidity); + let pairDispatcher = IPairDispatcher { contract_address: pair }; + let (amount0, amount1) = pairDispatcher.burn(to); + let (token0, _) = _sort_tokens(tokenA, tokenB); + let mut amountA = 0.into(); + let mut amountB = 0.into(); + if tokenA == token0 { + amountA = amount0; + amountB = amount1; + } else { + amountA = amount1; + amountB = amount0; + } + + assert(amountA >= amountAMin, 'insufficient A amount'); + assert(amountB >= amountBMin, 'insufficient B amount'); + + (amountA, amountB) + } + + // @notice Swaps an exact amount of input tokens for as many output tokens as possible, along the route determined by the path + // @dev `caller` should have already given the router an allowance of at least amountIn on the input token + // @param amountIn The amount of input tokens to send + // @param amountOutMin The minimum amount of output tokens that must be received for the transaction not to revert + // @param path Array of pair addresses through which swaps are chained + // @param to Recipient of the output tokens + // @param deadline Timestamp after which the transaction will revert + // @return amounts The input token amount and all subsequent output token amounts + fn swap_exact_tokens_for_tokens(ref self: ContractState, + amountIn: u256, + amountOutMin: u256, + path: Array::, + to: ContractAddress, + deadline: u64 + ) -> Array:: { + _ensure_deadline(deadline); + let factory = self._factory.read(); + let mut amounts = _get_amounts_out(factory, amountIn, path.span()); + assert(*amounts[amounts.len() - 1] >= amountOutMin, 'insufficient output amount'); + let pair = _pair_for(factory, *path[0], *path[1]); + let sender = get_caller_address(); + _transfer_token(*path[0], sender, pair, *amounts[0]); + InternalImpl::_swap(ref self, 0, path.len(), ref amounts, path.span(), to); + amounts + } + + // @notice Receive an exact amount of output tokens for as few input tokens as possible, along the route determined by the path + // @dev `caller` should have already given the router an allowance of at least amountInMax on the input token + // @param amountOut The amount of output tokens to receive + // @param amountInMax The maximum amount of input tokens that can be required before the transaction reverts + // @param path Array of pair addresses through which swaps are chained + // @param to Recipient of the output tokens + // @param deadline Timestamp after which the transaction will revert + // @return amounts The input token amount and all subsequent output token amounts + fn swap_tokens_for_exact_tokens(ref self: ContractState, + amountOut: u256, + amountInMax: u256, + path: Array::, + to: ContractAddress, + deadline: u64 + ) -> Array:: { + _ensure_deadline(deadline); + let factory = self._factory.read(); + let mut amounts = _get_amounts_in(factory, amountOut, path.span()); + assert(*amounts[0] <= amountInMax, 'excessive input amount'); + let pair = _pair_for(factory, *path[0], *path[1]); + let sender = get_caller_address(); + _transfer_token(*path[0], sender, pair, *amounts[0]); + InternalImpl::_swap(ref self, 0, path.len(), ref amounts, path.span(), to); + amounts + } + + fn swap_exact_tokens_for_tokens_supporting_fee_on_transfer_tokens(ref self: ContractState, + amount_in: u256, + amount_out_min: u256, + path: Array, + to: ContractAddress, + deadline: u64 + ) { + _ensure_deadline(deadline); + let paths = @path; //it also compiles with: let paths = path.span() + let caller = get_caller_address(); + IERC20Dispatcher{contract_address: *path.at(0)}.transfer_from( + caller, + IFactoryDispatcher{contract_address: self._factory.read()}.get_pair(*path.at(0), *path.at(1)), + amount_in + ); + let balance_before = IERC20Dispatcher { contract_address: *paths[paths.len() - 1] }.balance_of(to); + InternalImpl::_swap_supporting_fee_on_transfer_tokens(ref self, 0, path, to); + + assert( + (IERC20Dispatcher { contract_address: *paths[paths.len() - 1] }.balance_of(to) - balance_before) >= amount_out_min, + 'INSUFFICIENT_OUTPUT_AMOUNT' + ); + } + + // @notice This is used upgrade (Will push a upgrade without this to finalize) + // @dev Only Proxy_admin can call + // @param new_implementation_class New implementation hash + fn replace_implementation_class(ref self: ContractState, new_implementation_class: ClassHash) { + let sender = get_caller_address(); + let proxy_admin = self.Proxy_admin.read(); + assert(sender == proxy_admin, 'must be admin'); + assert(!new_implementation_class.is_zero(), 'must be non zero'); + replace_class_syscall(new_implementation_class).unwrap(); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + // + // Internals + // + + fn _add_liquidity(ref self: ContractState, + tokenA: ContractAddress, + tokenB: ContractAddress, + amountADesired: u256, + amountBDesired: u256, + amountAMin: u256, + amountBMin: u256, + ) -> (u256, u256) { + let factory = self._factory.read(); + let factoryDispatcher = IFactoryDispatcher { contract_address: factory }; + let pair = factoryDispatcher.get_pair(tokenA, tokenB); + + if (pair == contract_address_const::<0>()) { + factoryDispatcher.create_pair(tokenA, tokenB); + } + + let (reserveA, reserveB) = _get_reserves(factory, tokenA, tokenB); + + if (reserveA == 0.into() && reserveB == 0.into()) { + return (amountADesired, amountBDesired); + } else { + let amountBOptimal = _quote(amountADesired, reserveA, reserveB); + if amountBOptimal <= amountBDesired { + assert(amountBOptimal >= amountBMin, 'insufficient B amount'); + return (amountADesired, amountBOptimal); + } else { + let amountAOptimal = _quote(amountBDesired, reserveB, reserveA); + assert(amountAOptimal <= amountADesired, ''); + assert(amountAOptimal >= amountAMin, 'insufficient A amount'); + return (amountAOptimal, amountBDesired); + } + } + } + + fn _swap(ref self: ContractState, + current_index: u32, + amounts_len: u32, + ref amounts: Array::, + path: Span::, + _to: ContractAddress + ) { + let factory = self._factory.read(); + if (current_index == amounts_len - 1) { + return (); + } + let (token0, _) = _sort_tokens(*path[current_index], *path[current_index + 1]); + let mut amount0Out = 0.into(); + let mut amount1Out = 0.into(); + if (*path[current_index] == token0) { + amount1Out = *amounts[current_index + 1]; + } else { + amount0Out = *amounts[current_index + 1]; + } + let mut to: ContractAddress = _to; + if (current_index < (amounts_len - 2)) { + to = _pair_for(factory, *path[current_index + 1], *path[current_index + 2]); + } + let pair = _pair_for(factory, *path[current_index], *path[current_index + 1]); + let data = ArrayTrait::::new(); + let pairDispatcher = IPairDispatcher { contract_address: pair }; + pairDispatcher.swap(amount0Out, amount1Out, to, data); + return InternalImpl::_swap(ref self, current_index + 1, amounts_len, ref amounts, path, _to); + } + + fn _swap_supporting_fee_on_transfer_tokens(ref self: ContractState, index: u32 , path: Array, _to: ContractAddress){ + let factory = self._factory.read(); + if (index == path.len() - 1) { + return (); + } + let (token0, _token1) = _sort_tokens(*path[index], *path[index + 1]); + let pair = _pair_for(factory, *path[index], *path[index + 1]); + let (reserve_0, reserve_1) = _get_reserves(factory, *path[index], *path[index + 1]); + let (reserve_input, reserve_output) = if (*path[index] == token0) { + (reserve_0, reserve_1) + } else { + (reserve_1, reserve_0) + }; + let amount_input = IERC20Dispatcher{contract_address: *path[index]}.balance_of(pair) - reserve_input; + let amount_output = _get_amount_out(amount_input, reserve_input, reserve_output); + let (amount_0_out, amount_1_out) = if (*path[index] == token0) { + (0, amount_output) + } else { + (amount_output, 0) + }; + let mut to: ContractAddress = _to; + if (index < (path.len() - 2)) { + to = _pair_for(factory, *path[index + 1], *path[index + 2]); + } + let data = ArrayTrait::::new(); + let pairDispatcher = IPairDispatcher{ contract_address: pair }; + pairDispatcher.swap(amount_0_out, amount_1_out, to, data); + return InternalImpl::_swap_supporting_fee_on_transfer_tokens(ref self, index + 1, path, _to); + } + } + + // + // Internals LIBRARY + // + + fn _ensure_deadline(deadline: u64) { + let block_timestamp = get_block_timestamp(); + assert(deadline >= block_timestamp, 'expired'); + } + + fn _transfer_token( + token: ContractAddress, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) { + // let tokenDispatcher = IERC20Dispatcher { contract_address: token }; + // tokenDispatcher.transfer_from(sender, recipient, amount) // TODO dispatcher with error handling + + let mut calldata = Default::default(); + Serde::serialize(@sender, ref calldata); + Serde::serialize(@recipient, ref calldata); + Serde::serialize(@amount, ref calldata); + + let selector_for_transfer_from = 1555377517929037318987687899825758707538299441176447799544473656894800517992; + let selector_for_transferFrom = 116061167288211781254449158074459916871457383008289084697957612485591092000; + + let mut result = call_contract_syscall(token, selector_for_transfer_from, calldata.span()); + if (result.is_err()) { + result = call_contract_syscall(token, selector_for_transferFrom, calldata.span()); + } + result.unwrap_syscall(); // Additional error handling + } + + fn _sort_tokens( + tokenA: ContractAddress, tokenB: ContractAddress + ) -> (ContractAddress, ContractAddress) { + assert(tokenA != tokenB, 'must not be identical'); + let mut token0: ContractAddress = contract_address_const::<0>(); + let mut token1: ContractAddress = contract_address_const::<0>(); + if u256_from_felt252( + contract_address_to_felt252(tokenA) + ) < u256_from_felt252( + contract_address_to_felt252(tokenB) + ) { // TODO token comparison directly + token0 = tokenA; + token1 = tokenB; + } else { + token0 = tokenB; + token1 = tokenA; + } + + assert(!token0.is_zero(), 'must be non zero'); + (token0, token1) + } + + fn _pair_for(factory: ContractAddress, tokenA: ContractAddress, tokenB: ContractAddress) -> ContractAddress { + let (token0, token1) = _sort_tokens(tokenA, tokenB); + let factoryDispatcher = IFactoryDispatcher { contract_address: factory }; + let pair = factoryDispatcher.get_pair(token0, token1); + pair + } + + fn _get_reserves(factory: ContractAddress, tokenA: ContractAddress, tokenB: ContractAddress) -> (u256, u256) { + let (token0, _) = _sort_tokens(tokenA, tokenB); + let pair = _pair_for(factory, tokenA, tokenB); + let pairDispatcher = IPairDispatcher { contract_address: pair }; + let (reserve0, reserve1, _) = pairDispatcher.get_reserves(); + if (tokenA == token0) { + return (reserve0, reserve1); + } else { + return (reserve1, reserve0); + } + } + + fn _quote(amountA: u256, reserveA: u256, reserveB: u256) -> u256 { + assert(amountA > 0.into(), 'insufficient amount'); + assert(reserveA > 0.into() && reserveB > 0.into(), 'insufficient liquidity'); + + let amountB = (amountA * reserveB) / reserveA; + amountB + } + + fn _get_amount_out(amountIn: u256, reserveIn: u256, reserveOut: u256) -> u256 { + assert(amountIn > 0.into(), 'insufficient input amount'); + assert(reserveIn > 0.into() && reserveOut > 0.into(), 'insufficient liquidity'); + + let amountIn_with_fee = amountIn * 997.into(); + let numerator = amountIn_with_fee * reserveOut; + let denominator = (reserveIn * 1000.into()) + amountIn_with_fee; + + numerator / denominator + } + + fn _get_amount_in(amountOut: u256, reserveIn: u256, reserveOut: u256) -> u256 { + assert(amountOut > 0.into(), 'insufficient output amount'); + assert(reserveIn > 0.into() && reserveOut > 0.into(), 'insufficient liquidity'); + + let numerator = reserveIn * amountOut * 1000.into(); + let denominator = (reserveOut - amountOut) * 997.into(); + + (numerator / denominator) + 1.into() + } + + fn _get_amounts_out(factory: ContractAddress, amountIn: u256, path: Span::) -> Array:: { + assert(path.len() >= 2, 'invalid path'); + let mut amounts = ArrayTrait::::new(); + amounts.append(amountIn); + let mut current_index = 0; + loop { + if (current_index == path.len() - 1) { + break true; + } + let (reserveIn, reserveOut) = _get_reserves(factory, *path[current_index], *path[current_index + 1]); + amounts.append(_get_amount_out(*amounts[current_index], reserveIn, reserveOut)); + current_index += 1; + }; + amounts + } + + fn _get_amounts_in(factory: ContractAddress, amountOut: u256, path: Span::) -> Array:: { + assert(path.len() >= 2, 'invalid path'); + let mut amounts = ArrayTrait::::new(); + amounts.append(amountOut); + let mut current_index = path.len() - 1; + loop { + if (current_index == 0) { + break true; + } + let (reserveIn, reserveOut) = _get_reserves(factory, *path[current_index - 1], *path[current_index]); + amounts + .append( + _get_amount_in(*amounts[amounts.len() - 1], reserveIn, reserveOut) + ); + current_index -= 1; + }; + let mut final_amounts = ArrayTrait::::new(); + current_index = 0; + loop { // reversing array, TODO remove when set comes. + if (current_index == amounts.len()) { + break true; + } + final_amounts.append(*amounts[amounts.len() - 1 - current_index]); + current_index += 1; + }; + final_amounts + } +} diff --git a/src/interfaces/ownable_interface.cairo b/src/interfaces/ownable_interface.cairo new file mode 100644 index 0000000..8b0c092 --- /dev/null +++ b/src/interfaces/ownable_interface.cairo @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.8.0 (access/ownable/interface.cairo) + +use starknet::ContractAddress; + +#[starknet::interface] +trait IOwnable { + fn owner(self: @TState) -> ContractAddress; + fn transfer_ownership(ref self: TState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TState); +} + +#[starknet::interface] +trait IOwnableCamelOnly { + fn transferOwnership(ref self: TState, newOwner: ContractAddress); + fn renounceOwnership(ref self: TState); +} \ No newline at end of file diff --git a/src/lib.cairo b/src/lib.cairo new file mode 100644 index 0000000..b750402 --- /dev/null +++ b/src/lib.cairo @@ -0,0 +1,28 @@ +mod contracts{ + mod RouterC1V2; + mod PairC1; + mod FactoryC1; +} + +mod interfaces{ + mod ownable_interface; +} + +mod utils{ + mod reflect; + mod erc20; + mod ownable; + mod FlashSwapTest; +} + +#[cfg(test)] +mod tests{ + mod test_swap; + mod utils; + mod test_add_remove_liquidity; + mod test_create_pair; + mod test_deployment; + mod test_updates; + mod test_flash_swap; + mod test_protocol_fees; +} \ No newline at end of file diff --git a/src/tests/test_add_remove_liquidity.cairo b/src/tests/test_add_remove_liquidity.cairo new file mode 100644 index 0000000..f2eba11 --- /dev/null +++ b/src/tests/test_add_remove_liquidity.cairo @@ -0,0 +1 @@ +mod test_add_remove_liquidity; \ No newline at end of file diff --git a/src/tests/test_add_remove_liquidity/test_add_remove_liquidity.cairo b/src/tests/test_add_remove_liquidity/test_add_remove_liquidity.cairo new file mode 100644 index 0000000..5f43b16 --- /dev/null +++ b/src/tests/test_add_remove_liquidity/test_add_remove_liquidity.cairo @@ -0,0 +1,344 @@ +use core::result::ResultTrait; +use starknet:: { ContractAddress, ClassHash }; +use snforge_std::{ declare, ContractClassTrait, ContractClass, start_warp, start_prank, stop_prank, + spy_events, CheatTarget, SpyOn, EventSpy, EventFetcher, Event, EventAssertions }; + +use jediswap::tests::utils::utils::{ deployer_addr, token0, token1, burn_addr, user1, TOKEN_MULTIPLIER, TOKEN0_NAME, + TOKEN1_NAME, SYMBOL, MINIMUM_LIQUIDITY }; + +#[starknet::interface] +trait IERC20 { + // view functions + fn name(self: @TContractState) -> felt252; + fn symbol(self: @TContractState) -> felt252; + fn decimals(self: @TContractState) -> u8; + fn total_supply(self: @TContractState) -> u256; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + // external functions + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; + fn mint(ref self: TContractState, recipient: ContractAddress, amount: u256); +} + +#[starknet::interface] +trait IFactoryC1 { + // view functions + fn get_pair(self: @T, token0: ContractAddress, token1: ContractAddress) -> ContractAddress; + fn get_all_pairs(self: @T) -> (u32, Array::); + // external functions + fn create_pair(ref self: T, tokenA: ContractAddress, tokenB: ContractAddress) -> ContractAddress; +} + +#[starknet::interface] +trait IPairC1 { + // view functions + fn balance_of(self: @T, account: ContractAddress) -> u256; + fn get_reserves(self: @T) -> (u256, u256, u64); + fn total_supply(self: @T) -> u256; + // external functions + fn approve(ref self: T, spender: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +trait IRouterC1 { + // view functions + fn factory(self: @T) -> ContractAddress; + fn sort_tokens(self: @T, tokenA: ContractAddress, tokenB: ContractAddress) -> (ContractAddress, ContractAddress); + fn quote(self: @T, amountA: u256, reserveA: u256, reserveB: u256) -> u256; + fn get_amount_out(self: @T, amountIn: u256, reserveIn: u256, reserveOut: u256) -> u256; + fn get_amount_in(self: @T, amountOut: u256, reserveIn: u256, reserveOut: u256) -> u256; + fn get_amounts_out(self: @T, amountIn: u256, path: Array::) -> Array::; + fn get_amounts_in(self: @T, amountOut: u256, path: Array::) -> Array::; + // external functions + fn add_liquidity(ref self: T, tokenA: ContractAddress, tokenB: ContractAddress, amountADesired: u256, amountBDesired: u256, amountAMin: u256, amountBMin: u256, to: ContractAddress, deadline: u64) -> (u256, u256, u256); + fn remove_liquidity(ref self: T, tokenA: ContractAddress, tokenB: ContractAddress, liquidity: u256, amountAMin: u256, amountBMin: u256, to: ContractAddress, deadline: u64) -> (u256, u256); + fn swap_exact_tokens_for_tokens(ref self: T, amountIn: u256, amountOutMin: u256, path: Array::, to: ContractAddress, deadline: u64) -> Array::; + fn swap_tokens_for_exact_tokens(ref self: T, amountOut: u256, amountInMax: u256, path: Array::, to: ContractAddress, deadline: u64) -> Array::; + fn replace_implementation_class(ref self: T, new_implementation_class: ClassHash); +} + +fn deploy_contracts() -> (ContractAddress, ContractAddress) { + let pair_class = declare("PairC1").unwrap(); + + let mut factory_constructor_calldata = Default::default(); + Serde::serialize(@pair_class.class_hash, ref factory_constructor_calldata); + Serde::serialize(@deployer_addr(), ref factory_constructor_calldata); + let factory_class = declare("FactoryC1").unwrap(); + + let (factory_address, _) = factory_class.deploy(@factory_constructor_calldata).unwrap(); + + let mut router_constructor_calldata = Default::default(); + Serde::serialize(@factory_address, ref router_constructor_calldata); + let router_class = declare("RouterC1").unwrap(); + + let (router_address, _) = router_class.deploy(@router_constructor_calldata).unwrap(); + + (factory_address, router_address) +} + +fn deploy_erc20(initial_supply: u256) -> (ContractAddress, ContractAddress) { + let erc20_class = declare("ERC20").unwrap(); + + let mut token0_constructor_calldata = Default::default(); + Serde::serialize(@TOKEN0_NAME, ref token0_constructor_calldata); + Serde::serialize(@SYMBOL, ref token0_constructor_calldata); + Serde::serialize(@initial_supply, ref token0_constructor_calldata); + Serde::serialize(@user1(), ref token0_constructor_calldata); + let (token0_address, _) = erc20_class.deploy(@token0_constructor_calldata).unwrap(); + + let mut token1_constructor_calldata = Default::default(); + Serde::serialize(@TOKEN1_NAME, ref token1_constructor_calldata); + Serde::serialize(@SYMBOL, ref token1_constructor_calldata); + Serde::serialize(@initial_supply, ref token1_constructor_calldata); + Serde::serialize(@user1(), ref token1_constructor_calldata); + let (token1_address, _) = erc20_class.deploy(@token1_constructor_calldata).unwrap(); + + (token0_address, token1_address) +} + +#[test] +#[should_panic(expected: ('expired',))] +#[ignore] +fn test_add_liquidity_expired_deadline() { + let (_, router_address) = deploy_contracts(); + let router_safe_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let token_amount: u256 = 1; + let min_amount: u256 = 1; + let deadline: u64 = 0; + + start_prank(CheatTarget::One(router_address), user1()); + let (_amountA, _amountB, _liquidity) = router_safe_dispatcher.add_liquidity(token0(), token1(), token_amount, token_amount, + min_amount, min_amount, user1(), deadline); + stop_prank(CheatTarget::One(router_address)); + +} + +#[test] +fn test_add_remove_liquidity_created_pair(){ + // Setup + + let (factory_address, router_address) = deploy_contracts(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let initial_supply: u256 = 100 * TOKEN_MULTIPLIER; + let (token0_address, token1_address) = deploy_erc20(initial_supply); + let (sorted_token0_address, sorted_token1_address) = router_dispatcher.sort_tokens(token0_address, token1_address); + let pair_address = factory_dispatcher.create_pair(sorted_token0_address, sorted_token1_address); + let pair_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let (token0_address, token1_address) = (sorted_token0_address, sorted_token1_address); + let token0_erc20_dispatcher = IERC20Dispatcher { contract_address: token0_address }; + let token1_erc20_dispatcher = IERC20Dispatcher { contract_address: token1_address }; + + let amount_token0: u256 = 2 * TOKEN_MULTIPLIER; + let amount_token1: u256 = 4 * TOKEN_MULTIPLIER; + + // Add liquidity for first time + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.approve(router_address, amount_token0); + stop_prank(CheatTarget::One(token0_address)); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.approve(router_address, amount_token1); + stop_prank(CheatTarget::One(token1_address)); + + start_prank(CheatTarget::One(pair_address), user1()); + start_prank(CheatTarget::One(router_address), user1()); + let (amountA, amountB, _liquidity) = router_dispatcher.add_liquidity( + token0_address, token1_address, amount_token0, amount_token1, 1, 1, user1(), 0); + stop_prank(CheatTarget::One(pair_address)); + stop_prank(CheatTarget::One(router_address)); + + assert(amountA == amount_token0, 'amountA should be equal'); + assert(amountB == amount_token1, 'amountB should be equal'); + + + let (reserve_0, reserve_1, _) = pair_dispatcher.get_reserves(); + let totalSupply: u256 = pair_dispatcher.total_supply(); + + let totalSupply_mul_totalSupply: u256 = totalSupply * totalSupply; + let reserve_0_mul_reserve_1: u256 = reserve_0 * reserve_1; + assert(totalSupply_mul_totalSupply <= reserve_0_mul_reserve_1, 'totalSupply_mul less or equal'); + + let totalSupply1_mul_totalSupply1: u256 = (totalSupply + 1_u256) * (totalSupply + 1_u256); + assert(totalSupply1_mul_totalSupply1 > reserve_0_mul_reserve_1, 'totalSupply_mul greater'); + + // Add liquidity to pair which already has liquidity + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.approve(router_address, amount_token0); + stop_prank(CheatTarget::One(token0_address)); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.approve(router_address, amount_token1); + stop_prank(CheatTarget::One(token1_address)); + + start_prank(CheatTarget::One(pair_address), user1()); + start_prank(CheatTarget::One(router_address), user1()); + let (amountA, amountB, _liquidity) = router_dispatcher.add_liquidity( + token0_address, token1_address, amount_token0, amount_token1, 1, 1, user1(), 0); + stop_prank(CheatTarget::One(pair_address)); + stop_prank(CheatTarget::One(router_address)); + + assert(amountA == amount_token0, 'amountA again should be equal'); + assert(amountB == amount_token1, 'amountB again should be equal'); + + let (reserve_0, reserve_1, _) = pair_dispatcher.get_reserves(); + let totalSupply: u256 = pair_dispatcher.total_supply(); + + let totalSupply_mul_totalSupply: u256 = totalSupply * totalSupply; + let reserve_0_mul_reserve_1: u256 = reserve_0 * reserve_1; + assert(totalSupply_mul_totalSupply <= reserve_0_mul_reserve_1, 'totalSupply_mul again less'); + + let user_1_token_0_balance: u256 = token0_erc20_dispatcher.balance_of(user1()); + let expected_reserve_0: u256 = initial_supply - user_1_token_0_balance; + assert(expected_reserve_0 == reserve_0, 'reserve_0 should be equal'); + + let user_1_token_1_balance: u256 = token1_erc20_dispatcher.balance_of(user1()); + let expected_reserve_1: u256 = initial_supply - user_1_token_1_balance; + assert(expected_reserve_1 == reserve_1, 'reserve_1 should be equal'); + + let user_1_pair_balance: u256 = pair_dispatcher.balance_of(user1()); + let expected_total_supply: u256 = MINIMUM_LIQUIDITY + user_1_pair_balance; + assert(expected_total_supply == totalSupply, 'totalSupply should be equal'); + + // Remove liquidity + start_prank(CheatTarget::One(pair_address), user1()); + pair_dispatcher.approve(router_address, user_1_pair_balance); + stop_prank(CheatTarget::One(pair_address)); + + start_prank(CheatTarget::One(router_address), user1()); + let (_amountA_burn, _amountB_burn) = router_dispatcher.remove_liquidity( + token0_address, token1_address, user_1_pair_balance, 1, 1, user1(), 0); + stop_prank(CheatTarget::One(router_address)); + + let user_1_pair_balance_burn: u256 = pair_dispatcher.balance_of(user1()); + assert(user_1_pair_balance_burn == 0, 'user balance should be zero'); + + let totalSupply_burn: u256 = pair_dispatcher.total_supply(); + assert(totalSupply_burn == MINIMUM_LIQUIDITY, 'totalSupply should be min liq'); + + let burn_address_balance: u256 = pair_dispatcher.balance_of(burn_addr()); + assert(totalSupply_burn == burn_address_balance, 'totalSupply == burn balance'); + + let (reserve_0_burn, reserve_1_burn, _) = pair_dispatcher.get_reserves(); + let totalSupply_mul_totalSupply_burn: u256 = totalSupply_burn * totalSupply_burn; + let reserve_0_mul_reserve_1_burn: u256 = reserve_0_burn * reserve_1_burn; + assert(totalSupply_mul_totalSupply_burn <= reserve_0_mul_reserve_1_burn, 'totalSupply mul <= reserve'); +} + + +#[test] +fn test_add_remove_liquidity_for_non_created_pair(){ + // Setup + + let (factory_address, router_address) = deploy_contracts(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let initial_supply: u256 = 100 * TOKEN_MULTIPLIER; + let (token0_address, token1_address) = deploy_erc20(initial_supply); + let (sorted_token0_address, sorted_token1_address) = router_dispatcher.sort_tokens(token0_address, token1_address); + + let (token0_address, token1_address) = (sorted_token0_address, sorted_token1_address); + let token0_erc20_dispatcher = IERC20Dispatcher { contract_address: token0_address }; + let token1_erc20_dispatcher = IERC20Dispatcher { contract_address: token1_address }; + + let amount_token0: u256 = 2 * TOKEN_MULTIPLIER; + let amount_token1: u256 = 4 * TOKEN_MULTIPLIER; + + // Add liquidity for first time + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.approve(router_address, amount_token0); + stop_prank(CheatTarget::One(token0_address)); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.approve(router_address, amount_token1); + stop_prank(CheatTarget::One(token1_address)); + + start_prank(CheatTarget::One(router_address), user1()); + let (amountA, amountB, _liquidity) = router_dispatcher.add_liquidity( + token0_address, token1_address, amount_token0, amount_token1, 1, 1, user1(), 0); + stop_prank(CheatTarget::One(router_address)); + + assert(amountA == amount_token0, 'amountA again should be equal'); + assert(amountB == amount_token1, 'amountB again should be equal'); + + let pair_address: ContractAddress = factory_dispatcher.get_pair(token1_address, token0_address); + let pair_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let (reserve_0, reserve_1, _) = pair_dispatcher.get_reserves(); + let totalSupply: u256 = pair_dispatcher.total_supply(); + + let totalSupply_mul_totalSupply: u256 = totalSupply * totalSupply; + let reserve_0_mul_reserve_1: u256 = reserve_0 * reserve_1; + assert(totalSupply_mul_totalSupply <= reserve_0_mul_reserve_1, 'totalSupply_mul less or equal'); + + let totalSupply1_mul_totalSupply1: u256 = (totalSupply + 1_u256) * (totalSupply + 1_u256); + assert(totalSupply1_mul_totalSupply1 > reserve_0_mul_reserve_1, 'totalSupply_mul greater'); + + // Add liquidity to pair which already has liquidity + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.approve(router_address, amount_token0); + stop_prank(CheatTarget::One(token0_address)); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.approve(router_address, amount_token1); + stop_prank(CheatTarget::One(token1_address)); + + start_prank(CheatTarget::One(pair_address), user1()); + start_prank(CheatTarget::One(router_address), user1()); + let (amountA, amountB, _liquidity) = router_dispatcher.add_liquidity( + token0_address, token1_address, amount_token0, amount_token1, 1, 1, user1(), 0); + stop_prank(CheatTarget::One(pair_address)); + stop_prank(CheatTarget::One(router_address)); + + assert(amountA == amount_token0, 'amountA again should be equal'); + assert(amountB == amount_token1, 'amountB again should be equal'); + + let (reserve_0, reserve_1, _) = pair_dispatcher.get_reserves(); + let totalSupply: u256 = pair_dispatcher.total_supply(); + + let totalSupply_mul_totalSupply: u256 = totalSupply * totalSupply; + let reserve_0_mul_reserve_1: u256 = reserve_0 * reserve_1; + assert(totalSupply_mul_totalSupply <= reserve_0_mul_reserve_1, 'totalSupply_mul again less'); + + let user_1_token_0_balance: u256 = token0_erc20_dispatcher.balance_of(user1()); + let expected_reserve_0: u256 = initial_supply - user_1_token_0_balance; + assert(expected_reserve_0 == reserve_0, 'reserve_0 should be equal'); + + let user_1_token_1_balance: u256 = token1_erc20_dispatcher.balance_of(user1()); + let expected_reserve_1: u256 = initial_supply - user_1_token_1_balance; + assert(expected_reserve_1 == reserve_1, 'reserve_1 should be equal'); + + let user_1_pair_balance: u256 = pair_dispatcher.balance_of(user1()); + let expected_total_supply: u256 = MINIMUM_LIQUIDITY + user_1_pair_balance; + assert(expected_total_supply == totalSupply, 'totalSupply should be equal'); + + // Remove liquidity + start_prank(CheatTarget::One(pair_address), user1()); + pair_dispatcher.approve(router_address, user_1_pair_balance); + stop_prank(CheatTarget::One(pair_address)); + + start_prank(CheatTarget::One(router_address), user1()); + let (_amountA_burn, _amountB_burn) = router_dispatcher.remove_liquidity( + token0_address, token1_address, user_1_pair_balance, 1, 1, user1(), 0); + stop_prank(CheatTarget::One(router_address)); + + let user_1_pair_balance_burn: u256 = pair_dispatcher.balance_of(user1()); + assert(user_1_pair_balance_burn == 0, 'user balance should be zero'); + + let totalSupply_burn: u256 = pair_dispatcher.total_supply(); + assert(totalSupply_burn == MINIMUM_LIQUIDITY, 'totalSupply should be min liq'); + + let burn_address_balance: u256 = pair_dispatcher.balance_of(burn_addr()); + assert(totalSupply_burn == burn_address_balance, 'totalSupply == burn balance'); + + let (reserve_0_burn, reserve_1_burn, _) = pair_dispatcher.get_reserves(); + let totalSupply_mul_totalSupply_burn: u256 = totalSupply_burn * totalSupply_burn; + let reserve_0_mul_reserve_1_burn: u256 = reserve_0_burn * reserve_1_burn; + assert(totalSupply_mul_totalSupply_burn <= reserve_0_mul_reserve_1_burn, 'totalSupply mul <= reserve'); +} + +//todo: use spy_events. \ No newline at end of file diff --git a/src/tests/test_create_pair.cairo b/src/tests/test_create_pair.cairo new file mode 100644 index 0000000..dce6923 --- /dev/null +++ b/src/tests/test_create_pair.cairo @@ -0,0 +1 @@ +mod test_create_pair; \ No newline at end of file diff --git a/src/tests/test_create_pair/test_create_pair.cairo b/src/tests/test_create_pair/test_create_pair.cairo new file mode 100644 index 0000000..1bf178d --- /dev/null +++ b/src/tests/test_create_pair/test_create_pair.cairo @@ -0,0 +1,100 @@ +use starknet:: { ContractAddress, ClassHash }; +use snforge_std::{ declare, get_class_hash, ContractClassTrait, ContractClass }; + +use jediswap::tests::utils::utils::{ deployer_addr, token0, token1, zero_addr }; + +#[starknet::interface] +trait IFactoryC1 { + // view functions + fn get_pair(self: @T, token0: ContractAddress, token1: ContractAddress) -> ContractAddress; + fn get_all_pairs(self: @T) -> (u32, Array::); + fn get_num_of_pairs(self: @T) -> u32; + fn get_fee_to(self: @T) -> ContractAddress; + fn get_fee_to_setter(self: @T) -> ContractAddress; + fn get_pair_contract_class_hash(self: @T) -> ClassHash; + // external functions + fn create_pair(ref self: T, tokenA: ContractAddress, tokenB: ContractAddress) -> ContractAddress; + fn set_fee_to(ref self: T, new_fee_to: ContractAddress); + fn set_fee_to_setter(ref self: T, new_fee_to_setter: ContractAddress); + fn replace_implementation_class(ref self: T, new_implementation_class: ClassHash); + fn replace_pair_contract_hash(ref self: T, new_pair_contract_class: ClassHash); +} + +#[starknet::interface] +trait IRouterC1 { + // view functions + fn factory(self: @T) -> ContractAddress; + fn sort_tokens(self: @T, tokenA: ContractAddress, tokenB: ContractAddress) -> (ContractAddress, ContractAddress); + fn quote(self: @T, amountA: u256, reserveA: u256, reserveB: u256) -> u256; + fn get_amount_out(self: @T, amountIn: u256, reserveIn: u256, reserveOut: u256) -> u256; + fn get_amount_in(self: @T, amountOut: u256, reserveIn: u256, reserveOut: u256) -> u256; + fn get_amounts_out(self: @T, amountIn: u256, path: Array::) -> Array::; + fn get_amounts_in(self: @T, amountOut: u256, path: Array::) -> Array::; + // external functions + fn add_liquidity(ref self: T, tokenA: ContractAddress, tokenB: ContractAddress, amountADesired: u256, amountBDesired: u256, amountAMin: u256, amountBMin: u256, to: ContractAddress, deadline: u64) -> (u256, u256, u256); + fn remove_liquidity(ref self: T, tokenA: ContractAddress, tokenB: ContractAddress, liquidity: u256, amountAMin: u256, amountBMin: u256, to: ContractAddress, deadline: u64) -> (u256, u256); + fn swap_exact_tokens_for_tokens(ref self: T, amountIn: u256, amountOutMin: u256, path: Array::, to: ContractAddress, deadline: u64) -> Array::; + fn swap_tokens_for_exact_tokens(ref self: T, amountOut: u256, amountInMax: u256, path: Array::, to: ContractAddress, deadline: u64) -> Array::; + fn replace_implementation_class(ref self: T, new_implementation_class: ClassHash); +} + +fn deploy_factory(pair_class: ContractClass) -> ContractAddress { + let mut factory_constructor_calldata = Default::default(); + Serde::serialize(@pair_class.class_hash, ref factory_constructor_calldata); + Serde::serialize(@deployer_addr(), ref factory_constructor_calldata); + let factory_class = declare("FactoryC1").unwrap(); + + let (factory_address, _) = factory_class.deploy(@factory_constructor_calldata).unwrap(); + factory_address +} + +#[test] +#[should_panic(expected: ('must be non zero', ))] +fn test_create_pair_without_tokens() { + let pair_class = declare("PairC1").unwrap(); + let factory_address = deploy_factory(pair_class); + + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + + factory_dispatcher.create_pair(zero_addr(), zero_addr()); + factory_dispatcher.create_pair(token0(), zero_addr()); + factory_dispatcher.create_pair(zero_addr(), token0()); +} + +#[test] +#[should_panic(expected: ('must be different', ))] +fn test_create_pair_same_tokens() { + let pair_class = declare("PairC1").unwrap(); + let factory_address = deploy_factory(pair_class); + + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + + factory_dispatcher.create_pair(token0(), token0()); +} + +#[test] +#[should_panic(expected: ('pair already exists', ))] +fn test_create_pair_same_pair() { + let pair_class = declare("PairC1").unwrap(); + let factory_address = deploy_factory(pair_class); + + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + + let pair_address = factory_dispatcher.create_pair(token0(), token1()); + assert(pair_address != zero_addr(), 'result shouldn`t be 0'); + + let _pair_address = factory_dispatcher.create_pair(token0(), token1()); + // let _pair_address = factory_dispatcher.create_pair(token1(), token0()); +} + +#[test] +fn test_create2_deployed_pair() { + let pair_class = declare("PairC1").unwrap(); + let pair_class_class_hash = pair_class.class_hash; + let factory_address = deploy_factory(pair_class); + + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + + let pair_address = factory_dispatcher.create_pair(token0(), token1()); + assert(pair_class_class_hash == get_class_hash(pair_address), 'Incorrect class hash'); +} \ No newline at end of file diff --git a/src/tests/test_deployment.cairo b/src/tests/test_deployment.cairo new file mode 100644 index 0000000..bb15974 --- /dev/null +++ b/src/tests/test_deployment.cairo @@ -0,0 +1 @@ +mod test_deployment; \ No newline at end of file diff --git a/src/tests/test_deployment/test_deployment.cairo b/src/tests/test_deployment/test_deployment.cairo new file mode 100644 index 0000000..17ae36e --- /dev/null +++ b/src/tests/test_deployment/test_deployment.cairo @@ -0,0 +1,109 @@ +use starknet:: { ContractAddress, ClassHash }; +use snforge_std::{ declare, ContractClassTrait }; + +use jediswap::tests::utils::utils::{ deployer_addr, token0, token1 }; + +#[starknet::interface] +trait IRouterC1 { + // view functions + fn factory(self: @T) -> ContractAddress; + fn sort_tokens(self: @T, tokenA: ContractAddress, tokenB: ContractAddress) -> (ContractAddress, ContractAddress); +} + +#[starknet::interface] +trait IPairC1 { + // view functions + fn name(self: @T) -> felt252; + fn symbol(self: @T) -> felt252; + fn decimals(self: @T) -> u8; +} + +#[starknet::interface] +trait IFactoryC1 { + // view functions + fn get_pair(self: @T, token0: ContractAddress, token1: ContractAddress) -> ContractAddress; + fn get_all_pairs(self: @T) -> (u32, Array::); + // external functions + fn create_pair(ref self: T, tokenA: ContractAddress, tokenB: ContractAddress) -> ContractAddress; +} + +#[test] +fn test_deployment_pair_factory_router() { + // TODO Separate out once setup is available. + let pair_class = declare("PairC1").unwrap(); + + let mut factory_constructor_calldata = Default::default(); + Serde::serialize(@pair_class.class_hash, ref factory_constructor_calldata); + Serde::serialize(@deployer_addr(), ref factory_constructor_calldata); + let factory_class = declare("FactoryC1").unwrap(); + let (factory_address, _) = factory_class.deploy(@factory_constructor_calldata).unwrap(); + + let mut router_constructor_calldata = Default::default(); + Serde::serialize(@factory_address, ref router_constructor_calldata); + let router_class = declare("RouterC1").unwrap(); + let (router_address, _) = router_class.deploy(@router_constructor_calldata).unwrap(); + + // Create a Dispatcher object that will allow interacting with the deployed contract + let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let result = router_dispatcher.factory(); + assert(result == factory_address, 'Invalid Factory'); +} + +#[test] +fn test_pair() { + let pair_class = declare("PairC1").unwrap(); + + let mut factory_constructor_calldata = Default::default(); + Serde::serialize(@pair_class.class_hash, ref factory_constructor_calldata); + Serde::serialize(@deployer_addr(), ref factory_constructor_calldata); + let factory_class = declare("FactoryC1").unwrap(); + let (factory_address, _) = factory_class.deploy(@factory_constructor_calldata).unwrap(); + + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + + let pair_address: ContractAddress = factory_dispatcher.create_pair(token0(), token1()); + let pair_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let name: felt252 = pair_dispatcher.name(); + assert(name == 'JediSwap Pair', 'Invalid name'); + + let symbol: felt252 = pair_dispatcher.symbol(); + assert(symbol == 'JEDI-P', 'Invalid symbol'); + + let decimals: u8 = pair_dispatcher.decimals(); + assert(decimals == 18, 'Invalid decimals'); +} + +#[test] +fn test_pair_in_factory() { + let pair_class = declare("PairC1").unwrap(); + + let mut factory_constructor_calldata = Default::default(); + Serde::serialize(@pair_class.class_hash, ref factory_constructor_calldata); + Serde::serialize(@deployer_addr(), ref factory_constructor_calldata); + let factory_class = declare("FactoryC1").unwrap(); + let (factory_address, _) = factory_class.deploy(@factory_constructor_calldata).unwrap(); + + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + + let mut router_constructor_calldata = Default::default(); + Serde::serialize(@factory_address, ref router_constructor_calldata); + let router_class = declare("RouterC1").unwrap(); + let (router_address, _) = router_class.deploy(@router_constructor_calldata).unwrap(); + + let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let (token0_address, token1_address) = router_dispatcher.sort_tokens(token0(), token1()); + let pair_address = factory_dispatcher.create_pair(token0_address, token1_address); + + let pair_address_from_factory_1: ContractAddress = factory_dispatcher.get_pair(token0_address, token1_address); + assert(pair_address == pair_address_from_factory_1, 'Invalid pair 1 address'); + + let pair_address_from_factory_2: ContractAddress = factory_dispatcher.get_pair(token1_address, token0_address); + assert(pair_address == pair_address_from_factory_2, 'Invalid pair 2 address'); + + let (pairs_length, pairs) = factory_dispatcher.get_all_pairs(); + assert(pairs_length == 1, 'Invalid pairs length'); + assert(*pairs[0] == pair_address, 'Invalid pair address'); +} \ No newline at end of file diff --git a/src/tests/test_flash_swap.cairo b/src/tests/test_flash_swap.cairo new file mode 100644 index 0000000..e67d412 --- /dev/null +++ b/src/tests/test_flash_swap.cairo @@ -0,0 +1 @@ +mod test_flash_swap; \ No newline at end of file diff --git a/src/tests/test_flash_swap/test_flash_swap.cairo b/src/tests/test_flash_swap/test_flash_swap.cairo new file mode 100644 index 0000000..390067a --- /dev/null +++ b/src/tests/test_flash_swap/test_flash_swap.cairo @@ -0,0 +1,533 @@ +use starknet:: { ContractAddress, ClassHash, contract_address_try_from_felt252 }; +use snforge_std::{ declare, ContractClassTrait, ContractClass, start_prank, stop_prank, + spy_events,CheatTarget, SpyOn, EventSpy, EventFetcher, Event, EventAssertions }; + +use jediswap::tests::utils::utils::{ deployer_addr, user1, user2, TOKEN_MULTIPLIER, TOKEN0_NAME, + TOKEN1_NAME, SYMBOL }; + +#[starknet::interface] +trait IERC20 { + // view functions + fn name(self: @TContractState) -> felt252; + fn symbol(self: @TContractState) -> felt252; + fn decimals(self: @TContractState) -> u8; + fn total_supply(self: @TContractState) -> u256; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + // external functions + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +trait IFactoryC1 { + // view functions + fn get_pair(self: @T, token0: ContractAddress, token1: ContractAddress) -> ContractAddress; + fn get_all_pairs(self: @T) -> (u32, Array::); + fn create_pair(ref self: T, tokenA: ContractAddress, tokenB: ContractAddress) -> ContractAddress; +} + +#[starknet::interface] +trait IPairC1 { + // view functions + fn get_reserves(self: @T) -> (u256, u256, u64); + // external functions + fn swap(ref self: T, amount0Out: u256, amount1Out: u256, to: ContractAddress, data: Array::); +} + +#[starknet::interface] +trait IRouterC1 { + // view functions + fn factory(self: @T) -> ContractAddress; + fn sort_tokens(self: @T, tokenA: ContractAddress, tokenB: ContractAddress) -> (ContractAddress, ContractAddress); + // external functions + fn add_liquidity(ref self: T, tokenA: ContractAddress, tokenB: ContractAddress, amountADesired: u256, amountBDesired: u256, amountAMin: u256, amountBMin: u256, to: ContractAddress, deadline: u64) -> (u256, u256, u256); + fn remove_liquidity(ref self: T, tokenA: ContractAddress, tokenB: ContractAddress, liquidity: u256, amountAMin: u256, amountBMin: u256, to: ContractAddress, deadline: u64) -> (u256, u256); + fn swap_exact_tokens_for_tokens(ref self: T, amountIn: u256, amountOutMin: u256, path: Array::, to: ContractAddress, deadline: u64) -> Array::; + fn swap_tokens_for_exact_tokens(ref self: T, amountOut: u256, amountInMax: u256, path: Array::, to: ContractAddress, deadline: u64) -> Array::; +} + +#[starknet::interface] +trait IFlashSwapTest { + // external functions + fn jediswap_call(ref self: T, sender: ContractAddress, amount0Out: u256, amount1Out: u256, data: Array::); +} + +fn fee_recipient() -> ContractAddress { + contract_address_try_from_felt252('fee recipient').unwrap() +} + + +fn deploy_erc20(erc20_amount_per_user: u256) -> (ContractAddress, ContractAddress) { + let erc20_class = declare("ERC20").unwrap(); + let initial_supply: u256 = erc20_amount_per_user * 2; + + let mut token0_constructor_calldata = Default::default(); + Serde::serialize(@TOKEN0_NAME, ref token0_constructor_calldata); + Serde::serialize(@SYMBOL, ref token0_constructor_calldata); + Serde::serialize(@initial_supply, ref token0_constructor_calldata); + Serde::serialize(@user1(), ref token0_constructor_calldata); + let (token0_address, _) = erc20_class.deploy(@token0_constructor_calldata).unwrap(); + + let mut token1_constructor_calldata = Default::default(); + Serde::serialize(@TOKEN1_NAME, ref token1_constructor_calldata); + Serde::serialize(@SYMBOL, ref token1_constructor_calldata); + Serde::serialize(@initial_supply, ref token1_constructor_calldata); + Serde::serialize(@user1(), ref token1_constructor_calldata); + let (token1_address, _) = erc20_class.deploy(@token1_constructor_calldata).unwrap(); + + (token0_address, token1_address) +} + + +fn deploy_contracts() -> (ContractAddress, ContractAddress) { + let pair_class = declare("PairC1").unwrap(); + + let mut factory_constructor_calldata = Default::default(); + Serde::serialize(@pair_class.class_hash, ref factory_constructor_calldata); + Serde::serialize(@deployer_addr(), ref factory_constructor_calldata); + let factory_class = declare("FactoryC1").unwrap(); + let (factory_address, _) = factory_class.deploy(@factory_constructor_calldata).unwrap(); + + let mut router_constructor_calldata = Default::default(); + Serde::serialize(@factory_address, ref router_constructor_calldata); + let router_class = declare("RouterC1").unwrap(); + let (router_address, _) = router_class.deploy(@router_constructor_calldata).unwrap(); + + (factory_address, router_address) +} + + +#[test] +#[should_panic(expected: ('insufficient liquidity',))] +fn test_flash_swap_not_enough_liquidity() { + // Setup + let (factory_address, router_address) = deploy_contracts(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let mut flash_swap_constructor_calldata = Default::default(); + Serde::serialize(@factory_address, ref flash_swap_constructor_calldata); + let flash_swap_class = declare("FlashSwapTest").unwrap(); + let (flash_swap_address, _) = flash_swap_class.deploy(@flash_swap_constructor_calldata).unwrap(); + + let erc20_amount_per_user: u256 = 100 * TOKEN_MULTIPLIER; + let (token0_address, token1_address) = deploy_erc20(erc20_amount_per_user); + let (sorted_token0_address, sorted_token1_address) = router_dispatcher.sort_tokens(token0_address, token1_address); + let pair_address = factory_dispatcher.create_pair(sorted_token0_address, sorted_token1_address); + let _pair_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let (token0_address, token1_address) = (sorted_token0_address, sorted_token1_address); + let token0_erc20_dispatcher = IERC20Dispatcher { contract_address: token0_address }; + let token1_erc20_dispatcher = IERC20Dispatcher { contract_address: token1_address }; + + // Need to use transfer method because calling the _mint internal method on erc20 is not supported yet + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token0_address)); + + let user_1_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user1()); + let user_2_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_0_balance_initial == erc20_amount_per_user, 'user1 token0 eq initial amount'); + assert(user_2_token_0_balance_initial == erc20_amount_per_user, 'user2 token0 eq initial amount'); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token1_address)); + + let user_1_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user1()); + let user_2_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_1_balance_initial == erc20_amount_per_user, 'user1 token1 eq initial amount'); + assert(user_2_token_1_balance_initial == erc20_amount_per_user, 'user2 token1 eq initial amount'); + + let amount_token0_liq: u256 = 20 * TOKEN_MULTIPLIER; + let amount_token1_liq: u256 = 40 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.approve(router_address, amount_token0_liq); + stop_prank(CheatTarget::One(token0_address)); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.approve(router_address, amount_token1_liq); + stop_prank(CheatTarget::One(token1_address)); + + start_prank(CheatTarget::One(router_address), user1()); + let (_amountA, _amountB, _liquidity) = router_dispatcher.add_liquidity( + token0_address, + token1_address, + amount_token0_liq, + amount_token1_liq, + 1, + 1, + user1(), + 0, + ); + stop_prank(CheatTarget::One(router_address)); + + // Actual test + let amount_token_0: u256 = 200 * TOKEN_MULTIPLIER; + let pair_safe_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let mut data = ArrayTrait::::new(); + data.append(0); + start_prank(CheatTarget::One(pair_address), user2()); + pair_safe_dispatcher.swap(amount_token_0, 0, flash_swap_address, data); + stop_prank(CheatTarget::One(pair_address)); +} + +#[test] +#[should_panic(expected: ('invariant K',))] +fn test_flash_swap_no_repayment() { + // Setup + let (factory_address, router_address) = deploy_contracts(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let mut flash_swap_constructor_calldata = Default::default(); + Serde::serialize(@factory_address, ref flash_swap_constructor_calldata); + let flash_swap_class = declare("FlashSwapTest").unwrap(); + let (flash_swap_address, _) = flash_swap_class.deploy(@flash_swap_constructor_calldata).unwrap(); + + let erc20_amount_per_user: u256 = 100 * TOKEN_MULTIPLIER; + let (token0_address, token1_address) = deploy_erc20(erc20_amount_per_user); + let (sorted_token0_address, sorted_token1_address) = router_dispatcher.sort_tokens(token0_address, token1_address); + let pair_address = factory_dispatcher.create_pair(sorted_token0_address, sorted_token1_address); + let _pair_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let (token0_address, token1_address) = (sorted_token0_address, sorted_token1_address); + let token0_erc20_dispatcher = IERC20Dispatcher { contract_address: token0_address }; + let token1_erc20_dispatcher = IERC20Dispatcher { contract_address: token1_address }; + + // Need to use transfer method because calling the _mint internal method on erc20 is not supported yet + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token0_address)); + + let user_1_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user1()); + let user_2_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_0_balance_initial == erc20_amount_per_user, 'user1 token0 eq initial amount'); + assert(user_2_token_0_balance_initial == erc20_amount_per_user, 'user2 token0 eq initial amount'); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token1_address)); + + let user_1_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user1()); + let user_2_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_1_balance_initial == erc20_amount_per_user, 'user1 token1 eq initial amount'); + assert(user_2_token_1_balance_initial == erc20_amount_per_user, 'user2 token1 eq initial amount'); + + let amount_token0_liq: u256 = 20 * TOKEN_MULTIPLIER; + let amount_token1_liq: u256 = 40 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.approve(router_address, amount_token0_liq); + stop_prank(CheatTarget::One(token0_address)); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.approve(router_address, amount_token1_liq); + stop_prank(CheatTarget::One(token1_address)); + + start_prank(CheatTarget::One(router_address), user1()); + let (_amountA, _amountB, _liquidity) = router_dispatcher.add_liquidity( + token0_address, + token1_address, + amount_token0_liq, + amount_token1_liq, + 1, + 1, + user1(), + 0, + ); + stop_prank(CheatTarget::One(router_address)); + + // Actual test + let amount_token_0: u256 = 2 * TOKEN_MULTIPLIER; + let pair_safe_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let mut data = ArrayTrait::::new(); + data.append(0); + start_prank(CheatTarget::One(pair_address), user2()); + pair_safe_dispatcher.swap(amount_token_0, 0, flash_swap_address, data); + stop_prank(CheatTarget::One(pair_address)); +} + +#[test] +#[should_panic(expected: ('invariant K', ))] +fn test_flash_swap_not_enough_repayment() { + // Setup + let (factory_address, router_address) = deploy_contracts(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let mut flash_swap_constructor_calldata = Default::default(); + Serde::serialize(@factory_address, ref flash_swap_constructor_calldata); + let flash_swap_class = declare("FlashSwapTest").unwrap(); + let (flash_swap_address, _) = flash_swap_class.deploy(@flash_swap_constructor_calldata).unwrap(); + + let erc20_amount_per_user: u256 = 100 * TOKEN_MULTIPLIER; + let (token0_address, token1_address) = deploy_erc20(erc20_amount_per_user); + let (sorted_token0_address, sorted_token1_address) = router_dispatcher.sort_tokens(token0_address, token1_address); + let pair_address = factory_dispatcher.create_pair(sorted_token0_address, sorted_token1_address); + let _pair_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let (token0_address, token1_address) = (sorted_token0_address, sorted_token1_address); + let token0_erc20_dispatcher = IERC20Dispatcher { contract_address: token0_address }; + let token1_erc20_dispatcher = IERC20Dispatcher { contract_address: token1_address }; + + // Need to use transfer method because calling the _mint internal method on erc20 is not supported yet + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token0_address)); + + let user_1_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user1()); + let user_2_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_0_balance_initial == erc20_amount_per_user, 'user1 token0 eq initial amount'); + assert(user_2_token_0_balance_initial == erc20_amount_per_user, 'user2 token0 eq initial amount'); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token1_address)); + + let user_1_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user1()); + let user_2_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_1_balance_initial == erc20_amount_per_user, 'user1 token1 eq initial amount'); + assert(user_2_token_1_balance_initial == erc20_amount_per_user, 'user2 token1 eq initial amount'); + + let amount_token0_liq: u256 = 20 * TOKEN_MULTIPLIER; + let amount_token1_liq: u256 = 40 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.approve(router_address, amount_token0_liq); + stop_prank(CheatTarget::One(token0_address)); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.approve(router_address, amount_token1_liq); + stop_prank(CheatTarget::One(token1_address)); + + start_prank(CheatTarget::One(router_address), user1()); + let (_amountA, _amountB, _liquidity) = router_dispatcher.add_liquidity( + token0_address, + token1_address, + amount_token0_liq, + amount_token1_liq, + 1, + 1, + user1(), + 0, + ); + stop_prank(CheatTarget::One(router_address)); + + // Actual test + let amount_token_0: u256 = 2 * TOKEN_MULTIPLIER; + let amount_to_transfer_token_0: u256 = (amount_token_0 * 2) / 1000; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.transfer(flash_swap_address, amount_to_transfer_token_0); + stop_prank(CheatTarget::One(token0_address)); + + let flash_swap_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(flash_swap_address); + assert(flash_swap_token_0_balance_initial == amount_to_transfer_token_0, 'swapflash token0 eq init amount'); + + let pair_safe_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let mut data = ArrayTrait::::new(); + data.append(0); + start_prank(CheatTarget::One(pair_address), user2()); + pair_safe_dispatcher.swap(amount_token_0, 0, flash_swap_address, data); + stop_prank(CheatTarget::One(pair_address)); +} + + +#[test] +fn test_flash_swap_same_token_repayment() { + // Setup + let (factory_address, router_address) = deploy_contracts(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let mut flash_swap_constructor_calldata = Default::default(); + Serde::serialize(@factory_address, ref flash_swap_constructor_calldata); + let flash_swap_class = declare("FlashSwapTest").unwrap(); + let (flash_swap_address, _) = flash_swap_class.deploy(@flash_swap_constructor_calldata).unwrap(); + + let erc20_amount_per_user: u256 = 100 * TOKEN_MULTIPLIER; + let (token0_address, token1_address) = deploy_erc20(erc20_amount_per_user); + let (sorted_token0_address, sorted_token1_address) = router_dispatcher.sort_tokens(token0_address, token1_address); + let pair_address = factory_dispatcher.create_pair(sorted_token0_address, sorted_token1_address); + let pair_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let (token0_address, token1_address) = (sorted_token0_address, sorted_token1_address); + let token0_erc20_dispatcher = IERC20Dispatcher { contract_address: token0_address }; + let token1_erc20_dispatcher = IERC20Dispatcher { contract_address: token1_address }; + + // Need to use transfer method because calling the _mint internal method on erc20 is not supported yet + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token0_address)); + + let user_1_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user1()); + let user_2_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_0_balance_initial == erc20_amount_per_user, 'user1 token0 eq initial amount'); + assert(user_2_token_0_balance_initial == erc20_amount_per_user, 'user2 token0 eq initial amount'); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token1_address)); + + let user_1_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user1()); + let user_2_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_1_balance_initial == erc20_amount_per_user, 'user1 token1 eq initial amount'); + assert(user_2_token_1_balance_initial == erc20_amount_per_user, 'user2 token1 eq initial amount'); + + let amount_token0_liq: u256 = 20 * TOKEN_MULTIPLIER; + let amount_token1_liq: u256 = 40 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.approve(router_address, amount_token0_liq); + stop_prank(CheatTarget::One(token0_address)); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.approve(router_address, amount_token1_liq); + stop_prank(CheatTarget::One(token1_address)); + + start_prank(CheatTarget::One(router_address), user1()); + let (_amountA, _amountB, _liquidity) = router_dispatcher.add_liquidity( + token0_address, + token1_address, + amount_token0_liq, + amount_token1_liq, + 1, + 1, + user1(), + 0, + ); + stop_prank(CheatTarget::One(router_address)); + + // Actual test + let amount_token_0: u256 = 2 * TOKEN_MULTIPLIER; + let amount_to_transfer_token_0: u256 = (amount_token_0 * 4) / 1000; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.transfer(flash_swap_address, amount_to_transfer_token_0); + stop_prank(CheatTarget::One(token0_address)); + + let flash_swap_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(flash_swap_address); + assert(flash_swap_token_0_balance_initial == amount_to_transfer_token_0, 'swapflash token0 eq init amount'); + + let mut data = ArrayTrait::::new(); + data.append(0); + // let mut spy = spy_events(SpyOn::One(pair_address)); + start_prank(CheatTarget::One(pair_address), user2()); + pair_dispatcher.swap(amount_token_0, 0, flash_swap_address, data); + stop_prank(CheatTarget::One(pair_address)); + + let amount0In: u256 = amount_token_0 + amount_to_transfer_token_0; + + let mut event_data = Default::default(); + Serde::serialize(@user2(), ref event_data); + Serde::serialize(@amount0In, ref event_data); + Serde::serialize(@0_u256, ref event_data); + Serde::serialize(@amount_token_0, ref event_data); + Serde::serialize(@0_u256, ref event_data); + Serde::serialize(@flash_swap_address, ref event_data); + // spy.assert_emitted(@array![ + // Event { from: pair_address, name: 'Swap', keys: array![], data: event_data } + // ]); +} + +#[test] +fn test_flash_swap_other_token_repayment() { + // Setup + let (factory_address, router_address) = deploy_contracts(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let mut flash_swap_constructor_calldata = Default::default(); + Serde::serialize(@factory_address, ref flash_swap_constructor_calldata); + let flash_swap_class = declare("FlashSwapTest").unwrap(); + let (flash_swap_address, _) = flash_swap_class.deploy(@flash_swap_constructor_calldata).unwrap(); + + let erc20_amount_per_user: u256 = 100 * TOKEN_MULTIPLIER; + let (token0_address, token1_address) = deploy_erc20(erc20_amount_per_user); + let (sorted_token0_address, sorted_token1_address) = router_dispatcher.sort_tokens(token0_address, token1_address); + let pair_address = factory_dispatcher.create_pair(sorted_token0_address, sorted_token1_address); + let pair_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let (token0_address, token1_address) = (sorted_token0_address, sorted_token1_address); + let token0_erc20_dispatcher = IERC20Dispatcher { contract_address: token0_address }; + let token1_erc20_dispatcher = IERC20Dispatcher { contract_address: token1_address }; + + // Need to use transfer method because calling the _mint internal method on erc20 is not supported yet + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token0_address)); + + let user_1_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user1()); + let user_2_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_0_balance_initial == erc20_amount_per_user, 'user1 token0 eq initial amount'); + assert(user_2_token_0_balance_initial == erc20_amount_per_user, 'user2 token0 eq initial amount'); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token1_address)); + + let user_1_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user1()); + let user_2_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_1_balance_initial == erc20_amount_per_user, 'user1 token1 eq initial amount'); + assert(user_2_token_1_balance_initial == erc20_amount_per_user, 'user2 token1 eq initial amount'); + + let amount_token0_liq: u256 = 20 * TOKEN_MULTIPLIER; + let amount_token1_liq: u256 = 40 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.approve(router_address, amount_token0_liq); + stop_prank(CheatTarget::One(token0_address)); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.approve(router_address, amount_token1_liq); + stop_prank(CheatTarget::One(token1_address)); + + start_prank(CheatTarget::One(router_address), user1()); + let (_amountA, _amountB, _liquidity) = router_dispatcher.add_liquidity( + token0_address, + token1_address, + amount_token0_liq, + amount_token1_liq, + 1, + 1, + user1(), + 0, + ); + stop_prank(CheatTarget::One(router_address)); + + // Actual test + let amount_token_0: u256 = 2 * TOKEN_MULTIPLIER; + let amount_token_1: u256 = 4 * TOKEN_MULTIPLIER; + let amount_to_transfer_token_1: u256 = (amount_token_1 * 4) / 1000; + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.transfer(flash_swap_address, amount_to_transfer_token_1); + stop_prank(CheatTarget::One(token1_address)); + + let flash_swap_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(flash_swap_address); + assert(flash_swap_token_1_balance_initial == amount_to_transfer_token_1, 'swapflash token0 eq init amount'); + + let mut data = ArrayTrait::::new(); + data.append(0); + // let mut spy = spy_events(SpyOn::One(pair_address)); + start_prank(CheatTarget::One(pair_address), user2()); + pair_dispatcher.swap(amount_token_0, 0, flash_swap_address, data); + stop_prank(CheatTarget::One(pair_address)); + + let mut event_data = Default::default(); + Serde::serialize(@user2(), ref event_data); + Serde::serialize(@amount_token_0, ref event_data); + Serde::serialize(@amount_to_transfer_token_1, ref event_data); + Serde::serialize(@amount_token_0, ref event_data); + Serde::serialize(@0_u256, ref event_data); + Serde::serialize(@flash_swap_address, ref event_data); + // spy.assert_emitted(@array![ + // Event { from: pair_address, name: 'Swap', keys: array![], data: event_data } + // ]); +} \ No newline at end of file diff --git a/src/tests/test_protocol_fees.cairo b/src/tests/test_protocol_fees.cairo new file mode 100644 index 0000000..cf4b847 --- /dev/null +++ b/src/tests/test_protocol_fees.cairo @@ -0,0 +1 @@ +mod test_protocol_fees; \ No newline at end of file diff --git a/src/tests/test_protocol_fees/test_protocol_fees.cairo b/src/tests/test_protocol_fees/test_protocol_fees.cairo new file mode 100644 index 0000000..b44562a --- /dev/null +++ b/src/tests/test_protocol_fees/test_protocol_fees.cairo @@ -0,0 +1,190 @@ +use core::result::ResultTrait; +use starknet:: { ContractAddress, ClassHash, contract_address_try_from_felt252 }; +use snforge_std::{ declare, CheatTarget, ContractClassTrait, ContractClass, start_prank, stop_prank }; + +use jediswap::tests::utils::utils::{ deployer_addr, user1, user2, TOKEN_MULTIPLIER, TOKEN0_NAME, + TOKEN1_NAME, SYMBOL }; + +#[starknet::interface] +trait IERC20 { + // view functions + fn name(self: @TContractState) -> felt252; + fn symbol(self: @TContractState) -> felt252; + fn decimals(self: @TContractState) -> u8; + fn total_supply(self: @TContractState) -> u256; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + // external functions + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +trait IFactoryC1 { + // view functions + fn get_pair(self: @T, token0: ContractAddress, token1: ContractAddress) -> ContractAddress; + fn get_all_pairs(self: @T) -> (u32, Array::); + // external functions + fn create_pair(ref self: T, tokenA: ContractAddress, tokenB: ContractAddress) -> ContractAddress; + fn set_fee_to(ref self: T, new_fee_to: ContractAddress); +} + +#[starknet::interface] +trait IPairC1 { + // view functions + fn balance_of(self: @T, account: ContractAddress) -> u256; + fn get_reserves(self: @T) -> (u256, u256, u64); + // external functions + fn approve(ref self: T, spender: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +trait IRouterC1 { + // view functions + fn factory(self: @T) -> ContractAddress; + fn sort_tokens(self: @T, tokenA: ContractAddress, tokenB: ContractAddress) -> (ContractAddress, ContractAddress); + // external functions + fn add_liquidity(ref self: T, tokenA: ContractAddress, tokenB: ContractAddress, amountADesired: u256, amountBDesired: u256, amountAMin: u256, amountBMin: u256, to: ContractAddress, deadline: u64) -> (u256, u256, u256); + fn remove_liquidity(ref self: T, tokenA: ContractAddress, tokenB: ContractAddress, liquidity: u256, amountAMin: u256, amountBMin: u256, to: ContractAddress, deadline: u64) -> (u256, u256); + fn swap_exact_tokens_for_tokens(ref self: T, amountIn: u256, amountOutMin: u256, path: Array::, to: ContractAddress, deadline: u64) -> Array::; +} + +fn fee_recipient() -> ContractAddress { + contract_address_try_from_felt252('fee recipient').unwrap() +} + + +fn deploy_erc20(erc20_amount_per_user: u256) -> (ContractAddress, ContractAddress) { + let erc20_class = declare("ERC20").unwrap(); + let initial_supply: u256 = erc20_amount_per_user * 2; + + let mut token0_constructor_calldata = Default::default(); + Serde::serialize(@TOKEN0_NAME, ref token0_constructor_calldata); + Serde::serialize(@SYMBOL, ref token0_constructor_calldata); + Serde::serialize(@initial_supply, ref token0_constructor_calldata); + Serde::serialize(@user1(), ref token0_constructor_calldata); + let (token0_address, _) = erc20_class.deploy(@token0_constructor_calldata).unwrap(); + + let mut token1_constructor_calldata = Default::default(); + Serde::serialize(@TOKEN1_NAME, ref token1_constructor_calldata); + Serde::serialize(@SYMBOL, ref token1_constructor_calldata); + Serde::serialize(@initial_supply, ref token1_constructor_calldata); + Serde::serialize(@user1(), ref token1_constructor_calldata); + let (token1_address, _) = erc20_class.deploy(@token1_constructor_calldata).unwrap(); + + (token0_address, token1_address) +} + + +fn deploy_contracts() -> (ContractAddress, ContractAddress) { + let pair_class = declare("PairC1").unwrap(); + + let mut factory_constructor_calldata = Default::default(); + Serde::serialize(@pair_class.class_hash, ref factory_constructor_calldata); + Serde::serialize(@deployer_addr(), ref factory_constructor_calldata); + let factory_class = declare("FactoryC1").unwrap(); + let (factory_address, _) = factory_class.deploy(@factory_constructor_calldata).unwrap(); + + let mut router_constructor_calldata = Default::default(); + Serde::serialize(@factory_address, ref router_constructor_calldata); + let router_class = declare("RouterC1").unwrap(); + let (router_address, _) = router_class.deploy(@router_constructor_calldata).unwrap(); + + (factory_address, router_address) +} + +#[test] +fn test_protocol_fee(){ + // Setup + let (factory_address, router_address) = deploy_contracts(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let erc20_amount_per_user: u256 = 100 * TOKEN_MULTIPLIER; + let (token0_address, token1_address) = deploy_erc20(erc20_amount_per_user); + let (sorted_token0_address, sorted_token1_address) = router_dispatcher.sort_tokens(token0_address, token1_address); + let pair_address = factory_dispatcher.create_pair(sorted_token0_address, sorted_token1_address); + let pair_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let (token0_address, token1_address) = (sorted_token0_address, sorted_token1_address); + let token0_erc20_dispatcher = IERC20Dispatcher { contract_address: token0_address }; + let token1_erc20_dispatcher = IERC20Dispatcher { contract_address: token1_address }; + + start_prank(CheatTarget::One(factory_address), deployer_addr()); + factory_dispatcher.set_fee_to(fee_recipient()); + stop_prank(CheatTarget::One(factory_address)); + + // Need to use transfer method becuase calling the _mint internal method on erc20 is not supported yet + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token0_address)); + + let user_1_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user1()); + let user_2_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_0_balance_initial == erc20_amount_per_user, 'user1 token0 eq initial amount'); + assert(user_2_token_0_balance_initial == erc20_amount_per_user, 'user2 token0 eq initial amount'); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token1_address)); + + let user_1_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user1()); + let user_2_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_1_balance_initial == erc20_amount_per_user, 'user1 token1 eq initial amount'); + assert(user_2_token_1_balance_initial == erc20_amount_per_user, 'user2 token1 eq initial amount'); + + let amount_token0_liq: u256 = 20 * TOKEN_MULTIPLIER; + let amount_token1_liq: u256 = 40 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.approve(router_address, amount_token0_liq); + stop_prank(CheatTarget::One(token0_address)); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.approve(router_address, amount_token1_liq); + stop_prank(CheatTarget::One(token1_address)); + + start_prank(CheatTarget::One(router_address), user1()); + let (_amountA, _amountB, _liquidity) = router_dispatcher.add_liquidity( + token0_address, token1_address, amount_token0_liq, amount_token1_liq, 1, 1, user1(), 0); + stop_prank(CheatTarget::One(router_address)); + + // Actual test + + let amount_token_0: u256 = 2 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token0_address), user2()); + token0_erc20_dispatcher.approve(router_address, amount_token_0); + stop_prank(CheatTarget::One(token0_address)); + + let mut path = ArrayTrait::::new(); + path.append(token0_address); + path.append(token1_address); + start_prank(CheatTarget::One(router_address), user2()); + let amounts: Array:: = router_dispatcher.swap_exact_tokens_for_tokens(amount_token_0, 0, path, user2(), 0); + stop_prank(CheatTarget::One(router_address)); + + assert(amounts.len() == 2, 'should be 2'); + + // ## Remove liquidity + + let user_1_pair_balance: u256 = pair_dispatcher.balance_of(user1()); + + start_prank(CheatTarget::One(pair_address), user1()); + pair_dispatcher.approve(router_address, user_1_pair_balance); + stop_prank(CheatTarget::One(pair_address)); + + start_prank(CheatTarget::One(router_address), user1()); + let (_amountA_burn, _amountB_burn) = router_dispatcher.remove_liquidity( + token0_address, + token1_address, + user_1_pair_balance, + 1_u256, + 1_u256, + user1(), + 0); + stop_prank(CheatTarget::One(router_address)); + + let fee_recipient_pair_balance: u256 = pair_dispatcher.balance_of(fee_recipient()); + assert(fee_recipient_pair_balance > 0_u256, 'should be greater than 0'); +} \ No newline at end of file diff --git a/src/tests/test_swap.cairo b/src/tests/test_swap.cairo new file mode 100644 index 0000000..3b9aebe --- /dev/null +++ b/src/tests/test_swap.cairo @@ -0,0 +1 @@ +mod test_swap; \ No newline at end of file diff --git a/src/tests/test_swap/test_swap.cairo b/src/tests/test_swap/test_swap.cairo new file mode 100644 index 0000000..93ef49a --- /dev/null +++ b/src/tests/test_swap/test_swap.cairo @@ -0,0 +1,476 @@ +use starknet:: { ContractAddress, ClassHash }; +use snforge_std::{ declare, ContractClassTrait, ContractClass, start_prank, stop_prank, + spy_events, CheatTarget, SpyOn, EventSpy, EventFetcher, Event, EventAssertions }; + +use jediswap::tests::utils::utils::{ deployer_addr, user1, user2, TOKEN_MULTIPLIER, TOKEN0_NAME, + TOKEN1_NAME, TOKEN2_NAME, SYMBOL }; +use debug::PrintTrait; + +#[starknet::interface] +trait IERC20 { + // view functions + fn name(self: @TContractState) -> felt252; + fn symbol(self: @TContractState) -> felt252; + fn decimals(self: @TContractState) -> u8; + fn total_supply(self: @TContractState) -> u256; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + // external functions + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +trait IFactoryC1 { + // view functions + fn get_pair(self: @T, token0: ContractAddress, token1: ContractAddress) -> ContractAddress; + fn get_all_pairs(self: @T) -> (u32, Array::); + // external functions + fn create_pair(ref self: T, tokenA: ContractAddress, tokenB: ContractAddress) -> ContractAddress; +} + +#[starknet::interface] +trait IPairC1 { + // view functions + fn get_reserves(self: @T) -> (u256, u256, u64); +} + +#[starknet::interface] +trait IRouterC1 { + // view functions + fn factory(self: @TState) -> ContractAddress; + fn sort_tokens(self: @TState, tokenA: ContractAddress, tokenB: ContractAddress) -> (ContractAddress, ContractAddress); + fn quote(self: @TState, amountA: u256, reserveA: u256, reserveB: u256) -> u256; + fn get_amount_out(self: @TState, amountIn: u256, reserveIn: u256, reserveOut: u256) -> u256; + fn get_amount_in(self: @TState, amountOut: u256, reserveIn: u256, reserveOut: u256) -> u256; + fn get_amounts_out(self: @TState, amountIn: u256, path: Array::) -> Array::; + fn get_amounts_in(self: @TState, amountOut: u256, path: Array::) -> Array::; + // external functions + fn add_liquidity(ref self: TState, tokenA: ContractAddress, tokenB: ContractAddress, amountADesired: u256, amountBDesired: u256, amountAMin: u256, amountBMin: u256, to: ContractAddress, deadline: u64) -> (u256, u256, u256); + fn remove_liquidity(ref self: TState, tokenA: ContractAddress, tokenB: ContractAddress, liquidity: u256, amountAMin: u256, amountBMin: u256, to: ContractAddress, deadline: u64) -> (u256, u256); + fn swap_exact_tokens_for_tokens(ref self: TState, amountIn: u256, amountOutMin: u256, path: Array::, to: ContractAddress, deadline: u64) -> Array::; + fn swap_tokens_for_exact_tokens(ref self: TState, amountOut: u256, amountInMax: u256, path: Array::, to: ContractAddress, deadline: u64) -> Array::; + fn swap_exact_tokens_for_tokens_supporting_fee_on_transfer_tokens(ref self: TState, amount_in: u256, amount_out_min: u256, path: Array, to: ContractAddress, deadline: u64); + fn replace_implementation_class(ref self: TState, new_implementation_class: ClassHash); +} + +fn deploy_erc20(erc20_amount_per_user: u256) -> (ContractAddress, ContractAddress, ContractAddress) { + let erc20_class = declare("ERC20").unwrap(); + let initial_supply: u256 = erc20_amount_per_user * 2; + + let mut token0_constructor_calldata = Default::default(); + Serde::serialize(@TOKEN0_NAME, ref token0_constructor_calldata); + Serde::serialize(@SYMBOL, ref token0_constructor_calldata); + Serde::serialize(@initial_supply, ref token0_constructor_calldata); + Serde::serialize(@user1(), ref token0_constructor_calldata); + let (token0_address, _) = erc20_class.deploy(@token0_constructor_calldata).unwrap(); + + let mut token1_constructor_calldata = Default::default(); + Serde::serialize(@TOKEN1_NAME, ref token1_constructor_calldata); + Serde::serialize(@SYMBOL, ref token1_constructor_calldata); + Serde::serialize(@initial_supply, ref token1_constructor_calldata); + Serde::serialize(@user1(), ref token1_constructor_calldata); + let (token1_address, _) = erc20_class.deploy(@token1_constructor_calldata).unwrap(); + + let mut token2_constructor_calldata = Default::default(); + Serde::serialize(@TOKEN2_NAME, ref token2_constructor_calldata); + Serde::serialize(@SYMBOL, ref token2_constructor_calldata); + Serde::serialize(@initial_supply, ref token2_constructor_calldata); + Serde::serialize(@user1(), ref token2_constructor_calldata); + let (token2_address, _) = erc20_class.deploy(@token2_constructor_calldata).unwrap(); + + (token0_address, token1_address, token2_address) +} + +fn deploy_erc20_and_fee_on_transfer(erc20_amount_per_user: u256) -> (ContractAddress, ContractAddress, ContractAddress) { + let erc20_class = declare("ERC20").unwrap(); + let reflect_class = declare("REFLECT").unwrap(); // fee-on-transfer token standard + let initial_supply: u256 = erc20_amount_per_user * 2; + + //token0 is the ONLY fee-on-transfer token + let mut token0_constructor_calldata = Default::default(); + Serde::serialize(@TOKEN0_NAME, ref token0_constructor_calldata); + Serde::serialize(@SYMBOL, ref token0_constructor_calldata); + Serde::serialize(@initial_supply, ref token0_constructor_calldata); + Serde::serialize(@user1(), ref token0_constructor_calldata); + let (token0_address, _) = reflect_class.deploy(@token0_constructor_calldata).unwrap(); + + //token1 is regular ERC20 + let mut token1_constructor_calldata = Default::default(); + Serde::serialize(@TOKEN1_NAME, ref token1_constructor_calldata); + Serde::serialize(@SYMBOL, ref token1_constructor_calldata); + Serde::serialize(@initial_supply, ref token1_constructor_calldata); + Serde::serialize(@user1(), ref token1_constructor_calldata); + let (token1_address, _) = erc20_class.deploy(@token1_constructor_calldata).unwrap(); + + //token2 is regular ERC20 + let mut token2_constructor_calldata = Default::default(); + Serde::serialize(@TOKEN2_NAME, ref token2_constructor_calldata); + Serde::serialize(@SYMBOL, ref token2_constructor_calldata); + Serde::serialize(@initial_supply, ref token2_constructor_calldata); + Serde::serialize(@user1(), ref token2_constructor_calldata); + let (token2_address, _) = erc20_class.deploy(@token2_constructor_calldata).unwrap(); + + (token0_address, token1_address, token2_address) +} + +fn deploy_contracts() -> (ContractAddress, ContractAddress) { + let pair_class = declare("PairC1").unwrap(); + + let mut factory_constructor_calldata = Default::default(); + Serde::serialize(@pair_class.class_hash, ref factory_constructor_calldata); + Serde::serialize(@deployer_addr(), ref factory_constructor_calldata); + let factory_class = declare("FactoryC1").unwrap(); + let (factory_address, _) = factory_class.deploy(@factory_constructor_calldata).unwrap(); + + let mut router_constructor_calldata = Default::default(); + Serde::serialize(@factory_address, ref router_constructor_calldata); + let router_class = declare("RouterC1").unwrap(); + let (router_address, _) = router_class.deploy(@router_constructor_calldata).unwrap(); + + (factory_address, router_address) +} + +#[test] +fn test_swap_exact_0_to_1_fee_on_transfer(){ + // Setup + let (factory_address, router_address) = deploy_contracts(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let erc20_amount_per_user: u256 = 100 * TOKEN_MULTIPLIER; + let (token0_address, token1_address, _) = deploy_erc20_and_fee_on_transfer(erc20_amount_per_user); + let (sorted_token0_address, sorted_token1_address) = router_dispatcher.sort_tokens(token0_address, token1_address); + let pair_address = factory_dispatcher.create_pair(sorted_token0_address, sorted_token1_address); + let _pair_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let (token0_address, token1_address) = (sorted_token0_address, sorted_token1_address); + let token0_erc20_dispatcher = IERC20Dispatcher { contract_address: token0_address }; + let token1_erc20_dispatcher = IERC20Dispatcher { contract_address: token1_address }; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token0_address)); + + let user_1_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user1()); + let user_2_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user2()); + + let transfer_fee: u256 = erc20_amount_per_user * 1 / 100; // Calculate the 1% transfer fee + let user_1_token_0_lower_bound: u256 = erc20_amount_per_user - transfer_fee; + let user_1_token_0_upper_bound: u256 = erc20_amount_per_user + transfer_fee; + + assert(user_1_token_0_balance_initial >= user_1_token_0_lower_bound, 'u1 token0 balance >= range'); + assert(user_1_token_0_balance_initial <= user_1_token_0_upper_bound, 'u1 token0 balance <= range'); + + let user_2_token_0_lower_bound = erc20_amount_per_user - transfer_fee; + let user_2_token_0_upper_bound = erc20_amount_per_user; + assert(user_2_token_0_balance_initial >= user_2_token_0_lower_bound, 'u2 token0 balance out of range'); + assert(user_2_token_0_balance_initial <= user_2_token_0_upper_bound, 'u2 token0 balance out of range'); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token1_address)); + + let user_1_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user1()); + let user_2_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_1_balance_initial == erc20_amount_per_user, 'user1 token1 eq initial amount'); + assert(user_2_token_1_balance_initial == erc20_amount_per_user, 'user2 token1 eq initial amount'); + + let amount_token0_liq: u256 = 20 * TOKEN_MULTIPLIER; + let amount_token1_liq: u256 = 40 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.approve(router_address, amount_token0_liq); + stop_prank(CheatTarget::One(token0_address)); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.approve(router_address, amount_token1_liq); + stop_prank(CheatTarget::One(token1_address)); + + start_prank(CheatTarget::One(router_address), user1()); + let (_amountA, _amountB, _liquidity) = router_dispatcher.add_liquidity( + token0_address, token1_address, amount_token0_liq, amount_token1_liq, 1, 1, user1(), 0); + stop_prank(CheatTarget::One(router_address)); + + // Actual test + + let amount_token_0: u256 = 2 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token0_address), user2()); + token0_erc20_dispatcher.approve(router_address, amount_token_0); + stop_prank(CheatTarget::One(token0_address)); + + let mut path = ArrayTrait::::new(); + path.append(token0_address); + path.append(token1_address); + + start_prank(CheatTarget::One(router_address), user2()); + router_dispatcher.swap_exact_tokens_for_tokens_supporting_fee_on_transfer_tokens(amount_token_0, 0, path, user2(), 0); + stop_prank(CheatTarget::One(router_address)); + + let user_2_token_1_balance_final: u256 = token1_erc20_dispatcher.balance_of(user2()); + + let user_2_token_1_balance_difference = user_2_token_1_balance_final - user_2_token_1_balance_initial; + + let token_1_fee = user_2_token_1_balance_difference * 1 / 100; // Calculate the 1% fee + let token_1_lower_bound = user_2_token_1_balance_difference - token_1_fee; + let token_1_upper_bound = user_2_token_1_balance_difference; + + assert(user_2_token_1_balance_difference >= token_1_lower_bound, 'should be >= lower bound'); + assert(user_2_token_1_balance_difference <= token_1_upper_bound, 'should be <= upper bound'); +} + +#[test] +fn test_swap_exact_0_to_1(){ + // Setup + let (factory_address, router_address) = deploy_contracts(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let erc20_amount_per_user: u256 = 100 * TOKEN_MULTIPLIER; + let (token0_address, token1_address, _) = deploy_erc20(erc20_amount_per_user); + let (sorted_token0_address, sorted_token1_address) = router_dispatcher.sort_tokens(token0_address, token1_address); + let pair_address = factory_dispatcher.create_pair(sorted_token0_address, sorted_token1_address); + let _pair_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let (token0_address, token1_address) = (sorted_token0_address, sorted_token1_address); + let token0_erc20_dispatcher = IERC20Dispatcher { contract_address: token0_address }; + let token1_erc20_dispatcher = IERC20Dispatcher { contract_address: token1_address }; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token0_address)); + + let user_1_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user1()); + let user_2_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user2()); + + assert(user_1_token_0_balance_initial == erc20_amount_per_user, 'user1 token0 eq initial amount'); + assert(user_2_token_0_balance_initial == erc20_amount_per_user, 'user2 token0 eq initial amount'); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token1_address)); + + let user_1_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user1()); + let user_2_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_1_balance_initial == erc20_amount_per_user, 'user1 token1 eq initial amount'); + assert(user_2_token_1_balance_initial == erc20_amount_per_user, 'user2 token1 eq initial amount'); + + let amount_token0_liq: u256 = 20 * TOKEN_MULTIPLIER; + let amount_token1_liq: u256 = 40 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.approve(router_address, amount_token0_liq); + stop_prank(CheatTarget::One(token0_address)); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.approve(router_address, amount_token1_liq); + stop_prank(CheatTarget::One(token1_address)); + + start_prank(CheatTarget::One(router_address), user1()); + let (_amountA, _amountB, _liquidity) = router_dispatcher.add_liquidity( + token0_address, token1_address, amount_token0_liq, amount_token1_liq, 1, 1, user1(), 0); + stop_prank(CheatTarget::One(router_address)); + + // Actual test + + let amount_token_0: u256 = 2 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token0_address), user2()); + token0_erc20_dispatcher.approve(router_address, amount_token_0); + stop_prank(CheatTarget::One(token0_address)); + + let mut path = ArrayTrait::::new(); + path.append(token0_address); + path.append(token1_address); + + start_prank(CheatTarget::One(router_address), user2()); + let amounts: Array:: = router_dispatcher.swap_exact_tokens_for_tokens(amount_token_0, 0, path, user2(), 0); + stop_prank(CheatTarget::One(router_address)); + + assert(amounts.len() == 2, 'should be 2'); + + let amount0In: u256 = *amounts.at(0); + let amount1Out: u256 = *amounts.at(1); + + let user_2_token_0_balance_final: u256 = token0_erc20_dispatcher.balance_of(user2()); + let user_2_token_0_balance_difference = user_2_token_0_balance_initial - user_2_token_0_balance_final; + assert(user_2_token_0_balance_difference == amount0In, 'should be eq to amount0In'); + + let user_2_token_1_balance_final: u256 = token1_erc20_dispatcher.balance_of(user2()); + let user_2_token_1_balance_difference = user_2_token_1_balance_final - user_2_token_1_balance_initial; + assert(user_2_token_1_balance_difference == amount1Out, 'should be eq to amount1Out'); +} + +#[test] +fn test_swap_exact_1_to_0_fee_on_transfer(){ + // Setup + let (factory_address, router_address) = deploy_contracts(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let erc20_amount_per_user: u256 = 100 * TOKEN_MULTIPLIER; + let (token0_address, token1_address, _) = deploy_erc20_and_fee_on_transfer(erc20_amount_per_user); + let (sorted_token0_address, sorted_token1_address) = router_dispatcher.sort_tokens(token0_address, token1_address); + let pair_address = factory_dispatcher.create_pair(sorted_token0_address, sorted_token1_address); + let _pair_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let (token0_address, token1_address) = (sorted_token0_address, sorted_token1_address); + let token0_erc20_dispatcher = IERC20Dispatcher { contract_address: token0_address }; + let token1_erc20_dispatcher = IERC20Dispatcher { contract_address: token1_address }; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token0_address)); + + let user_1_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user1()); + let user_2_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user2()); + + let transfer_fee: u256 = erc20_amount_per_user * 1 / 100; // Calculate the 1% transfer fee + let user_1_token_0_lower_bound: u256 = erc20_amount_per_user - transfer_fee; + let user_1_token_0_upper_bound: u256 = erc20_amount_per_user + transfer_fee; + + assert(user_1_token_0_balance_initial >= user_1_token_0_lower_bound, 'u1 token0 balance >= range'); + assert(user_1_token_0_balance_initial <= user_1_token_0_upper_bound, 'u1 token0 balance <= range'); + + let user_2_token_0_lower_bound = erc20_amount_per_user - transfer_fee; + let user_2_token_0_upper_bound = erc20_amount_per_user; + assert(user_2_token_0_balance_initial >= user_2_token_0_lower_bound, 'u2 token0 balance out of range'); + assert(user_2_token_0_balance_initial <= user_2_token_0_upper_bound, 'u2 token0 balance out of range'); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token1_address)); + + let user_1_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user1()); + let user_2_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_1_balance_initial == erc20_amount_per_user, 'user1 token1 eq initial amount'); + assert(user_2_token_1_balance_initial == erc20_amount_per_user, 'user2 token1 eq initial amount'); + + let amount_token0_liq: u256 = 20 * TOKEN_MULTIPLIER; + let amount_token1_liq: u256 = 40 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.approve(router_address, amount_token0_liq); + stop_prank(CheatTarget::One(token0_address)); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.approve(router_address, amount_token1_liq); + stop_prank(CheatTarget::One(token1_address)); + + start_prank(CheatTarget::One(router_address), user1()); + let (_amountA, _amountB, _liquidity) = router_dispatcher.add_liquidity( + token0_address, token1_address, amount_token0_liq, amount_token1_liq, 1, 1, user1(), 0); + stop_prank(CheatTarget::One(router_address)); + // Actual test + + let amount_token_1: u256 = 2 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token1_address), user2()); + token1_erc20_dispatcher.approve(router_address, amount_token_1); + stop_prank(CheatTarget::One(token1_address)); + + let mut path = ArrayTrait::::new(); + path.append(token1_address); + path.append(token0_address); + + start_prank(CheatTarget::One(router_address), user2()); + let amounts: Array:: = router_dispatcher.swap_exact_tokens_for_tokens(amount_token_1, 0, path, user2(), 0); + stop_prank(CheatTarget::One(router_address)); + + assert(amounts.len() == 2, 'should be 2'); + + let amount1In: u256 = *amounts.at(0); + let amount0Out: u256 = *amounts.at(1); + + let user_2_token_0_balance_final: u256 = token0_erc20_dispatcher.balance_of(user2()); + let user_2_token_0_balance_difference = user_2_token_0_balance_final - user_2_token_0_balance_initial; + assert(user_2_token_0_balance_difference >= amount0Out, 'should be eq to amount0Out'); + + let user_2_token_1_balance_final: u256 = token1_erc20_dispatcher.balance_of(user2()); + let user_2_token_1_balance_difference = user_2_token_1_balance_initial - user_2_token_1_balance_final; + assert(user_2_token_1_balance_difference == amount1In, 'should be eq to amount1In'); +} + +#[test] +fn test_swap_exact_1_to_0(){ + // Setup + let (factory_address, router_address) = deploy_contracts(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + let router_dispatcher = IRouterC1Dispatcher { contract_address: router_address }; + + let erc20_amount_per_user: u256 = 100 * TOKEN_MULTIPLIER; + let (token0_address, token1_address, _) = deploy_erc20(erc20_amount_per_user); + let (sorted_token0_address, sorted_token1_address) = router_dispatcher.sort_tokens(token0_address, token1_address); + let pair_address = factory_dispatcher.create_pair(sorted_token0_address, sorted_token1_address); + let _pair_dispatcher = IPairC1Dispatcher { contract_address: pair_address }; + + let (token0_address, token1_address) = (sorted_token0_address, sorted_token1_address); + let token0_erc20_dispatcher = IERC20Dispatcher { contract_address: token0_address }; + let token1_erc20_dispatcher = IERC20Dispatcher { contract_address: token1_address }; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token0_address)); + + let user_1_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user1()); + let user_2_token_0_balance_initial: u256 = token0_erc20_dispatcher.balance_of(user2()); + + assert(user_1_token_0_balance_initial == erc20_amount_per_user, 'user1 token0 eq initial amount'); + assert(user_2_token_0_balance_initial == erc20_amount_per_user, 'user2 token0 eq initial amount'); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.transfer(user2(), erc20_amount_per_user); + stop_prank(CheatTarget::One(token1_address)); + + let user_1_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user1()); + let user_2_token_1_balance_initial: u256 = token1_erc20_dispatcher.balance_of(user2()); + assert(user_1_token_1_balance_initial == erc20_amount_per_user, 'user1 token1 eq initial amount'); + assert(user_2_token_1_balance_initial == erc20_amount_per_user, 'user2 token1 eq initial amount'); + + let amount_token0_liq: u256 = 20 * TOKEN_MULTIPLIER; + let amount_token1_liq: u256 = 40 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token0_address), user1()); + token0_erc20_dispatcher.approve(router_address, amount_token0_liq); + stop_prank(CheatTarget::One(token0_address)); + + start_prank(CheatTarget::One(token1_address), user1()); + token1_erc20_dispatcher.approve(router_address, amount_token1_liq); + stop_prank(CheatTarget::One(token1_address)); + + start_prank(CheatTarget::One(router_address), user1()); + let (_amountA, _amountB, _liquidity) = router_dispatcher.add_liquidity( + token0_address, token1_address, amount_token0_liq, amount_token1_liq, 1, 1, user1(), 0); + stop_prank(CheatTarget::One(router_address)); + + // Actual test + + let amount_token_1: u256 = 2 * TOKEN_MULTIPLIER; + + start_prank(CheatTarget::One(token1_address), user2()); + token1_erc20_dispatcher.approve(router_address, amount_token_1); + stop_prank(CheatTarget::One(token1_address)); + + let mut path = ArrayTrait::::new(); + path.append(token1_address); + path.append(token0_address); + + start_prank(CheatTarget::One(router_address), user2()); + let amounts: Array:: = router_dispatcher.swap_exact_tokens_for_tokens(amount_token_1, 0, path, user2(), 0); + stop_prank(CheatTarget::One(router_address)); + + assert(amounts.len() == 2, 'should be 2'); + + let amount1In: u256 = *amounts.at(0); + let amount0Out: u256 = *amounts.at(1); + + let user_2_token_0_balance_final: u256 = token0_erc20_dispatcher.balance_of(user2()); + let user_2_token_0_balance_difference = user_2_token_0_balance_final - user_2_token_0_balance_initial; + assert(user_2_token_0_balance_difference == amount0Out, 'should be eq to amount0Out'); + + let user_2_token_1_balance_final: u256 = token1_erc20_dispatcher.balance_of(user2()); + let user_2_token_1_balance_difference = user_2_token_1_balance_initial - user_2_token_1_balance_final; + assert(user_2_token_1_balance_difference == amount1In, 'should be eq to amount1In'); +} + diff --git a/src/tests/test_updates.cairo b/src/tests/test_updates.cairo new file mode 100644 index 0000000..4222cb7 --- /dev/null +++ b/src/tests/test_updates.cairo @@ -0,0 +1 @@ +mod test_updates; \ No newline at end of file diff --git a/src/tests/test_updates/test_updates.cairo b/src/tests/test_updates/test_updates.cairo new file mode 100644 index 0000000..39e5f84 --- /dev/null +++ b/src/tests/test_updates/test_updates.cairo @@ -0,0 +1,82 @@ +use core::result::ResultTrait; +use starknet:: { ContractAddress }; +use snforge_std::{ declare, CheatTarget, ContractClassTrait, ContractClass, start_prank, stop_prank }; + +use jediswap::tests::utils::utils::{ deployer_addr, user1, zero_addr }; + +#[starknet::interface] +trait IFactoryC1 { + // view functions + fn get_fee_to(self: @T) -> ContractAddress; + fn get_fee_to_setter(self: @T) -> ContractAddress; + // external functions + fn set_fee_to(ref self: T, new_fee_to: ContractAddress); + fn set_fee_to_setter(ref self: T, new_fee_to_setter: ContractAddress); +} + +#[starknet::interface] +trait IRouterC1 { + // view functions + fn factory(self: @T) -> ContractAddress; + fn sort_tokens(self: @T, tokenA: ContractAddress, tokenB: ContractAddress) -> (ContractAddress, ContractAddress); +} + +fn deploy_factory() -> ContractAddress { + let pair_class = declare("PairC1").unwrap(); + + let mut factory_constructor_calldata = Default::default(); + Serde::serialize(@pair_class.class_hash, ref factory_constructor_calldata); + Serde::serialize(@deployer_addr(), ref factory_constructor_calldata); + let factory_class = declare("FactoryC1").unwrap(); + + let (factory_address, _) = factory_class.deploy(@factory_constructor_calldata).unwrap(); + factory_address +} + + #[test] + #[should_panic(expected: ('must be fee to setter', ))] + fn test_create_pair_without_tokens() { + let factory_address = deploy_factory(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + factory_dispatcher.set_fee_to(user1()); + } + + #[test] + fn test_set_fee_to() { + let factory_address = deploy_factory(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + start_prank(CheatTarget::One(factory_address), deployer_addr()); + factory_dispatcher.set_fee_to(user1()); + stop_prank(CheatTarget::One(factory_address)); + let fee_to_address = factory_dispatcher.get_fee_to(); + assert(fee_to_address == user1(), 'fee_to should change'); + } + + #[test] + #[should_panic(expected: ('must be fee to setter', ))] + fn test_update_fee_to_setter_non_fee_to_setter() { + let factory_address = deploy_factory(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + factory_dispatcher.set_fee_to_setter(user1()); + } + + #[test] + #[should_panic(expected: ('must be non zero', ))] + fn test_update_fee_to_setter_zero() { + let factory_address = deploy_factory(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + start_prank(CheatTarget::One(factory_address), deployer_addr()); + factory_dispatcher.set_fee_to_setter(zero_addr()); + stop_prank(CheatTarget::One(factory_address)); + } + + #[test] + fn test_update_fee_to_setter() { + let factory_address = deploy_factory(); + let factory_dispatcher = IFactoryC1Dispatcher { contract_address: factory_address }; + start_prank(CheatTarget::One(factory_address), deployer_addr()); + factory_dispatcher.set_fee_to_setter(user1()); + stop_prank(CheatTarget::One(factory_address)); + let fee_to_setter_address = factory_dispatcher.get_fee_to_setter(); + assert(fee_to_setter_address == user1(), 'fee_to_setter should change'); + } \ No newline at end of file diff --git a/src/tests/utils.cairo b/src/tests/utils.cairo new file mode 100644 index 0000000..40679bc --- /dev/null +++ b/src/tests/utils.cairo @@ -0,0 +1 @@ +mod utils; \ No newline at end of file diff --git a/src/tests/utils/utils.cairo b/src/tests/utils/utils.cairo new file mode 100644 index 0000000..8911cc7 --- /dev/null +++ b/src/tests/utils/utils.cairo @@ -0,0 +1,36 @@ +use starknet:: { ContractAddress, contract_address_try_from_felt252, contract_address_const }; + +const TOKEN_MULTIPLIER: u256 = 1000000000000000000; //10**18 +const TOKEN0_NAME: felt252 = 'TOKEN0'; +const TOKEN1_NAME: felt252 = 'TOKEN1'; +const TOKEN2_NAME: felt252 = 'TOKEN2'; +const SYMBOL: felt252 = 'SYMBOL'; +const MINIMUM_LIQUIDITY: u256 = 1000; + +fn deployer_addr() -> ContractAddress { + contract_address_try_from_felt252('deployer').unwrap() +} + +fn token0() -> ContractAddress { + contract_address_try_from_felt252('token0').unwrap() +} + +fn token1() -> ContractAddress { + contract_address_try_from_felt252('token1').unwrap() +} + +fn zero_addr() -> ContractAddress { + contract_address_const::<0>() +} + +fn burn_addr() -> ContractAddress { + contract_address_const::<1>() +} + +fn user1() -> ContractAddress { + contract_address_try_from_felt252('user1').unwrap() +} + +fn user2() -> ContractAddress { + contract_address_try_from_felt252('user2').unwrap() +} \ No newline at end of file diff --git a/src/utils/FlashSwapTest.cairo b/src/utils/FlashSwapTest.cairo new file mode 100644 index 0000000..577ca3a --- /dev/null +++ b/src/utils/FlashSwapTest.cairo @@ -0,0 +1,76 @@ +use zeroable::Zeroable; +use starknet::{ContractAddress}; + +#[starknet::interface] +trait IFlashSwapTest { + fn jediswap_call(ref self: T, sender: ContractAddress, amount0Out: u256, amount1Out: u256, data: Array::); +} + +// +// External Interfaces +// +#[starknet::interface] +trait IERC20 { + fn balance_of(self: @T, account: ContractAddress) -> u256; + fn transfer(ref self: T, recipient: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +trait IFactory { + fn get_pair(self: @T, token0: ContractAddress, token1: ContractAddress) -> ContractAddress; +} + +#[starknet::interface] +trait IPair { + fn balance_of(self: @T, account: ContractAddress) -> u256; + fn token0(self: @T) -> ContractAddress; + fn token1(self: @T) -> ContractAddress; +} + + +#[starknet::contract] +mod FlashSwapTest { + use zeroable::Zeroable; + use starknet::{ContractAddress, SyscallResult, SyscallResultTrait, get_caller_address, get_contract_address}; + use super::{ + IERC20Dispatcher, IERC20DispatcherTrait, IPairDispatcher, IPairDispatcherTrait, IFactoryDispatcher, IFactoryDispatcherTrait + }; + + #[storage] + struct Storage { + _factory: ContractAddress, + } + + #[constructor] + fn constructor(ref self: ContractState, factory: ContractAddress) { + assert(!factory.is_zero(), 'factory can not be zero'); + self._factory.write(factory); + } + + #[abi(embed_v0)] + impl FlashSwapTest of super::IFlashSwapTest { + + fn jediswap_call(ref self: ContractState, sender: ContractAddress, amount0Out: u256, amount1Out: u256, data: Array::) { + let caller: ContractAddress = get_caller_address(); + let pairDispatcher = IPairDispatcher { contract_address: caller }; + let token0: ContractAddress = pairDispatcher.token0(); + let token1: ContractAddress = pairDispatcher.token1(); + + let factory: ContractAddress = self._factory.read(); + let factoryDispatcher = IFactoryDispatcher { contract_address: factory }; + let pair: ContractAddress = factoryDispatcher.get_pair(token0, token1); + + assert(caller == pair, 'FlashSwapTest::caller == pair'); + + let self_address: ContractAddress = get_contract_address(); + + let token0Dispatcher = IERC20Dispatcher { contract_address: token0 }; + let balance0: u256 = token0Dispatcher.balance_of(self_address); + token0Dispatcher.transfer(caller, balance0); + + let token1Dispatcher = IERC20Dispatcher { contract_address: token1 }; + let balance1: u256 = token1Dispatcher.balance_of(self_address); + token1Dispatcher.transfer(caller, balance1); + } + } +} \ No newline at end of file diff --git a/src/utils/erc20.cairo b/src/utils/erc20.cairo new file mode 100644 index 0000000..cd4e613 --- /dev/null +++ b/src/utils/erc20.cairo @@ -0,0 +1,180 @@ +use starknet::ContractAddress; + +#[starknet::interface] +trait IERC20 { + // view functions + fn name(self: @TContractState) -> felt252; + fn symbol(self: @TContractState) -> felt252; + fn decimals(self: @TContractState) -> u8; + fn total_supply(self: @TContractState) -> u256; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + // external functions + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from(ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) -> bool; + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; + fn increase_allowance(ref self: TContractState, spender: ContractAddress, added_value: u256) -> bool; + fn decrease_allowance(ref self: TContractState, spender: ContractAddress, subtracted_value: u256) -> bool; +} + +#[starknet::contract] +mod ERC20 { + use super::IERC20; + use starknet::get_caller_address; + use starknet::ContractAddress; + use starknet::contract_address_const; + use zeroable::Zeroable; + + #[storage] + struct Storage { + _name: felt252, + _symbol: felt252, + _total_supply: u256, + _balances: LegacyMap, + _allowances: LegacyMap<(ContractAddress, ContractAddress), u256>, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Transfer: Transfer, + Approval: Approval + } + + #[derive(Drop, starknet::Event)] + struct Transfer { + from: ContractAddress, + to: ContractAddress, + value: u256 + } + + #[derive(Drop, starknet::Event)] + struct Approval { + owner: ContractAddress, + spender: ContractAddress, + value: u256 + } + + #[constructor] + fn constructor(ref self: ContractState, name: felt252, symbol: felt252, initial_supply: u256, recipient: ContractAddress) { + InternalImpl::initializer(ref self, name, symbol); + InternalImpl::_mint(ref self, recipient, initial_supply); + } + + #[abi(embed_v0)] + impl ERC20 of super::IERC20 { + fn name(self: @ContractState) -> felt252 { + self._name.read() + } + + fn symbol(self: @ContractState) -> felt252 { + self._symbol.read() + } + + fn decimals(self: @ContractState) -> u8 { + 18 + } + + fn total_supply(self: @ContractState) -> u256 { + self._total_supply.read() + } + + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + self._balances.read(account) + } + + fn allowance(self: @ContractState, owner: ContractAddress, spender: ContractAddress) -> u256 { + self._allowances.read((owner, spender)) + } + + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { + let sender = get_caller_address(); + InternalImpl::_transfer(ref self, sender, recipient, amount); + true + } + + fn transfer_from(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) -> bool { + let caller = get_caller_address(); + InternalImpl::_spend_allowance(ref self, sender, caller, amount); + InternalImpl::_transfer(ref self, sender, recipient, amount); + true + } + + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { + let caller = get_caller_address(); + InternalImpl::_approve(ref self, caller, spender, amount); + true + } + + fn increase_allowance(ref self: ContractState, spender: ContractAddress, added_value: u256) -> bool { + InternalImpl::_increase_allowance(ref self, spender, added_value) + } + + fn decrease_allowance(ref self: ContractState, spender: ContractAddress, subtracted_value: u256) -> bool { + InternalImpl::_decrease_allowance(ref self, spender, subtracted_value) + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + /// + /// Internals + /// + + fn initializer(ref self: ContractState, name_: felt252, symbol_: felt252) { + self._name.write(name_); + self._symbol.write(symbol_); + } + + fn _increase_allowance(ref self: ContractState, spender: ContractAddress, added_value: u256) -> bool { + let caller = get_caller_address(); + InternalImpl::_approve(ref self, caller, spender, self._allowances.read((caller, spender)) + added_value); + true + } + + fn _decrease_allowance(ref self: ContractState, spender: ContractAddress, subtracted_value: u256) -> bool { + let caller = get_caller_address(); + InternalImpl::_approve(ref self, caller, spender, self._allowances.read((caller, spender)) - subtracted_value); + true + } + + fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + assert(!recipient.is_zero(), 'ERC20: mint to 0'); + self._total_supply.write(self._total_supply.read() + amount); + self._balances.write(recipient, self._balances.read(recipient) + amount); + self.emit(Transfer {from: contract_address_const::<0>(), to: recipient, value: amount}); + } + + fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) { + assert(!account.is_zero(), 'ERC20: burn from 0'); + self._total_supply.write(self._total_supply.read() - amount); + self._balances.write(account, self._balances.read(account) - amount); + self.emit(Transfer {from: account, to: contract_address_const::<0>(), value: amount}); + } + + fn _approve(ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256) { + assert(!owner.is_zero(), 'ERC20: approve from 0'); + assert(!spender.is_zero(), 'ERC20: approve to 0'); + self._allowances.write((owner, spender), amount); + self.emit(Approval {owner: owner, spender: spender, value: amount}); + } + + fn _transfer(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) { + assert(!sender.is_zero(), 'ERC20: transfer from 0'); + assert(!recipient.is_zero(), 'ERC20: transfer to 0'); + self._balances.write(sender, self._balances.read(sender) - amount); + self._balances.write(recipient, self._balances.read(recipient) + amount); + self.emit(Transfer {from: sender, to: recipient, value: amount}); + } + + fn _spend_allowance(ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256) { + let current_allowance = self._allowances.read((owner, spender)); + let ONES_MASK = 0xffffffffffffffffffffffffffffffff; + let is_unlimited_allowance = current_allowance.low == ONES_MASK + && current_allowance.high == ONES_MASK; + if !is_unlimited_allowance { + InternalImpl::_approve(ref self, owner, spender, current_allowance - amount); + } + } + } +} \ No newline at end of file diff --git a/src/utils/ownable.cairo b/src/utils/ownable.cairo new file mode 100644 index 0000000..0301a0a --- /dev/null +++ b/src/utils/ownable.cairo @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.8.0 (access/ownable/ownable.cairo) + +/// # Ownable Component +/// +/// The Ownable component provides a basic access control mechanism, where +/// there is an account (an owner) that can be granted exclusive access to +/// specific functions. +/// +/// The initial owner can be set by using the `initializer` function in +/// construction time. This can later be changed with `transfer_ownership`. +#[starknet::component] +mod OwnableComponent { + use jediswap::interfaces::ownable_interface; + use starknet::ContractAddress; + use starknet::get_caller_address; + + #[storage] + struct Storage { + Ownable_owner: ContractAddress + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + OwnershipTransferred: OwnershipTransferred + } + + #[derive(Drop, starknet::Event)] + struct OwnershipTransferred { + previous_owner: ContractAddress, + new_owner: ContractAddress, + } + + mod Errors { + const NOT_OWNER: felt252 = 'Caller is not the owner'; + const ZERO_ADDRESS_CALLER: felt252 = 'Caller is the zero address'; + const ZERO_ADDRESS_OWNER: felt252 = 'New owner is the zero address'; + } + + #[embeddable_as(OwnableImpl)] + impl Ownable< + TContractState, +HasComponent + > of ownable_interface::IOwnable> { + /// Returns the address of the current owner. + fn owner(self: @ComponentState) -> ContractAddress { + self.Ownable_owner.read() + } + + /// Transfers ownership of the contract to a new address. + /// + /// Requirements: + /// + /// - `new_owner` is not the zero address. + /// - The caller is the contract owner. + /// + /// Emits an `OwnershipTransferred` event. + fn transfer_ownership( + ref self: ComponentState, new_owner: ContractAddress + ) { + assert(!new_owner.is_zero(), Errors::ZERO_ADDRESS_OWNER); + self.assert_only_owner(); + self._transfer_ownership(new_owner); + } + + /// Leaves the contract without owner. It will not be possible to call `assert_only_owner` + /// functions anymore. Can only be called by the current owner. + /// + /// Requirements: + /// + /// - The caller is the contract owner. + /// + /// Emits an `OwnershipTransferred` event. + fn renounce_ownership(ref self: ComponentState) { + self.assert_only_owner(); + self._transfer_ownership(Zeroable::zero()); + } + } + + /// Adds camelCase support for `IOwnable`. + #[embeddable_as(OwnableCamelOnlyImpl)] + impl OwnableCamelOnly< + TContractState, +HasComponent + > of ownable_interface::IOwnableCamelOnly> { + fn transferOwnership(ref self: ComponentState, newOwner: ContractAddress) { + self.transfer_ownership(newOwner); + } + + fn renounceOwnership(ref self: ComponentState) { + self.renounce_ownership(); + } + } + + #[generate_trait] + impl InternalImpl< + TContractState, +HasComponent + > of InternalTrait { + /// Sets the contract's initial owner. + /// + /// This function should be called at construction time. + fn initializer(ref self: ComponentState, owner: ContractAddress) { + self._transfer_ownership(owner); + } + + /// Panics if called by any account other than the owner. Use this + /// to restrict access to certain functions to the owner. + fn assert_only_owner(self: @ComponentState) { + let owner: ContractAddress = self.Ownable_owner.read(); + let caller: ContractAddress = get_caller_address(); + assert(!caller.is_zero(), Errors::ZERO_ADDRESS_CALLER); + assert(caller == owner, Errors::NOT_OWNER); + } + + /// Transfers ownership of the contract to a new address. + /// + /// Internal function without access restriction. + /// + /// Emits an `OwnershipTransferred` event. + fn _transfer_ownership( + ref self: ComponentState, new_owner: ContractAddress + ) { + let previous_owner: ContractAddress = self.Ownable_owner.read(); + self.Ownable_owner.write(new_owner); + self + .emit( + OwnershipTransferred { previous_owner: previous_owner, new_owner: new_owner } + ); + } + } +} \ No newline at end of file diff --git a/src/utils/reflect.cairo b/src/utils/reflect.cairo new file mode 100644 index 0000000..0c000d7 --- /dev/null +++ b/src/utils/reflect.cairo @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: MIT + +// Welcome to Reflecter.Finance - Innovating the DeFi Space! +// We are a project dedicated to bringing cutting-edge solutions and novel approaches to decentralized finance. + +use starknet::ContractAddress; +use starknet::ClassHash; + +#[starknet::interface] + trait IERC20 { + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; + } + +#[starknet::interface] + trait IERC20CamelOnly { + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + } + +#[starknet::interface] + trait IREFLECT { + fn is_excluded(self: @TState, account: ContractAddress) -> bool; + fn r_total(self: @TState) -> u256; + fn total_fees(self: @TState) -> u256; + fn reflect(ref self: TState, tAmount: u256) -> bool; //return boolean for reflect + fn reflection_from_token(self: @TState, tAmount: u256, deductTransferFee: bool) -> u256; + fn token_from_reflection(self: @TState, rAmount: u256) -> u256; + fn exclude_account(ref self: TState, user: ContractAddress) -> bool;//return boolean for include + fn include_account(ref self: TState, user: ContractAddress) -> bool;//return boolean for exclude + } + +#[starknet::contract] +mod REFLECT { + use integer::BoundedInt; + use super::IERC20; + use super::IERC20CamelOnly; + use starknet::ContractAddress; + use starknet::get_caller_address; + use zeroable::Zeroable; + use super::IREFLECT; + use jediswap::utils::ownable::OwnableComponent as ownable_component; + + component!(path: ownable_component, storage: ownable, event: OwnableEvent); + + #[abi(embed_v0)] + impl OwnableImpl = ownable_component::OwnableImpl; + #[abi(embed_v0)] + impl OwnableCamelOnlyImpl = ownable_component::OwnableCamelOnlyImpl; + impl OwnableInternalImpl = ownable_component::InternalImpl; + + #[storage] + struct Storage { + _r_owned: LegacyMap, + _t_owned: LegacyMap, + _allowances: LegacyMap<(ContractAddress, ContractAddress), u256>, + _is_excluded: LegacyMap, + _excluded_index: u256, + _excluded_users: LegacyMap, + _r_total: u256, + _t_total: u256, + _t_fee_total: u256, + _name: felt252, + _symbol: felt252, + _decimals: u8, + #[substorage(v0)] + ownable: ownable_component::Storage + } + + // Events and other necessary structs + + #[constructor] + fn constructor(ref self: ContractState, _name: felt252, _symbol: felt252, _supply: u256, _creator: ContractAddress) { + self._name.write(_name); + self._symbol.write(_symbol); + self._decimals.write(9); + self.ownable.initializer(_creator); + let MAX: u256 = BoundedInt::max(); // 2^256 - 1 + self._t_total.write(_supply); + self._r_total.write(MAX - (MAX % self._t_total.read())); + self._r_owned.write(_creator, self._r_total.read()); + self.emit(Transfer { from: Zeroable::zero(), to: _creator, value: self._t_total.read() }); + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Transfer: Transfer, + Approval: Approval, + OwnableEvent: ownable_component::Event + } + + /// Emitted when tokens are moved from address `from` to address `to`. + #[derive(Drop, starknet::Event)] + struct Transfer { + #[key] + from: ContractAddress, + #[key] + to: ContractAddress, + value: u256 + } + + /// Emitted when the allowance of a `spender` for an `owner` is set by a call + /// to [approve](approve). `value` is the new allowance. + #[derive(Drop, starknet::Event)] + struct Approval { + #[key] + owner: ContractAddress, + #[key] + spender: ContractAddress, + value: u256 + } + + // + // External + // + + #[abi(embed_v0)] + impl ERC20Impl of IERC20 { + + /// Returns the name of the token. + fn name(self: @ContractState) -> felt252 { + self._name.read() + } + + /// Returns the ticker symbol of the token, usually a shorter version of the name. + fn symbol(self: @ContractState) -> felt252 { + self._symbol.read() + } + + /// Returns the number of decimals used to get its user representation. + fn decimals(self: @ContractState) -> u8 { + self._decimals.read() + } + + /// Returns the value of tokens in existence. + fn total_supply(self: @ContractState) -> u256 { + self._t_total.read() + } + + /// Returns the amount of tokens owned by `account`. + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + if self._is_excluded.read(account) { + return self._t_owned.read(account); + } + return self.token_from_reflection(self._r_owned.read(account)); + } + + /// Returns the remaining number of tokens that `spender` is + /// allowed to spend on behalf of `owner` through [transfer_from] + fn allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress + ) -> u256 { + self._allowances.read((owner, spender)) + } + + /// Moves `amount` tokens from the caller's token balance to `to`. + /// Emits a [Transfer] event. + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { + let sender = get_caller_address(); + self._transfer(sender, recipient, amount); + true + } + + /// Moves `amount` tokens from `from` to `to` using the allowance mechanism. + /// `amount` is then deducted from the caller's allowance. + /// Emits a [Transfer] event. + fn transfer_from(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) -> bool { + let caller = get_caller_address(); + self._approve(sender, caller, self._allowances.read((sender, caller)) - amount); + self._transfer(sender, recipient, amount); + true + } + + /// Sets `amount` as the allowance of `spender` over the caller’s tokens. + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { + let caller = get_caller_address(); + self._approve(caller, spender, amount); + true + } + } + + /// Increases the allowance granted from the caller to `spender` by `added_value`. + /// Emits an [Approval] event indicating the updated allowance. + #[abi(embed_v0)] + fn increase_allowance(ref self: ContractState, spender: ContractAddress, added_value: u256) -> bool { + let sender = get_caller_address(); + self._approve(sender, spender, self._allowances.read((sender, spender)) + added_value); + true + } + + /// Decreases the allowance granted from the caller to `spender` by `subtracted_value`. + /// Emits an [Approval] event indicating the updated allowance. + #[abi(embed_v0)] + fn decrease_allowance(ref self: ContractState, spender: ContractAddress, subtracted_value: u256) -> bool { + let sender = get_caller_address(); + self._approve(sender, spender, self._allowances.read((sender, spender)) - subtracted_value); + true + } + + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl of IERC20CamelOnly { + + fn totalSupply(self: @ContractState) -> u256 { + ERC20Impl::total_supply(self) + } + + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + ERC20Impl::balance_of(self, account) + } + + fn transferFrom(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) -> bool { + ERC20Impl::transfer_from(ref self, sender, recipient, amount) + } + } + + #[abi(embed_v0)] + fn increaseAllowance(ref self: ContractState, spender: ContractAddress, addedValue: u256) -> bool { + increase_allowance(ref self, spender, addedValue) + } + + #[abi(embed_v0)] + fn decreaseAllowance(ref self: ContractState, spender: ContractAddress, subtractedValue: u256) -> bool { + decrease_allowance(ref self, spender, subtractedValue) + } + + // Reflection Logic + + #[abi(embed_v0)] + impl REFLECTImpl of IREFLECT { + fn is_excluded(self: @ContractState, account: ContractAddress) -> bool{ + self._is_excluded.read(account) + } + + fn r_total(self: @ContractState) -> u256 { + self._r_total.read() + } + + fn total_fees(self: @ContractState) -> u256 { + self._t_fee_total.read() + } + + fn reflect(ref self: ContractState, tAmount: u256) -> bool { + let sender = get_caller_address(); + if self._is_excluded.read(sender) { + return false; // Excluded addresses cannot call this function + } + let (rAmount, _, _, _, _) = self._get_values(tAmount); + self._r_owned.write(sender, self._r_owned.read(sender) - rAmount); + self._r_total.write(self._r_total.read() - rAmount); + self._t_fee_total.write(self._t_fee_total.read() + tAmount); + return true; + } + + fn reflection_from_token(self: @ContractState, tAmount: u256, deductTransferFee: bool) -> u256 { + assert (tAmount <= self._t_total.read(), 'Amount must be less than supply'); + if !deductTransferFee { + let (rAmount, _, _, _, _) = self._get_values(tAmount); + return rAmount; + } else { + let (_, rTransferAmount, _, _, _) = self._get_values(tAmount); + return rTransferAmount; + } + } + + fn token_from_reflection(self: @ContractState, rAmount: u256) -> u256 { + assert(rAmount <= self._r_total.read(), 'Less than total reflections'); + let currentRate = self._get_rate(); + return rAmount / currentRate; + } + + fn exclude_account(ref self: ContractState, user: ContractAddress) -> bool { + self.ownable.assert_only_owner(); + if self._is_excluded.read(user) == false { + if self._r_owned.read(user) > 0 { + self._t_owned.write(user, self.token_from_reflection(self._r_owned.read(user))); + } + self._is_excluded.write(user, true); + let index = self._excluded_index.read(); + self._excluded_users.write(index, user); + self._excluded_index.write(index + 1); + return true; + } + return false; + } + + fn include_account(ref self: ContractState, user: ContractAddress) -> bool { + self.ownable.assert_only_owner(); + if self._is_excluded.read(user) == true { + self._t_owned.write(user, 0); // Reset the _t_owned balance for the user + self._is_excluded.write(user, false); + let count = self._excluded_index.read(); + let zero_address: ContractAddress = Zeroable::zero(); // Sentinel value for an empty slot + let mut i: u256 = 0; + loop { + if i >= count { + break; + } + if self._excluded_users.read(i) == user { + if i != count - 1 { + let last_user = self._excluded_users.read(count - 1); + self._excluded_users.write(i, last_user); // Move the last user to the current position + } + self._excluded_users.write(count - 1, zero_address); // Set the last address to the zero address + self._excluded_index.write(count - 1); // Decrement the count + break; + } + i = i + 1; + }; + return true; + } + return false; + } + + } + + // Internal functions + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn _approve(ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256) { + assert(!owner.is_zero(), 'Approve from the zero addr'); + assert(!spender.is_zero(), 'Approve to the zero addr'); + + self._allowances.write((owner, spender), amount); + + // Emit the Approval event + self.emit(Approval { owner: owner, spender: spender, value: amount }); + } + + fn _transfer(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256) { + assert(!sender.is_zero(), 'Transfer from the zero address'); + assert(!recipient.is_zero(), 'Transfer to the zero address'); + assert(amount > 0, 'Must be greater than zero'); + + let sender_is_excluded = self._is_excluded.read(sender); + let recipient_is_excluded = self._is_excluded.read(recipient); + + if sender_is_excluded && !recipient_is_excluded { + self._transfer_from_excluded(sender, recipient, amount); + } else if !sender_is_excluded && recipient_is_excluded { + self._transfer_to_excluded(sender, recipient, amount); + } else if !sender_is_excluded && !recipient_is_excluded { + self._transfer_standard(sender, recipient, amount); + } else if sender_is_excluded && recipient_is_excluded { + self._transfer_both_excluded(sender, recipient, amount); + } else { + self._transfer_standard(sender, recipient, amount); + } + } + + fn _transfer_standard(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, tAmount: u256) { + let (rAmount, rTransferAmount, rFee, tTransferAmount, tFee) = self._get_values(tAmount); + self._r_owned.write(sender, self._r_owned.read(sender) - rAmount); + self._r_owned.write(recipient, self._r_owned.read(recipient) + rTransferAmount); + self._reflect_fee(rFee, tFee); + self.emit(Transfer { from: sender, to: recipient, value: tTransferAmount }); + } + + fn _transfer_to_excluded(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, tAmount: u256) { + let (rAmount, rTransferAmount, rFee, tTransferAmount, tFee) = self._get_values(tAmount); + self._r_owned.write(sender, self._r_owned.read(sender) - rAmount); + self._t_owned.write(recipient, self._t_owned.read(recipient) + tTransferAmount); + self._r_owned.write(recipient, self._r_owned.read(recipient) + rTransferAmount); + self._reflect_fee(rFee, tFee); + self.emit(Transfer { from: sender, to: recipient, value: tTransferAmount }); + } + + fn _transfer_from_excluded(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, tAmount: u256) { + let (rAmount, rTransferAmount, rFee, tTransferAmount, tFee) = self._get_values(tAmount); + self._t_owned.write(sender, self._t_owned.read(sender) - tAmount); + self._r_owned.write(sender, self._r_owned.read(sender) - rAmount); + self._r_owned.write(recipient, self._r_owned.read(recipient) + rTransferAmount); + self._reflect_fee(rFee, tFee); + self.emit(Transfer { from: sender, to: recipient, value: tTransferAmount }); + } + + fn _transfer_both_excluded(ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, tAmount: u256) { + let (rAmount, rTransferAmount, rFee, tTransferAmount, tFee) = self._get_values(tAmount); + self._t_owned.write(sender, self._t_owned.read(sender) - tAmount); + self._r_owned.write(sender, self._r_owned.read(sender) - rAmount); + self._t_owned.write(recipient, self._t_owned.read(recipient) + tTransferAmount); + self._r_owned.write(recipient, self._r_owned.read(recipient) + rTransferAmount); + self._reflect_fee(rFee, tFee); + self.emit(Transfer { from: sender, to: recipient, value: tTransferAmount }); + } + + fn _reflect_fee(ref self: ContractState, r_fee: u256, t_fee: u256) { + self._r_total.write(self._r_total.read() - r_fee); + self._t_fee_total.write(self._t_fee_total.read() + t_fee); + } + + fn _get_values(self: @ContractState, t_amount: u256) -> (u256, u256, u256, u256, u256) { + let (t_transfer_amount, t_fee) = self._get_t_values(t_amount); + let current_rate = self._get_rate(); + let (r_amount, r_transfer_amount, r_fee) = self._get_r_values(t_amount, t_fee, current_rate); + return (r_amount, r_transfer_amount, r_fee, t_transfer_amount, t_fee); + } + + fn _get_t_values(self: @ContractState, t_amount: u256) -> (u256, u256) { + let t_fee = t_amount / 100; + let t_transfer_amount = t_amount - t_fee; + return (t_transfer_amount, t_fee); + } + + fn _get_r_values(self: @ContractState, t_amount: u256, t_fee: u256, current_rate: u256) -> (u256, u256, u256) { + let r_amount = t_amount * current_rate; + let r_fee = t_fee * current_rate; + let r_transfer_amount = r_amount - r_fee; + return (r_amount, r_transfer_amount, r_fee); + } + + fn _get_rate(self: @ContractState) -> u256 { + let (rSupply, tSupply) = self._get_current_supply(); + return rSupply / tSupply; + } + + fn _get_current_supply(self: @ContractState) -> (u256, u256) { + let r_supply = self._r_total.read(); + let t_supply = self._t_total.read(); + let excluded_count = self._excluded_index.read(); + + self._supply(r_supply, t_supply, excluded_count, 0) + } + + fn _supply(self: @ContractState, r_supply: u256, t_supply: u256, excluded_count: u256, i: u256,) -> (u256, u256) { + if i >= excluded_count { + if r_supply < (self._r_total.read() / self._t_total.read()) { + return (self._r_total.read(), self._t_total.read()); + } + return (r_supply, t_supply); + } + + let excluded_address = self._excluded_users.read(i); + let r_owned_value = self._r_owned.read(excluded_address); + let t_owned_value = self._t_owned.read(excluded_address); + + if r_owned_value > r_supply || t_owned_value > t_supply { + return (self._r_total.read(), self._t_total.read()); + } + + self._supply(r_supply - r_owned_value, t_supply - t_owned_value, excluded_count, i + 1) + } + + } + +} \ No newline at end of file diff --git a/tests/test_add_remove_liquidity.cairo b/tests/test_add_remove_liquidity.cairo deleted file mode 100644 index 0a85d41..0000000 --- a/tests/test_add_remove_liquidity.cairo +++ /dev/null @@ -1,660 +0,0 @@ -%lang starknet - -from starkware.cairo.common.cairo_builtins import HashBuiltin -from starkware.cairo.common.uint256 import Uint256, uint256_le, uint256_lt -from starkware.cairo.common.pow import pow - -from contracts.utils.math import uint256_checked_add, uint256_checked_mul, uint256_checked_sub_le - -const MINIMUM_LIQUIDITY = 1000; -const BURN_ADDRESS = 1; - -@contract_interface -namespace IERC20 { - func name() -> (name: felt) { - } - - func symbol() -> (symbol: felt) { - } - - func decimals() -> (decimals: felt) { - } - - func mint(recipient: felt, amount: Uint256) { - } - - func approve(spender: felt, amount: Uint256) -> (success: felt) { - } - - func totalSupply() -> (totalSupply: Uint256) { - } - - func balanceOf(account: felt) -> (balance: Uint256) { - } -} - -@contract_interface -namespace IPair { - func get_reserves() -> (reserve0: Uint256, reserve1: Uint256, block_timestamp_last: felt) { - } -} - -@contract_interface -namespace IRouter { - func factory() -> (address: felt) { - } - - func sort_tokens(tokenA: felt, tokenB: felt) -> (token0: felt, token1: felt) { - } - - func add_liquidity( - tokenA: felt, - tokenB: felt, - amountADesired: Uint256, - amountBDesired: Uint256, - amountAMin: Uint256, - amountBMin: Uint256, - to: felt, - deadline: felt, - ) -> (amountA: Uint256, amountB: Uint256, liquidity: Uint256) { - } - - func remove_liquidity( - tokenA: felt, - tokenB: felt, - liquidity: Uint256, - amountAMin: Uint256, - amountBMin: Uint256, - to: felt, - deadline: felt, - ) -> (amountA: Uint256, amountB: Uint256) { - } -} - -@contract_interface -namespace IFactory { - func create_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func get_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func get_all_pairs() -> (all_pairs_len: felt, all_pairs: felt*) { - } -} - -@external -func __setup__{syscall_ptr: felt*, range_check_ptr}() { - tempvar deployer_address = 123456789987654321; - tempvar user_1_address = 987654321123456789; - tempvar factory_address; - tempvar router_address; - tempvar token_0_address; - tempvar token_1_address; - %{ - context.deployer_address = ids.deployer_address - context.user_1_address = ids.user_1_address - context.declared_pair_proxy_class_hash = declare("contracts/PairProxy.cairo").class_hash - context.declared_pair_class_hash = declare("contracts/Pair.cairo").class_hash - context.declared_factory_class_hash = declare("contracts/Factory.cairo").class_hash - context.factory_address = deploy_contract("contracts/FactoryProxy.cairo", [context.declared_factory_class_hash, context.declared_pair_proxy_class_hash, context.declared_pair_class_hash, context.deployer_address]).contract_address - context.declared_router_class_hash = declare("contracts/Router.cairo").class_hash - context.router_address = deploy_contract("contracts/RouterProxy.cairo", [context.declared_router_class_hash, context.factory_address, context.deployer_address]).contract_address - context.token_0_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [11, 1, 18, 0, 0, context.deployer_address, context.deployer_address]).contract_address - context.token_1_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [22, 2, 6, 0, 0, context.deployer_address, context.deployer_address]).contract_address - ids.factory_address = context.factory_address - ids.router_address = context.router_address - ids.token_0_address = context.token_0_address - ids.token_1_address = context.token_1_address - %} - let (sorted_token_0_address, sorted_token_1_address) = IRouter.sort_tokens( - contract_address=router_address, tokenA=token_0_address, tokenB=token_1_address - ); - - let (pair_address) = IFactory.create_pair( - contract_address=factory_address, - token0=sorted_token_0_address, - token1=sorted_token_1_address, - ); - - %{ - context.sorted_token_0_address = ids.sorted_token_0_address - context.sorted_token_1_address = ids.sorted_token_1_address - context.pair_address = ids.pair_address - %} - return (); -} - -@external -func test_add_liquidity_expired_deadline{syscall_ptr: felt*, range_check_ptr}() { - alloc_locals; - - local token_0_address; - local token_1_address; - local router_address; - - %{ - ids.token_0_address = context.token_0_address - ids.token_1_address = context.token_1_address - ids.router_address = context.router_address - %} - - let (token_0_decimals) = IERC20.decimals(contract_address=token_0_address); - let (token_0_multiplier) = pow(10, token_0_decimals); - - let (token_1_decimals) = IERC20.decimals(contract_address=token_1_address); - let (token_1_multiplier) = pow(10, token_1_decimals); - - let amount_token_0 = 2 * token_0_multiplier; - let amount_token_1 = 4 * token_1_multiplier; - - %{ stop_warp = warp(1, target_contract_address=ids.router_address) %} - %{ expect_revert(error_message="Router::_ensure_deadline::expired") %} - IRouter.add_liquidity( - contract_address=router_address, - tokenA=token_0_address, - tokenB=token_1_address, - amountADesired=Uint256(amount_token_0, 0), - amountBDesired=Uint256(amount_token_1, 0), - amountAMin=Uint256(1, 0), - amountBMin=Uint256(1, 0), - to=100, - deadline=0, - ); - %{ stop_warp() %} - - return (); -} - -@external -func test_add_remove_liquidity_created_pair{ - syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr -}() { - alloc_locals; - - local token_0_address; - local token_1_address; - local router_address; - local user_1_address; - local pair_address; - - %{ - ids.token_0_address = context.sorted_token_0_address - ids.token_1_address = context.sorted_token_1_address - ids.router_address = context.router_address - ids.user_1_address = context.user_1_address - ids.pair_address = context.pair_address - %} - - let (token_0_decimals) = IERC20.decimals(contract_address=token_0_address); - let (token_0_multiplier) = pow(10, token_0_decimals); - - let (token_1_decimals) = IERC20.decimals(contract_address=token_1_address); - let (token_1_multiplier) = pow(10, token_1_decimals); - - let amount_to_mint_token_0 = 100 * token_0_multiplier; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.token_0_address) %} - IERC20.mint( - contract_address=token_0_address, - recipient=user_1_address, - amount=Uint256(amount_to_mint_token_0, 0), - ); - %{ stop_prank() %} - - let amount_to_mint_token_1 = 100 * token_1_multiplier; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.token_1_address) %} - IERC20.mint( - contract_address=token_1_address, - recipient=user_1_address, - amount=Uint256(amount_to_mint_token_1, 0), - ); - %{ stop_prank() %} - - // ## Add liquidity for first time - - let amount_token_0 = 2 * token_0_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.token_0_address) %} - IERC20.approve( - contract_address=token_0_address, spender=router_address, amount=Uint256(amount_token_0, 0) - ); - %{ stop_prank() %} - - let amount_token_1 = 4 * token_1_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.token_1_address) %} - IERC20.approve( - contract_address=token_1_address, spender=router_address, amount=Uint256(amount_token_1, 0) - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.router_address) %} - let (amountA: Uint256, amountB: Uint256, liquidity: Uint256) = IRouter.add_liquidity( - contract_address=router_address, - tokenA=token_0_address, - tokenB=token_1_address, - amountADesired=Uint256(amount_token_0, 0), - amountBDesired=Uint256(amount_token_1, 0), - amountAMin=Uint256(1, 0), - amountBMin=Uint256(1, 0), - to=user_1_address, - deadline=0, - ); - %{ stop_prank() %} - - assert amountA = Uint256(amount_token_0, 0); - assert amountB = Uint256(amount_token_1, 0); - // assert float(liquidity) == pytest.approx( - // math.sqrt(amount_token_0 * amount_token_1) - MINIMUM_LIQUIDITY) - %{ expect_events({"name": "Mint", "from_address": ids.pair_address, "data": [ids.router_address, ids.amountA.low, ids.amountA.high, ids.amountB.low, ids.amountB.high]}) %} - - let (reserve_0: Uint256, reserve_1: Uint256, block_timestamp_last) = IPair.get_reserves( - contract_address=pair_address - ); - let (totalSupply: Uint256) = IERC20.totalSupply(contract_address=pair_address); - - let (totalSupply_mul_totalSupply: Uint256) = uint256_checked_mul(totalSupply, totalSupply); - let (reserve_0_mul_reserve_1: Uint256) = uint256_checked_mul(reserve_0, reserve_1); - - let (is_total_supply_mul_lesser_than_equal_reserve_mul) = uint256_le( - totalSupply_mul_totalSupply, reserve_0_mul_reserve_1 - ); - assert is_total_supply_mul_lesser_than_equal_reserve_mul = 1; - - let (totalSupply_plus_1: Uint256) = uint256_checked_add(totalSupply, Uint256(1, 0)); - - let (totalSupply1_mul_totalSupply1: Uint256) = uint256_checked_mul( - totalSupply_plus_1, totalSupply_plus_1 - ); - - let (is_total_supply_1_mul_greater_than_reserve_mul) = uint256_lt( - reserve_0_mul_reserve_1, totalSupply1_mul_totalSupply1 - ); - assert is_total_supply_1_mul_greater_than_reserve_mul = 1; - - // ## Add liquidity to pair which already has liquidity - - let amount_token_0_again = 2 * token_0_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.token_0_address) %} - IERC20.approve( - contract_address=token_0_address, spender=router_address, amount=Uint256(amount_token_0, 0) - ); - %{ stop_prank() %} - - let amount_token_1_again = 4 * token_1_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.token_1_address) %} - IERC20.approve( - contract_address=token_1_address, spender=router_address, amount=Uint256(amount_token_1, 0) - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.router_address) %} - let ( - amountA_again: Uint256, amountB_again: Uint256, liquidity_again: Uint256 - ) = IRouter.add_liquidity( - contract_address=router_address, - tokenA=token_0_address, - tokenB=token_1_address, - amountADesired=Uint256(amount_token_0, 0), - amountBDesired=Uint256(amount_token_1, 0), - amountAMin=Uint256(1, 0), - amountBMin=Uint256(1, 0), - to=user_1_address, - deadline=0, - ); - %{ stop_prank() %} - - assert amountA_again = Uint256(amount_token_0_again, 0); - assert amountB_again = Uint256(amount_token_1_again, 0); - // assert float(liquidity) == pytest.approx( - // math.sqrt(amount_token_0 * amount_token_1) - MINIMUM_LIQUIDITY) - %{ expect_events({"name": "Mint", "from_address": ids.pair_address, "data": [ids.router_address, ids.amountA_again.low, ids.amountA_again.high, ids.amountB_again.low, ids.amountB_again.high]}) %} - - let ( - reserve_0_again: Uint256, reserve_1_again: Uint256, block_timestamp_last_again - ) = IPair.get_reserves(contract_address=pair_address); - let (totalSupply_again: Uint256) = IERC20.totalSupply(contract_address=pair_address); - - let (totalSupply_mul_totalSupply_again: Uint256) = uint256_checked_mul( - totalSupply_again, totalSupply_again - ); - let (reserve_0_mul_reserve_1_again: Uint256) = uint256_checked_mul( - reserve_0_again, reserve_1_again - ); - - let (is_total_supply_mul_lesser_than_equal_reserve_mul_again) = uint256_le( - totalSupply_mul_totalSupply, reserve_0_mul_reserve_1 - ); - assert is_total_supply_mul_lesser_than_equal_reserve_mul_again = 1; - - let (user_1_token_0_balance: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_1_address - ); - - let (expected_reserve_0: Uint256) = uint256_checked_sub_le( - Uint256(amount_to_mint_token_0, 0), user_1_token_0_balance - ); - assert expected_reserve_0 = reserve_0_again; - - let (user_1_token_1_balance: Uint256) = IERC20.balanceOf( - contract_address=token_1_address, account=user_1_address - ); - - let (expected_reserve_1: Uint256) = uint256_checked_sub_le( - Uint256(amount_to_mint_token_1, 0), user_1_token_1_balance - ); - assert expected_reserve_1 = reserve_1_again; - - let (user_1_pair_balance: Uint256) = IERC20.balanceOf( - contract_address=pair_address, account=user_1_address - ); - - let (expected_total_supply: Uint256) = uint256_checked_add( - Uint256(MINIMUM_LIQUIDITY, 0), user_1_pair_balance - ); - assert expected_total_supply = totalSupply_again; - - // ## Remove liquidity - - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.pair_address) %} - IERC20.approve( - contract_address=pair_address, spender=router_address, amount=user_1_pair_balance - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.router_address) %} - let (amountA_burn: Uint256, amountB_burn: Uint256) = IRouter.remove_liquidity( - contract_address=router_address, - tokenA=token_0_address, - tokenB=token_1_address, - liquidity=user_1_pair_balance, - amountAMin=Uint256(1, 0), - amountBMin=Uint256(1, 0), - to=user_1_address, - deadline=0, - ); - %{ stop_prank() %} - - %{ expect_events({"name": "Burn", "from_address": ids.pair_address, "data": [ids.router_address, ids.amountA_burn.low, ids.amountA_burn.high, ids.amountB_burn.low, ids.amountB_burn.high, ids.user_1_address]}) %} - - let (user_1_pair_balance_burn: Uint256) = IERC20.balanceOf( - contract_address=pair_address, account=user_1_address - ); - assert user_1_pair_balance_burn = Uint256(0, 0); - - let (totalSupply_burn: Uint256) = IERC20.totalSupply(contract_address=pair_address); - assert totalSupply_burn = Uint256(MINIMUM_LIQUIDITY, 0); - - let (burn_address_balance: Uint256) = IERC20.balanceOf( - contract_address=pair_address, account=BURN_ADDRESS - ); - assert totalSupply_burn = burn_address_balance; - - let ( - reserve_0_burn: Uint256, reserve_1_burn: Uint256, block_timestamp_last_burn - ) = IPair.get_reserves(contract_address=pair_address); - - let (totalSupply_mul_totalSupply_burn: Uint256) = uint256_checked_mul( - totalSupply_burn, totalSupply_burn - ); - let (reserve_0_mul_reserve_1_burn: Uint256) = uint256_checked_mul( - reserve_0_burn, reserve_1_burn - ); - - let (is_total_supply_mul_lesser_than_equal_reserve_mul_burn) = uint256_le( - totalSupply_mul_totalSupply_burn, reserve_0_mul_reserve_1_burn - ); - assert is_total_supply_mul_lesser_than_equal_reserve_mul_burn = 1; - - return (); -} - -@external -func test_add_remove_liquidity_for_non_created_pair{ - syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr -}() { - alloc_locals; - - local unsorted_token_0_address; - local unsorted_token_1_address; - local factory_address; - local router_address; - local user_1_address; - - %{ - ids.unsorted_token_0_address = context.token_0_address - ids.unsorted_token_1_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [33, 3, 18, 0, 0, context.deployer_address, context.deployer_address]).contract_address - ids.factory_address = context.factory_address - ids.router_address = context.router_address - ids.user_1_address = context.user_1_address - %} - - let (token_0_address, token_1_address) = IRouter.sort_tokens( - contract_address=router_address, - tokenA=unsorted_token_0_address, - tokenB=unsorted_token_1_address, - ); - - let (token_0_decimals) = IERC20.decimals(contract_address=token_0_address); - let (token_0_multiplier) = pow(10, token_0_decimals); - - let (token_1_decimals) = IERC20.decimals(contract_address=token_1_address); - let (token_1_multiplier) = pow(10, token_1_decimals); - - let amount_to_mint_token_0 = 100 * token_0_multiplier; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.token_0_address) %} - IERC20.mint( - contract_address=token_0_address, - recipient=user_1_address, - amount=Uint256(amount_to_mint_token_0, 0), - ); - %{ stop_prank() %} - - let amount_to_mint_token_1 = 100 * token_1_multiplier; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.token_1_address) %} - IERC20.mint( - contract_address=token_1_address, - recipient=user_1_address, - amount=Uint256(amount_to_mint_token_1, 0), - ); - %{ stop_prank() %} - - // ## Add liquidity for first time - - let amount_token_0 = 2 * token_0_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.token_0_address) %} - IERC20.approve( - contract_address=token_0_address, spender=router_address, amount=Uint256(amount_token_0, 0) - ); - %{ stop_prank() %} - - let amount_token_1 = 4 * token_1_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.token_1_address) %} - IERC20.approve( - contract_address=token_1_address, spender=router_address, amount=Uint256(amount_token_1, 0) - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.router_address) %} - let (amountA: Uint256, amountB: Uint256, liquidity: Uint256) = IRouter.add_liquidity( - contract_address=router_address, - tokenA=token_0_address, - tokenB=token_1_address, - amountADesired=Uint256(amount_token_0, 0), - amountBDesired=Uint256(amount_token_1, 0), - amountAMin=Uint256(1, 0), - amountBMin=Uint256(1, 0), - to=user_1_address, - deadline=0, - ); - %{ stop_prank() %} - - assert amountA = Uint256(amount_token_0, 0); - assert amountB = Uint256(amount_token_1, 0); - // assert float(liquidity) == pytest.approx( - // math.sqrt(amount_token_0 * amount_token_1) - MINIMUM_LIQUIDITY) - - let (pair_address) = IFactory.get_pair( - contract_address=factory_address, token0=token_1_address, token1=token_0_address - ); - - %{ expect_events({"name": "Mint", "from_address": ids.pair_address, "data": [ids.router_address, ids.amountA.low, ids.amountA.high, ids.amountB.low, ids.amountB.high]}) %} - - let (reserve_0: Uint256, reserve_1: Uint256, block_timestamp_last) = IPair.get_reserves( - contract_address=pair_address - ); - let (totalSupply: Uint256) = IERC20.totalSupply(contract_address=pair_address); - - let (totalSupply_mul_totalSupply: Uint256) = uint256_checked_mul(totalSupply, totalSupply); - let (reserve_0_mul_reserve_1: Uint256) = uint256_checked_mul(reserve_0, reserve_1); - - let (is_total_supply_mul_lesser_than_equal_reserve_mul) = uint256_le( - totalSupply_mul_totalSupply, reserve_0_mul_reserve_1 - ); - assert is_total_supply_mul_lesser_than_equal_reserve_mul = 1; - - let (totalSupply_plus_1: Uint256) = uint256_checked_add(totalSupply, Uint256(1, 0)); - - let (totalSupply1_mul_totalSupply1: Uint256) = uint256_checked_mul( - totalSupply_plus_1, totalSupply_plus_1 - ); - - let (is_total_supply_1_mul_greater_than_reserve_mul) = uint256_lt( - reserve_0_mul_reserve_1, totalSupply1_mul_totalSupply1 - ); - assert is_total_supply_1_mul_greater_than_reserve_mul = 1; - - // ## Add liquidity to pair which already has liquidity - - let amount_token_0_again = 2 * token_0_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.token_0_address) %} - IERC20.approve( - contract_address=token_0_address, spender=router_address, amount=Uint256(amount_token_0, 0) - ); - %{ stop_prank() %} - - let amount_token_1_again = 4 * token_1_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.token_1_address) %} - IERC20.approve( - contract_address=token_1_address, spender=router_address, amount=Uint256(amount_token_1, 0) - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.router_address) %} - let ( - amountA_again: Uint256, amountB_again: Uint256, liquidity_again: Uint256 - ) = IRouter.add_liquidity( - contract_address=router_address, - tokenA=token_0_address, - tokenB=token_1_address, - amountADesired=Uint256(amount_token_0, 0), - amountBDesired=Uint256(amount_token_1, 0), - amountAMin=Uint256(1, 0), - amountBMin=Uint256(1, 0), - to=user_1_address, - deadline=0, - ); - %{ stop_prank() %} - - assert amountA_again = Uint256(amount_token_0_again, 0); - assert amountB_again = Uint256(amount_token_1_again, 0); - // assert float(liquidity) == pytest.approx( - // math.sqrt(amount_token_0 * amount_token_1) - MINIMUM_LIQUIDITY) - %{ expect_events({"name": "Mint", "from_address": ids.pair_address, "data": [ids.router_address, ids.amountA_again.low, ids.amountA_again.high, ids.amountB_again.low, ids.amountB_again.high]}) %} - - let ( - reserve_0_again: Uint256, reserve_1_again: Uint256, block_timestamp_last_again - ) = IPair.get_reserves(contract_address=pair_address); - let (totalSupply_again: Uint256) = IERC20.totalSupply(contract_address=pair_address); - - let (totalSupply_mul_totalSupply_again: Uint256) = uint256_checked_mul( - totalSupply_again, totalSupply_again - ); - let (reserve_0_mul_reserve_1_again: Uint256) = uint256_checked_mul( - reserve_0_again, reserve_1_again - ); - - let (is_total_supply_mul_lesser_than_equal_reserve_mul_again) = uint256_le( - totalSupply_mul_totalSupply, reserve_0_mul_reserve_1 - ); - assert is_total_supply_mul_lesser_than_equal_reserve_mul_again = 1; - - let (user_1_token_0_balance: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_1_address - ); - - let (expected_reserve_0: Uint256) = uint256_checked_sub_le( - Uint256(amount_to_mint_token_0, 0), user_1_token_0_balance - ); - assert expected_reserve_0 = reserve_0_again; - - let (user_1_token_1_balance: Uint256) = IERC20.balanceOf( - contract_address=token_1_address, account=user_1_address - ); - - let (expected_reserve_1: Uint256) = uint256_checked_sub_le( - Uint256(amount_to_mint_token_1, 0), user_1_token_1_balance - ); - assert expected_reserve_1 = reserve_1_again; - - let (user_1_pair_balance: Uint256) = IERC20.balanceOf( - contract_address=pair_address, account=user_1_address - ); - - let (expected_total_supply: Uint256) = uint256_checked_add( - Uint256(MINIMUM_LIQUIDITY, 0), user_1_pair_balance - ); - assert expected_total_supply = totalSupply_again; - - // ## Remove liquidity - - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.pair_address) %} - IERC20.approve( - contract_address=pair_address, spender=router_address, amount=user_1_pair_balance - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.router_address) %} - let (amountA_burn: Uint256, amountB_burn: Uint256) = IRouter.remove_liquidity( - contract_address=router_address, - tokenA=token_0_address, - tokenB=token_1_address, - liquidity=user_1_pair_balance, - amountAMin=Uint256(1, 0), - amountBMin=Uint256(1, 0), - to=user_1_address, - deadline=0, - ); - %{ stop_prank() %} - - %{ expect_events({"name": "Burn", "from_address": ids.pair_address, "data": [ids.router_address, ids.amountA_burn.low, ids.amountA_burn.high, ids.amountB_burn.low, ids.amountB_burn.high, ids.user_1_address]}) %} - - let (user_1_pair_balance_burn: Uint256) = IERC20.balanceOf( - contract_address=pair_address, account=user_1_address - ); - assert user_1_pair_balance_burn = Uint256(0, 0); - - let (totalSupply_burn: Uint256) = IERC20.totalSupply(contract_address=pair_address); - assert totalSupply_burn = Uint256(MINIMUM_LIQUIDITY, 0); - - let (burn_address_balance: Uint256) = IERC20.balanceOf( - contract_address=pair_address, account=BURN_ADDRESS - ); - assert totalSupply_burn = burn_address_balance; - - let ( - reserve_0_burn: Uint256, reserve_1_burn: Uint256, block_timestamp_last_burn - ) = IPair.get_reserves(contract_address=pair_address); - - let (totalSupply_mul_totalSupply_burn: Uint256) = uint256_checked_mul( - totalSupply_burn, totalSupply_burn - ); - let (reserve_0_mul_reserve_1_burn: Uint256) = uint256_checked_mul( - reserve_0_burn, reserve_1_burn - ); - - let (is_total_supply_mul_lesser_than_equal_reserve_mul_burn) = uint256_le( - totalSupply_mul_totalSupply_burn, reserve_0_mul_reserve_1_burn - ); - assert is_total_supply_mul_lesser_than_equal_reserve_mul_burn = 1; - - return (); -} diff --git a/tests/test_create_pair.cairo b/tests/test_create_pair.cairo deleted file mode 100644 index ddcb121..0000000 --- a/tests/test_create_pair.cairo +++ /dev/null @@ -1,153 +0,0 @@ -%lang starknet - -from protostar.asserts import assert_not_eq - -@contract_interface -namespace IFactory { - func create_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func get_fee_to_setter() -> (address: felt) { - } -} - -@contract_interface -namespace IRouter { - func sort_tokens(tokenA: felt, tokenB: felt) -> (token0: felt, token1: felt) { - } -} - -@external -func __setup__{syscall_ptr: felt*, range_check_ptr}() { - tempvar deployer_address = 123456789987654321; - tempvar factory_address; - tempvar token_0_address; - tempvar token_1_address; - %{ - context.deployer_address = ids.deployer_address - context.declared_pair_proxy_class_hash = declare("contracts/PairProxy.cairo").class_hash - context.declared_pair_class_hash = declare("contracts/Pair.cairo").class_hash - context.declared_factory_class_hash = declare("contracts/Factory.cairo").class_hash - context.factory_address = deploy_contract("contracts/FactoryProxy.cairo", [context.declared_factory_class_hash, context.declared_pair_proxy_class_hash, context.declared_pair_class_hash, context.deployer_address]).contract_address - context.token_0_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [11, 1, 18, 0, 0, context.deployer_address, context.deployer_address]).contract_address - context.token_1_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [22, 2, 6, 0, 0, context.deployer_address, context.deployer_address]).contract_address - ids.factory_address = context.factory_address - ids.token_0_address = context.token_0_address - ids.token_1_address = context.token_1_address - %} - - return (); -} - -@external -func test_create_pair_without_tokens{syscall_ptr: felt*, range_check_ptr}() { - tempvar factory_address; - tempvar token_0_address; - - %{ - ids.factory_address = context.factory_address - ids.token_0_address = context.token_0_address - %} - - %{ expect_revert(error_message="Factory::create_pair::tokenA and tokenB must be non zero") %} - let (pair_address) = IFactory.create_pair(contract_address=factory_address, token0=0, token1=0); - - %{ expect_revert(error_message="Factory::create_pair::tokenA and tokenB must be non zero") %} - let (pair_address) = IFactory.create_pair( - contract_address=factory_address, token0=token_0_address, token1=0 - ); - - return (); -} - -@external -func test_create_pair_same_tokens{syscall_ptr: felt*, range_check_ptr}() { - tempvar factory_address; - tempvar token_0_address; - - %{ - ids.factory_address = context.factory_address - ids.token_0_address = context.token_0_address - %} - - %{ expect_revert(error_message="Factory::create_pair::tokenA and tokenB must be different") %} - let (pair_address) = IFactory.create_pair( - contract_address=factory_address, token0=token_0_address, token1=token_0_address - ); - - return (); -} - -@external -func test_create_pair_same_pair{syscall_ptr: felt*, range_check_ptr}() { - tempvar factory_address; - tempvar token_0_address; - tempvar token_1_address; - - %{ - ids.factory_address = context.factory_address - ids.token_0_address = context.token_0_address - ids.token_1_address = context.token_1_address - %} - - let (pair_address) = IFactory.create_pair( - contract_address=factory_address, token0=token_0_address, token1=token_1_address - ); - - assert_not_eq(pair_address, 0); - - %{ expect_revert(error_message="Factory::create_pair::pair already exists for tokenA and tokenB") %} - let (pair_address) = IFactory.create_pair( - contract_address=factory_address, token0=token_0_address, token1=token_1_address - ); - %{ expect_revert(error_message="Factory::create_pair::pair already exists for tokenA and tokenB") %} - let (pair_address) = IFactory.create_pair( - contract_address=factory_address, token0=token_1_address, token1=token_0_address - ); - - return (); -} - -@external -func test_create2_deployed_pair{syscall_ptr: felt*, range_check_ptr}() { - tempvar declared_pair_proxy_class_hash; - tempvar declared_pair_class_hash; - tempvar factory_address; - tempvar router_address; - tempvar token_0_address; - tempvar token_1_address; - tempvar create2_pair_address; - - %{ - ids.declared_pair_proxy_class_hash = context.declared_pair_proxy_class_hash - ids.declared_pair_class_hash = context.declared_pair_class_hash - ids.factory_address = context.factory_address - context.declared_router_class_hash = declare("contracts/Router.cairo").class_hash - ids.router_address = deploy_contract("contracts/RouterProxy.cairo", [context.declared_router_class_hash, context.factory_address, context.deployer_address]).contract_address - ids.token_0_address = context.token_0_address - ids.token_1_address = context.token_1_address - %} - - let (sorted_token_0_address, sorted_token_1_address) = IRouter.sort_tokens( - contract_address=router_address, tokenA=token_0_address, tokenB=token_1_address - ); - let (fee_to_setter_address) = IFactory.get_fee_to_setter(contract_address=factory_address); - - %{ - from starkware.starknet.core.os.contract_address.contract_address import calculate_contract_address_from_hash - from starkware.cairo.lang.vm.crypto import pedersen_hash - salt = pedersen_hash(ids.sorted_token_0_address, ids.sorted_token_1_address) - constructor_calldata = [ids.declared_pair_class_hash, ids.sorted_token_0_address, ids.sorted_token_1_address, ids.fee_to_setter_address] - ids.create2_pair_address = calculate_contract_address_from_hash(salt=salt, class_hash=ids.declared_pair_proxy_class_hash, deployer_address=ids.factory_address, constructor_calldata=constructor_calldata) - %} - - let (pair_address) = IFactory.create_pair( - contract_address=factory_address, - token0=sorted_token_0_address, - token1=sorted_token_1_address, - ); - - assert pair_address = create2_pair_address; - - return (); -} diff --git a/tests/test_deployment.cairo b/tests/test_deployment.cairo deleted file mode 100644 index 52f3f50..0000000 --- a/tests/test_deployment.cairo +++ /dev/null @@ -1,169 +0,0 @@ -%lang starknet - -@contract_interface -namespace IERC20 { - func name() -> (name: felt) { - } - - func symbol() -> (symbol: felt) { - } - - func decimals() -> (decimals: felt) { - } -} - -@contract_interface -namespace IRouter { - func factory() -> (address: felt) { - } - - func sort_tokens(tokenA: felt, tokenB: felt) -> (token0: felt, token1: felt) { - } -} - -@contract_interface -namespace IFactory { - func create_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func get_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func get_all_pairs() -> (all_pairs_len: felt, all_pairs: felt*) { - } -} - -@contract_interface -namespace IProxy { - func get_admin() -> (admin: felt) { - } -} - -@external -func __setup__{syscall_ptr: felt*, range_check_ptr}() { - tempvar deployer_address = 123456789987654321; - tempvar factory_address; - tempvar router_address; - tempvar token_0_address; - tempvar token_1_address; - %{ - context.deployer_address = ids.deployer_address - context.declared_pair_proxy_class_hash = declare("contracts/PairProxy.cairo").class_hash - context.declared_pair_class_hash = declare("contracts/Pair.cairo").class_hash - context.declared_factory_class_hash = declare("contracts/Factory.cairo").class_hash - context.factory_address = deploy_contract("contracts/FactoryProxy.cairo", [context.declared_factory_class_hash, context.declared_pair_proxy_class_hash, context.declared_pair_class_hash, context.deployer_address]).contract_address - context.declared_router_class_hash = declare("contracts/Router.cairo").class_hash - context.router_address = deploy_contract("contracts/RouterProxy.cairo", [context.declared_router_class_hash, context.factory_address, context.deployer_address]).contract_address - context.token_0_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [11, 1, 18, 0, 0, context.deployer_address, context.deployer_address]).contract_address - context.token_1_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [22, 2, 6, 0, 0, context.deployer_address, context.deployer_address]).contract_address - ids.factory_address = context.factory_address - ids.router_address = context.router_address - ids.token_0_address = context.token_0_address - ids.token_1_address = context.token_1_address - %} - let (sorted_token_0_address, sorted_token_1_address) = IRouter.sort_tokens( - contract_address=router_address, tokenA=token_0_address, tokenB=token_1_address - ); - - let (pair_address) = IFactory.create_pair( - contract_address=factory_address, - token0=sorted_token_0_address, - token1=sorted_token_1_address, - ); - - %{ context.pair_address = ids.pair_address %} - return (); -} - -@external -func test_pair{syscall_ptr: felt*, range_check_ptr}() { - tempvar pair_address; - - %{ ids.pair_address = context.pair_address %} - - let (name) = IERC20.name(contract_address=pair_address); - assert name = 'JediSwap Pair'; - - let (symbol) = IERC20.symbol(contract_address=pair_address); - assert symbol = 'JEDI-P'; - - let (decimals) = IERC20.decimals(contract_address=pair_address); - assert decimals = 18; - - return (); -} - -@external -func test_pair_in_factory{syscall_ptr: felt*, range_check_ptr}() { - tempvar token_0_address; - tempvar token_1_address; - tempvar factory_address; - tempvar pair_address; - - %{ - ids.factory_address = context.factory_address - ids.token_0_address = context.token_0_address - ids.token_1_address = context.token_1_address - ids.pair_address = context.pair_address - %} - - let (pair_address_from_factory_1) = IFactory.get_pair( - contract_address=factory_address, token0=token_0_address, token1=token_1_address - ); - assert pair_address_from_factory_1 = pair_address; - - let (pair_address_from_factory_2) = IFactory.get_pair( - contract_address=factory_address, token0=token_1_address, token1=token_0_address - ); - assert pair_address_from_factory_2 = pair_address; - - let (all_pairs_len, all_pairs: felt*) = IFactory.get_all_pairs( - contract_address=factory_address - ); - assert all_pairs_len = 1; - assert [all_pairs] = pair_address; - - return (); -} - -@external -func test_factory_in_router{syscall_ptr: felt*, range_check_ptr}() { - tempvar factory_address; - tempvar router_address; - - %{ - ids.factory_address = context.factory_address - ids.router_address = context.router_address - %} - - let (factory_address_from_router) = IRouter.factory(contract_address=router_address); - assert factory_address_from_router = factory_address; - - return (); -} - -@external -func test_proxy_admins{syscall_ptr: felt*, range_check_ptr}() { - tempvar deployer_address; - tempvar factory_address; - tempvar router_address; - tempvar pair_address; - - %{ - ids.deployer_address = context.deployer_address - ids.factory_address = context.factory_address - ids.router_address = context.router_address - ids.pair_address = context.pair_address - %} - - let (factory_admin) = IProxy.get_admin(contract_address=factory_address); - assert factory_admin = deployer_address; - - let (router_admin) = IProxy.get_admin(contract_address=router_address); - assert router_admin = deployer_address; - - let (pair_admin) = IProxy.get_admin(contract_address=pair_address); - assert pair_admin = deployer_address; - - return (); -} diff --git a/tests/test_flash_swap.cairo b/tests/test_flash_swap.cairo deleted file mode 100644 index 8ad8332..0000000 --- a/tests/test_flash_swap.cairo +++ /dev/null @@ -1,522 +0,0 @@ -%lang starknet - -from starkware.cairo.common.cairo_builtins import HashBuiltin -from starkware.cairo.common.uint256 import Uint256, uint256_le, uint256_lt -from starkware.cairo.common.pow import pow -from starkware.cairo.common.alloc import alloc - -from contracts.utils.math import uint256_checked_add, uint256_checked_mul, uint256_checked_sub_le - -const MINIMUM_LIQUIDITY = 1000; -const BURN_ADDRESS = 1; - -@contract_interface -namespace IERC20 { - func name() -> (name: felt) { - } - - func symbol() -> (symbol: felt) { - } - - func decimals() -> (decimals: felt) { - } - - func mint(recipient: felt, amount: Uint256) { - } - - func approve(spender: felt, amount: Uint256) -> (success: felt) { - } - - func totalSupply() -> (totalSupply: Uint256) { - } - - func balanceOf(account: felt) -> (balance: Uint256) { - } -} - -@contract_interface -namespace IPair { - func get_reserves() -> (reserve0: Uint256, reserve1: Uint256, block_timestamp_last: felt) { - } - - func swap(amount0Out: Uint256, amount1Out: Uint256, to: felt, data_len: felt, data: felt*) { - } -} - -@contract_interface -namespace IRouter { - func factory() -> (address: felt) { - } - - func sort_tokens(tokenA: felt, tokenB: felt) -> (token0: felt, token1: felt) { - } - - func add_liquidity( - tokenA: felt, - tokenB: felt, - amountADesired: Uint256, - amountBDesired: Uint256, - amountAMin: Uint256, - amountBMin: Uint256, - to: felt, - deadline: felt, - ) -> (amountA: Uint256, amountB: Uint256, liquidity: Uint256) { - } - - func remove_liquidity( - tokenA: felt, - tokenB: felt, - liquidity: Uint256, - amountAMin: Uint256, - amountBMin: Uint256, - to: felt, - deadline: felt, - ) -> (amountA: Uint256, amountB: Uint256) { - } - - func swap_exact_tokens_for_tokens( - amountIn: Uint256, - amountOutMin: Uint256, - path_len: felt, - path: felt*, - to: felt, - deadline: felt, - ) -> (amounts_len: felt, amounts: Uint256*) { - } - - func swap_tokens_for_exact_tokens( - amountOut: Uint256, - amountInMax: Uint256, - path_len: felt, - path: felt*, - to: felt, - deadline: felt, - ) -> (amounts_len: felt, amounts: Uint256*) { - } -} - -@contract_interface -namespace IFactory { - func create_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func get_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func get_all_pairs() -> (all_pairs_len: felt, all_pairs: felt*) { - } -} - -@external -func __setup__{syscall_ptr: felt*, range_check_ptr}() { - alloc_locals; - - tempvar deployer_address = 123456789987654321; - tempvar user_1_address = 987654321123456789; - tempvar user_2_address = 987654331133456789; - tempvar factory_address; - local router_address; - local token_0_address; - local token_1_address; - local flash_swap_test_address; - %{ - context.deployer_address = ids.deployer_address - context.user_1_address = ids.user_1_address - context.user_2_address = ids.user_2_address - context.declared_pair_proxy_class_hash = declare("contracts/PairProxy.cairo").class_hash - context.declared_pair_class_hash = declare("contracts/Pair.cairo").class_hash - context.declared_factory_class_hash = declare("contracts/Factory.cairo").class_hash - context.factory_address = deploy_contract("contracts/FactoryProxy.cairo", [context.declared_factory_class_hash, context.declared_pair_proxy_class_hash, context.declared_pair_class_hash, context.deployer_address]).contract_address - context.declared_router_class_hash = declare("contracts/Router.cairo").class_hash - context.router_address = deploy_contract("contracts/RouterProxy.cairo", [context.declared_router_class_hash, context.factory_address, context.deployer_address]).contract_address - context.token_0_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [11, 1, 18, 0, 0, context.deployer_address, context.deployer_address]).contract_address - context.token_1_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [22, 2, 6, 0, 0, context.deployer_address, context.deployer_address]).contract_address - context.flash_swap_test_address = deploy_contract("contracts/test/FlashSwapTest.cairo", [context.factory_address]).contract_address - ids.factory_address = context.factory_address - ids.router_address = context.router_address - ids.token_0_address = context.token_0_address - ids.token_1_address = context.token_1_address - ids.flash_swap_test_address = context.flash_swap_test_address - %} - let (sorted_token_0_address, sorted_token_1_address) = IRouter.sort_tokens( - contract_address=router_address, tokenA=token_0_address, tokenB=token_1_address - ); - - let (pair_address) = IFactory.create_pair( - contract_address=factory_address, - token0=sorted_token_0_address, - token1=sorted_token_1_address, - ); - - %{ - context.sorted_token_0_address = ids.sorted_token_0_address - context.sorted_token_1_address = ids.sorted_token_1_address - context.pair_address = ids.pair_address - %} - - let (token_0_decimals) = IERC20.decimals(contract_address=sorted_token_0_address); - let (token_0_multiplier) = pow(10, token_0_decimals); - - let (token_1_decimals) = IERC20.decimals(contract_address=sorted_token_1_address); - let (token_1_multiplier) = pow(10, token_1_decimals); - - let amount_to_mint_token_0 = 100 * token_0_multiplier; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.sorted_token_0_address) %} - IERC20.mint( - contract_address=sorted_token_0_address, - recipient=user_1_address, - amount=Uint256(amount_to_mint_token_0, 0), - ); - IERC20.mint( - contract_address=sorted_token_0_address, - recipient=user_2_address, - amount=Uint256(amount_to_mint_token_0, 0), - ); - %{ stop_prank() %} - - let amount_to_mint_token_1 = 100 * token_1_multiplier; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.sorted_token_1_address) %} - IERC20.mint( - contract_address=sorted_token_1_address, - recipient=user_1_address, - amount=Uint256(amount_to_mint_token_1, 0), - ); - IERC20.mint( - contract_address=sorted_token_1_address, - recipient=user_2_address, - amount=Uint256(amount_to_mint_token_1, 0), - ); - %{ stop_prank() %} - - // ## Add liquidity for first time - - let amount_token_0 = 20 * token_0_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.sorted_token_0_address) %} - IERC20.approve( - contract_address=sorted_token_0_address, - spender=router_address, - amount=Uint256(amount_token_0, 0), - ); - %{ stop_prank() %} - - let amount_token_1 = 40 * token_1_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.sorted_token_1_address) %} - IERC20.approve( - contract_address=sorted_token_1_address, - spender=router_address, - amount=Uint256(amount_token_1, 0), - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.router_address) %} - let (amountA: Uint256, amountB: Uint256, liquidity: Uint256) = IRouter.add_liquidity( - contract_address=router_address, - tokenA=sorted_token_0_address, - tokenB=sorted_token_1_address, - amountADesired=Uint256(amount_token_0, 0), - amountBDesired=Uint256(amount_token_1, 0), - amountAMin=Uint256(1, 0), - amountBMin=Uint256(1, 0), - to=user_1_address, - deadline=0, - ); - %{ stop_prank() %} - - return (); -} - -@external -func test_flash_swap_not_enough_liquidity{ - syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr -}() { - alloc_locals; - - local token_0_address; - local token_1_address; - local router_address; - local user_2_address; - local pair_address; - local flash_swap_test_address; - - %{ - ids.token_0_address = context.sorted_token_0_address - ids.token_1_address = context.sorted_token_1_address - ids.router_address = context.router_address - ids.user_2_address = context.user_2_address - ids.pair_address = context.pair_address - ids.flash_swap_test_address = context.flash_swap_test_address - %} - - let (user_2_token_0_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_2_address - ); - let (user_2_token_1_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_1_address, account=user_2_address - ); - let (reserve_0_initial: Uint256, reserve_1_initial: Uint256, _) = IPair.get_reserves( - contract_address=pair_address - ); - - let (token_0_decimals) = IERC20.decimals(contract_address=token_0_address); - let (token_0_multiplier) = pow(10, token_0_decimals); - local amount_token_0 = 200 * token_0_multiplier; - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.pair_address) %} - let data: felt* = alloc(); - assert [data] = 0; - %{ expect_revert(error_message="Pair::swap::insufficient liquidity") %} - IPair.swap( - contract_address=pair_address, - amount0Out=Uint256(amount_token_0, 0), - amount1Out=Uint256(0, 0), - to=flash_swap_test_address, - data_len=1, - data=data, - ); - %{ stop_prank() %} - - return (); -} - -@external -func test_flash_swap_no_repayment{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}( - ) { - alloc_locals; - - local token_0_address; - local token_1_address; - local router_address; - local user_2_address; - local pair_address; - local flash_swap_test_address; - - %{ - ids.token_0_address = context.sorted_token_0_address - ids.token_1_address = context.sorted_token_1_address - ids.router_address = context.router_address - ids.user_2_address = context.user_2_address - ids.pair_address = context.pair_address - ids.flash_swap_test_address = context.flash_swap_test_address - %} - - let (user_2_token_0_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_2_address - ); - let (user_2_token_1_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_1_address, account=user_2_address - ); - let (reserve_0_initial: Uint256, reserve_1_initial: Uint256, _) = IPair.get_reserves( - contract_address=pair_address - ); - - let (token_0_decimals) = IERC20.decimals(contract_address=token_0_address); - let (token_0_multiplier) = pow(10, token_0_decimals); - local amount_token_0 = 2 * token_0_multiplier; - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.pair_address) %} - let data: felt* = alloc(); - assert [data] = 0; - %{ expect_revert(error_message="Pair::swap::invariant K") %} - IPair.swap( - contract_address=pair_address, - amount0Out=Uint256(amount_token_0, 0), - amount1Out=Uint256(0, 0), - to=flash_swap_test_address, - data_len=1, - data=data, - ); - %{ stop_prank() %} - - return (); -} - -@external -func test_flash_swap_not_enough_repayment{ - syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr -}() { - alloc_locals; - - local token_0_address; - local token_1_address; - local router_address; - local user_2_address; - local pair_address; - local flash_swap_test_address; - - %{ - ids.token_0_address = context.sorted_token_0_address - ids.token_1_address = context.sorted_token_1_address - ids.router_address = context.router_address - ids.user_2_address = context.user_2_address - ids.pair_address = context.pair_address - ids.flash_swap_test_address = context.flash_swap_test_address - %} - - let (user_2_token_0_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_2_address - ); - let (user_2_token_1_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_1_address, account=user_2_address - ); - let (reserve_0_initial: Uint256, reserve_1_initial: Uint256, _) = IPair.get_reserves( - contract_address=pair_address - ); - - let (token_0_decimals) = IERC20.decimals(contract_address=token_0_address); - let (token_0_multiplier) = pow(10, token_0_decimals); - local amount_token_0 = 2 * token_0_multiplier; - - let amount_to_mint_token_0 = amount_token_0 * 2 / 1000; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.token_0_address) %} - IERC20.mint( - contract_address=token_0_address, - recipient=flash_swap_test_address, - amount=Uint256(amount_to_mint_token_0, 0), - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.pair_address) %} - let data: felt* = alloc(); - assert [data] = 0; - %{ expect_revert(error_message="Pair::swap::invariant K") %} - IPair.swap( - contract_address=pair_address, - amount0Out=Uint256(amount_token_0, 0), - amount1Out=Uint256(0, 0), - to=flash_swap_test_address, - data_len=1, - data=data, - ); - %{ stop_prank() %} - - return (); -} - -@external -func test_flash_swap_same_token_repayment{ - syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr -}() { - alloc_locals; - - local token_0_address; - local token_1_address; - local router_address; - local user_2_address; - local pair_address; - local flash_swap_test_address; - - %{ - ids.token_0_address = context.sorted_token_0_address - ids.token_1_address = context.sorted_token_1_address - ids.router_address = context.router_address - ids.user_2_address = context.user_2_address - ids.pair_address = context.pair_address - ids.flash_swap_test_address = context.flash_swap_test_address - %} - - let (user_2_token_0_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_2_address - ); - let (user_2_token_1_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_1_address, account=user_2_address - ); - let (reserve_0_initial: Uint256, reserve_1_initial: Uint256, _) = IPair.get_reserves( - contract_address=pair_address - ); - - let (token_0_decimals) = IERC20.decimals(contract_address=token_0_address); - let (token_0_multiplier) = pow(10, token_0_decimals); - local amount_token_0 = 2 * token_0_multiplier; - - let amount_to_mint_token_0 = amount_token_0 * 4 / 1000; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.token_0_address) %} - IERC20.mint( - contract_address=token_0_address, - recipient=flash_swap_test_address, - amount=Uint256(amount_to_mint_token_0, 0), - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.pair_address) %} - let data: felt* = alloc(); - assert [data] = 0; - IPair.swap( - contract_address=pair_address, - amount0Out=Uint256(amount_token_0, 0), - amount1Out=Uint256(0, 0), - to=flash_swap_test_address, - data_len=1, - data=data, - ); - %{ stop_prank() %} - - %{ expect_events({"name": "Swap", "from_address": ids.pair_address, "data": [ids.user_2_address, ids.amount_token_0 + ids.amount_to_mint_token_0, 0, 0, 0, ids.amount_token_0, 0, 0, 0, ids.flash_swap_test_address]}) %} - - return (); -} - -@external -func test_flash_swap_other_token_repayment{ - syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr -}() { - alloc_locals; - - local token_0_address; - local token_1_address; - local router_address; - local user_2_address; - local pair_address; - local flash_swap_test_address; - - %{ - ids.token_0_address = context.sorted_token_0_address - ids.token_1_address = context.sorted_token_1_address - ids.router_address = context.router_address - ids.user_2_address = context.user_2_address - ids.pair_address = context.pair_address - ids.flash_swap_test_address = context.flash_swap_test_address - %} - - let (user_2_token_0_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_2_address - ); - let (user_2_token_1_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_1_address, account=user_2_address - ); - let (reserve_0_initial: Uint256, reserve_1_initial: Uint256, _) = IPair.get_reserves( - contract_address=pair_address - ); - - let (token_0_decimals) = IERC20.decimals(contract_address=token_0_address); - let (token_0_multiplier) = pow(10, token_0_decimals); - local amount_token_0 = 2 * token_0_multiplier; - - let (token_1_decimals) = IERC20.decimals(contract_address=token_1_address); - let (token_1_multiplier) = pow(10, token_1_decimals); - local amount_token_1 = 4 * token_1_multiplier; - let amount_to_mint_token_1 = amount_token_1 * 4 / 1000; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.token_1_address) %} - IERC20.mint( - contract_address=token_1_address, - recipient=flash_swap_test_address, - amount=Uint256(amount_to_mint_token_1, 0), - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.pair_address) %} - let data: felt* = alloc(); - assert [data] = 0; - IPair.swap( - contract_address=pair_address, - amount0Out=Uint256(amount_token_0, 0), - amount1Out=Uint256(0, 0), - to=flash_swap_test_address, - data_len=1, - data=data, - ); - %{ stop_prank() %} - - %{ expect_events({"name": "Swap", "from_address": ids.pair_address, "data": [ids.user_2_address, ids.amount_token_0, 0, ids.amount_to_mint_token_1, 0, ids.amount_token_0, 0, 0, 0, ids.flash_swap_test_address]}) %} - - return (); -} diff --git a/tests/test_multicall.cairo b/tests/test_multicall.cairo deleted file mode 100644 index 9482902..0000000 --- a/tests/test_multicall.cairo +++ /dev/null @@ -1,152 +0,0 @@ -%lang starknet - -from starkware.cairo.common.uint256 import Uint256 -from starkware.cairo.common.alloc import alloc -from starkware.starknet.common.syscalls import get_block_number - -@contract_interface -namespace IERC20 { - func name() -> (name: felt) { - } - - func symbol() -> (symbol: felt) { - } - - func decimals() -> (decimals: felt) { - } - - func mint(recipient: felt, amount: Uint256) { - } - - func approve(spender: felt, amount: Uint256) -> (success: felt) { - } - - func totalSupply() -> (totalSupply: Uint256) { - } - - func balanceOf(account: felt) -> (balance: Uint256) { - } -} - -@contract_interface -namespace IMulticall { - func aggregate(calls_len: felt, calls: felt*) -> (block_number: felt, result_len: felt, result: felt*) { - } - - func get_current_block_timestamp() -> (block_timestamp: felt) { - } -} - -@external -func __setup__{syscall_ptr: felt*, range_check_ptr}() { - tempvar deployer_address = 123456789987654321; - tempvar user_0_address = 987654321123456789; - tempvar user_1_address = 987654331133456789; - tempvar multicall_address; - tempvar token_0_address; - tempvar token_1_address; - %{ - context.deployer_address = ids.deployer_address - context.user_0_address = ids.user_0_address - context.user_1_address = ids.user_1_address - context.multicall_address = deploy_contract("contracts/utils/Multicall.cairo").contract_address - context.token_0_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [11, 1, 18, 0, 0, context.deployer_address, context.deployer_address]).contract_address - context.token_1_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [22, 2, 6, 0, 0, context.deployer_address, context.deployer_address]).contract_address - ids.multicall_address = context.multicall_address - ids.token_0_address = context.token_0_address - ids.token_1_address = context.token_1_address - %} - - let amount_to_mint_token = 1000; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.token_0_address) %} - IERC20.mint( - contract_address=token_0_address, - recipient=user_0_address, - amount=Uint256(amount_to_mint_token, 0), - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.token_1_address) %} - IERC20.mint( - contract_address=token_1_address, - recipient=user_1_address, - amount=Uint256(amount_to_mint_token, 0), - ); - %{ stop_prank() %} - return (); -} - -@external -func test_multicall{syscall_ptr: felt*, range_check_ptr}() { - tempvar user_0_address; - tempvar user_1_address; - tempvar multicall_address; - tempvar token_0_address; - tempvar token_1_address; - - %{ - ids.user_0_address = context.user_0_address - ids.user_1_address = context.user_1_address - ids.multicall_address = context.multicall_address - ids.token_0_address = context.token_0_address - ids.token_1_address = context.token_1_address - %} - - let calls: felt* = alloc(); - let call_1: felt* = alloc(); - let call_2: felt* = alloc(); - let call_3: felt* = alloc(); - let call_4: felt* = alloc(); - - assert [calls] = token_0_address; - assert [calls + 1] = 134830404806214277570220174593674215737759987247891306080029841794115377321; // get_selector_from_name('decimals') - assert [calls + 2] = 0; - - assert [calls + 3] = token_1_address; - assert [calls + 4] = 134830404806214277570220174593674215737759987247891306080029841794115377321; // get_selector_from_name('decimals') - assert [calls + 5] = 0; - - assert [calls + 6] = token_0_address; - assert [calls + 7] = 1307730684388977109649524593492043083703013045633289330664425380824804018030; // get_selector_from_name('balanceOf') - assert [calls + 8] = 1; - assert [calls + 9] = user_0_address; - - assert [calls + 10] = token_1_address; - assert [calls + 11] = 1307730684388977109649524593492043083703013045633289330664425380824804018030; // get_selector_from_name('balanceOf') - assert [calls + 12] = 1; - assert [calls + 13] = user_1_address; - - - %{ stop_roll = roll(123, target_contract_address=ids.multicall_address) %} - let (block_number: felt, result_len: felt, result: felt*) = IMulticall.aggregate( - contract_address=multicall_address, calls_len=14, calls=calls - ); - %{ stop_roll() %} - - assert block_number = 123; - assert [result] = 18; - assert [result + 1] = 6; - assert [result + 2] = 1000; - assert [result + 4] = 1000; - - return (); -} - -@external -func test_multicall_timestamp{syscall_ptr: felt*, range_check_ptr}() { - tempvar multicall_address; - - %{ - ids.multicall_address = context.multicall_address - %} - - %{ stop_warp = warp(123456, target_contract_address=ids.multicall_address) %} - let (block_timestamp: felt) = IMulticall.get_current_block_timestamp( - contract_address=multicall_address - ); - %{ stop_warp() %} - - assert block_timestamp = 123456; - - return (); -} diff --git a/tests/test_protocol_fee.cairo b/tests/test_protocol_fee.cairo deleted file mode 100644 index 1915fc6..0000000 --- a/tests/test_protocol_fee.cairo +++ /dev/null @@ -1,314 +0,0 @@ -%lang starknet - -from starkware.cairo.common.cairo_builtins import HashBuiltin -from starkware.cairo.common.uint256 import Uint256, uint256_le, uint256_lt -from starkware.cairo.common.pow import pow -from starkware.cairo.common.alloc import alloc - -from contracts.utils.math import uint256_checked_add, uint256_checked_mul, uint256_checked_sub_le - -const MINIMUM_LIQUIDITY = 1000; -const BURN_ADDRESS = 1; - -@contract_interface -namespace IERC20 { - func name() -> (name: felt) { - } - - func symbol() -> (symbol: felt) { - } - - func decimals() -> (decimals: felt) { - } - - func mint(recipient: felt, amount: Uint256) { - } - - func approve(spender: felt, amount: Uint256) -> (success: felt) { - } - - func totalSupply() -> (totalSupply: Uint256) { - } - - func balanceOf(account: felt) -> (balance: Uint256) { - } -} - -@contract_interface -namespace IPair { - func get_reserves() -> (reserve0: Uint256, reserve1: Uint256, block_timestamp_last: felt) { - } -} - -@contract_interface -namespace IRouter { - func factory() -> (address: felt) { - } - - func sort_tokens(tokenA: felt, tokenB: felt) -> (token0: felt, token1: felt) { - } - - func add_liquidity( - tokenA: felt, - tokenB: felt, - amountADesired: Uint256, - amountBDesired: Uint256, - amountAMin: Uint256, - amountBMin: Uint256, - to: felt, - deadline: felt, - ) -> (amountA: Uint256, amountB: Uint256, liquidity: Uint256) { - } - - func remove_liquidity( - tokenA: felt, - tokenB: felt, - liquidity: Uint256, - amountAMin: Uint256, - amountBMin: Uint256, - to: felt, - deadline: felt, - ) -> (amountA: Uint256, amountB: Uint256) { - } - - func swap_exact_tokens_for_tokens( - amountIn: Uint256, - amountOutMin: Uint256, - path_len: felt, - path: felt*, - to: felt, - deadline: felt, - ) -> (amounts_len: felt, amounts: Uint256*) { - } - - func swap_tokens_for_exact_tokens( - amountOut: Uint256, - amountInMax: Uint256, - path_len: felt, - path: felt*, - to: felt, - deadline: felt, - ) -> (amounts_len: felt, amounts: Uint256*) { - } -} - -@contract_interface -namespace IFactory { - func create_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func get_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func get_all_pairs() -> (all_pairs_len: felt, all_pairs: felt*) { - } - - func set_fee_to(new_fee_to: felt) { - } -} - -@external -func __setup__{syscall_ptr: felt*, range_check_ptr}() { - alloc_locals; - - tempvar deployer_address = 123456789987654321; - tempvar user_1_address = 987654321123456789; - tempvar user_2_address = 987654331133456789; - tempvar fee_recipient_address = 987654301103456789; - local factory_address; - local router_address; - local token_0_address; - local token_1_address; - %{ - context.deployer_address = ids.deployer_address - context.user_1_address = ids.user_1_address - context.user_2_address = ids.user_2_address - context.fee_recipient_address = ids.fee_recipient_address - context.declared_pair_proxy_class_hash = declare("contracts/PairProxy.cairo").class_hash - context.declared_pair_class_hash = declare("contracts/Pair.cairo").class_hash - context.declared_factory_class_hash = declare("contracts/Factory.cairo").class_hash - context.factory_address = deploy_contract("contracts/FactoryProxy.cairo", [context.declared_factory_class_hash, context.declared_pair_proxy_class_hash, context.declared_pair_class_hash, context.deployer_address]).contract_address - context.declared_router_class_hash = declare("contracts/Router.cairo").class_hash - context.router_address = deploy_contract("contracts/RouterProxy.cairo", [context.declared_router_class_hash, context.factory_address, context.deployer_address]).contract_address - context.token_0_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [11, 1, 18, 0, 0, context.deployer_address, context.deployer_address]).contract_address - context.token_1_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [22, 2, 6, 0, 0, context.deployer_address, context.deployer_address]).contract_address - ids.factory_address = context.factory_address - ids.router_address = context.router_address - ids.token_0_address = context.token_0_address - ids.token_1_address = context.token_1_address - %} - - %{ stop_prank = start_prank(ids.deployer_address, target_contract_address=ids.factory_address) %} - IFactory.set_fee_to(contract_address=factory_address, new_fee_to=fee_recipient_address); - %{ stop_prank() %} - - let (sorted_token_0_address, sorted_token_1_address) = IRouter.sort_tokens( - contract_address=router_address, tokenA=token_0_address, tokenB=token_1_address - ); - - let (pair_address) = IFactory.create_pair( - contract_address=factory_address, - token0=sorted_token_0_address, - token1=sorted_token_1_address, - ); - - %{ - context.sorted_token_0_address = ids.sorted_token_0_address - context.sorted_token_1_address = ids.sorted_token_1_address - context.pair_address = ids.pair_address - %} - - let (token_0_decimals) = IERC20.decimals(contract_address=sorted_token_0_address); - let (token_0_multiplier) = pow(10, token_0_decimals); - - let (token_1_decimals) = IERC20.decimals(contract_address=sorted_token_1_address); - let (token_1_multiplier) = pow(10, token_1_decimals); - - let amount_to_mint_token_0 = 100 * token_0_multiplier; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.sorted_token_0_address) %} - IERC20.mint( - contract_address=sorted_token_0_address, - recipient=user_1_address, - amount=Uint256(amount_to_mint_token_0, 0), - ); - IERC20.mint( - contract_address=sorted_token_0_address, - recipient=user_2_address, - amount=Uint256(amount_to_mint_token_0, 0), - ); - %{ stop_prank() %} - - let amount_to_mint_token_1 = 100 * token_1_multiplier; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.sorted_token_1_address) %} - IERC20.mint( - contract_address=sorted_token_1_address, - recipient=user_1_address, - amount=Uint256(amount_to_mint_token_1, 0), - ); - IERC20.mint( - contract_address=sorted_token_1_address, - recipient=user_2_address, - amount=Uint256(amount_to_mint_token_1, 0), - ); - %{ stop_prank() %} - - // ## Add liquidity for first time - - let amount_token_0 = 20 * token_0_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.sorted_token_0_address) %} - IERC20.approve( - contract_address=sorted_token_0_address, - spender=router_address, - amount=Uint256(amount_token_0, 0), - ); - %{ stop_prank() %} - - let amount_token_1 = 40 * token_1_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.sorted_token_1_address) %} - IERC20.approve( - contract_address=sorted_token_1_address, - spender=router_address, - amount=Uint256(amount_token_1, 0), - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.router_address) %} - let (amountA: Uint256, amountB: Uint256, liquidity: Uint256) = IRouter.add_liquidity( - contract_address=router_address, - tokenA=sorted_token_0_address, - tokenB=sorted_token_1_address, - amountADesired=Uint256(amount_token_0, 0), - amountBDesired=Uint256(amount_token_1, 0), - amountAMin=Uint256(1, 0), - amountBMin=Uint256(1, 0), - to=user_1_address, - deadline=0, - ); - %{ stop_prank() %} - - return (); -} - -@external -func test_protocol_fee{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { - alloc_locals; - - local token_0_address; - local token_1_address; - local router_address; - local user_1_address; - local user_2_address; - local fee_recipient_address; - local pair_address; - - %{ - ids.token_0_address = context.sorted_token_0_address - ids.token_1_address = context.sorted_token_1_address - ids.router_address = context.router_address - ids.user_1_address = context.user_1_address - ids.user_2_address = context.user_2_address - ids.fee_recipient_address = context.fee_recipient_address - ids.pair_address = context.pair_address - %} - - let (token_0_decimals) = IERC20.decimals(contract_address=token_0_address); - let (token_0_multiplier) = pow(10, token_0_decimals); - local amount_token_0 = 2 * token_0_multiplier; - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.token_0_address) %} - IERC20.approve( - contract_address=token_0_address, spender=router_address, amount=Uint256(amount_token_0, 0) - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.router_address) %} - let path: felt* = alloc(); - assert [path] = token_0_address; - assert [path + 1] = token_1_address; - let (amounts_len: felt, amounts: Uint256*) = IRouter.swap_exact_tokens_for_tokens( - contract_address=router_address, - amountIn=Uint256(amount_token_0, 0), - amountOutMin=Uint256(0, 0), - path_len=2, - path=path, - to=user_2_address, - deadline=0, - ); - %{ stop_prank() %} - - // ## Remove liquidity - - let (user_1_pair_balance: Uint256) = IERC20.balanceOf( - contract_address=pair_address, account=user_1_address - ); - - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.pair_address) %} - IERC20.approve( - contract_address=pair_address, spender=router_address, amount=user_1_pair_balance - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.router_address) %} - let (amountA_burn: Uint256, amountB_burn: Uint256) = IRouter.remove_liquidity( - contract_address=router_address, - tokenA=token_0_address, - tokenB=token_1_address, - liquidity=user_1_pair_balance, - amountAMin=Uint256(1, 0), - amountBMin=Uint256(1, 0), - to=user_1_address, - deadline=0, - ); - %{ stop_prank() %} - - let (fee_recipient_pair_balance: Uint256) = IERC20.balanceOf( - contract_address=pair_address, account=fee_recipient_address - ); - - let (is_fee_recipient_pair_balance_greater_than_0) = uint256_lt( - Uint256(0, 0), fee_recipient_pair_balance - ); - assert is_fee_recipient_pair_balance_greater_than_0 = 1; - - return (); -} diff --git a/tests/test_swap.cairo b/tests/test_swap.cairo deleted file mode 100644 index 9aa6d70..0000000 --- a/tests/test_swap.cairo +++ /dev/null @@ -1,635 +0,0 @@ -%lang starknet - -from starkware.cairo.common.cairo_builtins import HashBuiltin -from starkware.cairo.common.uint256 import Uint256, uint256_le, uint256_lt -from starkware.cairo.common.pow import pow -from starkware.cairo.common.alloc import alloc - -from contracts.utils.math import uint256_checked_add, uint256_checked_mul, uint256_checked_sub_le - -const MINIMUM_LIQUIDITY = 1000; -const BURN_ADDRESS = 1; - -@contract_interface -namespace IERC20 { - func name() -> (name: felt) { - } - - func symbol() -> (symbol: felt) { - } - - func decimals() -> (decimals: felt) { - } - - func mint(recipient: felt, amount: Uint256) { - } - - func approve(spender: felt, amount: Uint256) -> (success: felt) { - } - - func totalSupply() -> (totalSupply: Uint256) { - } - - func balanceOf(account: felt) -> (balance: Uint256) { - } -} - -@contract_interface -namespace IPair { - func get_reserves() -> (reserve0: Uint256, reserve1: Uint256, block_timestamp_last: felt) { - } -} - -@contract_interface -namespace IRouter { - func factory() -> (address: felt) { - } - - func sort_tokens(tokenA: felt, tokenB: felt) -> (token0: felt, token1: felt) { - } - - func add_liquidity( - tokenA: felt, - tokenB: felt, - amountADesired: Uint256, - amountBDesired: Uint256, - amountAMin: Uint256, - amountBMin: Uint256, - to: felt, - deadline: felt, - ) -> (amountA: Uint256, amountB: Uint256, liquidity: Uint256) { - } - - func remove_liquidity( - tokenA: felt, - tokenB: felt, - liquidity: Uint256, - amountAMin: Uint256, - amountBMin: Uint256, - to: felt, - deadline: felt, - ) -> (amountA: Uint256, amountB: Uint256) { - } - - func swap_exact_tokens_for_tokens( - amountIn: Uint256, - amountOutMin: Uint256, - path_len: felt, - path: felt*, - to: felt, - deadline: felt, - ) -> (amounts_len: felt, amounts: Uint256*) { - } - - func swap_tokens_for_exact_tokens( - amountOut: Uint256, - amountInMax: Uint256, - path_len: felt, - path: felt*, - to: felt, - deadline: felt, - ) -> (amounts_len: felt, amounts: Uint256*) { - } -} - -@contract_interface -namespace IFactory { - func create_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func get_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func get_all_pairs() -> (all_pairs_len: felt, all_pairs: felt*) { - } -} - -@external -func __setup__{syscall_ptr: felt*, range_check_ptr}() { - alloc_locals; - - tempvar deployer_address = 123456789987654321; - tempvar user_1_address = 987654321123456789; - tempvar user_2_address = 987654331133456789; - tempvar factory_address; - local router_address; - local token_0_address; - local token_1_address; - %{ - context.deployer_address = ids.deployer_address - context.user_1_address = ids.user_1_address - context.user_2_address = ids.user_2_address - context.declared_pair_proxy_class_hash = declare("contracts/PairProxy.cairo").class_hash - context.declared_pair_class_hash = declare("contracts/Pair.cairo").class_hash - context.declared_factory_class_hash = declare("contracts/Factory.cairo").class_hash - context.factory_address = deploy_contract("contracts/FactoryProxy.cairo", [context.declared_factory_class_hash, context.declared_pair_proxy_class_hash, context.declared_pair_class_hash, context.deployer_address]).contract_address - context.declared_router_class_hash = declare("contracts/Router.cairo").class_hash - context.router_address = deploy_contract("contracts/RouterProxy.cairo", [context.declared_router_class_hash, context.factory_address, context.deployer_address]).contract_address - context.token_0_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [11, 1, 18, 0, 0, context.deployer_address, context.deployer_address]).contract_address - context.token_1_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [22, 2, 6, 0, 0, context.deployer_address, context.deployer_address]).contract_address - ids.factory_address = context.factory_address - ids.router_address = context.router_address - ids.token_0_address = context.token_0_address - ids.token_1_address = context.token_1_address - %} - let (sorted_token_0_address, sorted_token_1_address) = IRouter.sort_tokens( - contract_address=router_address, tokenA=token_0_address, tokenB=token_1_address - ); - - let (pair_address) = IFactory.create_pair( - contract_address=factory_address, - token0=sorted_token_0_address, - token1=sorted_token_1_address, - ); - - %{ - context.sorted_token_0_address = ids.sorted_token_0_address - context.sorted_token_1_address = ids.sorted_token_1_address - context.pair_address = ids.pair_address - %} - - let (token_0_decimals) = IERC20.decimals(contract_address=sorted_token_0_address); - let (token_0_multiplier) = pow(10, token_0_decimals); - - let (token_1_decimals) = IERC20.decimals(contract_address=sorted_token_1_address); - let (token_1_multiplier) = pow(10, token_1_decimals); - - let amount_to_mint_token_0 = 100 * token_0_multiplier; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.sorted_token_0_address) %} - IERC20.mint( - contract_address=sorted_token_0_address, - recipient=user_1_address, - amount=Uint256(amount_to_mint_token_0, 0), - ); - IERC20.mint( - contract_address=sorted_token_0_address, - recipient=user_2_address, - amount=Uint256(amount_to_mint_token_0, 0), - ); - %{ stop_prank() %} - - let amount_to_mint_token_1 = 100 * token_1_multiplier; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.sorted_token_1_address) %} - IERC20.mint( - contract_address=sorted_token_1_address, - recipient=user_1_address, - amount=Uint256(amount_to_mint_token_1, 0), - ); - IERC20.mint( - contract_address=sorted_token_1_address, - recipient=user_2_address, - amount=Uint256(amount_to_mint_token_1, 0), - ); - %{ stop_prank() %} - - // ## Add liquidity for first time - - let amount_token_0 = 20 * token_0_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.sorted_token_0_address) %} - IERC20.approve( - contract_address=sorted_token_0_address, - spender=router_address, - amount=Uint256(amount_token_0, 0), - ); - %{ stop_prank() %} - - let amount_token_1 = 40 * token_1_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.sorted_token_1_address) %} - IERC20.approve( - contract_address=sorted_token_1_address, - spender=router_address, - amount=Uint256(amount_token_1, 0), - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.router_address) %} - let (amountA: Uint256, amountB: Uint256, liquidity: Uint256) = IRouter.add_liquidity( - contract_address=router_address, - tokenA=sorted_token_0_address, - tokenB=sorted_token_1_address, - amountADesired=Uint256(amount_token_0, 0), - amountBDesired=Uint256(amount_token_1, 0), - amountAMin=Uint256(1, 0), - amountBMin=Uint256(1, 0), - to=user_1_address, - deadline=0, - ); - %{ stop_prank() %} - - return (); -} - -@external -func test_swap_exact_0_to_1{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { - alloc_locals; - - local token_0_address; - local token_1_address; - local router_address; - local user_2_address; - local pair_address; - - %{ - ids.token_0_address = context.sorted_token_0_address - ids.token_1_address = context.sorted_token_1_address - ids.router_address = context.router_address - ids.user_2_address = context.user_2_address - ids.pair_address = context.pair_address - %} - - let (user_2_token_0_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_2_address - ); - let (user_2_token_1_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_1_address, account=user_2_address - ); - let (reserve_0_initial: Uint256, reserve_1_initial: Uint256, _) = IPair.get_reserves( - contract_address=pair_address - ); - - let (token_0_decimals) = IERC20.decimals(contract_address=token_0_address); - let (token_0_multiplier) = pow(10, token_0_decimals); - local amount_token_0 = 2 * token_0_multiplier; - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.token_0_address) %} - IERC20.approve( - contract_address=token_0_address, spender=router_address, amount=Uint256(amount_token_0, 0) - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.router_address) %} - let path: felt* = alloc(); - assert [path] = token_0_address; - assert [path + 1] = token_1_address; - let (amounts_len: felt, amounts: Uint256*) = IRouter.swap_exact_tokens_for_tokens( - contract_address=router_address, - amountIn=Uint256(amount_token_0, 0), - amountOutMin=Uint256(0, 0), - path_len=2, - path=path, - to=user_2_address, - deadline=0, - ); - %{ stop_prank() %} - - local amount1Out: Uint256; - assert amount1Out = [amounts + (amounts_len - 1) * Uint256.SIZE]; - - %{ expect_events({"name": "Swap", "from_address": ids.pair_address, "data": [ids.router_address, ids.amount_token_0, 0, 0, 0, 0, 0, ids.amount1Out.low, ids.amount1Out.high, ids.user_2_address]}) %} - - let (user_2_token_0_balance_final: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_2_address - ); - let (user_2_token_1_balance_final: Uint256) = IERC20.balanceOf( - contract_address=token_1_address, account=user_2_address - ); - let (reserve_0_final: Uint256, reserve_1_final: Uint256, _) = IPair.get_reserves( - contract_address=pair_address - ); - - let (user_2_token_0_balance_difference: Uint256) = uint256_checked_sub_le( - user_2_token_0_balance_initial, user_2_token_0_balance_final - ); - let (user_2_token_1_balance_difference: Uint256) = uint256_checked_sub_le( - user_2_token_1_balance_final, user_2_token_1_balance_initial - ); - - assert user_2_token_0_balance_difference = [amounts]; - assert user_2_token_1_balance_difference = [amounts + (amounts_len - 1) * Uint256.SIZE]; - - return (); -} - -@external -func test_swap_0_to_exact_1{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { - alloc_locals; - - local token_0_address; - local token_1_address; - local router_address; - local user_2_address; - local pair_address; - - %{ - ids.token_0_address = context.sorted_token_0_address - ids.token_1_address = context.sorted_token_1_address - ids.router_address = context.router_address - ids.user_2_address = context.user_2_address - ids.pair_address = context.pair_address - %} - - let (user_2_token_0_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_2_address - ); - let (user_2_token_1_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_1_address, account=user_2_address - ); - let (reserve_0_initial: Uint256, reserve_1_initial: Uint256, _) = IPair.get_reserves( - contract_address=pair_address - ); - - let (token_1_decimals) = IERC20.decimals(contract_address=token_1_address); - let (token_1_multiplier) = pow(10, token_1_decimals); - local amount_token_1 = 2 * token_1_multiplier; - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.token_0_address) %} - IERC20.approve( - contract_address=token_0_address, spender=router_address, amount=Uint256(10 ** 30, 0) - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.router_address) %} - let path: felt* = alloc(); - assert [path] = token_0_address; - assert [path + 1] = token_1_address; - let (amounts_len: felt, amounts: Uint256*) = IRouter.swap_tokens_for_exact_tokens( - contract_address=router_address, - amountOut=Uint256(amount_token_1, 0), - amountInMax=Uint256(10 ** 30, 0), - path_len=2, - path=path, - to=user_2_address, - deadline=0, - ); - %{ stop_prank() %} - - local amount0In: Uint256; - assert amount0In = [amounts]; - - %{ expect_events({"name": "Swap", "from_address": ids.pair_address, "data": [ids.router_address, ids.amount0In.low, ids.amount0In.high, 0, 0, 0, 0, ids.amount_token_1, 0, ids.user_2_address]}) %} - - let (user_2_token_0_balance_final: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_2_address - ); - let (user_2_token_1_balance_final: Uint256) = IERC20.balanceOf( - contract_address=token_1_address, account=user_2_address - ); - let (reserve_0_final: Uint256, reserve_1_final: Uint256, _) = IPair.get_reserves( - contract_address=pair_address - ); - - let (user_2_token_0_balance_difference: Uint256) = uint256_checked_sub_le( - user_2_token_0_balance_initial, user_2_token_0_balance_final - ); - let (user_2_token_1_balance_difference: Uint256) = uint256_checked_sub_le( - user_2_token_1_balance_final, user_2_token_1_balance_initial - ); - - assert user_2_token_0_balance_difference = [amounts]; - assert user_2_token_1_balance_difference = [amounts + (amounts_len - 1) * Uint256.SIZE]; - - return (); -} - -@external -func test_swap_exact_0_to_2{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { - alloc_locals; - - local token_0_address; - local token_1_address; - local token_2_address; - local factory_address; - local router_address; - local user_1_address; - local user_2_address; - local pair_address; - - %{ - ids.token_0_address = context.sorted_token_0_address - ids.token_1_address = context.sorted_token_1_address - ids.token_2_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [33, 3, 18, 0, 0, context.deployer_address, context.deployer_address]).contract_address - ids.factory_address = context.factory_address - ids.router_address = context.router_address - ids.user_1_address = context.user_1_address - ids.user_2_address = context.user_2_address - ids.pair_address = context.pair_address - %} - - // ## Create other pair - - let (sorted_token_1_address, sorted_token_2_address) = IRouter.sort_tokens( - contract_address=router_address, tokenA=token_1_address, tokenB=token_2_address - ); - - let (other_pair_address) = IFactory.create_pair( - contract_address=factory_address, - token0=sorted_token_1_address, - token1=sorted_token_2_address, - ); - - // ## Add liquidity for first time - - let (token_1_decimals) = IERC20.decimals(contract_address=token_1_address); - let (token_1_multiplier) = pow(10, token_1_decimals); - - let (token_2_decimals) = IERC20.decimals(contract_address=token_2_address); - let (token_2_multiplier) = pow(10, token_2_decimals); - - let amount_to_mint_token_2 = 100 * token_2_multiplier; - %{ stop_prank = start_prank(context.deployer_address, target_contract_address=ids.token_2_address) %} - IERC20.mint( - contract_address=token_2_address, - recipient=user_1_address, - amount=Uint256(amount_to_mint_token_2, 0), - ); - %{ stop_prank() %} - - let amount_token_1 = 20 * token_1_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.token_1_address) %} - IERC20.approve( - contract_address=token_1_address, spender=router_address, amount=Uint256(amount_token_1, 0) - ); - %{ stop_prank() %} - - let amount_token_2 = 4 * token_2_multiplier; - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.token_2_address) %} - IERC20.approve( - contract_address=token_2_address, spender=router_address, amount=Uint256(amount_token_2, 0) - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_1_address, target_contract_address=ids.router_address) %} - let (amountA: Uint256, amountB: Uint256, liquidity: Uint256) = IRouter.add_liquidity( - contract_address=router_address, - tokenA=token_1_address, - tokenB=token_2_address, - amountADesired=Uint256(amount_token_1, 0), - amountBDesired=Uint256(amount_token_2, 0), - amountAMin=Uint256(1, 0), - amountBMin=Uint256(1, 0), - to=user_1_address, - deadline=0, - ); - %{ stop_prank() %} - - let (user_2_token_0_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_2_address - ); - let (user_2_token_2_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_2_address, account=user_2_address - ); - let (reserve_0_0_initial: Uint256, reserve_1_0_initial: Uint256, _) = IPair.get_reserves( - contract_address=pair_address - ); - let ( - reserve_other_0_initial: Uint256, reserve_other_1_initial: Uint256, _ - ) = IPair.get_reserves(contract_address=other_pair_address); - - let (token_0_decimals) = IERC20.decimals(contract_address=token_0_address); - let (token_0_multiplier) = pow(10, token_0_decimals); - local amount_token_0 = 2 * token_0_multiplier; - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.token_0_address) %} - IERC20.approve( - contract_address=token_0_address, spender=router_address, amount=Uint256(amount_token_0, 0) - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.router_address) %} - let path: felt* = alloc(); - assert [path] = token_0_address; - assert [path + 1] = token_1_address; - assert [path + 2] = token_2_address; - let (amounts_len: felt, amounts: Uint256*) = IRouter.swap_exact_tokens_for_tokens( - contract_address=router_address, - amountIn=Uint256(amount_token_0, 0), - amountOutMin=Uint256(0, 0), - path_len=3, - path=path, - to=user_2_address, - deadline=0, - ); - %{ stop_prank() %} - - local amount1Out: Uint256; - assert amount1Out = [amounts + (amounts_len - 2) * Uint256.SIZE]; - - local amount2Out: Uint256; - assert amount2Out = [amounts + (amounts_len - 1) * Uint256.SIZE]; - - %{ expect_events({"name": "Swap", "from_address": ids.pair_address, "data": [ids.router_address, ids.amount_token_0, 0, 0, 0, 0, 0, ids.amount1Out.low, ids.amount1Out.high, ids.other_pair_address]}) %} - - let (user_2_token_0_balance_final: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_2_address - ); - let (user_2_token_2_balance_final: Uint256) = IERC20.balanceOf( - contract_address=token_2_address, account=user_2_address - ); - let (reserve_0_0_final: Uint256, reserve_1_0_final: Uint256, _) = IPair.get_reserves( - contract_address=pair_address - ); - let (reserve_other_0_final: Uint256, reserve_other_1_final: Uint256, _) = IPair.get_reserves( - contract_address=other_pair_address - ); - - local reserve_0_1_initial: Uint256; - local reserve_1_1_initial: Uint256; - local reserve_0_1_final: Uint256; - local reserve_1_1_final: Uint256; - if (token_1_address == sorted_token_1_address) { - %{ expect_events({"name": "Swap", "from_address": ids.other_pair_address, "data": [ids.router_address, ids.amount1Out.low, ids.amount1Out.high, 0, 0, 0, 0, ids.amount2Out.low, ids.amount2Out.high, ids.user_2_address]}) %} - assert reserve_0_1_initial = reserve_other_0_initial; - assert reserve_1_1_initial = reserve_other_1_initial; - assert reserve_0_1_final = reserve_other_0_final; - assert reserve_1_1_final = reserve_other_1_final; - } else { - %{ expect_events({"name": "Swap", "from_address": ids.other_pair_address, "data": [ids.router_address, 0, 0, ids.amount1Out.low, ids.amount1Out.high, ids.amount2Out.low, ids.amount2Out.high, 0, 0, ids.user_2_address]}) %} - assert reserve_0_1_initial = reserve_other_1_initial; - assert reserve_1_1_initial = reserve_other_0_initial; - assert reserve_0_1_final = reserve_other_1_final; - assert reserve_1_1_final = reserve_other_0_final; - } - - let (user_2_token_0_balance_difference: Uint256) = uint256_checked_sub_le( - user_2_token_0_balance_initial, user_2_token_0_balance_final - ); - let (user_2_token_2_balance_difference: Uint256) = uint256_checked_sub_le( - user_2_token_2_balance_final, user_2_token_2_balance_initial - ); - - assert user_2_token_0_balance_difference = [amounts]; - assert user_2_token_2_balance_difference = [amounts + (amounts_len - 1) * Uint256.SIZE]; - - return (); -} - -@external -func test_swap_exact_1_to_0{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() { - alloc_locals; - - local token_0_address; - local token_1_address; - local router_address; - local user_2_address; - local pair_address; - - %{ - ids.token_0_address = context.sorted_token_0_address - ids.token_1_address = context.sorted_token_1_address - ids.router_address = context.router_address - ids.user_2_address = context.user_2_address - ids.pair_address = context.pair_address - %} - - let (user_2_token_0_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_2_address - ); - let (user_2_token_1_balance_initial: Uint256) = IERC20.balanceOf( - contract_address=token_1_address, account=user_2_address - ); - let (reserve_0_initial: Uint256, reserve_1_initial: Uint256, _) = IPair.get_reserves( - contract_address=pair_address - ); - - let (token_1_decimals) = IERC20.decimals(contract_address=token_1_address); - let (token_1_multiplier) = pow(10, token_1_decimals); - local amount_token_1 = 2 * token_1_multiplier; - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.token_1_address) %} - IERC20.approve( - contract_address=token_1_address, spender=router_address, amount=Uint256(amount_token_1, 0) - ); - %{ stop_prank() %} - - %{ stop_prank = start_prank(ids.user_2_address, target_contract_address=ids.router_address) %} - let path: felt* = alloc(); - assert [path] = token_1_address; - assert [path + 1] = token_0_address; - let (amounts_len: felt, amounts: Uint256*) = IRouter.swap_exact_tokens_for_tokens( - contract_address=router_address, - amountIn=Uint256(amount_token_1, 0), - amountOutMin=Uint256(0, 0), - path_len=2, - path=path, - to=user_2_address, - deadline=0, - ); - %{ stop_prank() %} - - local amount0Out: Uint256; - assert amount0Out = [amounts + (amounts_len - 1) * Uint256.SIZE]; - - %{ expect_events({"name": "Swap", "from_address": ids.pair_address, "data": [ids.router_address, 0, 0, ids.amount_token_1, 0, ids.amount0Out.low, ids.amount0Out.high, 0, 0, ids.user_2_address]}) %} - - let (user_2_token_0_balance_final: Uint256) = IERC20.balanceOf( - contract_address=token_0_address, account=user_2_address - ); - let (user_2_token_1_balance_final: Uint256) = IERC20.balanceOf( - contract_address=token_1_address, account=user_2_address - ); - let (reserve_0_final: Uint256, reserve_1_final: Uint256, _) = IPair.get_reserves( - contract_address=pair_address - ); - - let (user_2_token_0_balance_difference: Uint256) = uint256_checked_sub_le( - user_2_token_0_balance_final, user_2_token_0_balance_initial - ); - let (user_2_token_1_balance_difference: Uint256) = uint256_checked_sub_le( - user_2_token_1_balance_initial, user_2_token_1_balance_final - ); - - assert user_2_token_1_balance_difference = [amounts]; - assert user_2_token_0_balance_difference = [amounts + (amounts_len - 1) * Uint256.SIZE]; - - return (); -} diff --git a/tests/test_updates.cairo b/tests/test_updates.cairo deleted file mode 100644 index c6093d9..0000000 --- a/tests/test_updates.cairo +++ /dev/null @@ -1,228 +0,0 @@ -%lang starknet - -@contract_interface -namespace IFactory { - func create_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func set_fee_to(new_fee_to: felt) { - } - - func set_fee_to_setter(new_fee_to_setter: felt) { - } - - func get_fee_to() -> (address: felt) { - } - - func get_fee_to_setter() -> (address: felt) { - } -} - -@contract_interface -namespace IRouter { - func factory() -> (address: felt) { - } - - func sort_tokens(tokenA: felt, tokenB: felt) -> (token0: felt, token1: felt) { - } -} - -@contract_interface -namespace IProxy { - func set_admin(new_admin: felt) { - } - - func get_admin() -> (admin: felt) { - } -} - -@external -func __setup__{syscall_ptr: felt*, range_check_ptr}() { - tempvar deployer_address = 123456789987654321; - tempvar factory_address; - tempvar router_address; - tempvar token_0_address; - tempvar token_1_address; - %{ - context.deployer_address = ids.deployer_address - context.declared_pair_proxy_class_hash = declare("contracts/PairProxy.cairo").class_hash - context.declared_pair_class_hash = declare("contracts/Pair.cairo").class_hash - context.declared_factory_class_hash = declare("contracts/Factory.cairo").class_hash - context.factory_address = deploy_contract("contracts/FactoryProxy.cairo", [context.declared_factory_class_hash, context.declared_pair_proxy_class_hash, context.declared_pair_class_hash, context.deployer_address]).contract_address - context.declared_router_class_hash = declare("contracts/Router.cairo").class_hash - context.router_address = deploy_contract("contracts/RouterProxy.cairo", [context.declared_router_class_hash, context.factory_address, context.deployer_address]).contract_address - context.token_0_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [11, 1, 18, 0, 0, context.deployer_address, context.deployer_address]).contract_address - context.token_1_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [22, 2, 6, 0, 0, context.deployer_address, context.deployer_address]).contract_address - ids.factory_address = context.factory_address - ids.router_address = context.router_address - ids.token_0_address = context.token_0_address - ids.token_1_address = context.token_1_address - %} - let (sorted_token_0_address, sorted_token_1_address) = IRouter.sort_tokens( - contract_address=router_address, tokenA=token_0_address, tokenB=token_1_address - ); - - let (pair_address) = IFactory.create_pair( - contract_address=factory_address, - token0=sorted_token_0_address, - token1=sorted_token_1_address, - ); - - %{ context.pair_address = ids.pair_address %} - return (); -} - -@external -func test_set_fee_to_non_fee_to_setter{syscall_ptr: felt*, range_check_ptr}() { - tempvar factory_address; - - %{ ids.factory_address = context.factory_address %} - - %{ expect_revert(error_message="Factory::set_fee_to::Caller must be fee to setter") %} - IFactory.set_fee_to(contract_address=factory_address, new_fee_to=200); - - return (); -} - -@external -func test_set_fee_to{syscall_ptr: felt*, range_check_ptr}() { - tempvar deployer_address; - tempvar factory_address; - - %{ - ids.deployer_address = context.deployer_address - ids.factory_address = context.factory_address - %} - - %{ stop_prank = start_prank(ids.deployer_address, target_contract_address=ids.factory_address) %} - tempvar new_fee_to_address = 200; - IFactory.set_fee_to(contract_address=factory_address, new_fee_to=new_fee_to_address); - %{ stop_prank() %} - - let (get_fee_to_address) = IFactory.get_fee_to(contract_address=factory_address); - assert get_fee_to_address = new_fee_to_address; - - return (); -} - -@external -func test_update_fee_to_setter_non_fee_to_setter{syscall_ptr: felt*, range_check_ptr}() { - tempvar factory_address; - - %{ ids.factory_address = context.factory_address %} - - %{ expect_revert(error_message="Factory::set_fee_to_setter::Caller must be fee to setter") %} - IFactory.set_fee_to_setter(contract_address=factory_address, new_fee_to_setter=200); - - return (); -} - -@external -func test_update_fee_to_setter_zero{syscall_ptr: felt*, range_check_ptr}() { - tempvar deployer_address; - tempvar factory_address; - - %{ - ids.deployer_address = context.deployer_address - ids.factory_address = context.factory_address - %} - - %{ stop_prank = start_prank(ids.deployer_address, target_contract_address=ids.factory_address) %} - %{ expect_revert(error_message="Factory::set_fee_to_setter::new_fee_to_setter must be non zero") %} - IFactory.set_fee_to_setter(contract_address=factory_address, new_fee_to_setter=0); - %{ stop_prank() %} - - return (); -} - -@external -func test_update_fee_to_setter{syscall_ptr: felt*, range_check_ptr}() { - tempvar deployer_address; - tempvar factory_address; - - %{ - ids.deployer_address = context.deployer_address - ids.factory_address = context.factory_address - %} - - %{ stop_prank = start_prank(ids.deployer_address, target_contract_address=ids.factory_address) %} - tempvar new_fee_to_setter_address = 200; - IFactory.set_fee_to_setter( - contract_address=factory_address, new_fee_to_setter=new_fee_to_setter_address - ); - %{ stop_prank() %} - - let (get_fee_to_setter_address) = IFactory.get_fee_to_setter(contract_address=factory_address); - assert get_fee_to_setter_address = new_fee_to_setter_address; - - return (); -} - -@external -func test_update_admin_non_admin{syscall_ptr: felt*, range_check_ptr}() { - tempvar factory_address; - tempvar router_address; - tempvar pair_address; - - %{ - ids.factory_address = context.factory_address - ids.router_address = context.router_address - ids.pair_address = context.pair_address - %} - - %{ expect_revert(error_message="Proxy: caller is not admin") %} - IProxy.set_admin(contract_address=factory_address, new_admin=200); - - %{ expect_revert(error_message="Proxy: caller is not admin") %} - IProxy.set_admin(contract_address=router_address, new_admin=200); - - %{ expect_revert(error_message="Proxy: caller is not admin") %} - IProxy.set_admin(contract_address=pair_address, new_admin=200); - - return (); -} - -@external -func test_update_admin{syscall_ptr: felt*, range_check_ptr}() { - tempvar deployer_address; - tempvar factory_address; - tempvar router_address; - tempvar pair_address; - - %{ - ids.deployer_address = context.deployer_address - ids.factory_address = context.factory_address - ids.router_address = context.router_address - ids.pair_address = context.pair_address - %} - - %{ stop_prank = start_prank(ids.deployer_address, target_contract_address=ids.factory_address) %} - tempvar new_admin = 200; - IProxy.set_admin( - contract_address=factory_address, new_admin=new_admin - ); - %{ stop_prank() %} - - let (factory_admin) = IProxy.get_admin(contract_address=factory_address); - assert factory_admin = new_admin; - - %{ stop_prank = start_prank(ids.deployer_address, target_contract_address=ids.router_address) %} - IProxy.set_admin( - contract_address=router_address, new_admin=new_admin - ); - %{ stop_prank() %} - - let (router_admin) = IProxy.get_admin(contract_address=router_address); - assert router_admin = new_admin; - - %{ stop_prank = start_prank(ids.deployer_address, target_contract_address=ids.pair_address) %} - IProxy.set_admin( - contract_address=pair_address, new_admin=new_admin - ); - %{ stop_prank() %} - - let (pair_admin) = IProxy.get_admin(contract_address=pair_address); - assert pair_admin = new_admin; - - return (); -} diff --git a/tests/test_upgrades.cairo b/tests/test_upgrades.cairo deleted file mode 100644 index e8a7538..0000000 --- a/tests/test_upgrades.cairo +++ /dev/null @@ -1,201 +0,0 @@ -%lang starknet - -@contract_interface -namespace IFactory { - func create_pair(token0: felt, token1: felt) -> (pair: felt) { - } - - func set_fee_to(new_fee_to: felt) { - } - - func set_fee_to_setter(new_fee_to_setter: felt) { - } - - func get_fee_to() -> (address: felt) { - } - - func get_fee_to_setter() -> (address: felt) { - } -} - -@contract_interface -namespace IRouter { - func factory() -> (address: felt) { - } - - func sort_tokens(tokenA: felt, tokenB: felt) -> (token0: felt, token1: felt) { - } -} - -@contract_interface -namespace IProxy { - func upgrade(new_implementation: felt) { - } - - func set_admin(new_admin: felt) { - } - - func get_admin() -> (admin: felt) { - } - - func get_implementation_hash() -> (implementation: felt) { - } -} - -@contract_interface -namespace IV2 { - func test_v2_contract() -> (success: felt) { - } -} - -@external -func __setup__{syscall_ptr: felt*, range_check_ptr}() { - tempvar deployer_address = 123456789987654321; - tempvar factory_address; - tempvar router_address; - tempvar token_0_address; - tempvar token_1_address; - %{ - context.deployer_address = ids.deployer_address - context.declared_pair_proxy_class_hash = declare("contracts/PairProxy.cairo").class_hash - context.declared_pair_class_hash = declare("contracts/Pair.cairo").class_hash - context.declared_factory_class_hash = declare("contracts/Factory.cairo").class_hash - context.factory_address = deploy_contract("contracts/FactoryProxy.cairo", [context.declared_factory_class_hash, context.declared_pair_proxy_class_hash, context.declared_pair_class_hash, context.deployer_address]).contract_address - context.declared_router_class_hash = declare("contracts/Router.cairo").class_hash - context.router_address = deploy_contract("contracts/RouterProxy.cairo", [context.declared_router_class_hash, context.factory_address, context.deployer_address]).contract_address - context.token_0_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [11, 1, 18, 0, 0, context.deployer_address, context.deployer_address]).contract_address - context.token_1_address = deploy_contract("lib/cairo_contracts/src/openzeppelin/token/erc20/presets/ERC20Mintable.cairo", [22, 2, 6, 0, 0, context.deployer_address, context.deployer_address]).contract_address - ids.factory_address = context.factory_address - ids.router_address = context.router_address - ids.token_0_address = context.token_0_address - ids.token_1_address = context.token_1_address - %} - let (sorted_token_0_address, sorted_token_1_address) = IRouter.sort_tokens( - contract_address=router_address, tokenA=token_0_address, tokenB=token_1_address - ); - - let (pair_address) = IFactory.create_pair( - contract_address=factory_address, - token0=sorted_token_0_address, - token1=sorted_token_1_address, - ); - - %{ context.pair_address = ids.pair_address %} - return (); -} - -@external -func test_upgrade_implementation_non_admin{syscall_ptr: felt*, range_check_ptr}() { - tempvar factory_address; - tempvar router_address; - tempvar pair_address; - tempvar declared_factory_v2_class_hash; - tempvar declared_router_v2_class_hash; - tempvar declared_pair_v2_class_hash; - - %{ - ids.factory_address = context.factory_address - ids.router_address = context.router_address - ids.pair_address = context.pair_address - ids.declared_factory_v2_class_hash = declare("contracts/test/FactoryV2.cairo").class_hash - ids.declared_router_v2_class_hash = declare("contracts/test/RouterV2.cairo").class_hash - ids.declared_pair_v2_class_hash = declare("contracts/test/PairV2.cairo").class_hash - %} - - %{ expect_revert(error_message="Proxy: caller is not admin") %} - IProxy.upgrade(contract_address=factory_address, new_implementation=declared_factory_v2_class_hash); - - %{ expect_revert(error_message="Proxy: caller is not admin") %} - IProxy.upgrade(contract_address=router_address, new_implementation=declared_router_v2_class_hash); - - %{ expect_revert(error_message="Proxy: caller is not admin") %} - IProxy.upgrade(contract_address=pair_address, new_implementation=declared_pair_v2_class_hash); - - return (); -} - -@external -func test_upgrade_implementation{syscall_ptr: felt*, range_check_ptr}() { - tempvar deployer_address; - tempvar factory_address; - tempvar router_address; - tempvar pair_address; - tempvar declared_factory_v2_class_hash; - tempvar declared_router_v2_class_hash; - tempvar declared_pair_v2_class_hash; - - %{ - ids.deployer_address = context.deployer_address - ids.factory_address = context.factory_address - ids.router_address = context.router_address - ids.pair_address = context.pair_address - ids.declared_factory_v2_class_hash = declare("contracts/test/FactoryV2.cairo").class_hash - ids.declared_router_v2_class_hash = declare("contracts/test/RouterV2.cairo").class_hash - ids.declared_pair_v2_class_hash = declare("contracts/test/PairV2.cairo").class_hash - %} - - // Upgrade Factory implementation contract - - %{ expect_revert(error_type="ENTRY_POINT_NOT_FOUND_IN_CONTRACT") %} - IV2.test_v2_contract(contract_address=factory_address); - - let (fee_to_setter_address_initial) = IFactory.get_fee_to_setter(contract_address=factory_address); - - %{ stop_prank = start_prank(ids.deployer_address, target_contract_address=ids.factory_address) %} - IProxy.upgrade( - contract_address=factory_address, new_implementation=declared_factory_v2_class_hash - ); - %{ stop_prank() %} - - let (factory_implementation) = IProxy.get_implementation_hash(contract_address=factory_address); - assert factory_implementation = declared_factory_v2_class_hash; - - let (factory_v2_success) = IV2.test_v2_contract(contract_address=factory_address); - assert factory_v2_success = 1; - - // Assert factory state is persisted after upgrade - let (fee_to_setter_address_final) = IFactory.get_fee_to_setter(contract_address=factory_address); - assert fee_to_setter_address_final = fee_to_setter_address_initial; - - // Upgrade Router impelementation contract - - %{ expect_revert(error_type="ENTRY_POINT_NOT_FOUND_IN_CONTRACT") %} - IV2.test_v2_contract(contract_address=deployer_address); - - let (factory_address_from_router_initial) = IRouter.factory(contract_address=router_address); - - %{ stop_prank = start_prank(ids.deployer_address, target_contract_address=ids.router_address) %} - IProxy.upgrade( - contract_address=router_address, new_implementation=declared_router_v2_class_hash - ); - %{ stop_prank() %} - - let (router_implementation) = IProxy.get_implementation_hash(contract_address=router_address); - assert router_implementation = declared_router_v2_class_hash; - - let (router_v2_success) = IV2.test_v2_contract(contract_address=router_address); - assert router_v2_success = 1; - - // Assert Router state is persisted after upgrade - let (factory_address_from_router_final) = IRouter.factory(contract_address=router_address); - assert factory_address_from_router_final = factory_address_from_router_initial; - - // Upgrade Pair implementation contract - - %{ expect_revert(error_type="ENTRY_POINT_NOT_FOUND_IN_CONTRACT") %} - IV2.test_v2_contract(contract_address=pair_address); - - %{ stop_prank = start_prank(ids.deployer_address, target_contract_address=ids.pair_address) %} - IProxy.upgrade( - contract_address=pair_address, new_implementation=declared_pair_v2_class_hash - ); - %{ stop_prank() %} - - let (pair_implementation) = IProxy.get_implementation_hash(contract_address=pair_address); - assert pair_implementation = declared_pair_v2_class_hash; - - let (pair_v2_success) = IV2.test_v2_contract(contract_address=pair_address); - assert pair_v2_success = 1; - - return (); -}