JustBet / Pump

[WP-H1] Calculation error in the calculateSaleReturn() function

In the TokenFactory.sol#sell() function, users receive an amount termed fromPoolReserve_. However, within the calculateSaleReturn algorithm, when _sellAmount equals _supply and _connectorWeight reaches MAX_WEIGHT, fromPoolReserve_ is actually calculated as the protocolFee.

function calculateSaleReturn(uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _sellAmount) public returns (uint256 ethAmount_,  uint256 fromPoolReserve_, uint256 toProtocolReserve_) {
    // Validate input
    require(_supply > 0 && _connectorBalance > 0 && _connectorWeight > 0 && _connectorWeight <= MAX_WEIGHT && _sellAmount <= _supply);

    // Special case for 0 sell amount
    if (_sellAmount == 0) {
      return (0, 0, 0);
    }

    // Special case for selling the entire supply
    if (_sellAmount == _supply) {
      ethAmount_ = _connectorBalance * protocolFeePercentage / MAX_FEE;
      fromPoolReserve_ = ethAmount_;
      toProtocolReserve_ = _connectorBalance - ethAmount_;
    }

    // Special case if the weight = 100%
    if (_connectorWeight == MAX_WEIGHT) {
      ethAmount_ = (_connectorBalance * _sellAmount / _supply) * protocolFeePercentage / MAX_FEE;
      fromPoolReserve_ = ethAmount_;
      toProtocolReserve_ = _connectorBalance - ethAmount_;
    }

    uint256 result;
    uint8 precision;
    uint256 baseD = _supply - _sellAmount;
    (result, precision) = power(_supply, baseD, MAX_WEIGHT, _connectorWeight); // Call power function for precision
    uint256 oldBalance = _connectorBalance * result;
    uint256 newBalance = _connectorBalance << precision;

    ethAmount_ =  (oldBalance - newBalance) / result; // Calculate ether amount returned
    toProtocolReserve_ = ethAmount_ * protocolFeePercentage / MAX_FEE; // Calculate protocol fee
    fromPoolReserve_ = ethAmount_ - toProtocolReserve_;
}

https://github.com/WINRLabs/pump-contracts/blob/ee3c31d0ff798f9dc4614509074cbd7c2d2d019b/contracts/TokenFactory.sol#L202-L234

function sell(
    address tokenAddress,
    uint256 amount
) public saleEnabled(tokenAddress) {
    require(amount > 0, "Amount must be greater than 0");

    Token token = Token(tokenAddress);
    (
        uint256 ethAmount_,
        uint256 fromPoolReserve_,
        uint256 toPoolReserve_
    ) = token.sell(msg.sender, amount, poolReserve[tokenAddress]);
    require(ethAmount_ > 0, "Ether amount must be greater than 0");

    // Update pool reserve and protocol reserve
    poolReserve[tokenAddress] -= fromPoolReserve_;
    protocolReserve += toPoolReserve_;

    // Check if the token is still in sale
    if(poolReserve[tokenAddress] < defaultThreshold) {
        saleEnded[tokenAddress] = false;
    }

    // Transfer ETH to seller
    msg.sender.call{value: fromPoolReserve_}("");
    emit Sell(
        tokenAddress,
        msg.sender,
        amount,
        poolReserve[tokenAddress],
        fromPoolReserve_
    );
}

https://github.com/WINRLabs/pump-contracts/blob/ee3c31d0ff798f9dc4614509074cbd7c2d2d019b/contracts/BondingCurve.sol#L42-L51

function sell(address _seller, uint256 _sellAmount, uint256 _poolBalance) public onlyOwner returns(uint256 ethAmount_,  uint256 fromPoolReserve_, uint256 toPoolReserve_)  {
    require(_sellAmount > 0 && balanceOf(_seller) >= _sellAmount);

    // Calculate the amount of ETH to return and reserves distribution
    (ethAmount_, fromPoolReserve_, toPoolReserve_) = calculateSaleReturn(totalSupply(), _poolBalance, reserveRatio, _sellAmount);
    
    // Burn tokens from the seller
    _burn(_seller, _sellAmount);
}

Recommendation

if (_sellAmount == _supply) {
    ethAmount_ = _connectorBalance;
    toProtocolReserve_ = ethAmount_ * protocolFeePercentage / MAX_FEE;
    fromPoolReserve_ = ethAmount_ - toProtocolReserve_;
    return (fromPoolReserve_, fromPoolReserve_, toProtocolReserve_);
}

if (_connectorWeight == MAX_WEIGHT) {
    ethAmount_ = _connectorBalance * _sellAmount / _supply;
    toProtocolReserve_ = ethAmount_ * protocolFeePercentage / MAX_FEE;
    fromPoolReserve_ = ethAmount_ - toProtocolReserve_;
    return (fromPoolReserve_, fromPoolReserve_, toProtocolReserve_);
}
function calculateSaleReturn(uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _sellAmount) public returns (uint256 ethAmount_,  uint256 fromPoolReserve_, uint256 toProtocolReserve_) {
    // Validate input
    require(_supply > 0 && _connectorBalance > 0 && _connectorWeight > 0 && _connectorWeight <= MAX_WEIGHT && _sellAmount <= _supply);

    // Special case for 0 sell amount
    if (_sellAmount == 0) {
      return (0, 0, 0);
    }

    // Special case for selling the entire supply
    if (_sellAmount == _supply) {
      ethAmount_ = _connectorBalance * protocolFeePercentage / MAX_FEE;
      fromPoolReserve_ = ethAmount_;
      toProtocolReserve_ = _connectorBalance - ethAmount_;
    }

    // Special case if the weight = 100%
    if (_connectorWeight == MAX_WEIGHT) {
      ethAmount_ = (_connectorBalance * _sellAmount / _supply) * protocolFeePercentage / MAX_FEE;
      fromPoolReserve_ = ethAmount_;
      toProtocolReserve_ = _connectorBalance - ethAmount_;
    }

    uint256 result;
    uint8 precision;
    uint256 baseD = _supply - _sellAmount;
    (result, precision) = power(_supply, baseD, MAX_WEIGHT, _connectorWeight); // Call power function for precision
    uint256 oldBalance = _connectorBalance * result;
    uint256 newBalance = _connectorBalance << precision;

    ethAmount_ =  (oldBalance - newBalance) / result; // Calculate ether amount returned
    toProtocolReserve_ = ethAmount_ * protocolFeePercentage / MAX_FEE; // Calculate protocol fee
    fromPoolReserve_ = ethAmount_ - toProtocolReserve_;
}

[WP-H2] Attacker can maliciously create pool on Uniswap in advance, causing uniswapFactory.createPair() to revert.

This results in the ETH in poolReserve[tokenAddress] being unable to add liquidity and consequently getting locked in the contract.

uniswapFactory.createPair() will fail when the pool already exists.

function createUniswapPool(address tokenAddress) public onlyRole(POOL_CREATOR) {
    // Check if token exists
    require(isToken[tokenAddress], "Token does not exist");

    // Get the pool reserve
    uint256 poolReserveETH = poolReserve[tokenAddress];

    // Check if the token is still in sale
    require(poolReserveETH >= defaultThreshold, "token still in sale");

    // Create Uniswap V2 pool with WETH
    address _pair = uniswapFactory.createPair(tokenAddress, address(weth));

    // Store the pair address
@@ 250,281 @@ uniswapPools[tokenAddress] = _pair; Token token = Token(tokenAddress); // Mint the remaining tokens from the max supply uint256 amountTokenDesired = maxSupply - token.totalSupply(); token.mintRest(address(this), amountTokenDesired); token.approve(address(uniswapRouter), amountTokenDesired); // Set pool reserve to 0 poolReserve[tokenAddress] = 0; // Add liquidity to the pool uniswapRouter.addLiquidityETH{value: ethAmountToPool}( tokenAddress, amountTokenDesired, 0, // TODO: check the minimum amount ethAmountToPool, address(this), block.timestamp + 20 minutes ); // Transfer the protocol fee cut feeTo.call{value: poolReserveETH - ethAmountToPool}(""); emit PoolCreated( tokenAddress, _pair, poolReserveETH, amountTokenDesired );
}
function createPair(address tokenA, address tokenB) external returns (address pair) {
    require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
    (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
    require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
    require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
    bytes memory bytecode = type(UniswapV2Pair).creationCode;
    bytes32 salt = keccak256(abi.encodePacked(token0, token1));
    assembly {
        pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
    }
    IUniswapV2Pair(pair).initialize(token0, token1);
    getPair[token0][token1] = pair;
    getPair[token1][token0] = pair; // populate mapping in the reverse direction
    allPairs.push(pair);
    emit PairCreated(token0, token1, pair, allPairs.length);
}

Recommendation

Consider creating the pool beforehand and add the pool address to the transfer blacklist, and only remove it from the blacklist when the threshold is reached.

So that no one can add liquidity before the system allows.

function createUniswapPool(address tokenAddress) public onlyRole(POOL_CREATOR) {
    // Check if token exists
    require(isToken[tokenAddress], "Token does not exist");

    // Get the pool reserve
    uint256 poolReserveETH = poolReserve[tokenAddress];

    // Check if the token is still in sale
    require(poolReserveETH >= defaultThreshold, "token still in sale");

    // Create Uniswap V2 pool with WETH
    address _pair = uniswapFactory.createPair(tokenAddress, address(weth));

    // Store the pair address
@@ 250,281 @@ uniswapPools[tokenAddress] = _pair; Token token = Token(tokenAddress); // Mint the remaining tokens from the max supply uint256 amountTokenDesired = maxSupply - token.totalSupply(); token.mintRest(address(this), amountTokenDesired); token.approve(address(uniswapRouter), amountTokenDesired); // Set pool reserve to 0 poolReserve[tokenAddress] = 0; // Add liquidity to the pool uniswapRouter.addLiquidityETH{value: ethAmountToPool}( tokenAddress, amountTokenDesired, 0, // TODO: check the minimum amount ethAmountToPool, address(this), block.timestamp + 20 minutes ); // Transfer the protocol fee cut feeTo.call{value: poolReserveETH - ethAmountToPool}(""); emit PoolCreated( tokenAddress, _pair, poolReserveETH, amountTokenDesired );
}
function createPair(address tokenA, address tokenB) external returns (address pair) {
    require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
    (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
    require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
    require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
    bytes memory bytecode = type(UniswapV2Pair).creationCode;
    bytes32 salt = keccak256(abi.encodePacked(token0, token1));
    assembly {
        pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
    }
    IUniswapV2Pair(pair).initialize(token0, token1);
    getPair[token0][token1] = pair;
    getPair[token1][token0] = pair; // populate mapping in the reverse direction
    allPairs.push(pair);
    emit PairCreated(token0, token1, pair, allPairs.length);
}

[WP-H3] All the low-level calls that transfer ETH should incorporate logic to check if the transaction succeeds.

If it fails, there could potentially be a loss. On L226, if the user cannot receive the funds, it won’t revert, but it will burn the user’s tokens.

L288 will freeze the ETH, including the contract fee, inside the contract.

 function sell(
    address tokenAddress,
    uint256 amount
) public saleEnabled(tokenAddress) {
@@ 206,223 @@ require(amount > 0, "Amount must be greater than 0"); Token token = Token(tokenAddress); ( uint256 ethAmount_, uint256 fromPoolReserve_, uint256 toPoolReserve_ ) = token.sell(msg.sender, amount, poolReserve[tokenAddress]); require(ethAmount_ > 0, "Ether amount must be greater than 0"); // Update pool reserve and protocol reserve poolReserve[tokenAddress] -= fromPoolReserve_; protocolReserve += toPoolReserve_; // Check if the token is still in sale if(poolReserve[tokenAddress] < defaultThreshold) { saleEnded[tokenAddress] = false; }
// Transfer ETH to seller msg.sender.call{value: fromPoolReserve_}("");
@@ 227,233 @@ emit Sell( tokenAddress, msg.sender, amount, poolReserve[tokenAddress], fromPoolReserve_ );
}
function createUniswapPool(address tokenAddress) public onlyRole(POOL_CREATOR) {
@@ 237,271 @@ // Check if token exists require(isToken[tokenAddress], "Token does not exist"); // Get the pool reserve uint256 poolReserveETH = poolReserve[tokenAddress]; // Check if the token is still in sale require(poolReserveETH >= defaultThreshold, "token still in sale"); // Create Uniswap V2 pool with WETH address _pair = uniswapFactory.createPair(tokenAddress, address(weth)); // Store the pair address uniswapPools[tokenAddress] = _pair; Token token = Token(tokenAddress); // Mint the remaining tokens from the max supply uint256 amountTokenDesired = maxSupply - token.totalSupply(); token.mintRest(address(this), amountTokenDesired); token.approve(address(uniswapRouter), amountTokenDesired); // Set pool reserve to 0 poolReserve[tokenAddress] = 0; // Add liquidity to the pool uniswapRouter.addLiquidityETH{value: ethAmountToPool}( tokenAddress, amountTokenDesired, 0, // TODO: check the minimum amount ethAmountToPool, address(this), block.timestamp + 20 minutes );
// Transfer the protocol fee cut feeTo.call{value: poolReserveETH - ethAmountToPool}("");
@@ 276,281 @@ emit PoolCreated( tokenAddress, _pair, poolReserveETH, amountTokenDesired );
}
function claimProtocolReserve(address _to) public onlyRole(DEFAULT_ADMIN_ROLE) {
    require(protocolReserve > 0, "Protocol reserve is empty");
    _to.call{value: protocolReserve}("");
    protocolReserve = 0;
}

Recommendation

Every low-level call must include a check for success.

// Transfer ETH to seller
(bool success,) =  msg.sender.call{value: fromPoolReserve_}("");
require(success);
 function sell(
    address tokenAddress,
    uint256 amount
) public saleEnabled(tokenAddress) {
@@ 206,223 @@ require(amount > 0, "Amount must be greater than 0"); Token token = Token(tokenAddress); ( uint256 ethAmount_, uint256 fromPoolReserve_, uint256 toPoolReserve_ ) = token.sell(msg.sender, amount, poolReserve[tokenAddress]); require(ethAmount_ > 0, "Ether amount must be greater than 0"); // Update pool reserve and protocol reserve poolReserve[tokenAddress] -= fromPoolReserve_; protocolReserve += toPoolReserve_; // Check if the token is still in sale if(poolReserve[tokenAddress] < defaultThreshold) { saleEnded[tokenAddress] = false; }
// Transfer ETH to seller msg.sender.call{value: fromPoolReserve_}("");
@@ 227,233 @@ emit Sell( tokenAddress, msg.sender, amount, poolReserve[tokenAddress], fromPoolReserve_ );
}
function createUniswapPool(address tokenAddress) public onlyRole(POOL_CREATOR) {
@@ 237,271 @@ // Check if token exists require(isToken[tokenAddress], "Token does not exist"); // Get the pool reserve uint256 poolReserveETH = poolReserve[tokenAddress]; // Check if the token is still in sale require(poolReserveETH >= defaultThreshold, "token still in sale"); // Create Uniswap V2 pool with WETH address _pair = uniswapFactory.createPair(tokenAddress, address(weth)); // Store the pair address uniswapPools[tokenAddress] = _pair; Token token = Token(tokenAddress); // Mint the remaining tokens from the max supply uint256 amountTokenDesired = maxSupply - token.totalSupply(); token.mintRest(address(this), amountTokenDesired); token.approve(address(uniswapRouter), amountTokenDesired); // Set pool reserve to 0 poolReserve[tokenAddress] = 0; // Add liquidity to the pool uniswapRouter.addLiquidityETH{value: ethAmountToPool}( tokenAddress, amountTokenDesired, 0, // TODO: check the minimum amount ethAmountToPool, address(this), block.timestamp + 20 minutes );
// Transfer the protocol fee cut feeTo.call{value: poolReserveETH - ethAmountToPool}("");
@@ 276,281 @@ emit PoolCreated( tokenAddress, _pair, poolReserveETH, amountTokenDesired );
}
function claimProtocolReserve(address _to) public onlyRole(DEFAULT_ADMIN_ROLE) {
    require(protocolReserve > 0, "Protocol reserve is empty");
    _to.call{value: protocolReserve}("");
    protocolReserve = 0;
}

[WP-H4] DEFAULT_ADMIN_ROLE can initiate a reentrancy attack in claimProtocolReserve to steal all the funds and rug users.

For example, if there are a total of 1000 ETH in the contract, and protocolReserve=500. One call could transfer 500 ETH out, and then re-enter claimProtocolReserve() within the receive method, which could drain the remaining 500 ETH.

function claimProtocolReserve(address _to) public onlyRole(DEFAULT_ADMIN_ROLE) {
    require(protocolReserve > 0, "Protocol reserve is empty");
    _to.call{value: protocolReserve}("");
    protocolReserve = 0;
}

Recommendation

Consider adding a nonReentrant modifier to claimProtocolReserve().

function claimProtocolReserve(address _to) public onlyRole(DEFAULT_ADMIN_ROLE) {
    require(protocolReserve > 0, "Protocol reserve is empty");
    _to.call{value: protocolReserve}("");
    protocolReserve = 0;
}

[WP-M5] Update of defaultThreshold can result in freezing of funds.

Once a token's poolReserve reaches defaultThreshold, saleEnded[tokenAddress] will be set to true.

function buy(
    address tokenAddress
) public payable saleEnabled(tokenAddress) {
    require(msg.value > 0, "Ether amount must be greater than 0");
@@ 169,180 @@ Token token = Token(tokenAddress); ( uint256 _tokensToMint, uint256 _toPoolReserve, uint256 _toProtocolReserve ) = token.buy(msg.sender, msg.value, poolReserve[tokenAddress]); require(_tokensToMint > 0, "Tokens to mint must be greater than 0"); // Update pool reserve and protocol reserve poolReserve[tokenAddress] += _toPoolReserve;
if(poolReserve[tokenAddress] >= defaultThreshold) { saleEnded[tokenAddress] = true; }
@@ 185,197 @@ require( poolReserve[tokenAddress] <= defaultThreshold, "Buy exceeds the threshold" ); protocolReserve += _toProtocolReserve; emit Buy( tokenAddress, msg.sender, _tokensToMint, poolReserve[tokenAddress], msg.value );
}

If the admin increases defaultThreshold after saleEnded[tokenAddress] = true but before createUniswapPool, then the UniswapPool will not meet the requirements for creation.

function createUniswapPool(address tokenAddress) public onlyRole(POOL_CREATOR) {
    // Check if token exists
    require(isToken[tokenAddress], "Token does not exist");

    // Get the pool reserve
    uint256 poolReserveETH = poolReserve[tokenAddress];

    // Check if the token is still in sale
    require(poolReserveETH >= defaultThreshold, "token still in sale");

    // Create Uniswap V2 pool with WETH
    address _pair = uniswapFactory.createPair(tokenAddress, address(weth));

@@ 249,281 @@ // Store the pair address uniswapPools[tokenAddress] = _pair; Token token = Token(tokenAddress); // Mint the remaining tokens from the max supply uint256 amountTokenDesired = maxSupply - token.totalSupply(); token.mintRest(address(this), amountTokenDesired); token.approve(address(uniswapRouter), amountTokenDesired); // Set pool reserve to 0 poolReserve[tokenAddress] = 0; // Add liquidity to the pool uniswapRouter.addLiquidityETH{value: ethAmountToPool}( tokenAddress, amountTokenDesired, 0, // TODO: check the minimum amount ethAmountToPool, address(this), block.timestamp + 20 minutes ); // Transfer the protocol fee cut feeTo.call{value: poolReserveETH - ethAmountToPool}(""); emit PoolCreated( tokenAddress, _pair, poolReserveETH, amountTokenDesired );
}

Recommendation

function createUniswapPool(address tokenAddress) public onlyRole(POOL_CREATOR) {
    // Check if token exists
    require(isToken[tokenAddress], "Token does not exist");

    // Get the pool reserve
    uint256 poolReserveETH = poolReserve[tokenAddress];

    // Check if the token is still in sale
    require(saleEnded[tokenAddress] "token still in sale");

    // Create Uniswap V2 pool with WETH
    address _pair = uniswapFactory.createPair(tokenAddress, address(weth));
@@ 248,281 @@ // Store the pair address uniswapPools[tokenAddress] = _pair; Token token = Token(tokenAddress); // Mint the remaining tokens from the max supply uint256 amountTokenDesired = maxSupply - token.totalSupply(); token.mintRest(address(this), amountTokenDesired); token.approve(address(uniswapRouter), amountTokenDesired); // Set pool reserve to 0 poolReserve[tokenAddress] = 0; // Add liquidity to the pool uniswapRouter.addLiquidityETH{value: ethAmountToPool}( tokenAddress, amountTokenDesired, 0, // TODO: check the minimum amount ethAmountToPool, address(this), block.timestamp + 20 minutes ); // Transfer the protocol fee cut feeTo.call{value: poolReserveETH - ethAmountToPool}(""); emit PoolCreated( tokenAddress, _pair, poolReserveETH, amountTokenDesired );
}
function buy(
    address tokenAddress
) public payable saleEnabled(tokenAddress) {
    require(msg.value > 0, "Ether amount must be greater than 0");
@@ 169,180 @@ Token token = Token(tokenAddress); ( uint256 _tokensToMint, uint256 _toPoolReserve, uint256 _toProtocolReserve ) = token.buy(msg.sender, msg.value, poolReserve[tokenAddress]); require(_tokensToMint > 0, "Tokens to mint must be greater than 0"); // Update pool reserve and protocol reserve poolReserve[tokenAddress] += _toPoolReserve;
if(poolReserve[tokenAddress] >= defaultThreshold) { saleEnded[tokenAddress] = true; }
@@ 185,197 @@ require( poolReserve[tokenAddress] <= defaultThreshold, "Buy exceeds the threshold" ); protocolReserve += _toProtocolReserve; emit Buy( tokenAddress, msg.sender, _tokensToMint, poolReserve[tokenAddress], msg.value );
}
function createUniswapPool(address tokenAddress) public onlyRole(POOL_CREATOR) {
    // Check if token exists
    require(isToken[tokenAddress], "Token does not exist");

    // Get the pool reserve
    uint256 poolReserveETH = poolReserve[tokenAddress];

    // Check if the token is still in sale
    require(poolReserveETH >= defaultThreshold, "token still in sale");

    // Create Uniswap V2 pool with WETH
    address _pair = uniswapFactory.createPair(tokenAddress, address(weth));

@@ 249,281 @@ // Store the pair address uniswapPools[tokenAddress] = _pair; Token token = Token(tokenAddress); // Mint the remaining tokens from the max supply uint256 amountTokenDesired = maxSupply - token.totalSupply(); token.mintRest(address(this), amountTokenDesired); token.approve(address(uniswapRouter), amountTokenDesired); // Set pool reserve to 0 poolReserve[tokenAddress] = 0; // Add liquidity to the pool uniswapRouter.addLiquidityETH{value: ethAmountToPool}( tokenAddress, amountTokenDesired, 0, // TODO: check the minimum amount ethAmountToPool, address(this), block.timestamp + 20 minutes ); // Transfer the protocol fee cut feeTo.call{value: poolReserveETH - ethAmountToPool}(""); emit PoolCreated( tokenAddress, _pair, poolReserveETH, amountTokenDesired );
}

[WP-M6] calculateSaleReturn() should return early when handling special cases.

When handling the special cases, the results should be returned early after calculation. If not, subsequent code will incorrectly override these results.

function calculateSaleReturn(uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _sellAmount) public returns (uint256 ethAmount_,  uint256 fromPoolReserve_, uint256 toProtocolReserve_) {
@@ 89,96 @@ // Validate input require(_supply > 0 && _connectorBalance > 0 && _connectorWeight > 0 && _connectorWeight <= MAX_WEIGHT && _sellAmount <= _supply); // Special case for 0 sell amount if (_sellAmount == 0) { return (0, 0, 0); }
// Special case for selling the entire supply if (_sellAmount == _supply) { ethAmount_ = _connectorBalance * protocolFeePercentage / MAX_FEE; fromPoolReserve_ = ethAmount_; toProtocolReserve_ = _connectorBalance - ethAmount_; } // Special case if the weight = 100% if (_connectorWeight == MAX_WEIGHT) { ethAmount_ = (_connectorBalance * _sellAmount / _supply) * protocolFeePercentage / MAX_FEE; fromPoolReserve_ = ethAmount_; toProtocolReserve_ = _connectorBalance - ethAmount_; } uint256 result; uint8 precision; uint256 baseD = _supply - _sellAmount; (result, precision) = power(_supply, baseD, MAX_WEIGHT, _connectorWeight); // Call power function for precision uint256 oldBalance = _connectorBalance * result; uint256 newBalance = _connectorBalance << precision; ethAmount_ = (oldBalance - newBalance) / result; // Calculate ether amount returned toProtocolReserve_ = ethAmount_ * protocolFeePercentage / MAX_FEE; // Calculate protocol fee fromPoolReserve_ = ethAmount_ - toProtocolReserve_; }

As a result, when _sellAmount == _supply, baseD will be 0, and the transaction will revert due to division by 0 in power().

function power(uint256 _baseN, uint256 _baseD, uint32 _expN, uint32 _expD) internal returns (uint256, uint8) {
    uint256 lnBaseTimesExp = ln(_baseN, _baseD) * _expN / _expD;
    uint8 precision = findPositionInMaxExpArray(lnBaseTimesExp);
    return (fixedExp(lnBaseTimesExp >> (MAX_PRECISION - precision), precision), precision);
  }
function ln(uint256 _numerator, uint256 _denominator) internal returns (uint256) {
    assert(_numerator <= MAX_NUM);

    uint256 res = 0;
    uint256 x = _numerator * FIXED_1 / _denominator;

@@ 206,224 @@ // If x >= 2, then we compute the integer part of log2(x), which is larger than 0. if (x >= FIXED_2) { uint8 count = floorLog2(x / FIXED_1); x >>= count; // now x < 2 res = count * FIXED_1; } // If x > 1, then we compute the fraction part of log2(x), which is larger than 0. if (x > FIXED_1) { for (uint8 i = MAX_PRECISION; i > 0; --i) { x = (x * x) / FIXED_1; // now 1 < x < 4 if (x >= FIXED_2) { x >>= 1; // now 1 < x < 2 res += ONE << (i - 1); } } } return (res * LN2_MANTISSA) >> LN2_EXPONENT;
}
function calculateSaleReturn(uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _sellAmount) public returns (uint256 ethAmount_,  uint256 fromPoolReserve_, uint256 toProtocolReserve_) {
@@ 89,96 @@ // Validate input require(_supply > 0 && _connectorBalance > 0 && _connectorWeight > 0 && _connectorWeight <= MAX_WEIGHT && _sellAmount <= _supply); // Special case for 0 sell amount if (_sellAmount == 0) { return (0, 0, 0); }
// Special case for selling the entire supply if (_sellAmount == _supply) { ethAmount_ = _connectorBalance * protocolFeePercentage / MAX_FEE; fromPoolReserve_ = ethAmount_; toProtocolReserve_ = _connectorBalance - ethAmount_; } // Special case if the weight = 100% if (_connectorWeight == MAX_WEIGHT) { ethAmount_ = (_connectorBalance * _sellAmount / _supply) * protocolFeePercentage / MAX_FEE; fromPoolReserve_ = ethAmount_; toProtocolReserve_ = _connectorBalance - ethAmount_; } uint256 result; uint8 precision; uint256 baseD = _supply - _sellAmount; (result, precision) = power(_supply, baseD, MAX_WEIGHT, _connectorWeight); // Call power function for precision uint256 oldBalance = _connectorBalance * result; uint256 newBalance = _connectorBalance << precision; ethAmount_ = (oldBalance - newBalance) / result; // Calculate ether amount returned toProtocolReserve_ = ethAmount_ * protocolFeePercentage / MAX_FEE; // Calculate protocol fee fromPoolReserve_ = ethAmount_ - toProtocolReserve_; }
function power(uint256 _baseN, uint256 _baseD, uint32 _expN, uint32 _expD) internal returns (uint256, uint8) {
    uint256 lnBaseTimesExp = ln(_baseN, _baseD) * _expN / _expD;
    uint8 precision = findPositionInMaxExpArray(lnBaseTimesExp);
    return (fixedExp(lnBaseTimesExp >> (MAX_PRECISION - precision), precision), precision);
  }
function ln(uint256 _numerator, uint256 _denominator) internal returns (uint256) {
    assert(_numerator <= MAX_NUM);

    uint256 res = 0;
    uint256 x = _numerator * FIXED_1 / _denominator;

@@ 206,224 @@ // If x >= 2, then we compute the integer part of log2(x), which is larger than 0. if (x >= FIXED_2) { uint8 count = floorLog2(x / FIXED_1); x >>= count; // now x < 2 res = count * FIXED_1; } // If x > 1, then we compute the fraction part of log2(x), which is larger than 0. if (x > FIXED_1) { for (uint8 i = MAX_PRECISION; i > 0; --i) { x = (x * x) / FIXED_1; // now 1 < x < 4 if (x >= FIXED_2) { x >>= 1; // now 1 < x < 2 res += ONE << (i - 1); } } } return (res * LN2_MANTISSA) >> LN2_EXPONENT;
}

[WP-L7] Lack of sanity check for defaultThreshold and ethAmountToPool.

Based on the business logic, defaultThreshold needs to be bigger than ethAmountToPool.

The current implementation sets them separately, which allows defaultThreshold < ethAmountToPool.

 // Function to set default threshold for sale
function setDefaultThreshold(uint256 _defaultThreshold) public onlyRole(DEFAULT_ADMIN_ROLE) {
    defaultThreshold = _defaultThreshold;
}

// Function to set ETH amount to pool for liquidity
function setEthAmountToPool(uint256 _ethAmountToPool) public onlyRole(DEFAULT_ADMIN_ROLE) {
    ethAmountToPool = _ethAmountToPool;
}

Recommendation

Consider adding a new function:

function setDefaultThresholdAndEthAmountToPool(uint256 _defaultThreshold, uint256 _ethAmountToPool) public onlyRole(DEFAULT_ADMIN_ROLE) {
    require(_defaultThreshold > _ethAmountToPool, "Threshold must be greater than eth amount to pool");

    defaultThreshold = _defaultThreshold;
    ethAmountToPool = _ethAmountToPool;
}
 // Function to set default threshold for sale
function setDefaultThreshold(uint256 _defaultThreshold) public onlyRole(DEFAULT_ADMIN_ROLE) {
    defaultThreshold = _defaultThreshold;
}

// Function to set ETH amount to pool for liquidity
function setEthAmountToPool(uint256 _ethAmountToPool) public onlyRole(DEFAULT_ADMIN_ROLE) {
    ethAmountToPool = _ethAmountToPool;
}

[WP-L8] Reaching the stage of adding liquidity to the pool is too difficult; it requires poolReserve[tokenAddress] == defaultThreshold.

It is recommended to remove the require(poolReserve[tokenAddress] <= defaultThreshold, ...) at line 185.

The requirements in TokenFactory.sol#buy() in combination with the in sale check on createUniswapPool() essentially require the poolReserve to be exactly defaultThreshold, not 1 wei more, not 1 wei less.

In other words, the current implementation mandates that the frontend calculate the exact amount to be added to successfully bring the poolReserve to the desired level.

Which is unnecessary and most likely unintended, as the surplus poolReserve is expected and handled properly at L274 already.

function buy(
    address tokenAddress
) public payable saleEnabled(tokenAddress) {
@@ 168,178 @@ require(msg.value > 0, "Ether amount must be greater than 0"); Token token = Token(tokenAddress); ( uint256 _tokensToMint, uint256 _toPoolReserve, uint256 _toProtocolReserve ) = token.buy(msg.sender, msg.value, poolReserve[tokenAddress]); require(_tokensToMint > 0, "Tokens to mint must be greater than 0"); // Update pool reserve and protocol reserve
poolReserve[tokenAddress] += _toPoolReserve; if(poolReserve[tokenAddress] >= defaultThreshold) { saleEnded[tokenAddress] = true; } require( poolReserve[tokenAddress] <= defaultThreshold, "Buy exceeds the threshold" );
@@ 189,197 @@ protocolReserve += _toProtocolReserve; emit Buy( tokenAddress, msg.sender, _tokensToMint, poolReserve[tokenAddress], msg.value );
}
function createUniswapPool(address tokenAddress) public onlyRole(POOL_CREATOR) {
    // Check if token exists
    require(isToken[tokenAddress], "Token does not exist");

    // Get the pool reserve
    uint256 poolReserveETH = poolReserve[tokenAddress];

    // Check if the token is still in sale
    require(poolReserveETH >= defaultThreshold, "token still in sale");

@@ 246,272 @@ // Create Uniswap V2 pool with WETH address _pair = uniswapFactory.createPair(tokenAddress, address(weth)); // Store the pair address uniswapPools[tokenAddress] = _pair; Token token = Token(tokenAddress); // Mint the remaining tokens from the max supply uint256 amountTokenDesired = maxSupply - token.totalSupply(); token.mintRest(address(this), amountTokenDesired); token.approve(address(uniswapRouter), amountTokenDesired); // Set pool reserve to 0 poolReserve[tokenAddress] = 0; // Add liquidity to the pool uniswapRouter.addLiquidityETH{value: ethAmountToPool}( tokenAddress, amountTokenDesired, 0, // TODO: check the minimum amount ethAmountToPool, address(this), block.timestamp + 20 minutes );
// Transfer the protocol fee cut feeTo.call{value: poolReserveETH - ethAmountToPool}("");
@@ 276,281 @@ emit PoolCreated( tokenAddress, _pair, poolReserveETH, amountTokenDesired );
}
function buy(
    address tokenAddress
) public payable saleEnabled(tokenAddress) {
@@ 168,178 @@ require(msg.value > 0, "Ether amount must be greater than 0"); Token token = Token(tokenAddress); ( uint256 _tokensToMint, uint256 _toPoolReserve, uint256 _toProtocolReserve ) = token.buy(msg.sender, msg.value, poolReserve[tokenAddress]); require(_tokensToMint > 0, "Tokens to mint must be greater than 0"); // Update pool reserve and protocol reserve
poolReserve[tokenAddress] += _toPoolReserve; if(poolReserve[tokenAddress] >= defaultThreshold) { saleEnded[tokenAddress] = true; } require( poolReserve[tokenAddress] <= defaultThreshold, "Buy exceeds the threshold" );
@@ 189,197 @@ protocolReserve += _toProtocolReserve; emit Buy( tokenAddress, msg.sender, _tokensToMint, poolReserve[tokenAddress], msg.value );
}
function createUniswapPool(address tokenAddress) public onlyRole(POOL_CREATOR) {
    // Check if token exists
    require(isToken[tokenAddress], "Token does not exist");

    // Get the pool reserve
    uint256 poolReserveETH = poolReserve[tokenAddress];

    // Check if the token is still in sale
    require(poolReserveETH >= defaultThreshold, "token still in sale");

@@ 246,272 @@ // Create Uniswap V2 pool with WETH address _pair = uniswapFactory.createPair(tokenAddress, address(weth)); // Store the pair address uniswapPools[tokenAddress] = _pair; Token token = Token(tokenAddress); // Mint the remaining tokens from the max supply uint256 amountTokenDesired = maxSupply - token.totalSupply(); token.mintRest(address(this), amountTokenDesired); token.approve(address(uniswapRouter), amountTokenDesired); // Set pool reserve to 0 poolReserve[tokenAddress] = 0; // Add liquidity to the pool uniswapRouter.addLiquidityETH{value: ethAmountToPool}( tokenAddress, amountTokenDesired, 0, // TODO: check the minimum amount ethAmountToPool, address(this), block.timestamp + 20 minutes );
// Transfer the protocol fee cut feeTo.call{value: poolReserveETH - ethAmountToPool}("");
@@ 276,281 @@ emit PoolCreated( tokenAddress, _pair, poolReserveETH, amountTokenDesired );
}

[WP-G9] Check invariants first to save gas

Consider:

  • Moving require(poolReserve[tokenAddress] <= defaultThreshold, ...) before if(poolReserve[tokenAddress] >= defaultThreshold)
  • Changing if(poolReserve[tokenAddress] >= defaultThreshold) to if (poolReserve[tokenAddress] == defaultThreshold)

This will:

  • Enhance code readability by clearly stating the conditions under which saleEnded[tokenAddress] = true is executed
  • Avoid wasting gas: when poolReserve[tokenAddress] > defaultThreshold there is no need to modify the storage saleEnded[tokenAddress]
    function buy(
        address tokenAddress
    ) public payable saleEnabled(tokenAddress) {
        require(msg.value > 0, "Ether amount must be greater than 0");
        Token token = Token(tokenAddress);
        (
            uint256 _tokensToMint,
            uint256 _toPoolReserve,
            uint256 _toProtocolReserve
        ) = token.buy(msg.sender, msg.value, poolReserve[tokenAddress]);

        require(_tokensToMint > 0, "Tokens to mint must be greater than 0");

        // Update pool reserve and protocol reserve
        poolReserve[tokenAddress] += _toPoolReserve;

        if(poolReserve[tokenAddress] >= defaultThreshold) {
            saleEnded[tokenAddress] = true;
        }

        require(
            poolReserve[tokenAddress] <= defaultThreshold,
            "Buy exceeds the threshold"
        );
        protocolReserve += _toProtocolReserve;

        emit Buy(
            tokenAddress,
            msg.sender,
            _tokensToMint,
            poolReserve[tokenAddress],
            msg.value
        );
    }

Recommendation

Consider changing to:

    function buy(
        address tokenAddress
    ) public payable saleEnabled(tokenAddress) {
        require(msg.value > 0, "Ether amount must be greater than 0");
        Token token = Token(tokenAddress);
        (
            uint256 _tokensToMint,
            uint256 _toPoolReserve,
            uint256 _toProtocolReserve
        ) = token.buy(msg.sender, msg.value, poolReserve[tokenAddress]);

        require(_tokensToMint > 0, "Tokens to mint must be greater than 0");

        // Update pool reserve and protocol reserve
        poolReserve[tokenAddress] += _toPoolReserve;

        require(
            poolReserve[tokenAddress] <= defaultThreshold,
            "Buy exceeds the threshold"
        );
        if (poolReserve[tokenAddress] == defaultThreshold) {
            saleEnded[tokenAddress] = true;
        }
        protocolReserve += _toProtocolReserve;

        emit Buy(
            tokenAddress,
            msg.sender,
            _tokensToMint,
            poolReserve[tokenAddress],
            msg.value
        );
    }
    function buy(
        address tokenAddress
    ) public payable saleEnabled(tokenAddress) {
        require(msg.value > 0, "Ether amount must be greater than 0");
        Token token = Token(tokenAddress);
        (
            uint256 _tokensToMint,
            uint256 _toPoolReserve,
            uint256 _toProtocolReserve
        ) = token.buy(msg.sender, msg.value, poolReserve[tokenAddress]);

        require(_tokensToMint > 0, "Tokens to mint must be greater than 0");

        // Update pool reserve and protocol reserve
        poolReserve[tokenAddress] += _toPoolReserve;

        if(poolReserve[tokenAddress] >= defaultThreshold) {
            saleEnded[tokenAddress] = true;
        }

        require(
            poolReserve[tokenAddress] <= defaultThreshold,
            "Buy exceeds the threshold"
        );
        protocolReserve += _toProtocolReserve;

        emit Buy(
            tokenAddress,
            msg.sender,
            _tokensToMint,
            poolReserve[tokenAddress],
            msg.value
        );
    }

[WP-N10] Adding a 20-minute deadline to the Uniswap router contract call is meaningless.

Uniswap's code only checks if deadline >= block.timestamp.

modifier ensure(uint deadline) {
    require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
    _;
}

Therefore, adding 20 mins does not change anything.

Consider using block.timestamp instead for simplicity.

function createUniswapPool(address tokenAddress) public onlyRole(POOL_CREATOR) {
@@ 237,261 @@ // Check if token exists require(isToken[tokenAddress], "Token does not exist"); // Get the pool reserve uint256 poolReserveETH = poolReserve[tokenAddress]; // Check if the token is still in sale require(poolReserveETH >= defaultThreshold, "token still in sale"); // Create Uniswap V2 pool with WETH address _pair = uniswapFactory.createPair(tokenAddress, address(weth)); // Store the pair address uniswapPools[tokenAddress] = _pair; Token token = Token(tokenAddress); // Mint the remaining tokens from the max supply uint256 amountTokenDesired = maxSupply - token.totalSupply(); token.mintRest(address(this), amountTokenDesired); token.approve(address(uniswapRouter), amountTokenDesired); // Set pool reserve to 0 poolReserve[tokenAddress] = 0;
// Add liquidity to the pool uniswapRouter.addLiquidityETH{value: ethAmountToPool}( tokenAddress, amountTokenDesired, 0, // TODO: check the minimum amount ethAmountToPool, address(this), block.timestamp + 20 minutes );
@@ 272,281 @@ // Transfer the protocol fee cut feeTo.call{value: poolReserveETH - ethAmountToPool}(""); emit PoolCreated( tokenAddress, _pair, poolReserveETH, amountTokenDesired );
}
modifier ensure(uint deadline) {
    require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
    _;
}
function createUniswapPool(address tokenAddress) public onlyRole(POOL_CREATOR) {
@@ 237,261 @@ // Check if token exists require(isToken[tokenAddress], "Token does not exist"); // Get the pool reserve uint256 poolReserveETH = poolReserve[tokenAddress]; // Check if the token is still in sale require(poolReserveETH >= defaultThreshold, "token still in sale"); // Create Uniswap V2 pool with WETH address _pair = uniswapFactory.createPair(tokenAddress, address(weth)); // Store the pair address uniswapPools[tokenAddress] = _pair; Token token = Token(tokenAddress); // Mint the remaining tokens from the max supply uint256 amountTokenDesired = maxSupply - token.totalSupply(); token.mintRest(address(this), amountTokenDesired); token.approve(address(uniswapRouter), amountTokenDesired); // Set pool reserve to 0 poolReserve[tokenAddress] = 0;
// Add liquidity to the pool uniswapRouter.addLiquidityETH{value: ethAmountToPool}( tokenAddress, amountTokenDesired, 0, // TODO: check the minimum amount ethAmountToPool, address(this), block.timestamp + 20 minutes );
@@ 272,281 @@ // Transfer the protocol fee cut feeTo.call{value: poolReserveETH - ethAmountToPool}(""); emit PoolCreated( tokenAddress, _pair, poolReserveETH, amountTokenDesired );
}

[WP-I11] Consider implementing slippage control in TokenFactory.buy(address tokenAddress) payable to avoid unexpected outcomes due to front-running and other issues.

Other transactions can affect the outcome of a user's TokenFactory.buy() transaction. It might be necessary for users to control the output and revert the transaction when the output does not meet the specified requirements, similar to slippage control in a swap transaction.

https://github.com/WINRLabs/pump-contracts/blob/ee3c31d0ff798f9dc4614509074cbd7c2d2d019b/contracts/TokenFactory.sol#L164-L198

// Function to buy tokens
function buy(
    address tokenAddress
) public payable saleEnabled(tokenAddress) {
    require(msg.value > 0, "Ether amount must be greater than 0");
    Token token = Token(tokenAddress);
    (
        uint256 _tokensToMint,
        uint256 _toPoolReserve,
        uint256 _toProtocolReserve
    ) = token.buy(msg.sender, msg.value, poolReserve[tokenAddress]);

    require(_tokensToMint > 0, "Tokens to mint must be greater than 0");

@@ 178,197 @@ // Update pool reserve and protocol reserve poolReserve[tokenAddress] += _toPoolReserve; if(poolReserve[tokenAddress] >= defaultThreshold) { saleEnded[tokenAddress] = true; } require( poolReserve[tokenAddress] <= defaultThreshold, "Buy exceeds the threshold" ); protocolReserve += _toProtocolReserve; emit Buy( tokenAddress, msg.sender, _tokensToMint, poolReserve[tokenAddress], msg.value );
}

https://github.com/WINRLabs/pump-contracts/blob/ee3c31d0ff798f9dc4614509074cbd7c2d2d019b/contracts/BondingCurve.sol#L31-L40

// Function to buy tokens from the bonding curve
function buy(address _buyer, uint256 _ethAmount, uint256 _poolBalance) public onlyOwner returns(uint256 tokensToMint_,  uint256 _toPoolReserve, uint256 _toProtocolReserve)  { // TODO: onlyFactory
    require(_ethAmount > 0);

    // Calculate the amount of tokens to mint and reserves distribution
    (tokensToMint_, _toPoolReserve, _toProtocolReserve) = calculatePurchaseReturn(totalSupply(), _poolBalance, reserveRatio, _ethAmount);
    
    // Mint tokens to the buyer
    _mint(_buyer, tokensToMint_);
}
@@ 24,39 @@ /** * @dev Given a token supply, connector balance, weight, and a deposit amount (in the connector token), * calculates the return for a given conversion (in the main token) * * Formula: * Return = _supply * ((1 + _depositAmount / _connectorBalance) ^ (_connectorWeight / 1000000) - 1) * * @param _supply Token total supply * @param _connectorBalance Total connector balance * @param _connectorWeight Connector weight, represented in ppm, 1-1000000 * @param _depositAmount Deposit amount, in connector token * * @return tokensToMint_ Amount of tokens minted * @return toPoolReserve_ Amount to pool reserve * @return toProtocolReserve_ Amount to protocol reserve */
function calculatePurchaseReturn( uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _depositAmount) public returns (uint256 tokensToMint_, uint256 toPoolReserve_, uint256 toProtocolReserve_) {
@@ 46,61 @@ // Validate input require(_supply > 0 && _connectorBalance > 0 && _connectorWeight > 0 && _connectorWeight <= MAX_WEIGHT); // Special case for 0 deposit amount if (_depositAmount == 0) { return (0, 0, 0); } toProtocolReserve_ = _depositAmount * protocolFeePercentage / MAX_FEE; // Calculate protocol fee toPoolReserve_ = _depositAmount - toProtocolReserve_; // Calculate amount to pool reserve // Special case if the weight = 100% if (_connectorWeight == MAX_WEIGHT) { tokensToMint_ = _supply * toPoolReserve_ / _connectorBalance; return (tokensToMint_, toPoolReserve_, toProtocolReserve_); }
uint256 result; uint8 precision; uint256 baseN = toPoolReserve_ + _connectorBalance; (result, precision) = power(baseN, _connectorBalance, _connectorWeight, MAX_WEIGHT); // Call power function for precision uint256 temp = ((_supply * result) >> precision) + 1; // Calculate tokens to mint tokensToMint_ = temp - _supply; }

sell() is the same situation; it should add a minEthAmount as a slippage control, e.g.: require(fromPoolReserve_ > minEthAmount).

function sell(
    address tokenAddress,
    uint256 amount
) public saleEnabled(tokenAddress) {
    require(amount > 0, "Amount must be greater than 0");
@@ 206,222 @@ Token token = Token(tokenAddress); ( uint256 ethAmount_, uint256 fromPoolReserve_, uint256 toPoolReserve_ ) = token.sell(msg.sender, amount, poolReserve[tokenAddress]); require(ethAmount_ > 0, "Ether amount must be greater than 0"); // Update pool reserve and protocol reserve poolReserve[tokenAddress] -= fromPoolReserve_; protocolReserve += toPoolReserve_; // Check if the token is still in sale if(poolReserve[tokenAddress] < defaultThreshold) { saleEnded[tokenAddress] = false; }
// Transfer ETH to seller msg.sender.call{value: fromPoolReserve_}(""); emit Sell( tokenAddress, msg.sender, amount, poolReserve[tokenAddress], fromPoolReserve_ ); }
@@ 24,39 @@ /** * @dev Given a token supply, connector balance, weight, and a deposit amount (in the connector token), * calculates the return for a given conversion (in the main token) * * Formula: * Return = _supply * ((1 + _depositAmount / _connectorBalance) ^ (_connectorWeight / 1000000) - 1) * * @param _supply Token total supply * @param _connectorBalance Total connector balance * @param _connectorWeight Connector weight, represented in ppm, 1-1000000 * @param _depositAmount Deposit amount, in connector token * * @return tokensToMint_ Amount of tokens minted * @return toPoolReserve_ Amount to pool reserve * @return toProtocolReserve_ Amount to protocol reserve */
function calculatePurchaseReturn( uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _depositAmount) public returns (uint256 tokensToMint_, uint256 toPoolReserve_, uint256 toProtocolReserve_) {
@@ 46,61 @@ // Validate input require(_supply > 0 && _connectorBalance > 0 && _connectorWeight > 0 && _connectorWeight <= MAX_WEIGHT); // Special case for 0 deposit amount if (_depositAmount == 0) { return (0, 0, 0); } toProtocolReserve_ = _depositAmount * protocolFeePercentage / MAX_FEE; // Calculate protocol fee toPoolReserve_ = _depositAmount - toProtocolReserve_; // Calculate amount to pool reserve // Special case if the weight = 100% if (_connectorWeight == MAX_WEIGHT) { tokensToMint_ = _supply * toPoolReserve_ / _connectorBalance; return (tokensToMint_, toPoolReserve_, toProtocolReserve_); }
uint256 result; uint8 precision; uint256 baseN = toPoolReserve_ + _connectorBalance; (result, precision) = power(baseN, _connectorBalance, _connectorWeight, MAX_WEIGHT); // Call power function for precision uint256 temp = ((_supply * result) >> precision) + 1; // Calculate tokens to mint tokensToMint_ = temp - _supply; }
function sell(
    address tokenAddress,
    uint256 amount
) public saleEnabled(tokenAddress) {
    require(amount > 0, "Amount must be greater than 0");
@@ 206,222 @@ Token token = Token(tokenAddress); ( uint256 ethAmount_, uint256 fromPoolReserve_, uint256 toPoolReserve_ ) = token.sell(msg.sender, amount, poolReserve[tokenAddress]); require(ethAmount_ > 0, "Ether amount must be greater than 0"); // Update pool reserve and protocol reserve poolReserve[tokenAddress] -= fromPoolReserve_; protocolReserve += toPoolReserve_; // Check if the token is still in sale if(poolReserve[tokenAddress] < defaultThreshold) { saleEnded[tokenAddress] = false; }
// Transfer ETH to seller msg.sender.call{value: fromPoolReserve_}(""); emit Sell( tokenAddress, msg.sender, amount, poolReserve[tokenAddress], fromPoolReserve_ ); }