Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lack of precision vuln updated to precision loss. #86

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@
- [Default Visibility](./vulnerabilities/default-visibility.md)
- [Insufficient Access Control](./vulnerabilities/insufficient-access-control.md)
- [Off-By-One](./vulnerabilities/off-by-one.md)
- [Lack of Precision](./vulnerabilities/lack-of-precision.md)
- [Precision Loss](./vulnerabilities/precision-loss.md)
- [Unbounded Return Data](./vulnerabilities/unbounded-return-data.md)
- [Using ``msg.value`` in a Loop](./vulnerabilities/msgvalue-loop.md)
14 changes: 0 additions & 14 deletions vulnerabilities/lack-of-precision.md

This file was deleted.

44 changes: 44 additions & 0 deletions vulnerabilities/msgvalue-loop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Using ``msg.value`` in a Loop
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will have to merge/rebase to remove this file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the latest commit!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's still included for some reason. Just me?

Copy link
Contributor Author

@0xSandyy 0xSandyy Jun 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no changes in the latest commit for msgvalue-loop.md file but file changes shows msgvalue-loop.md file being updated.

Also, Nothing seems to work even after merging and rebasing with both msgValue and master branch. I think it is because one commit from msgValue got included here and it is causing the problem. As this PR has no conflicts with the base branch, and msgvalue-loop.md content hasn't been changed and is similar to the master branch, it can be merged without any issues.

Or we can close this PR and create a new one.


The value of ``msg.value`` in a transaction’s call never gets updated, even if the called contract ends up sending some or all of the ETH to another contract. This means that using ``msg.value`` in ``for`` or ``while`` loops, without extra accounting logic, will either lead to the transaction reverting (when there are no longer sufficient funds for later iterations), or to the contract being drained (when the contract itself has an ETH balance).

```solidity
contract depositer {
function deposit(address weth) payable external {
for (uint i = 0; i < 5; i ++) {
WETH(weth).deposit{value: msg.value}();
}
}
}
```
In the above example, first iteration will use all the ``msg.value`` for the external call and all other iterations can:
- Drain the contract if enough ETH balance exists inside the contract to cover all the iterations.
- Revert if enough ETH balance doesn't exist inside the contract to cover all the iterations.
- Succeed if the external implementation succeeds with zero value transfers.

Also, if a function has a check like ``require(msg.value == 1e18, "Not Enough Balance")``, that function can be called multiple times in a same transaction by sending ``1 ether`` once as ``msg.value`` is not updated in a transaction call.

```solidity
function batchBuy(address[] memory addr) external payable{
mapping (uint => address) nft;

for (uint i = 0; i < addr.length; i++) {
buy1NFT(addr[i])
}

function buy1NFT(address to) internal {
if (msg.value < 1 ether) { // buy unlimited times after sending 1 ether once
revert("Not enough ether");
}
nft[numero] = address;
}
}
```

Thus, using ``msg.value`` inside a loop is dangerous because this might allow the sender to ``re-use`` the ``msg.value``.

Reuse of ``msg.value`` can also show up with payable multicalls. Multicalls enable a user to submit a list of transactions to avoid paying the 21,000 gas transaction fee over and over. However, If ``msg.value`` gets ``re-used`` while looping through the functions to execute, it can cause a serious issue like the [Opyn Hack](https://peckshield.medium.com/opyn-hacks-root-cause-analysis-c65f3fe249db).

## Sources
- https://www.rareskills.io/post/smart-contract-security#:~:text=Using%20msg.,show%20up%20with%20payable%20multicalls.
- https://trustchain.medium.com/ethereum-msg-value-reuse-vulnerability-5afd0aa2bcef
39 changes: 39 additions & 0 deletions vulnerabilities/precision-loss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
## Precision Loss

Solidity uses fixed point arithmetic, that means it doesn't support decimal value. As a result, any decimal value is truncated. Thus, performing numerical operations can cause precision loss especially when division is performed before multiplication.

```solidity
uint a = 11;
uint b = 2;
uint c = 10;

function funcA() public {
return a / b * c;
}

function funcB() public {
return a * c / b;
}
```

In the above code example, ``funcA()`` returns:
```soldiity
11 / 2 * 10 = 50
```
and ``funcB()`` returns:
```solidity
11 * 2 / 10 = 55
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should actually return 2. I think your example is mixed up

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, Fixed in the latest commit!

```
Solidity truncates any non-integer result to the nearest lower integer. Thus in ``funcA()``, instead of ``11 / 2 = 5.5``, ``0.5`` is truncated and the result amounts to ``5``. This results in the function returning ``50`` instead of ``55`` due to division before multiplication whereas multiplication before division returns the correct value.

Also, if the Numerator is lower than the Denominator during division, the result will be zero in solidity.

```solidity
uint num = 10;
uint den = 20;

function foo() public pure returns(uint result) {
result = num / den; // returns 0
}
```
In regular math, the function ``foo()`` will return ``10 / 20 = 0.5``. But, the function returns ``0`` due to integer truncation in solidity. Therefore, It needs to be always made sure that numerator is greater than the denominator to avoid unexpected results.