calculateSaleReturn()
functionIn 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_;
}
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_
);
}
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);
}
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_;
}
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);
}
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);
}
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;
}
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;
}
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;
}
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;
}
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
);
}
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
);
}
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;
}
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;
}
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;
}
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
);
}
Consider:
require(poolReserve[tokenAddress] <= defaultThreshold, ...)
before if(poolReserve[tokenAddress] >= defaultThreshold)
if(poolReserve[tokenAddress] >= defaultThreshold)
to if (poolReserve[tokenAddress] == defaultThreshold)
This will:
saleEnded[tokenAddress] = true
is executedpoolReserve[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
);
}
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
);
}
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
);
}
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.
// 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
);
}
// 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_
);
}