Skip to content

Commit c1912cc

Browse files
committed
Merge branch 'master' into fix-use-allContracts
2 parents 99b04d5 + 35e43f5 commit c1912cc

File tree

34 files changed

+298
-108
lines changed

34 files changed

+298
-108
lines changed

.github/workflows/echidna.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ jobs:
167167
contract: ${{ matrix.contract }}
168168
config: ${{ matrix.config }}
169169
output-file: ${{ matrix.files }}.out
170-
solc-version: ${{ matrix.solc-version || '0.5.11' }}
170+
solc-version: ${{ matrix.solc-version || '0.8.0' }}
171171
echidna-workdir: ${{ matrix.workdir }}
172172
echidna-version: edge
173173
crytic-args: ${{ matrix.crytic-args || '' }}

SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
- [Access Controls](./not-so-smart-contracts/algorand/access_controls/README.md)
2828
- [Asset Id Check](./not-so-smart-contracts/algorand/asset_id_check/README.md)
2929
- [Denial of Service](./not-so-smart-contracts/algorand/denial_of_service/README.md)
30+
- [Inner Transaction Fee](./not-so-smart-contracts/algorand/inner_transaction_fee/README.md)
31+
- [Clear State Transaction Check](./not-so-smart-contracts/algorand/clear_state_transaction_check/README.md)
3032
- [Cairo](./not-so-smart-contracts/cairo/README.md)
3133
- [Improper access controls](./not-so-smart-contracts/cairo/access_controls/README.md)
3234
- [Integer division errors](./not-so-smart-contracts/cairo/integer_division/README.md)

learn_evm/arithmetic-checks.md

Lines changed: 40 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ these features are absent in the EVM.
77
Consequently, these safeguards must be incorporated within the machine's constraints.
88

99
Starting with Solidity version 0.8.0 the compiler automatically includes over and underflow protection in all arithmetic operations.
10-
Prior to version 0.8.0, developers were required to implement these checks manually, often using a library known as [SafeMath](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol), originally developed by OpenZeppelin.
10+
Prior to version 0.8.0, developers were required to implement these checks manually, often using a library known as [SafeMath](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.2/contracts/utils/math/SafeMath.sol), originally developed by OpenZeppelin.
1111
The compiler incorporates arithmetic checks in a manner similar to SafeMath, through additional operations.
1212

1313
As the Solidity language has evolved, the compiler has generated increasingly optimized code for arithmetic checks. This trend is also observed in smart contract development in general, where highly optimized arithmetic code written in low-level assembly is becoming more common. However, there is still a lack of comprehensive resources explaining the nuances of how the EVM handles arithmetic for signed and unsigned integers of 256 bits and less.
@@ -154,25 +154,25 @@ The first bit of an integer represents the sign, with `0` indicating a positive
154154
For positive integers (those with a sign bit of `0`), their binary representation is the same as their unsigned bit representation.
155155
However, the negative domain is shifted to lie "above" the positive domain.
156156

157-
$$uint256 \text{ domain}$$
157+
$$\text{uint256 domain}$$
158158

159159
$$
160-
├\underset{0}{─}────────────────────────────\underset{\hskip -2em 2^{256} - 1}{─}┤
160+
├\underset{\hskip -0.5em 0}{─}────────────────────────────\underset{\hskip -3em 2^{256} - 1}{─}┤
161161
$$
162162

163163
```solidity
164164
0x0000000000000000000000000000000000000000000000000000000000000000 // 0
165165
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff // uint256_max
166166
```
167167

168-
$$int256 \text{ domain}$$
168+
$$\text{int256 domain}$$
169169

170170
$$
171-
\overset{\hskip 1em positive}{
172-
├\underset{0}{─}────────────\underset{\hskip -2em 2^{255} - 1}{─}┤
171+
\overset{positive}{
172+
├\underset{\hskip -0.5em 0}{─}────────────\underset{\hskip -3em 2^{255} - 1}{─}┤
173173
}
174-
\overset{\hskip 1em negative}{
175-
├────\underset{\hskip -3.5em - 2^{255}}────────\underset{\hskip -0.4 em -1}{─}┤
174+
\overset{negative}{
175+
├──\underset{\hskip -2.1em - 2^{255}}{─}──────────\underset{\hskip -1 em -1}{─}┤
176176
}
177177
$$
178178

@@ -184,11 +184,11 @@ $$
184184
```
185185

186186
The maximum positive integer that can be represented in a two's complement system using 256 bits is
187-
`0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff` which is roughly equal to half of the maximum value that can be represented using uint256.
187+
`0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff` which is roughly equal to half of the maximum value that can be represented using `uint256`.
188188
The most significant bit of this number is `0`, while all other bits are `1`.
189189

190190
On the other hand, all negative numbers start with a `1` as their first bit.
191-
If we look at the underlying hex representation of these numbers, they are all greater than or equal to the smallest integer that can be represented using int256, which is `0x8000000000000000000000000000000000000000000000000000000000000000`. The integer's binary representation is a `1` followed by 255 `0`'s.
191+
If we look at the underlying hex representation of these numbers, they are all greater than or equal to the smallest integer that can be represented using `int256`, which is `0x8000000000000000000000000000000000000000000000000000000000000000`. The integer's binary representation is a `1` followed by 255 `0`'s.
192192

193193
To obtain the negative value of an integer in a two's complement system, we flip the underlying bits and add `1`: `-a = ~a + 1`.
194194
An example illustrates this.
@@ -421,7 +421,7 @@ On a 64-bit system, integer addition works in the same way as before.
421421
= 0x0000000000000001 // int64(1)
422422
```
423423

424-
However, when performing the same calculations on a 256-bit machine, we need to extend the sign of the int64 value over all unused bits,
424+
However, when performing the same calculations on a 256-bit machine, we need to extend the sign of the `int64` value over all unused bits,
425425
otherwise the value won't be interpreted correctly.
426426

427427
```solidity
@@ -505,60 +505,56 @@ function overflowInt64(int256 value) public pure returns (bool overflow) {
505505
```
506506

507507
We can simplify the expression to a single comparison if we can shift the disjointed number domain back so that it's connected.
508-
To accomplish this, we subtract the smallest negative int64 `type(int64).min` from a value (or add the underlying unsigned value).
509-
A better way to understand this is by visualizing the signed integer number domain in relation to the unsigned domain (which is demonstrated here using int128).
508+
To accomplish this, we subtract the smallest negative `int64` (`type(int64).min`) from a value (or add the underlying unsigned value).
509+
A better way to understand this is by visualizing the signed integer number domain in relation to the unsigned domain (which is demonstrated here using `int128`).
510510

511-
$$uint256 \text{ domain}$$
511+
$$\text{uint256 domain}$$
512512

513513
$$
514-
├\underset{0}{─}────────────────────────────\underset{\hskip -2em 2^{256} - 1}{─}┤
514+
├\underset{\hskip -0.5em 0}{─}────────────────────────────\underset{\hskip -3em 2^{256} - 1}{─}┤
515515
$$
516516

517-
$$int256 \text{ domain}$$
517+
$$\text{int256 domain}$$
518518

519519
$$
520-
\overset{\hskip 1em positive}{
521-
├\underset{0}{─}────────────\underset{\hskip -2em 2^{255} - 1}{─}┤
520+
\overset{positive}{
521+
├\underset{\hskip -0.5em 0}{─}────────────\underset{\hskip -3em 2^{255} - 1}{─}┤
522522
}
523-
\overset{\hskip 1em negative}{
524-
├────\underset{\hskip -3.5em - 2^{255}}────────\underset{\hskip -0.4 em -1}{─}┤
523+
\overset{negative}{
524+
├──\underset{\hskip -2.1em - 2^{255}}{─}──────────\underset{\hskip -1 em -1}{─}┤
525525
}
526526
$$
527527

528-
The domain for uint128/int128 can be visualized as follows.
528+
The domain for `uint128`/`int128` can be visualized as follows.
529529

530-
$$uint128 \text{ domain}$$
530+
$$\text{uint128 domain}$$
531531

532532
$$
533-
├\underset{0}─────────────\underset{\hskip -2em 2^{128}-1}─┤
534-
\phantom{───────────────}
533+
├\underset{\hskip -0.5em 0}─────────────\underset{\hskip -3em 2^{128}-1}─┤
534+
\hskip 7em
535535
$$
536536

537-
$$int128 \text{ domain}$$
537+
$$\text{int128 domain}$$
538538

539539
$$
540-
\overset{\hskip 1em positive}{
541-
├\underset{0}{─}────\underset{\hskip -2em 2^{127} - 1}{─}┤
542-
}
543-
\phantom{────────────────}
544-
\overset{\hskip 1em negative}{
545-
├────\underset{\hskip -3.5em - 2^{127}}─\underset{\hskip -0.4 em -1}{─}┤
546-
}
540+
├\underset{\hskip -0.5em 0}{─}────\underset{\hskip -3em 2^{127} - 1}─\overset{\hskip -3em positive}{┤}
541+
\hskip 7em
542+
├──\underset{\hskip -2.1em - 2^{127}}───\underset{\hskip -1 em -1}{─}\overset{\hskip -3em negative}{┤}
547543
$$
548544

549545
Note that the scales of the number ranges in the previous section do not accurately depict the magnitude of numbers that are representable with the different types and only serve as a visualization. We can represent twice as many numbers with only one additional bit, yet the uint256 domain has twice the number of bits compared to uint128.
550546

551-
After subtracting `type(int128).min` (or adding $2^{127}$) and essentially shifting the domains to the right, we get the following, connected set of values.
547+
After subtracting `type(int128).min` (or adding `2**127`) and essentially shifting the domains to the right, we get the following, connected set of values.
552548

553549
$$
554-
├\underset{0}─────────────\underset{\hskip -2em 2^{128}-1}─┤
555-
\phantom{───────────────}
550+
├\underset{\hskip -0.5em 0}{─}────────────\underset{\hskip -3em 2^{128}-1}─┤
551+
\hskip 7em
556552
$$
557553

558554
$$
559-
\overset{\hskip 1em negative}{├──────┤}
560-
\overset{\hskip 1em positive}{├──────┤}
561-
\phantom{───────────────}
555+
├──────\overset{\hskip -3em negative}{┤}
556+
├──────\overset{\hskip -3em positive}{┤}
557+
\hskip 7em
562558
$$
563559

564560
If we interpret the shifted value as an unsigned integer, we only need to check whether it exceeds the maximum unsigned integer `type(uint128).max`.
@@ -685,7 +681,7 @@ An example might help explain the second case.
685681
```solidity
686682
0xffffffffffffffff800000000000000000000000000000000000000000000000 // type(int192).min
687683
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff // -1
688-
= 0x0000000000000000800000000000000000000000000000000000000000000000 // type(int192).min (when seen as a int192)
684+
= 0x0000000000000000800000000000000000000000000000000000000000000000 // type(int192).min (when looking at the first 192 bits)
689685
```
690686

691687
A method to address this issue is to always start by sign-extending or cleaning the result before attempting to reconstruct the other multiplicand.
@@ -715,9 +711,10 @@ function checkedMulInt192_2(int192 a, int192 b) public pure returns (int192 c) {
715711
In conclusion, we hope this article has served as an informative guide on signed integer arithmetic within the EVM and the two's complement system.
716712
We have explored:
717713

718-
- the added complexity from handling signed over unsigned integers
719-
- the intricacies involved in managing sub 32-byte types
720-
- the significance of `signextend` and opcodes related to signed integers
721-
- the importance of bit-cleaning
714+
- How the EVM makes use of the two's complement representation
715+
- How integer values are interpreted as signed or unsigned depending on the opcodes used
716+
- The added complexity from handling arithmetic for signed vs. unsigned integers
717+
- The intricacies involved in managing sub 32-byte types
718+
- The importance of bit-cleaning and the significance of `signextend`
722719

723720
While low-level optimizations are attractive, they are also heavily error-prone. This article aims to deepen one's understanding of low-level arithmetic, to reduce these risks. Nevertheless, it is crucial to integrate custom low-level optimizations only after thorough manual analysis, automated testing, and to document any non-obvious assumptions.

not-so-smart-contracts/algorand/README.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,19 @@ Each _Not So Smart Contract_ includes a standard set of information:
1414

1515
## Vulnerabilities
1616

17-
| Not So Smart Contract | Description | Applicable to smart signatures | Applicable to smart contracts |
18-
| ------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------ | ----------------------------- |
19-
| [Rekeying](rekeying) | Attacker rekeys an account | yes | yes |
20-
| [Unchecked Transaction Fees](unchecked_transaction_fee) | Attacker sets excessive fees for smart signature transactions | yes | no |
21-
| [Closing Account](closing_account) | Attacker closes smart signature accounts | yes | no |
22-
| [Closing Asset](closing_asset) | Attacker transfers entire asset balance of a smart signature | yes | no |
23-
| [Group Size Check](group_size_check) | Contract does not check transaction group size | yes | yes |
24-
| [Time-based Replay Attack](time_based_replay_attack) | Contract does not use lease for periodic payments | yes | no |
25-
| [Access Controls](access_controls) | Contract does not enfore access controls for updating and deleting application | no | yes |
26-
| [Asset Id Check](asset_id_check) | Contract does not check asset id for asset transfer operations | yes | yes |
27-
| [Denial of Service](denial_of_service) | Attacker stalls contract execution by opting out of a asset | yes | yes |
17+
| Not So Smart Contract | Description | Applicable to smart signatures | Applicable to smart contracts |
18+
| -------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------ | ----------------------------- |
19+
| [Rekeying](rekeying) | Attacker rekeys an account | yes | yes |
20+
| [Unchecked Transaction Fees](unchecked_transaction_fee) | Attacker sets excessive fees for smart signature transactions | yes | no |
21+
| [Closing Account](closing_account) | Attacker closes smart signature accounts | yes | no |
22+
| [Closing Asset](closing_asset) | Attacker transfers entire asset balance of a smart signature | yes | no |
23+
| [Group Size Check](group_size_check) | Contract does not check transaction group size | yes | yes |
24+
| [Time-based Replay Attack](time_based_replay_attack) | Contract does not use lease for periodic payments | yes | no |
25+
| [Access Controls](access_controls) | Contract does not enfore access controls for updating and deleting application | no | yes |
26+
| [Asset Id Check](asset_id_check) | Contract does not check asset id for asset transfer operations | yes | yes |
27+
| [Denial of Service](denial_of_service) | Attacker stalls contract execution by opting out of a asset | yes | yes |
28+
| [Inner Transaction Fee](inner_transaction_fee) | Inner transaction fee should be set to zero | no | yes |
29+
| [Clear State Transaction Check](clear_state_transaction_check) | Contract does not check OnComplete field of an Application Call | yes | yes |
2830

2931
## Credits
3032

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Clear State Transaction Check
2+
3+
The lack of checks on the OnComplete field of the application calls might allow an attacker to execute the clear state program instead of the approval program, breaking core validations.
4+
5+
## Description
6+
7+
Algorand applications make use of group transactions to realize operations that may not be possible using a single transaction model. Some operations require that other transactions in the group call certain methods and applications. These requirements are asserted by validating that the transactions are ApplicationCall transactions. However, the OnComplete field of these transactions is not always validated, allowing an attacker to submit ClearState ApplicationCall transactions. The ClearState transaction invokes the clear state program instead of the intended approval program of the application.
8+
9+
## Exploit Scenario
10+
11+
A protocol offers flash loans from a liquidity pool. The flash loan operation is implemented using two methods: `take_flash_loan` and `pay_flash_loan`. `take_flash_loan` method transfers the assets to the user and `pay_flash_loan` verifies that the user has returned the borrowed assets. `take_flash_loan` verifies that a later transaction in the group calls the `pay_flash_loan` method. However, It does not validate the OnComplete field.
12+
13+
```py
14+
@router.method(no_op=CallConfig.CALL)
15+
def take_flash_loan(offset: abi.Uint64, amount: abi.Uint64) -> Expr:
16+
return Seq([
17+
# Ensure the pay_flash_loan method is called
18+
Assert(And(
19+
Gtxn[Txn.group_index() + offset.get()].type_enum == TxnType.ApplicationCall,
20+
Gtxn[Txn.group_index() + offset.get()].application_id() == Global.current_application_id(),
21+
Gtxn[Txn.group_index() + offset.get()].application_args[0] == MethodSignature("pay_flash_loan(uint64)")
22+
)),
23+
# Perform other validations, transfer assets to the user, update the global state
24+
# [...]
25+
])
26+
27+
@router.method(no_op=CallConfig.CALL)
28+
def pay_flash_loan(offset: abi.Uint64) -> Expr:
29+
return Seq([
30+
# Validate the "take_flash_loan" transaction at `Txn.group_index() - offset.get()`
31+
# Ensure the user has returned the funds to the pool along with the fee. Fail the transaction otherwise
32+
# [...]
33+
])
34+
```
35+
36+
An attacker constructs a valid group transaction for flash loan but sets the OnComplete field of `pay_flash_loan` call to ClearState. The clear state program is executed for complete_flash_loan call, which does not validate that the attacker has returned the funds. The attacker steals all the assets in the pool.
37+
38+
## Recommendations
39+
40+
Validate the OnComplete field of the ApplicationCall transactions.

0 commit comments

Comments
 (0)