function buy(uint256 numTokens) public payable {
require(block.timestamp <= startTime + SALE_PERIOD, "ICO is over");
// 1 ETH = 10 Tokens (1 Token = 0.1 ETH)
require(msg.value == (numTokens * 10) / 100, "wrong ETH amount sent");
token.mint(msg.sender, numTokens);
}
function refund(uint256 numTokens) public {
require(block.timestamp < startTime + SALE_PERIOD, "ICO is over");
token.burn(msg.sender, numTokens);
// 1 ETH = 10 Tokens (1 Token = 0.1 ETH)
payable(msg.sender).call{value: (numTokens * 10) / 100}("");
}
잠시동안 곰곰히 고민을 했는데... 답이 안나와서 해설을 봤다.
Arithmetic Overflow/Underflow에 대해서는 잘 알고 있다고 생각했는데 생각지도 못한 방법이 있었다.
require(msg.value == (numTokens * 10) / 100, "wrong ETH amount sent");
여기서 당연히 msg.value
를 이빠이 넣어서 overflow를 발생시킬 것이라고 생각했으나..
(numTokens * 10) / 100
를 0으로 만들 수 있는 방식이 있었다.
numTokens
자리에 MAX_UINT / 10 + 1
값을 넣는 것이였다.
(MAX_UINT = 2 ** 256 - 1
)
MAX_UINT / 10 + 1
에 10을 곱하면 MAX_UINT + 10
이 된다.
이 값은 MAX_UINT + 1
(= 0) + 9
로, 즉 최종적으로 분자의 값은 9이다.
마지막으로 9 / 100
이 연산되면 0으로 버려져서 최종적으로
msg.value == 0
을 만족하게 된다.
마지막 4번 문제였는데 결국 혼자 풀지를 못했는데...
동일한 매커니즘이였다.
function transfer(address _to, uint256 _value) external returns (bool) {
require(balances[msg.sender] >= _value, "Not enough balance");
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
return true;
}
function batchTransfer(address[] memory _receivers, uint256 _value) external returns (bool) {
uint256 totalAmount = _receivers.length * _value;
require(_value > 0, "Value can't be 0");
require(balances[msg.sender] >= totalAmount, "Not enough tokens");
balances[msg.sender].sub(totalAmount);
for (uint256 i = 0; i < _receivers.length; i++) {
balances[_receivers[i]] = balances[_receivers[i]].add(_value);
}
return true;
}
uint256 totalAmount = _receivers.length * _value;
여기서 totalAmount를 0으로 만들면 되는 것이였다.
_receivers
배열의 길이를 2로 두고, _value
를 MAX_UINT / 2 + 1
로 두면 된다.
처음에는 해설을 보고 '이렇게 두면 2를 곱했을 때 MAX_UINT + 2가 되는 것 아닌가?' 하는 생각을 했었다.
이해하는데 한참 걸렸다.
MAX_UINT/2 + 1
의 값이 2**255
가 된다는 것을 이해하는 것이 중요하다.
2**256 - 1 / 2
는 2**255 - 0.5
이다.
그래서 나는 당연히 - 0.5
부분을 날려버리고 생각했는데, 여기서부터 이해가 잘못되었었다.
2**255 + 0.5
라면 날리는 것이 맞지만, 빼게 되면 소숫점 앞의 정수 부분이 아예 달라지게 된다.
즉 5 + 0.5
는 5.5
로 소숫점을 버리면 5
가 되지만,
5 - 0.5
는 4.5
로 소숫점을 버리면 4
가 된다.
최종적으로는 5 - 1
과 같아지는 상황이다.
다시 위의 예시로 돌아가면, (2**256 - 1) / 2
는 2**255 - 0.5
가 되고, 이것은 2**255 - 1
과 같은 값이다.
따라서 여기에 + 1을 해주니, 2**255
와 같은 값이 되는 것이다.
최종적으로 2
를 곱해주게 되면 2**256
이 되어 Overflow가 발생하여 0
이 되게 된다.