-
Notifications
You must be signed in to change notification settings - Fork 62
feat(launchpadv2): add indexing events to BondingV5 and FRouterV3 #174
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: main
Are you sure you want to change the base?
Changes from all commits
7c34f35
b7df3ae
b8674f8
d8a8674
ebc45da
6cb2ecc
a7adaf9
3efab8f
5b7477b
beeef47
a3511cc
477c4dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -114,11 +114,26 @@ contract BondingV5 is | |
| /// @notice Raw `extParams_` from `preLaunch` (per agent token). Backend can read as canonical payload; not updated by `setFeeDelegation`. | ||
| mapping(address => bytes) public tokenPreLaunchExtParams; | ||
|
|
||
| /// @dev Appended in upgrade v2 — must remain last state variable (append-only layout). | ||
| /// @dev Appended in upgrade v2 (append-only layout). | ||
| /// Fake initial virtual liquidity frozen at preLaunch for migration price continuity. | ||
| /// If unset (pre-upgrade token), graduation uses BondingConfig legacyInitialVirtualLiq. | ||
| mapping(address => uint256) public tokenFakeInitialVirtualLiq; | ||
|
|
||
| /// @dev Appended in upgrade v3 — decoded from `extParams_` at preLaunch (append-only layout). | ||
| /// Robotics flag (bit 2 of the flags word). See extParams encoding notes above `preLaunch`. | ||
| mapping(address => bool) public isRobotics; | ||
|
|
||
| /// @notice Fee delegation type per token: 0 = none, 1 = address, 2 = twitter | ||
| /// (decoded from bits 3-4 of the `extParams_` flags word). | ||
| mapping(address => uint8) public feeDelegationType; | ||
|
|
||
| /// @notice Fee delegation recipient from the `extParams_` trailing word, stored as a fixed | ||
| /// `bytes32` right-aligned integer: the recipient address as `uint160` for type 1, or | ||
| /// the twitter account id as `uint64` for type 2 (the vault is only created just before | ||
| /// `launch`). `bytes32(0)` when no delegation. Fixed-size to save gas vs dynamic bytes. | ||
| /// @dev MUST remain the last state variable (append-only layout). | ||
| mapping(address => bytes32) public feeDelegationRecipient; | ||
|
|
||
| event PreLaunched( | ||
| address indexed token, | ||
| address indexed pair, | ||
|
|
@@ -144,6 +159,36 @@ contract BondingV5 is | |
|
|
||
| event FeeDelegationUpdated(address indexed token, bool isFeeDelegation); | ||
|
|
||
| /// @notice Emitted at preLaunch with the extended launch settings decoded from `extParams_`. | ||
| event PreLaunchExtParams( | ||
| address indexed token, | ||
| bool isFeeDelegation, | ||
| bool isRobotics, | ||
| uint8 feeDelegationType, | ||
| bytes32 feeDelegationRecipient | ||
| ); | ||
|
|
||
| event TokenCreated( | ||
| uint256 virtualId, | ||
| address indexed creator, | ||
| address indexed token, | ||
| string name, | ||
| string symbol, | ||
| uint256 maxSupply, | ||
| uint256 saleAmount, | ||
| uint256 graduationThreshold, | ||
| uint256 initialVirtualLiquidity, | ||
| uint256 initialPrice, | ||
| uint256 initialPurchase, | ||
| address quoteAsset, | ||
| address pair, | ||
| string description, | ||
| string image, | ||
| BondingConfig.LaunchParams launchParams, | ||
| uint256 startTime, | ||
| uint256 startTimeDelay | ||
| ); | ||
|
|
||
| error InvalidTokenStatus(); | ||
| error InvalidInput(); | ||
| error SlippageTooHigh(); | ||
|
|
@@ -157,12 +202,38 @@ contract BondingV5 is | |
| _disableInitializers(); | ||
| } | ||
|
|
||
| /// @dev `extParams` first 32-byte word — flags in the LSB (same byte as `abi.encode(bool)`): | ||
| /// bit 0: `isFeeDelegation` | ||
| /// bit 1: skip `" by Virtuals"` suffix (unset ⇒ append; V1-compatible) | ||
| /// Empty `extParams` or length < 32 ⇒ both flags false ⇒ fee delegation off, suffix on. | ||
| /// @dev `extParams` encoding — append-only and length-tolerant so it stays backward | ||
| /// compatible and can be freely extended: | ||
| /// | ||
| /// Word 0 (`extParams[0:32]`) is a flags bitfield (same LSB byte as `abi.encode(bool)`): | ||
| /// bit 0: `isFeeDelegation` | ||
| /// bit 1: skip `" by Virtuals"` suffix (unset ⇒ append; V1-compatible) | ||
| /// bit 2: `isRobotics` | ||
| /// bits 3-4: `feeDelegationType` (0 = none, 1 = address, 2 = twitter) | ||
| /// remaining bits: reserved = 0, available for future flags/enums. | ||
| /// | ||
| /// Trailing word (`extParams[32:64]`, present only when needed) carries the | ||
| /// `feeDelegationRecipient` as a fixed `bytes32`, ABI-encoded as | ||
| /// `abi.encode(uint256 flags, bytes32 recipient)` with the recipient a right-aligned integer: | ||
| /// - type 1 (address): the recipient address as `uint160` | ||
| /// - type 2 (twitter): the twitter account id as `uint64` | ||
| /// The recipient is opaque to this contract (stored/emitted as-is); off-chain decodes by type. | ||
| /// Future fields are appended after this word and decoded defensively by length. | ||
| /// | ||
| /// Rules that MUST hold for every future change: | ||
| /// - Never repurpose or move an existing bit/offset (bit 0 / bit 1 semantics are frozen). | ||
| /// - Decoders tolerate shorter and longer payloads: read a field only when the length | ||
| /// covers it, otherwise use the default; ignore trailing bytes / higher bits. | ||
| /// | ||
| /// Empty `extParams` or length < 32 ⇒ all flags false ⇒ fee delegation off, suffix on, | ||
| /// robotics off, no delegation type/recipient (identical to the pre-v3 behaviour). | ||
| uint256 private constant EXT_PARAMS_FLAG_FEE_DELEGATION = 1; | ||
| uint256 private constant EXT_PARAMS_FLAG_SKIP_SUFFIX = 2; | ||
| uint256 private constant EXT_PARAMS_FLAG_ROBOTICS = 4; // bit 2 | ||
| // `feeDelegationType` occupies bits 3-4 of the flags word: shift right by 3 to bring | ||
| // those bits down, then mask with 0x3 (2 bits) to isolate them (0 = none, 1 = address, 2 = twitter). | ||
| uint256 private constant EXT_PARAMS_FEE_DELEGATION_TYPE_SHIFT = 3; | ||
| uint256 private constant EXT_PARAMS_FEE_DELEGATION_TYPE_MASK = 0x3; | ||
|
|
||
| function _loadExtParamsWord( | ||
| bytes calldata extParams | ||
|
|
@@ -187,6 +258,38 @@ contract BondingV5 is | |
| return (_loadExtParamsWord(extParams) & EXT_PARAMS_FLAG_SKIP_SUFFIX) == 0; | ||
| } | ||
|
|
||
| function _decodeIsRobotics( | ||
| bytes calldata extParams | ||
| ) internal pure returns (bool) { | ||
| return (_loadExtParamsWord(extParams) & EXT_PARAMS_FLAG_ROBOTICS) != 0; | ||
| } | ||
|
|
||
| function _decodeFeeDelegationType( | ||
| bytes calldata extParams | ||
| ) internal pure returns (uint8) { | ||
| return | ||
| uint8( | ||
| (_loadExtParamsWord(extParams) >> | ||
| EXT_PARAMS_FEE_DELEGATION_TYPE_SHIFT) & | ||
| EXT_PARAMS_FEE_DELEGATION_TYPE_MASK | ||
| ); | ||
| } | ||
|
|
||
| /// @dev Reads the trailing `feeDelegationRecipient` word (2nd word of | ||
| /// `abi.encode(uint256 flags, bytes32 recipient)`) when present. Length-gated so legacy | ||
| /// flags-only payloads (length < 64) return `bytes32(0)`, and future longer payloads | ||
| /// (extra words appended after the recipient) still decode this word safely. | ||
| function _decodeFeeDelegationRecipient( | ||
| bytes calldata extParams | ||
| ) internal pure returns (bytes32 recipient) { | ||
| if (extParams.length < 64) { | ||
| return bytes32(0); | ||
| } | ||
| assembly ("memory-safe") { | ||
| recipient := calldataload(add(extParams.offset, 32)) | ||
| } | ||
| } | ||
|
|
||
| function initialize( | ||
| address factory_, | ||
| address router_, | ||
|
|
@@ -202,8 +305,11 @@ contract BondingV5 is | |
| bondingConfig = BondingConfig(bondingConfig_); | ||
| } | ||
|
|
||
| /// @param extParams_ Optional extension payload (first 32 bytes). V1: `abi.encode(bool isFeeDelegation)`. | ||
| /// V2: set bit 1 of the word to skip `" by Virtuals"` (bit 1 unset ⇒ append). Use `""` for defaults. | ||
| /// @param extParams_ Optional extension payload. See the extParams encoding notes above for the | ||
| /// full (append-only, backward-compatible) layout. V1: `abi.encode(bool isFeeDelegation)`. | ||
| /// V2: set bit 1 of the flags word to skip `" by Virtuals"`. V3: bit 2 = `isRobotics`, | ||
| /// bits 3-4 = `feeDelegationType`, with the optional `feeDelegationRecipient` appended as | ||
| /// `abi.encode(uint256 flags, bytes recipient)`. Use `""` for defaults. | ||
| function preLaunch( | ||
| string memory name_, | ||
| string memory ticker_, | ||
|
|
@@ -403,6 +509,14 @@ contract BondingV5 is | |
| isFeeDelegation[token] = isFeeDelegation_; | ||
| tokenPreLaunchExtParams[token] = extParams_; | ||
|
|
||
| // Decode and store the extended launch settings so on-chain is the source of truth. | ||
| bool isRobotics_ = _decodeIsRobotics(extParams_); | ||
| uint8 feeDelegationType_ = _decodeFeeDelegationType(extParams_); | ||
| bytes32 feeDelegationRecipient_ = _decodeFeeDelegationRecipient(extParams_); | ||
| isRobotics[token] = isRobotics_; | ||
| feeDelegationType[token] = feeDelegationType_; | ||
| feeDelegationRecipient[token] = feeDelegationRecipient_; | ||
|
|
||
| // Set Data struct fields | ||
| newToken.data.token = token; | ||
| newToken.data.name = tokenName; | ||
|
|
@@ -435,6 +549,36 @@ contract BondingV5 is | |
| tokenLaunchParams[token] | ||
| ); | ||
|
|
||
| emit PreLaunchExtParams( | ||
| token, | ||
| isFeeDelegation_, | ||
| isRobotics_, | ||
| feeDelegationType_, | ||
| feeDelegationRecipient_ | ||
| ); | ||
|
|
||
| // Net quote target at the graduation threshold (excludes buy tax / anti-sniper fees). | ||
| emit TokenCreated( | ||
| tokenInfo[token].virtualId, | ||
| msg.sender, | ||
| token, | ||
| name_, | ||
| ticker_, | ||
| configInitialSupply * (10 ** IAgentTokenV4(token).decimals()), | ||
| bondingCurveSupply, | ||
| gradThreshold, | ||
| liquidity * 2, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Virtual liquidity value doubledMedium Severity
Reviewed by Cursor Bugbot for commit a3511cc. Configure here. |
||
| price, | ||
| initialPurchase, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistent price units across eventsMedium Severity
Additional Locations (1)Reviewed by Cursor Bugbot for commit d8a8674. Configure here. |
||
| assetToken, | ||
| pair, | ||
| desc_, | ||
| img_, | ||
| tokenLaunchParams[token], | ||
| actualStartTime, | ||
| actualStartTimeDelay | ||
| ); | ||
|
|
||
| return (token, pair, tokenInfo[token].virtualId, initialPurchase); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,6 +53,18 @@ contract FRouterV3 is | |
| IBondingV5ForRouter public bondingV5; | ||
| IBondingConfigForRouter public bondingConfig; | ||
|
|
||
| struct TradeEventData { | ||
| address token; | ||
| address trader; | ||
| address pair; | ||
| bool isBuy; | ||
| uint256 amountIn; | ||
| uint256 amountOut; | ||
| uint256 amount; | ||
| uint256 taxFee; | ||
| uint256 antiSniperFee; | ||
| } | ||
|
|
||
| event PrivatePoolDrained( | ||
| address indexed token, | ||
| address indexed recipient, | ||
|
|
@@ -67,6 +79,22 @@ contract FRouterV3 is | |
| uint256 veTokenAmount | ||
| ); | ||
|
|
||
| event TradeExecuted( | ||
| address indexed token, | ||
| address indexed trader, | ||
| address indexed pair, | ||
| bool isBuy, | ||
| address quoteAsset, | ||
| uint256 amountIn, | ||
| uint256 amountOut, | ||
| uint256 amount, | ||
| uint256 taxFee, | ||
| uint256 antiSniperFee, | ||
| uint256 reserveTokenAfter, | ||
| uint256 reserveAssetAfter, | ||
| uint256 lastPrice | ||
| ); | ||
|
|
||
| /// @custom:oz-upgrades-unsafe-allow constructor | ||
| constructor() { | ||
| _disableInitializers(); | ||
|
|
@@ -184,6 +212,20 @@ contract FRouterV3 is | |
|
|
||
| pair.swap(amountIn, 0, 0, amountOut); | ||
|
|
||
| _emitTradeExecuted( | ||
| TradeEventData({ | ||
| token: tokenAddress, | ||
| trader: to, | ||
| pair: pairAddress, | ||
| isBuy: false, | ||
| amountIn: amountIn, | ||
| amountOut: amountOut, | ||
| amount: amount, | ||
| taxFee: normalTxFee, | ||
| antiSniperFee: antiSniperTxFee | ||
| }) | ||
| ); | ||
|
|
||
| return (amountIn, amountOut); | ||
| } | ||
|
|
||
|
|
@@ -240,6 +282,20 @@ contract FRouterV3 is | |
|
|
||
| IFPairV2(pair).swap(0, amountOut, amount, 0); | ||
|
|
||
| _emitTradeExecuted( | ||
| TradeEventData({ | ||
| token: tokenAddress, | ||
| trader: to, | ||
| pair: pair, | ||
| isBuy: true, | ||
| amountIn: amountIn, | ||
| amountOut: amountOut, | ||
| amount: amount, | ||
| taxFee: normalTxFee, | ||
| antiSniperFee: antiSniperTxFee | ||
| }) | ||
| ); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Launch buy mislabels indexed traderMedium Severity
Additional Locations (1)Reviewed by Cursor Bugbot for commit 7c34f35. Configure here. |
||
|
|
||
| return (amount, amountOut); | ||
| } | ||
|
|
||
|
|
@@ -372,6 +428,30 @@ contract FRouterV3 is | |
| return finalTaxStartTime; | ||
| } | ||
|
|
||
| function _emitTradeExecuted(TradeEventData memory trade) private { | ||
| IFPairV2 pair = IFPairV2(trade.pair); | ||
| (uint256 remainingForSale, uint256 totalRaised) = pair.getReserves(); | ||
| uint256 lastPrice = remainingForSale == 0 | ||
| ? 0 | ||
| : ((totalRaised * 1 ether) / remainingForSale); // last price multiplied by 10 * 10^18 | ||
|
|
||
| emit TradeExecuted( | ||
| trade.token, | ||
| trade.trader, | ||
| trade.pair, | ||
| trade.isBuy, | ||
| assetToken, | ||
| trade.amountIn, | ||
| trade.amountOut, | ||
| trade.amount, | ||
| trade.taxFee, | ||
| trade.antiSniperFee, | ||
| remainingForSale, | ||
| totalRaised, | ||
| lastPrice | ||
| ); | ||
| } | ||
|
|
||
| function hasAntiSniperTax(address pairAddress) public view returns (bool) { | ||
| return | ||
| _calculateAntiSniperBuyTax(pairAddress) > 0 || | ||
|
|
||


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.
TokenCreated omits key indexing fields
High Severity
TokenCreatedis missingapplicationIdandtargetRaiseAmounteven though the emit comment describes the net quote target,applicationIdis already available inpreLaunch, andindexingEvents.jsasserts both fields. Indexers still need RPC for those values, and the new test cannot pass against this ABI.Additional Locations (2)
contracts/launchpadv2/BondingV5.sol#L559-L580test/launchpadv5/indexingEvents.js#L83-L91Reviewed by Cursor Bugbot for commit a3511cc. Configure here.