Timeswap contest - eierina's results

Like Uniswap, but for lending & borrowing.

General Information

Platform: Code4rena

Start Date: 20/01/2023

Pot Size: $90,500 USDC

Total HM: 10

Participants: 59

Period: 7 days

Judge: Picodes

Total Solo HM: 4

Id: 206

League: ETH

Timeswap

Findings Distribution

Researcher Performance

Rank: 10/59

Findings: 2

Award: $2,548.64

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: eierina

Also found by: adriro

Labels

bug
2 (Med Risk)
primary issue
satisfactory
selected for report
M-01

Awards

2081.7015 USDC - $2,081.70

External Links

Lines of code

https://github.com/code-423n4/2023-01-timeswap/blob/3be51465583552cce76816a05170fda7da68596a/packages/v2-token/src/base/ERC1155Enumerable.sol#L92-L101 https://github.com/code-423n4/2023-01-timeswap/blob/3be51465583552cce76816a05170fda7da68596a/packages/v2-token/src/base/ERC1155Enumerable.sol#L116-L121 https://github.com/code-423n4/2023-01-timeswap/blob/3be51465583552cce76816a05170fda7da68596a/packages/v2-token/src/base/ERC1155Enumerable.sol#L136-L149

Vulnerability details

Impact

When minting and burning tokens,the ERC1155Enumerable implementation does not correctly update the following states:

  • uint256[] private _allTokens;
  • mapping(uint256 => uint256) private _allTokensIndex;
  • mapping(address => uint256) internal _currentIndex;

In particular:

  • the _allTokens array length (and therefore the totalSupply()) always increases (never decreases)
  • the _allTokensIndex[id] always increases
  • the _curentIndex[from] always increases

Proof of Concept

NOTE: the following test requires some private states of ERC1155Enumerable.sol to be set from private to internal.

contract HelperERC1155 is ERC1155Enumerable, ERC1155Holder {

    constructor() ERC1155("Test") {
    }

    function mint(uint256 id, uint256 amount) external {
        _mint(msg.sender, id, amount, bytes(""));
    }

    function burn(uint256 id, uint256 amount) external {
        _burn(msg.sender, id, amount);
    }

    function currentIndex(address owner) external view returns (uint256) {
        return _currentIndex[owner];
    }

    function allTokensIndex(uint256 id) external view returns (uint256) {
        return _allTokensIndex[id];
    }

    function allTokens(uint256 idx) external view returns (uint256) {
        return _allTokens[idx];
    }

    function idTotalSupply(uint256 id) external view returns (uint256) {
        return _idTotalSupply[id];
    }
}

contract BugTest is Test, ERC1155Holder {

    function testImplError() public {
        HelperERC1155 token = new HelperERC1155();

        for(uint i=0; i<10; i++){
            token.mint(i, 1+i);
        }

        for(uint i=0; i<10; i++){
            token.burn(i, 1+i);
            assertEq(token.idTotalSupply(i), 0); // OK
            assertEq(token.allTokensIndex(i), i); // NOT OK (should be 0)
        }
        
        assertEq(token.totalSupply(), 10); // NOT OK (should be 0)
        assertEq(token.currentIndex(address(this)), 10); // NOT OK (should be 0)
    }

    function testImplFixed() public {
        HelperERC1155 token = new HelperERC1155();

        for(uint i=0; i<10; i++){
            token.mint(i, 1+i);
        }

        for(uint i=0; i<10; i++){
            token.burn(i, 1+i);
            assertEq(token.idTotalSupply(i), 0); // OK
            assertEq(token.allTokensIndex(i), 0); // OK
        }
        
        assertEq(token.totalSupply(), 0); // OK
        assertEq(token.currentIndex(address(this)), 0); // OK
    }
}

Before fix forge test --match-contract BugTest -vvv outputs:

Running 2 tests for test/Audit2.t.sol:BugTest [PASS] testImplError() (gas: 2490610) [FAIL. Reason: Assertion failed.] testImplFixed() (gas: 2560628) Test result: FAILED. 1 passed; 1 failed; finished in 2.05ms

After fix forge test --match-contract BugTest -vvv outputs:

Running 2 tests for test/Audit2.t.sol:BugTest [FAIL. Reason: Assertion failed.] testImplError() (gas: 2558695) [PASS] testImplFixed() (gas: 2489080) Test result: FAILED. 1 passed; 1 failed; finished in 2.22ms

Tools Used

n/a

Correct the implementation to update states correctly. Patch provided below for reference.

diff --git a/packages/v2-token/src/base/ERC1155Enumerable.sol b/packages/v2-token/src/base/ERC1155Enumerable.sol
index 4ec23ff..ef67bca 100644
--- a/packages/v2-token/src/base/ERC1155Enumerable.sol
+++ b/packages/v2-token/src/base/ERC1155Enumerable.sol
@@ -91,8 +91,8 @@ abstract contract ERC1155Enumerable is IERC1155Enumerable, ERC1155 {
     /// @dev Remove token enumeration list if necessary.
     function _removeTokenEnumeration(address from, address to, uint256 id, uint256 amount) internal {
         if (to == address(0)) {
-            if (_idTotalSupply[id] == 0 && _additionalConditionRemoveTokenFromAllTokensEnumeration(id)) _removeTokenFromAllTokensEnumeration(id);
             _idTotalSupply[id] -= amount;
+            if (_idTotalSupply[id] == 0 && _additionalConditionRemoveTokenFromAllTokensEnumeration(id)) _removeTokenFromAllTokensEnumeration(id);
         }

         if (from != address(0) && from != to) {
@@ -114,8 +114,7 @@ abstract contract ERC1155Enumerable is IERC1155Enumerable, ERC1155 {
     /// @param to address representing the new owner of the given token ID
     /// @param tokenId uint256 ID of the token to be added to the tokens list of the given address
     function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
-        _currentIndex[to] += 1;
-        uint256 length = _currentIndex[to];
+        uint256 length = _currentIndex[to]++;
         _ownedTokens[to][length] = tokenId;
         _ownedTokensIndex[tokenId] = length;
     }
@@ -134,7 +133,7 @@ abstract contract ERC1155Enumerable is IERC1155Enumerable, ERC1155 {
     /// @param from address representing the previous owner of the given token ID
     /// @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
     function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
-        uint256 lastTokenIndex = _currentIndex[from] - 1;
+        uint256 lastTokenIndex = --_currentIndex[from];
         uint256 tokenIndex = _ownedTokensIndex[tokenId];

         if (tokenIndex != lastTokenIndex) {

#0 - c4-judge

2023-02-02T22:14:19Z

Picodes marked the issue as duplicate of #250

#1 - c4-judge

2023-02-02T22:15:53Z

Picodes marked the issue as not a duplicate

#2 - Picodes

2023-02-02T22:17:51Z

Dup of #250 and #228

#3 - c4-judge

2023-02-02T22:18:04Z

Picodes marked the issue as duplicate of #228

#4 - c4-judge

2023-02-12T21:54:49Z

Picodes marked the issue as not a duplicate

#5 - c4-judge

2023-02-12T21:55:00Z

Picodes changed the severity to QA (Quality Assurance)

#6 - c4-judge

2023-02-12T21:55:17Z

This previously downgraded issue has been upgraded by Picodes

#7 - c4-judge

2023-02-12T21:55:17Z

This previously downgraded issue has been upgraded by Picodes

#8 - Picodes

2023-02-12T21:56:06Z

Splitting issue in 2

#9 - c4-judge

2023-02-12T21:56:33Z

Picodes marked the issue as primary issue

#10 - Picodes

2023-02-12T21:58:48Z

There are 2 bugs highlighted here:

  • the check is incorrectly made before the state update in _removeTokenEnumeration
  • the order in which _currentIndex is updated

#11 - Picodes

2023-02-12T21:59:28Z

So splitting this finding in 2.

#12 - c4-judge

2023-02-12T22:28:13Z

Picodes marked the issue as selected for report

#13 - c4-judge

2023-02-12T22:35:46Z

Picodes marked the issue as satisfactory

#14 - vhawk19

2023-02-17T11:21:56Z

Updated the ERC1155Enumerable.sol implementation, which should resolve these issues.

#15 - liveactionllama

2023-02-28T07:01:45Z

Per conversation with the judge @Picodes - updating the issue title here, since this has been split into two issues.

Findings Information

🌟 Selected for report: adriro

Also found by: chaduke, eierina, hansfriese, mookimgo

Labels

2 (Med Risk)
satisfactory
duplicate-248

Awards

466.9417 USDC - $466.94

External Links

Judge has assessed an item in Issue #272 as 2 risk. The relevant finding follows:

function _removeTokenEnumeration(address from, address to, uint256 id, uint256 amount) internal { if (to == address(0)) {

  • if (_idTotalSupply[id] == 0 && _additionalConditionRemoveTokenFromAllTokensEnumeration(id)) _removeTokenFromAllTokensEnumeration(id); _idTotalSupply[id] -= amount;
  • if (_idTotalSupply[id] == 0 && _additionalConditionRemoveTokenFromAllTokensEnumeration(id)) _removeTokenFromAllTokensEnumeration(id); }

#0 - c4-judge

2023-02-12T21:55:44Z

Picodes marked the issue as duplicate of #248

#1 - c4-judge

2023-02-12T22:32:58Z

Picodes marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter