Bob decides to buy 5 shares for tokenID 1 and expect it costs 0.01605 NOTE tokens, but a malicious user frontruns the transaction and Bob ended up buying the shares for 0.041549999999999998 resulting in a 158.879% price increase, while the malicious user sells the shares back with profit.
Front running Buy Orders
✔ Deploy (8356ms)
✔ Alice Creates Share
✔ Users Buys Shares
✔ Exploit (12021ms)
Bob balance before: 1000000000000000000000
Bob expected balance after: 999983950000000000000
Bob actual balance after: 999958450000000000002
Attacker balance before: 1000000000000000000000
Attacker balance after: 1000023167250000000000
Tools Used
Hardhat & Solidity
Recommended Mitigation Steps
Adding a global _txCounter can prevent Bob from executing the transaction, because the _txCounter mismatched.
uint256 private _txCounter = 0;
function buy(uint256 _id, uint256 _amount, uint256 txCounter) external {
require(shareData[_id].creator != msg.sender, "Creator cannot buy");
require(txCounter == _txCounter, "txCounter mismatch");
(uint256 price, uint256 fee) = getBuyPrice(_id, _amount); // Reverts for non-existing ID
SafeERC20.safeTransferFrom(token, msg.sender, address(this), price + fee);
// The reward calculation has to use the old rewards value (pre fee-split) to not include the fees of this buy
// The rewardsLastClaimedValue then needs to be updated with the new value such that the user cannot claim fees of this buy
uint256 rewardsSinceLastClaim = _getRewardsSinceLastClaim(_id);
// Split the fee among holder, creator and platform
_splitFees(_id, fee, shareData[_id].tokensInCirculation);
rewardsLastClaimedValue[_id][msg.sender] = shareData[_id].shareHolderRewardsPerTokenScaled;
shareData[_id].tokenCount += _amount;
shareData[_id].tokensInCirculation += _amount;
tokensByAddress[_id][msg.sender] += _amount;
if (rewardsSinceLastClaim > 0) {
SafeERC20.safeTransfer(token, msg.sender, rewardsSinceLastClaim);
}
++_txCounter;
emit SharesBought(_id, msg.sender, _amount, price, fee);
}
Front running Buy Orders
✔ Deploy (8378ms)
✔ Alice Creates Share
✔ Users Buys Shares
✔ Exploit (12020ms)
Bob balance before: 1000000000000000000000
Bob expected balance after: 999983950000000000000 // tx is reverted
Bob actual balance after: 1000000000000000000000 // tx is reverted
Attacker balance before: 1000000000000000000000
Attacker balance after: 999998246500000000000
Or a more complex integration of Commitment-Reveal Scheme
Lines of code
https://github.com/code-423n4/2023-11-canto/blob/335930cd53cf9a137504a57f1215be52c6d67cb3/1155tech-contracts/src/Market.sol#L150-L169
Vulnerability details
Impact
Bob decides to buy 5 shares for tokenID 1 and expect it costs
0.01605
NOTE tokens, but a malicious user frontruns the transaction and Bob ended up buying the shares for0.041549999999999998
resulting in a 158.879% price increase, while the malicious user sells the shares back with profit.Proof of Concept
Sandwich contract
Results
Tools Used
Hardhat & Solidity
Recommended Mitigation Steps
Adding a global _txCounter can prevent Bob from executing the transaction, because the _txCounter mismatched.
Or a more complex integration of Commitment-Reveal Scheme
https://medium.com/coinmonks/commit-reveal-scheme-in-solidity-c06eba4091bb
Assessed type
Other