Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions contracts/launchpadv2/BondingV5.sol
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,27 @@ contract BondingV5 is

event FeeDelegationUpdated(address indexed token, bool isFeeDelegation);

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();
Expand Down Expand Up @@ -423,6 +444,28 @@ contract BondingV5 is
tokenLaunchParams[token]
);

// 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,
price,
initialPurchase,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent price units across events

Medium Severity

TokenCreated sets initialPrice as bondingCurveSupply / liquidity (token-per-quote, unscaled), while TradeExecuted sets lastPrice as (reserveAsset * 1 ether) / reserveToken (quote-per-token, 1e18-scaled). A log-only indexer treating both as the same price series will show a discontinuous or inverted curve at the first trade.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d8a8674. Configure here.

assetToken,
pair,
desc_,
img_,
tokenLaunchParams[token],
actualStartTime,
actualStartTimeDelay
);

return (token, pair, tokenInfo[token].virtualId, initialPurchase);
}

Expand Down
80 changes: 80 additions & 0 deletions contracts/launchpadv2/FRouterV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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();
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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
})
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Launch buy mislabels indexed trader

Medium Severity

TradeExecuted sets the indexed trader to the router to address (token recipient). On launch(), the creator’s initial purchase calls _buy with address(this) as to, so the event attributes that buy to the bonding contract instead of the creator, while quote and token amounts still reflect a real trade.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7c34f35. Configure here.


return (amount, amountOut);
}

Expand Down Expand Up @@ -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 ||
Expand Down
1 change: 1 addition & 0 deletions test/launchpadv5/bondingV5Fixture.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ async function setupBondingV5Test() {
scheduledLaunchParams,
deployParams,
bondingCurveParams,
FAKE_INITIAL_VIRTUAL_LIQ, // acfFakeInitialVirtualLiq_ (8th arg added on current main)
],
{ initializer: "initialize" }
);
Expand Down
Loading