My solution for the fifth ethernaut challenge: https://ethernaut.openzeppelin.com/level/0x63bE8347A617476CA461649897238A31835a32CE
We have a Token contract that contains balances for address and a total supply.
mapping(address => uint) balances;
uint public totalSupply;
We have a constructor which initializes a contract instance with a provided supply and gives that entire balance to the contract deployer
constructor(uint _initialSupply) public {
balances[msg.sender] = totalSupply = _initialSupply;
}
There is a function which allows a transfer from the senders balance to the provided address. It simply checks that there is enough of a balance to be sent in the senders address, deducts the amount from the sender and assigns it to the to address.
function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}
Notably here, the computation is being done on uint values which can be overflowed. I tried out the transfer function using the Remix IDE and found that I was able to transfer 5 tokens to some other address. This worked since 5 was less than the amount in my wallet (initialized with 20 tokens).
First, I tried to pass in a negative value to the function. My thinking is that the value would be treated as a uint during smart contract execution, which would be a very large number. This approach does not work though and I got an error from the EVM
instance.transfer("0x...", -10);
// Error encoding arguments: Error: value out-of-bounds (argument=null, value="-5", code=INVALID_ARGUMENT, version=abi/5.5.0)
So it seems like it wont work to just try passing in a negative number. Thinking about it some more, I noticed that on this line
require(balances[msg.sender] - _value >= 0);
An integer overflow can also be induced. ie. if I try to send more than I have in my wallet, balances[msg.sender] - _value will actually overflow and return some large value. I tried this out
instance.transfer("0x...", 25);
instance.balanceOf("<my address>");
// uint256: balance 115792089237316195423570985008687907853269984665640564039457584007913129639931
My wallet now has several trillions of tokens ðŸ¤
During the investigation, I found that this will actually not be an issue using a newer solidity version to compile our code (0.8.0 and above).
Arithmetic operations revert on underflow and overflow. You can use
unchecked { ... }to use the previous wrapping behaviour.From https://docs.soliditylang.org/en/latest/080-breaking-changes.html
This issue can be prevented on lower solidity versions with OpenZeppeling
SafeMath.
