-
Notifications
You must be signed in to change notification settings - Fork 6
feat(tokenomics): transfer LP position NFT to liquidity owner #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: next
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ pub mod StreamComponent { | |
| use core::poseidon::PoseidonTrait; | ||
| use ekubo::interfaces::core::{ICoreDispatcher, ICoreDispatcherTrait}; | ||
| use ekubo::interfaces::erc20::IERC20Dispatcher as EkuboIERC20Dispatcher; | ||
| use ekubo::interfaces::erc721::{IERC721Dispatcher, IERC721DispatcherTrait}; | ||
| use ekubo::interfaces::extensions::twamm::OrderKey; | ||
| use ekubo::interfaces::positions::{IPositionsDispatcher, IPositionsDispatcherTrait}; | ||
| use ekubo::lens::token_registry::{ITokenRegistryDispatcher, ITokenRegistryDispatcherTrait}; | ||
|
|
@@ -55,6 +56,8 @@ pub mod StreamComponent { | |
| Stream_primary_min_liquidity: u128, | ||
| /// Liquidity position ID | ||
| Stream_liquidity_position_id: u64, | ||
| /// Liquidity owner (receives LP position NFT) | ||
| Stream_liquidity_owner: ContractAddress, | ||
| /// Primary pool ID | ||
| Stream_pool_id: u256, | ||
| /// Distribution orders | ||
|
|
@@ -72,6 +75,7 @@ pub mod StreamComponent { | |
| pub enum Event { | ||
| PoolInitialized: PoolInitialized, | ||
| LiquidityProvided: LiquidityProvided, | ||
| LiquidityPositionTransferred: LiquidityPositionTransferred, | ||
| DistributionStarted: DistributionStarted, | ||
| ProceedsClaimed: ProceedsClaimed, | ||
| Preminted: Preminted, | ||
|
|
@@ -102,6 +106,14 @@ pub mod StreamComponent { | |
| pub token1_amount: u256, | ||
| } | ||
|
|
||
| /// Emitted when the LP position NFT is transferred to the liquidity owner | ||
| #[derive(Drop, starknet::Event)] | ||
| pub struct LiquidityPositionTransferred { | ||
| pub position_id: u64, | ||
| #[key] | ||
| pub owner: ContractAddress, | ||
| } | ||
|
|
||
| /// Emitted when a distribution order starts | ||
| #[derive(Drop, starknet::Event)] | ||
| pub struct DistributionStarted { | ||
|
|
@@ -235,6 +247,10 @@ pub mod StreamComponent { | |
| self.Stream_pool_id.read() | ||
| } | ||
|
|
||
| fn get_liquidity_owner(self: @ComponentState<TContractState>) -> ContractAddress { | ||
| self.Stream_liquidity_owner.read() | ||
| } | ||
|
Comment on lines
+250
to
+252
|
||
|
|
||
| fn get_deployment_state(self: @ComponentState<TContractState>) -> u8 { | ||
| self.Stream_deployment_state.read() | ||
| } | ||
|
|
@@ -290,6 +306,13 @@ pub mod StreamComponent { | |
| }, | ||
| ); | ||
|
|
||
| // Transfer LP position NFT to liquidity owner | ||
| let nft_address = positions_dispatcher.get_nft_address(); | ||
| let nft = IERC721Dispatcher { contract_address: nft_address }; | ||
| let liquidity_owner = self.Stream_liquidity_owner.read(); | ||
| nft.transfer_from(get_contract_address(), liquidity_owner, position_id.into()); | ||
| self.emit(LiquidityPositionTransferred { position_id, owner: liquidity_owner }); | ||
|
Comment on lines
+309
to
+314
|
||
|
|
||
| (position_id, liquidity, token0_cleared, token1_cleared) | ||
| } | ||
|
|
||
|
|
@@ -384,6 +407,10 @@ pub mod StreamComponent { | |
| ); | ||
| assert(liquidity_config.stream_token_amount > 0, Errors::STREAM_INVALID_STREAM_AMOUNT); | ||
| assert(liquidity_config.paired_token_amount > 0, Errors::STREAM_INVALID_PAIRED_AMOUNT); | ||
| assert( | ||
| liquidity_config.liquidity_owner != zero_address, | ||
| Errors::STREAM_INVALID_LIQUIDITY_OWNER, | ||
| ); | ||
|
|
||
| // Store configuration | ||
| self.Stream_factory.write(factory); | ||
|
|
@@ -402,6 +429,7 @@ pub mod StreamComponent { | |
| self.Stream_primary_stream_token_amount.write(liquidity_config.stream_token_amount); | ||
| self.Stream_primary_paired_token_amount.write(liquidity_config.paired_token_amount); | ||
| self.Stream_primary_min_liquidity.write(liquidity_config.min_liquidity); | ||
| self.Stream_liquidity_owner.write(liquidity_config.liquidity_owner); | ||
|
|
||
| // Store distribution orders | ||
| let order_count: u32 = distribution_orders.len(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -46,6 +46,7 @@ fn deploy_stream_token_with_multiple_orders() -> StreamTokenSetup { | |||||||||||||||||||||||||||||||
| stream_token_amount: 1000_u128 * 1_000_000_000_000_000_000, | ||||||||||||||||||||||||||||||||
| paired_token_amount: 100_u128 * 1_000_000_000_000_000_000, | ||||||||||||||||||||||||||||||||
| min_liquidity: 1, | ||||||||||||||||||||||||||||||||
| liquidity_owner: OWNER(), | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Create 2 orders with SAME buy_token and fee to trigger increase_sell_amount | ||||||||||||||||||||||||||||||||
|
|
@@ -102,6 +103,26 @@ fn deploy_stream_token_with_multiple_orders() -> StreamTokenSetup { | |||||||||||||||||||||||||||||||
| // Full Lifecycle Tests with Mocked Ekubo | ||||||||||||||||||||||||||||||||
| // ============================================================================ | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Mock NFT address returned by get_nft_address | ||||||||||||||||||||||||||||||||
| fn MOCK_NFT() -> starknet::ContractAddress { | ||||||||||||||||||||||||||||||||
| 'MOCK_NFT'.try_into().unwrap() | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /// Set up mocks for the LP NFT transfer (get_nft_address + transfer_from) | ||||||||||||||||||||||||||||||||
| fn mock_nft_transfer(mock_positions: starknet::ContractAddress) { | ||||||||||||||||||||||||||||||||
| start_mock_call(mock_positions, selector!("get_nft_address"), MOCK_NFT()); | ||||||||||||||||||||||||||||||||
| start_mock_call(mock_positions, selector!("transfer_from"), ()); | ||||||||||||||||||||||||||||||||
| start_mock_call(MOCK_NFT(), selector!("transferFrom"), ()); | ||||||||||||||||||||||||||||||||
| start_mock_call(MOCK_NFT(), selector!("transfer_from"), ()); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| fn stop_mock_nft_transfer(mock_positions: starknet::ContractAddress) { | ||||||||||||||||||||||||||||||||
| stop_mock_call(mock_positions, selector!("get_nft_address")); | ||||||||||||||||||||||||||||||||
| stop_mock_call(mock_positions, selector!("transfer_from")); | ||||||||||||||||||||||||||||||||
| stop_mock_call(MOCK_NFT(), selector!("transferFrom")); | ||||||||||||||||||||||||||||||||
|
Comment on lines
+115
to
+122
|
||||||||||||||||||||||||||||||||
| start_mock_call(MOCK_NFT(), selector!("transferFrom"), ()); | |
| start_mock_call(MOCK_NFT(), selector!("transfer_from"), ()); | |
| } | |
| fn stop_mock_nft_transfer(mock_positions: starknet::ContractAddress) { | |
| stop_mock_call(mock_positions, selector!("get_nft_address")); | |
| stop_mock_call(mock_positions, selector!("transfer_from")); | |
| stop_mock_call(MOCK_NFT(), selector!("transferFrom")); | |
| start_mock_call(MOCK_NFT(), selector!("transfer_from"), ()); | |
| } | |
| fn stop_mock_nft_transfer(mock_positions: starknet::ContractAddress) { | |
| stop_mock_call(mock_positions, selector!("get_nft_address")); | |
| stop_mock_call(mock_positions, selector!("transfer_from")); |
Copilot
AI
Feb 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar issue as above - line 122 stops mock for transferFrom (camelCase) but this is unnecessary. Only the snake_case variant transfer_from is used in the actual implementation. Consider removing line 122 to match the actual Cairo naming conventions.
| stop_mock_call(MOCK_NFT(), selector!("transferFrom")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mock_nft_transfer and stop_mock_nft_transfer helper functions include mock calls that are not necessary for the test to pass and can be removed for clarity. The code under test only calls get_nft_address on the positions contract and transfer_from on the NFT contract. The mocks for transfer_from on the positions contract and transferFrom (with a capital F) on the NFT contract are not needed.
fn mock_nft_transfer(mock_positions: starknet::ContractAddress) {
start_mock_call(mock_positions, selector!("get_nft_address"), MOCK_NFT());
start_mock_call(MOCK_NFT(), selector!("transfer_from"), ());
}
fn stop_mock_nft_transfer(mock_positions: starknet::ContractAddress) {
stop_mock_call(mock_positions, selector!("get_nft_address"));
stop_mock_call(MOCK_NFT(), selector!("transfer_from"));
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new error constant is added correctly, but there should be a corresponding test in the test_constants.cairo file to verify the error message. While that file isn't modified in this PR, this is a gap in test coverage that should be addressed to maintain consistency with other error constants.