Astaria contest - IllIllI's results

On a mission is to build a highly liquid NFT lending market.

General Information

Platform: Code4rena

Start Date: 05/01/2023

Pot Size: $90,500 USDC

Total HM: 55

Participants: 103

Period: 14 days

Judge: Picodes

Total Solo HM: 18

Id: 202

League: ETH

Astaria

Findings Distribution

Researcher Performance

Rank: 40/103

Findings: 2

Award: $290.13

QA:
grade-a
Gas:
grade-b

🌟 Selected for report: 0

šŸš€ Solo Findings: 0

Summary

Low Risk Issues

IssueInstances
[L‑01]Array lengths not checked1
[L‑02]Signatures vulnerable to malleability attacks2
[L‑03]tokenURI() does not follow EIP-7211
[L‑04]_safeMint() should be used rather than _mint() wherever possible2

Total: 6 instances over 4 issues

Non-critical Issues

IssueInstances
[N‑01]Invalid/extraneous/optional function definitions in interface1
[N‑02]Missing initializer modifier1
[N‑03]Adding a return statement when the function defines a named return variable, is redundant1
[N‑04]override function arguments that are unused should have the variable name removed or commented out to avoid compiler warnings8
[N‑05]constants should be defined rather than using magic numbers35
[N‑06]Events that mark critical parameter changes should contain both the old and the new value3
[N‑07]pragma experimental ABIEncoderV2 is deprecated2
[N‑08]Expressions for constant values such as a call to keccak256(), should use immutable rather than constant4
[N‑09]Inconsistent spacing in comments26
[N‑10]Lines are too long11
[N‑11]Inconsistent method of specifying a floating pragma5
[N‑12]Variable names that consist of all capital letters should be reserved for constant/immutable variables37
[N‑13]Using >/>= without specifying an upper bound is unsafe5
[N‑14]Typos6
[N‑15]Misplaced SPDX identifier1
[N‑16]File is missing NatSpec16
[N‑17]NatSpec is incomplete57
[N‑18]Not using the named return variables anywhere in the function is confusing25
[N‑19]Duplicated require()/revert() checks should be refactored to a modifier or function6
[N‑20]Consider using delete rather than assigning zero to clear values5
[N‑21]Avoid the use of sensitive terms1
[N‑22]Large assembly blocks should have extensive comments1
[N‑23]Contracts should have full test coverage1
[N‑24]Large or complicated code bases should implement fuzzing tests1
[N‑25]Function ordering does not follow the Solidity style guide69
[N‑26]Contract does not follow the Solidity style guide's suggested layout ordering21

Total: 349 instances over 26 issues

Low Risk Issues

[L‑01] Array lengths not checked

If the length of the arrays are not required to be of the same length, user operations may not be fully executed

There is 1 instance of this issue:

File: src/ClearingHouse.sol

90      function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
91        external
92        view
93:       returns (uint256[] memory output)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L90-L93

[L‑02] Signatures vulnerable to malleability attacks

ecrecover() accepts as valid, two versions of signatures, meaning an attacker can use the same signature twice. Consider adding checks for signature malleability, or using OpenZeppelin's ECDSA library to perform the extra checks necessary in order to prevent this attack.

There are 2 instances of this issue:

File: lib/gpl/src/ERC20-Cloned.sol

121         address recoveredAddress = ecrecover(
122           keccak256(
123             abi.encodePacked(
124               "\x19\x01",
125               DOMAIN_SEPARATOR(),
126               keccak256(
127                 abi.encode(
128                   PERMIT_TYPEHASH,
129                   owner,
130                   spender,
131                   value,
132                   _loadERC20Slot().nonces[owner]++,
133                   deadline
134                 )
135               )
136             )
137           ),
138           v,
139           r,
140           s
141:        );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC20-Cloned.sol#L121-L141

File: src/VaultImplementation.sol

246       address recovered = ecrecover(
247         keccak256(
248           _encodeStrategyData(
249             s,
250             params.lienRequest.strategy,
251             params.lienRequest.merkle.root
252           )
253         ),
254         params.lienRequest.v,
255         params.lienRequest.r,
256         params.lienRequest.s
257:      );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L246-L257

[L‑03] tokenURI() does not follow EIP-721

The EIP states that tokenURI() "Throws if _tokenId is not a valid NFT", which the code below does not do. If the NFT has not yet been minted, tokenURI() should revert

There is 1 instance of this issue:

File: src/CollateralToken.sol

401     function tokenURI(uint256 collateralId)
402       public
403       view
404       virtual
405       override(ERC721, IERC721)
406       returns (string memory)
407     {
408       (address underlyingAsset, uint256 assetId) = getUnderlying(collateralId);
409       return ERC721(underlyingAsset).tokenURI(assetId);
410:    }

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L401-L410

[L‑04] _safeMint() should be used rather than _mint() wherever possible

_mint() is discouraged in favor of _safeMint() which ensures that the recipient is either an EOA or implements IERC721Receiver. Both OpenZeppelin and solmate have versions of this function

There are 2 instances of this issue:

File: src/CollateralToken.sol

588:        _mint(from_, collateralId);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L588

File: src/LienToken.sol

455:      _mint(params.receiver, newLienId);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L455

Non-critical Issues

[N‑01] Invalid/extraneous/optional function definitions in interface

There is 1 instance of this issue:

File: src/interfaces/IERC721.sol

/// @audit tokenURI(uint256) isn't defined with those arguments in the standard IERC721 definition
20:     function tokenURI(uint256 id) external view returns (string memory);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IERC721.sol#L20

[N‑02] Missing initializer modifier

The contract extends Initializable/ReentrancyGuardUpgradeable but does not use the initializer modifier anywhere

There is 1 instance of this issue:

File: lib/gpl/src/ERC721.sol

10:   abstract contract ERC721 is Initializable, IERC721 {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC721.sol#L10

[N‑03] Adding a return statement when the function defines a named return variable, is redundant

There is 1 instance of this issue:

File: src/AstariaRouter.sol

758:      return vaultAddr;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L758

[N‑04] override function arguments that are unused should have the variable name removed or commented out to avoid compiler warnings

There are 8 instances of this issue:

File: src/ClearingHouse.sol

189:      address operator_,

190:      address from_,

191:      uint256 tokenId_,

192:      bytes calldata data_

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L189

File: src/PublicVault.sol

413:    function _beforeCommitToLien(IAstariaRouter.Commitment calldata params)

575:    function afterDeposit(uint256 assets, uint256 shares)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L413

File: src/WithdrawProxy.sol

143:    function deposit(uint256 assets, address receiver)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L143

[N‑05] constants should be defined rather than using magic numbers

Even assembly can benefit from using readable constants instead of hex/numeric literals

There are 35 instances of this issue:

File: lib/gpl/src/ERC4626-Cloned.sol

/// @audit 10e18
132:      return supply == 0 ? 10e18 : shares.mulDivUp(totalAssets(), supply);

/// @audit 10e18
140:      return supply == 0 ? 10e18 : assets.mulDivUp(supply, totalAssets());

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC4626-Cloned.sol#L132

File: lib/gpl/src/ERC721.sol

/// @audit 0x01ffc9a7
190:        interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165

/// @audit 0x80ac58cd
191:        interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721

/// @audit 0x5b5e139f
192:        interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC721.sol#L190

File: src/AstariaRouter.sol

/// @audit 1000
111:      s.liquidationFeeDenominator = uint32(1000);

/// @audit 7
113:      s.minEpochLength = uint32(7 days);

/// @audit 100
117:      s.buyoutFeeNumerator = uint32(100);

/// @audit 1000
118:      s.buyoutFeeDenominator = uint32(1000);

/// @audit 5
119:      s.minDurationIncrease = uint32(5 days);

/// @audit 0
418:          mstore(0, OUTOFBOUND_ERROR_SELECTOR)

/// @audit 0
419:          revert(0, ONE_WORD)

/// @audit 1_000
645:          endingPrice: 1_000 wei

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L111

File: src/BeaconProxy.sol

/// @audit 0
/// @audit 0
34:         calldatacopy(0, 0, calldatasize())

/// @audit 0
/// @audit 0
/// @audit 0
38:         let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)

/// @audit 0
/// @audit 0
41:         returndatacopy(0, 0, returndatasize())

/// @audit 0
46:           revert(0, returndatasize())

/// @audit 0
45:         case 0 {

/// @audit 0
49:           return(0, returndatasize())

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/BeaconProxy.sol#L34

File: src/CollateralToken.sol

/// @audit 0xffffffff
167:          : bytes4(0xffffffff);

/// @audit 0xffffffff
182:          : bytes4(0xffffffff);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L167

File: src/LienToken.sol

/// @audit 5
67:       s.maxLiens = uint8(5);

/// @audit 1000
342:      auctionData.endAmount = uint88(1000 wei);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L67

File: src/PublicVault.sol

/// @audit 100
104:        return 100 gwei;

/// @audit 1e18
315:          .mulDivDown(1e18, totalSupply())

/// @audit 1e18
333:            totalAssets().mulDivDown(s.liquidationWithdrawRatio, 1e18)

/// @audit 10000
604:        uint256 fee = x.mulDivDown(VAULT_FEE(), 10000);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L104

File: src/VaultImplementation.sol

/// @audit 0x19
/// @audit 0x01
187:        abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator(), hash);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L187

File: src/WithdrawProxy.sol

/// @audit 1e18
260:          (s.expected - balance).mulWadDown(1e18 - s.withdrawRatio)

/// @audit 1e18
264:          (balance - s.expected).mulWadDown(1e18 - s.withdrawRatio)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L260

[N‑06] Events that mark critical parameter changes should contain both the old and the new value

This should especially be done if the new value is not required to be different from the old value

There are 3 instances of this issue:

File: lib/gpl/src/ERC721.sol

/// @audit setApprovalForAll()
103:      emit ApprovalForAll(msg.sender, operator, approved);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC721.sol#L103

File: src/VaultImplementation.sol

/// @audit setDelegate()
214:      emit DelegateUpdated(delegate_);

/// @audit setDelegate()
215:      emit AllowListUpdated(delegate_, true);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L214

[N‑07] pragma experimental ABIEncoderV2 is deprecated

Use pragma abicoder v2 instead

There are 2 instances of this issue:

File: src/CollateralToken.sol

16:   pragma experimental ABIEncoderV2;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L16

File: src/LienToken.sol

16:   pragma experimental ABIEncoderV2;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L16

[N‑08] Expressions for constant values such as a call to keccak256(), should use immutable rather than constant

While it doesn't save any gas because the compiler knows that developers often make this mistake, it's still best to use the right tool for the task at hand. There is a difference between constant variables and immutable variables, and they should each be used in their appropriate contexts. constants should be used for literal values written into the code, and immutable variables should be used for expressions, or values calculated in, or passed into the constructor.

There are 4 instances of this issue:

File: lib/gpl/src/ERC20-Cloned.sol

15      bytes32 private constant PERMIT_TYPEHASH =
16        keccak256(
17          "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
18:       );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC20-Cloned.sol#L15-L18

File: src/VaultImplementation.sol

44      bytes32 public constant STRATEGY_TYPEHASH =
45:       keccak256("StrategyDetails(uint256 nonce,uint256 deadline,bytes32 root)");

47      bytes32 constant EIP_DOMAIN =
48        keccak256(
49          "EIP712Domain(string version,uint256 chainId,address verifyingContract)"
50:       );

51:     bytes32 constant VERSION = keccak256("0");

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L44-L45

[N‑09] Inconsistent spacing in comments

Some lines use // x and some use //x. The instances below point out the usages that don't follow the majority, within each file

There are 26 instances of this issue:

File: src/AstariaRouter.sol

65:     // cast --to-bytes32 $(cast sig "OutOfBoundError()")

116:      //63419583966; // 200% apy / second

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L65

File: src/ClearingHouse.sol

71:       //only execute from the conduit

117:      uint256 encodedMetaData, //retrieve token address from the encoded data

130:        roundUp: true //we are a consideration we round up

174:      bytes calldata data //empty from seaport

176:      //data is empty and useless

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L71

File: src/CollateralToken.sol

120:        //revert no auction

126:        //revert auction params dont match

133:        //auction hasn't ended yet

291:      //look to see if we have a security handler for this asset

305:      //trigger the flash action on the receiver

507:      //get total Debt and ensure its being sold for more than that

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L120

File: src/interfaces/IAstariaRouter.sol

74:       uint32 minInterestBPS; // was uint64

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IAstariaRouter.sol#L74

File: src/LienToken.sol

155:        // should not be able to purchase lien if any lien in the stack is expired (and will be liquidated)

315:          // update the public vault state and get the liquidation accountant back if any

804:      // Blocking off payments for a lien that has exceeded the lien.end to prevent repayment unless the msg.sender() is the AuctionHouse

831:        //      // slope does not need to be updated if paying off the rest, since we neutralize slope in beforePayment()

838:          // since the openLiens count is only positive when there are liens that haven't been paid off

839:          // that should be liquidated, this lien should not be counted anymore

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L155

File: src/PublicVault.sol

173:      //this will underflow if not enough balance

508:      require(msg.sender == owner()); //owner is "strategist"

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L173

File: src/VaultImplementation.sol

123:      address, // operator_

124:      address, // from_

125:      uint256, // tokenId_

126:      bytes calldata // data_

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L123

[N‑10] Lines are too long

Usually lines in source code are limited to 80 characters. Today's screens are much larger so it's reasonable to stretch this in some cases. Since the files will most likely reside in GitHub, and GitHub starts using a scroll bar in all cases when the length is over 164 characters, the lines below should be split when they reach that length

There are 11 instances of this issue:

File: lib/gpl/src/interfaces/IERC4626RouterBase.sol

10:    The base router is a multicall style router inspired by Uniswap v3 with built-in features for permit, WETH9 wrap/unwrap, and ERC20 token pulling/sweeping/approving.

15:    NOTE the router is capable of pulling any approved token from your wallet. This is only possible when your address is msg.sender, but regardless be careful when interacting with the router or ERC4626 Vaults.

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/interfaces/IERC4626RouterBase.sol#L10

File: src/AstariaRouter.sol

75:      * @dev Setup transfer authority and set up addresses for deployed CollateralToken, LienToken, TransferProxy contracts, as well as PublicVault and SoloVault implementations to clone.

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L75

File: src/interfaces/ICollateralToken.sol

100:     * @notice Executes a FlashAction using locked collateral. A valid FlashAction performs a specified action with the collateral within a single transaction and must end with the collateral being returned to the Vault it was locked in.

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/ICollateralToken.sol#L100

File: src/interfaces/IWithdrawProxy.sol

27:      * @notice Called at epoch boundary, computes the ratio between the funds of withdrawing liquidity providers and the balance of the underlying PublicVault so that claim() proportionally pays optimized-out to all parties.

35:      * @param finalAuctionDelta The timestamp by which the auction being added is guaranteed to end. As new auctions are added to the WithdrawProxy, this value will strictly increase as all auctions have the same maximum duration.

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IWithdrawProxy.sol#L27

File: src/LienToken.sol

42:    * @notice This contract handles the creation, payments, buyouts, and liquidations of tokenized NFT-collateralized debt (liens). Vaults which originate loans against supported collateral are issued a LienToken representing the right to loan repayments and auctioned funds on liquidation.

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L42

File: src/VaultImplementation.sol

282:     * Starts by depositing collateral and take optimized-out a lien against it. Next, verifies the merkle proof for a loan commitment. Vault owners are then rewarded fees for successful loan origination.

363:     * @notice Retrieves the recipient of loan repayments. For PublicVaults (VAULT_TYPE 2), this is always the vault address. For PrivateVaults, retrieves the owner() of the vault.

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L282

File: src/WithdrawProxy.sol

54:       uint88 expected; // The sum of the remaining debt (amountOwed) accrued against the NFT at the timestamp when it is liquidated. yIntercept (virtual assets) of a PublicVault are not modified on liquidation, only once an auction is completed.

56:       uint256 withdrawReserveReceived; // amount received from PublicVault. The WETH balance of this contract - withdrawReserveReceived = amount received from liquidations.

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L54

[N‑11] Inconsistent method of specifying a floating pragma

Some files use >=, some use ^. The instances below are examples of the method that has the fewest instances for a specific version. Note that using >= without also specifying <= will lead to failures to compile, or external project incompatability, when the major version changes and there are breaking-changes, so ^ should be preferred regardless of the instance counts

There are 5 instances of this issue:

File: lib/gpl/src/ERC4626RouterBase.sol

2:    pragma solidity ^0.8.17;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC4626RouterBase.sol#L2

File: lib/gpl/src/ERC4626Router.sol

2:    pragma solidity ^0.8.17;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC4626Router.sol#L2

File: lib/gpl/src/ERC721.sol

2:    pragma solidity ^0.8.16;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC721.sol#L2

File: lib/gpl/src/interfaces/IERC4626RouterBase.sol

2:    pragma solidity ^0.8.17;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/interfaces/IERC4626RouterBase.sol#L2

File: lib/gpl/src/interfaces/IERC4626Router.sol

2:    pragma solidity ^0.8.17;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/interfaces/IERC4626Router.sol#L2

[N‑12] Variable names that consist of all capital letters should be reserved for constant/immutable variables

If the variable needs to be different based on which class it comes from, a view/pure function should be used instead (e.g. like this).

There are 37 instances of this issue:

File: src/AstariaRouter.sol

84:       Authority _AUTHORITY,

85:       ICollateralToken _COLLATERAL_TOKEN,

86:       ILienToken _LIEN_TOKEN,

87:       ITransferProxy _TRANSFER_PROXY,

88:       address _VAULT_IMPL,

89:       address _SOLO_IMPL,

90:       address _WITHDRAW_IMPL,

91:       address _BEACON_PROXY_IMPL,

92:       address _CLEARING_HOUSE_IMPL

329:        (uint8 TYPE, address addr) = abi.decode(data, (uint8, address));

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L84

File: src/ClearingHouse.sol

221:      IAstariaRouter ASTARIA_ROUTER = IAstariaRouter(_getArgAddress(0));

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L221

File: src/CollateralToken.sol

81:       Authority AUTHORITY_,

82:       ITransferProxy TRANSFER_PROXY_,

83:       ILienToken LIEN_TOKEN_,

84:       ConsiderationInterface SEAPORT_

93:       bytes32 CONDUIT_KEY = Bytes32AddressLib.fillLast12Bytes(address(this));

140:      ClearingHouse CH = ClearingHouse(payable(s.clearingHouse[collateralId]));

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L81

File: src/interfaces/IAstariaRouter.sol

67:       ERC20 WETH; //20

68:       ICollateralToken COLLATERAL_TOKEN; //20

69:       ILienToken LIEN_TOKEN; //20

70:       ITransferProxy TRANSFER_PROXY; //20

72:       address BEACON_PROXY_IMPLEMENTATION; //20

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IAstariaRouter.sol#L67

File: src/interfaces/ICollateralToken.sol

52:       ITransferProxy TRANSFER_PROXY;

53:       ILienToken LIEN_TOKEN;

54:       IAstariaRouter ASTARIA_ROUTER;

55:       ConsiderationInterface SEAPORT;

56:       ConduitControllerInterface CONDUIT_CONTROLLER;

57:       address CONDUIT;

58:       bytes32 CONDUIT_KEY;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/ICollateralToken.sol#L52

File: src/interfaces/ILienToken.sol

38:       address WETH;

39:       ITransferProxy TRANSFER_PROXY;

40:       IAstariaRouter ASTARIA_ROUTER;

41:       ICollateralToken COLLATERAL_TOKEN;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/ILienToken.sol#L38

File: src/LienToken.sol

59:     function initialize(Authority _AUTHORITY, ITransferProxy _TRANSFER_PROXY)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L59

File: src/TransferProxy.sol

24:     constructor(Authority _AUTHORITY) Auth(msg.sender, _AUTHORITY) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/TransferProxy.sol#L24

File: src/VaultImplementation.sol

234:      ERC721 CT = ERC721(address(COLLATERAL_TOKEN()));

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L234

[N‑13] Using >/>= without specifying an upper bound is unsafe

There will be breaking changes in future versions of solidity, and at that point your code will no longer be compatable. While you may have the specific version to use in a configuration file, others that include your source files may not.

There are 5 instances of this issue:

File: lib/gpl/src/ERC20-Cloned.sol

2:    pragma solidity >=0.8.16;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC20-Cloned.sol#L2

File: lib/gpl/src/ERC4626-Cloned.sol

2:    pragma solidity >=0.8.16;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC4626-Cloned.sol#L2

File: lib/gpl/src/interfaces/IMulticall.sol

4:    pragma solidity >=0.7.5;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/interfaces/IMulticall.sol#L4

File: lib/gpl/src/interfaces/IUniswapV3Factory.sol

2:    pragma solidity >=0.5.0;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/interfaces/IUniswapV3Factory.sol#L2

File: lib/gpl/src/interfaces/IUniswapV3PoolState.sol

2:    pragma solidity >=0.5.0;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/interfaces/IUniswapV3PoolState.sol#L2

[N‑14] Typos

There are 6 instances of this issue:

File: lib/gpl/src/interfaces/IUniswapV3Factory.sol

/// @audit unenabled
38:       /// @param fee The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/interfaces/IUniswapV3Factory.sol#L38

File: src/interfaces/IERC4626.sol

/// @audit redeemption
230:     * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IERC4626.sol#L230

File: src/interfaces/IV3PositionManager.sol

/// @audit acheive
122:    /// @return amount0 The amount of token0 to acheive resulting liquidity

/// @audit acheive
123:    /// @return amount1 The amount of token1 to acheive resulting liquidity

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IV3PositionManager.sol#L122

File: src/PublicVault.sol

/// @audit calcualtion
307:      // reset liquidationWithdrawRatio to prepare for re calcualtion

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L307

File: src/Vault.sol

/// @audit vautls
90:       //invalid action private vautls can only be the owner or strategist

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/Vault.sol#L90

[N‑15] Misplaced SPDX identifier

The SPDX identifier should be on the very first line of the file

There is 1 instance of this issue:

File: lib/gpl/src/interfaces/IMulticall.sol

1:    // forked from https://github.com/Uniswap/v3-periphery/blob/main/contracts/interfaces/IMulticall.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/interfaces/IMulticall.sol#L1

[N‑16] File is missing NatSpec

There are 16 instances of this issue:

File: lib/gpl/src/ERC4626-Cloned.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC4626-Cloned.sol

File: lib/gpl/src/ERC4626RouterBase.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC4626RouterBase.sol

File: lib/gpl/src/ERC4626Router.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC4626Router.sol

File: src/AstariaVaultBase.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaVaultBase.sol

File: src/ClearingHouse.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol

File: src/interfaces/IAstariaVaultBase.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IAstariaVaultBase.sol

File: src/interfaces/IERC721.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IERC721.sol

File: src/interfaces/IFlashAction.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IFlashAction.sol

File: src/interfaces/IRouterBase.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IRouterBase.sol

File: src/interfaces/ISecurityHook.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/ISecurityHook.sol

File: src/interfaces/IStrategyValidator.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IStrategyValidator.sol

File: src/interfaces/ITransferProxy.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/ITransferProxy.sol

File: src/interfaces/IVaultImplementation.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IVaultImplementation.sol

File: src/TransferProxy.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/TransferProxy.sol

File: src/Vault.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/Vault.sol

File: src/WithdrawVaultBase.sol

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawVaultBase.sol

[N‑17] NatSpec is incomplete

There are 57 instances of this issue:

File: src/AstariaRouter.sol

/// @audit Missing: '@param _WITHDRAW_IMPL'
/// @audit Missing: '@param _BEACON_PROXY_IMPL'
/// @audit Missing: '@param _CLEARING_HOUSE_IMPL'
74      /**
75       * @dev Setup transfer authority and set up addresses for deployed CollateralToken, LienToken, TransferProxy contracts, as well as PublicVault and SoloVault implementations to clone.
76       * @param _AUTHORITY The authority manager.
77       * @param _COLLATERAL_TOKEN The address of the deployed CollateralToken contract.
78       * @param _LIEN_TOKEN The address of the deployed LienToken contract.
79       * @param _TRANSFER_PROXY The address of the deployed TransferProxy contract.
80       * @param _VAULT_IMPL The address of a base implementation of VaultImplementation for cloning.
81       * @param _SOLO_IMPL The address of a base implementation of a PrivateVault for cloning.
82       */
83      function initialize(
84        Authority _AUTHORITY,
85        ICollateralToken _COLLATERAL_TOKEN,
86        ILienToken _LIEN_TOKEN,
87        ITransferProxy _TRANSFER_PROXY,
88        address _VAULT_IMPL,
89        address _SOLO_IMPL,
90        address _WITHDRAW_IMPL,
91        address _BEACON_PROXY_IMPL,
92        address _CLEARING_HOUSE_IMPL
93:     ) external initializer {

/// @audit Missing: '@param s'
/// @audit Missing: '@param underlying'
/// @audit Missing: '@param vaultFee'
/// @audit Missing: '@param allowList'
/// @audit Missing: '@param depositCap'
705     /**
706      * @dev Deploys a new Vault.
707      * @param epochLength The length of each epoch for a new PublicVault. If 0, deploys a PrivateVault.
708      * @param delegate The address of the Vault delegate.
709      * @param allowListEnabled Whether or not the Vault has an LP whitelist.
710      * @return vaultAddr The address for the new Vault.
711      */
712     function _newVault(
713       RouterStorage storage s,
714       address underlying,
715       uint256 epochLength,
716       address delegate,
717       uint256 vaultFee,
718       bool allowListEnabled,
719       address[] memory allowList,
720       uint256 depositCap
721:    ) internal returns (address vaultAddr) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L74-L93

File: src/CollateralToken.sol

/// @audit Missing: '@param s'
/// @audit Missing: '@param underlyingAsset'
/// @audit Missing: '@param collateralId'
348     /**
349      * @dev Transfers locked collateral to a specified address and deletes the reference to the CollateralToken for that NFT.
350      * @param releaseTo The address to send the NFT to.
351      */
352     function _releaseToAddress(
353       CollateralStorage storage s,
354       Asset memory underlyingAsset,
355       uint256 collateralId,
356:      address releaseTo

/// @audit Missing: '@param address'
/// @audit Missing: '@param bytes'
547     /**
548      * @dev Mints a new CollateralToken wrapping an NFT.
549      * @param from_ the owner of the collateral deposited
550      * @param tokenId_ The NFT token ID
551      * @return a static return of the receive signature
552      */
553     function onERC721Received(
554       address, /* operator_ */
555       address from_,
556       uint256 tokenId_,
557       bytes calldata // calldata data_
558:    ) external override whenNotPaused returns (bytes4) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L348-L356

File: src/interfaces/IAstariaRouter.sol

/// @audit Missing: '@param timeToSecondEpochEnd'
129     /**
130      * @notice Validates the incoming loan commitment.
131      * @param commitment The commitment proofs and requested loan data for each loan.
132      * @return lien the new Lien data.
133      */
134     function validateCommitment(
135       IAstariaRouter.Commitment calldata commitment,
136       uint256 timeToSecondEpochEnd
137:    ) external returns (ILienToken.Lien memory lien);

/// @audit Missing: '@return'
147      * @param depositCap the deposit cap for the vault if any
148      */
149     function newPublicVault(
150       uint256 epochLength,
151       address delegate,
152       address underlying,
153       uint256 vaultFee,
154       bool allowListEnabled,
155       address[] calldata allowList,
156       uint256 depositCap
157:    ) external returns (address);

/// @audit Missing: '@param recipient'
183     /**
184      * @notice Create a new lien against a CollateralToken.
185      * @param params The valid proof and lien details for the new loan.
186      * @return The ID of the created lien.
187      */
188     function requestLienPosition(
189       IAstariaRouter.Commitment calldata params,
190       address recipient
191     )
192       external
193       returns (
194         uint256,
195         ILienToken.Stack[] memory,
196:        uint256

/// @audit Missing: '@return'
209      * @param includeBuffer Adds the current auctionWindowBuffer if true.
210      */
211:    function getAuctionWindow(bool includeBuffer) external view returns (uint256);

/// @audit Missing: '@param implType'
274     /**
275      * @notice Returns the address for the current implementation of a contract from the ImplementationType enum.
276      * @return impl The address of the clone implementation.
277      */
278:    function getImpl(uint8 implType) external view returns (address impl);

/// @audit Missing: '@return'
286      * @param stack The Stack of existing Liens against the CollateralToken.
287      */
288     function isValidRefinance(
289       ILienToken.Lien calldata newLien,
290       uint8 position,
291       ILienToken.Stack[] calldata stack
292:    ) external view returns (bool);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IAstariaRouter.sol#L129-L137

File: src/interfaces/ICollateralToken.sol

/// @audit Missing: '@return'
129      * @param params The auction data.
130      */
131     function auctionVault(AuctionVaultParams calldata params)
132       external
133:      returns (OrderParameters memory);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/ICollateralToken.sol#L129-L133

File: src/interfaces/ILienToken.sol

/// @audit Missing: '@param auctionWindow'
/// @audit Missing: '@param stack'
/// @audit Missing: '@param liquidator'
117     /**
118      * @notice Stops accruing interest for all liens against a single CollateralToken.
119      * @param collateralId The ID for the  CollateralToken of the NFT used as collateral for the liens.
120      */
121     function stopLiens(
122       uint256 collateralId,
123       uint256 auctionWindow,
124       Stack[] calldata stack,
125:      address liquidator

/// @audit Missing: '@return'
130      * @param stack the lien
131      */
132     function getBuyout(Stack calldata stack)
133       external
134       view
135:      returns (uint256 owed, uint256 buyout);

/// @audit Missing: '@return'
157      * @param stack the Lien
158      */
159:    function getInterest(Stack calldata stack) external returns (uint256);

/// @audit Missing: '@return'
163      * @param collateralId the Lien to compute a point for
164      */
165     function getCollateralState(uint256 collateralId)
166       external
167       view
168:      returns (bytes32);

/// @audit Missing: '@return'
172      * @param stack the Lien to compute a point for
173      */
174     function getAmountOwingAtLiquidation(ILienToken.Stack calldata stack)
175       external
176       view
177:      returns (uint256);

/// @audit Missing: '@return'
181      * @param params LienActionEncumber data containing CollateralToken information and lien parameters (rate, duration, and amount, rate, and debt caps).
182      */
183     function createLien(LienActionEncumber memory params)
184       external
185       returns (
186         uint256 lienId,
187         Stack[] memory stack,
188:        uint256 slope

/// @audit Missing: '@return'
193      * @param params The LienActionBuyout data specifying the lien position, receiver address, and underlying CollateralToken information of the lien.
194      */
195     function buyoutLien(LienActionBuyout memory params)
196       external
197:      returns (Stack[] memory, Stack memory);

/// @audit Missing: '@param token'
/// @audit Missing: '@param auctionStack'
199     /**
200      * @notice Called by the ClearingHouse (through Seaport) to pay back debt with auction funds.
201      * @param collateralId The CollateralId of the liquidated NFT.
202      * @param payment The payment amount.
203      */
204     function payDebtViaClearingHouse(
205       address token,
206       uint256 collateralId,
207       uint256 payment,
208:      AuctionStack[] memory auctionStack

/// @audit Missing: '@param collateralId'
/// @audit Missing: '@return'
211     /**
212      * @notice Make a payment for the debt against a CollateralToken.
213      * @param stack the stack to pay against
214      * @param amount The amount to pay against the debt.
215      */
216     function makePayment(
217       uint256 collateralId,
218       Stack[] memory stack,
219       uint256 amount
220:    ) external returns (Stack[] memory newStack);

/// @audit Missing: '@return'
246      * @param collateralId The ID of the CollateralToken.
247      */
248     function getAuctionData(uint256 collateralId)
249       external
250       view
251:      returns (AuctionData memory);

/// @audit Missing: '@return'
255      * @param collateralId The ID of the CollateralToken.
256      */
257     function getAuctionLiquidator(uint256 collateralId)
258       external
259       view
260:      returns (address liquidator);

/// @audit Missing: '@return'
264      * @param stack The stack data for active liens against the CollateralToken.
265      */
266     function getMaxPotentialDebtForCollateral(ILienToken.Stack[] memory stack)
267       external
268       view
269:      returns (uint256);

/// @audit Missing: '@return'
274      * @param end The timestamp to accrue potential debt until.
275      */
276     function getMaxPotentialDebtForCollateral(
277       ILienToken.Stack[] memory stack,
278       uint256 end
279:    ) external view returns (uint256);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/ILienToken.sol#L117-L125

File: src/interfaces/IPublicVault.sol

/// @audit Missing: '@return'
92       * @param end time to compute the end for
93       */
94:     function getLienEpoch(uint64 end) external view returns (uint64);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IPublicVault.sol#L92-L94

File: src/interfaces/IWithdrawProxy.sol

/// @audit Missing: '@return'
45       * @param withdrawProxy The address of the withdrawProxy to drain to.
46       */
47      function drain(uint256 amount, address withdrawProxy)
48        external
49:       returns (uint256);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IWithdrawProxy.sol#L45-L49

File: src/LienToken.sol

/// @audit Missing: '@return'
253      * @param timestamp The timestamp at which to compute interest for.
254      */
255     function _getInterest(Stack memory stack, uint256 timestamp)
256       internal
257       pure
258:      returns (uint256)

/// @audit Missing: '@param s'
/// @audit Missing: '@return'
658     /**
659      * @dev Have a specified payer make a payment for the debt against a CollateralToken.
660      * @param stack the stack for the payment
661      * @param totalCapitalAvailable The amount to pay against the debts
662      */
663     function _makePayment(
664       LienStorage storage s,
665       Stack[] calldata stack,
666       uint256 totalCapitalAvailable
667:    ) internal returns (Stack[] memory newStack) {

/// @audit Missing: '@param timestamp'
756     /**
757      * @dev Computes the debt owed to a Lien at a specified timestamp.
758      * @param stack The specified Lien.
759      * @return The amount owed to the Lien at the specified timestamp.
760      */
761     function _getOwed(Stack memory stack, uint256 timestamp)
762       internal
763       pure
764:      returns (uint88)

/// @audit Missing: '@param s'
/// @audit Missing: '@param position'
/// @audit Missing: '@return'
784     /**
785      * @dev Make a payment from a payer to a specific lien against a CollateralToken.
786      * @param activeStack The stack
787      * @param amount The amount to pay against the debt.
788      * @param payer The address to make the payment.
789      */
790     function _payment(
791       LienStorage storage s,
792       Stack[] memory activeStack,
793       uint8 position,
794       uint256 amount,
795       address payer
796:    ) internal returns (Stack[] memory, uint256) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L253-L258

File: src/PublicVault.sol

/// @audit Missing: '@return'
249      * @param receiver The receiver of the resulting VaultToken shares.
250      */
251     function deposit(uint256 amount, address receiver)
252       public
253       override(ERC4626Cloned)
254       whenNotPaused
255:      returns (uint256)

/// @audit Missing: '@param lienEnd'
/// @audit Missing: '@param lienSlope'
435     /**
436      * @dev Hook for updating the slope of the PublicVault after a LienToken is issued.
437      * @param lienId The ID of the lien.
438      */
439     function _afterCommitToLien(
440       uint40 lienEnd,
441       uint256 lienId,
442:      uint256 lienSlope

/// @audit Missing: '@param s'
592     /**
593      * @dev Handles the dilutive fees (on lien repayments) for strategists in VaultTokens.
594      * @param interestOwing the owingInterest for the lien
595      * @param amount The amount that was paid.
596      */
597     function _handleStrategistInterestReward(
598       VaultData storage s,
599       uint256 interestOwing,
600:      uint256 amount

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L249-L255

File: src/VaultImplementation.sol

/// @audit Missing: '@param strategy'
/// @audit Missing: '@param root'
/// @audit Missing: '@return'
164     /*
165      * @notice encodes the data for a 712 signature
166      * @param tokenContract The address of the token contract
167      * @param tokenId The id of the token
168      * @param amount The amount of the token
169      */
170     function encodeStrategyData(
171       IAstariaRouter.StrategyDetailsParam calldata strategy,
172       bytes32 root
173:    ) external view returns (bytes memory) {

/// @audit Missing: '@param stack'
/// @audit Missing: '@return'
308     /**
309      * @notice Buy optimized-out a lien to replace it with new terms.
310      * @param position The position of the specified lien.
311      * @param incomingTerms The loan terms of the new lien.
312      */
313     function buyoutLien(
314       ILienToken.Stack[] calldata stack,
315       uint8 position,
316       IAstariaRouter.Commitment calldata incomingTerms
317     )
318       external
319       whenNotPaused
320:      returns (ILienToken.Stack[] memory, ILienToken.Stack memory)

/// @audit Missing: '@return'
377      * @param receiver The borrower requesting the loan.
378      */
379     function _requestLienAndIssuePayout(
380       IAstariaRouter.Commitment calldata c,
381       address receiver
382     )
383       internal
384       returns (
385         uint256 newLienId,
386         ILienToken.Stack[] memory stack,
387         uint256 slope,
388:        uint256 payout

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L164-L173

File: src/WithdrawProxy.sol

/// @audit Missing: '@return'
130      * @param shares The number of shares to mint.
131      */
132     function mint(uint256 shares, address receiver)
133       public
134       virtual
135       override(ERC4626Cloned, IERC4626)
136:      returns (uint256 assets)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L130-L136

[N‑18] Not using the named return variables anywhere in the function is confusing

Consider changing the variable to be an unnamed one

There are 25 instances of this issue:

File: lib/gpl/src/ERC4626Router.sol

/// @audit sharesOut
24      function depositToVault(
25        IERC4626 vault,
26        address to,
27        uint256 amount,
28        uint256 minSharesOut
29:     ) external payable override returns (uint256 sharesOut) {

/// @audit sharesOut
35      function depositMax(
36        IERC4626 vault,
37        address to,
38        uint256 minSharesOut
39:     ) public payable override returns (uint256 sharesOut) {

/// @audit amountOut
49      function redeemMax(
50        IERC4626 vault,
51        address to,
52        uint256 minAmountOut
53:     ) public payable override returns (uint256 amountOut) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC4626Router.sol#L24-L29

File: src/AstariaRouter.sol

/// @audit amountIn
123     function mint(
124       IERC4626 vault,
125       address to,
126       uint256 shares,
127       uint256 maxAmountIn
128     )
129       public
130       payable
131       virtual
132       override
133       validVault(address(vault))
134:      returns (uint256 amountIn)

/// @audit sharesOut
139     function deposit(
140       IERC4626 vault,
141       address to,
142       uint256 amount,
143       uint256 minSharesOut
144     )
145       public
146       payable
147       virtual
148       override
149       validVault(address(vault))
150:      returns (uint256 sharesOut)

/// @audit sharesOut
155     function withdraw(
156       IERC4626 vault,
157       address to,
158       uint256 amount,
159       uint256 maxSharesOut
160     )
161       public
162       payable
163       virtual
164       override
165       validVault(address(vault))
166:      returns (uint256 sharesOut)

/// @audit amountOut
171     function redeem(
172       IERC4626 vault,
173       address to,
174       uint256 shares,
175       uint256 minAmountOut
176     )
177       public
178       payable
179       virtual
180       override
181       validVault(address(vault))
182:      returns (uint256 amountOut)

/// @audit assets
187     function redeemFutureEpoch(
188       IPublicVault vault,
189       uint256 shares,
190       address receiver,
191       uint64 epoch
192:    ) public virtual validVault(address(vault)) returns (uint256 assets) {

/// @audit lien
426     function validateCommitment(
427       IAstariaRouter.Commitment calldata commitment,
428       uint256 timeToSecondEpochEnd
429:    ) public view returns (ILienToken.Lien memory lien) {

/// @audit stack
/// @audit payout
761     function _executeCommitment(
762       RouterStorage storage s,
763       IAstariaRouter.Commitment memory c
764     )
765       internal
766       returns (
767         uint256,
768         ILienToken.Stack[] memory stack,
769:        uint256 payout

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L123-L134

File: src/CollateralToken.sol

/// @audit validOrderMagicValue
157     function isValidOrder(
158       bytes32 orderHash,
159       address caller,
160       address offerer,
161       bytes32 zoneHash
162:    ) external view returns (bytes4 validOrderMagicValue) {

/// @audit validOrderMagicValue
171     function isValidOrderIncludingExtraData(
172       bytes32 orderHash,
173       address caller,
174       AdvancedOrder calldata order,
175       bytes32[] calldata priorOrderHashes,
176       CriteriaResolver[] calldata criteriaResolvers
177:    ) external view returns (bytes4 validOrderMagicValue) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L157-L162

File: src/LienToken.sol

/// @audit newStack
107     function buyoutLien(ILienToken.LienActionBuyout calldata params)
108       external
109       validateStack(params.encumber.lien.collateralId, params.encumber.stack)
110:      returns (Stack[] memory, Stack memory newStack)

/// @audit newSlot
424     function _createLien(
425       LienStorage storage s,
426       ILienToken.LienActionEncumber memory params
427:    ) internal returns (uint256 newLienId, ILienToken.Stack memory newSlot) {

/// @audit owed
/// @audit buyout
577     function getBuyout(Stack calldata stack)
578       public
579       view
580:      returns (uint256 owed, uint256 buyout)

/// @audit newStack
596     function makePayment(
597       uint256 collateralId,
598       Stack[] calldata stack,
599       uint256 amount
600     )
601       public
602       validateStack(collateralId, stack)
603:      returns (Stack[] memory newStack)

/// @audit maxPotentialDebt
706     function getMaxPotentialDebtForCollateral(Stack[] memory stack)
707       public
708       view
709       validateStack(stack[0].lien.collateralId, stack)
710:      returns (uint256 maxPotentialDebt)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L107-L110

File: src/PublicVault.sol

/// @audit assets
138     function redeemFutureEpoch(
139       uint256 shares,
140       address receiver,
141       address owner,
142       uint64 epoch
143:    ) public virtual returns (uint256 assets) {

/// @audit timeToSecondEpochEnd
713     function _timeToSecondEndIfPublic()
714       internal
715       view
716       override
717:      returns (uint256 timeToSecondEpochEnd)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L138-L143

File: src/VaultImplementation.sol

/// @audit timeToSecondEpochEnd
353     function _timeToSecondEndIfPublic()
354       internal
355       view
356       virtual
357:      returns (uint256 timeToSecondEpochEnd)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L353-L357

File: src/WithdrawProxy.sol

/// @audit assets
132     function mint(uint256 shares, address receiver)
133       public
134       virtual
135       override(ERC4626Cloned, IERC4626)
136:      returns (uint256 assets)

/// @audit shares
163     function withdraw(
164       uint256 assets,
165       address receiver,
166       address owner
167     )
168       public
169       virtual
170       override(ERC4626Cloned, IERC4626)
171       onlyWhenNoActiveAuction
172:      returns (uint256 shares)

/// @audit assets
184     function redeem(
185       uint256 shares,
186       address receiver,
187       address owner
188     )
189       public
190       virtual
191       override(ERC4626Cloned, IERC4626)
192       onlyWhenNoActiveAuction
193:      returns (uint256 assets)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L132-L136

[N‑19] Duplicated require()/revert() checks should be refactored to a modifier or function

The compiler will inline the function, which will avoid JUMP instructions usually associated with functions

There are 6 instances of this issue:

File: lib/gpl/src/ERC721.sol

200:      require(to != address(0), "INVALID_RECIPIENT");

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC721.sol#L200

File: src/AstariaRouter.sol

347:      require(msg.sender == s.guardian);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L347

File: src/ClearingHouse.sol

216:      require(msg.sender == address(ASTARIA_ROUTER.COLLATERAL_TOKEN()));

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L216

File: src/PublicVault.sol

259:        require(s.allowList[receiver]);

687       require(
688         currentEpoch != 0 &&
689           msg.sender == s.epochData[currentEpoch - 1].withdrawProxy
690:      );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L259

File: src/VaultImplementation.sol

96:       require(msg.sender == owner()); //owner is "strategist"

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L96

[N‑20] Consider using delete rather than assigning zero to clear values

The delete keyword more closely matches the semantics of what is being done, and draws more attention to the changing of state, which may lead to a more thorough audit of its associated logic

There are 5 instances of this issue:

File: src/PublicVault.sol

308:      s.liquidationWithdrawRatio = 0;

327:            s.withdrawReserve = 0;

377:          s.withdrawReserve = 0;

511:      s.strategistUnclaimedShares = 0;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L308

File: src/WithdrawProxy.sol

284:      s.finalAuctionEnd = 0;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L284

[N‑21] Avoid the use of sensitive terms

Use alternative variants, e.g. allowlist/denylist instead of whitelist/blacklist

There is 1 instance of this issue:

File: src/AstariaRouter.sol

709:     * @param allowListEnabled Whether or not the Vault has an LP whitelist.

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L709

[N‑22] Large assembly blocks should have extensive comments

Assembly blocks are take a lot more time to audit than normal Solidity code, and often have gotchas and side-effects that the Solidity versions of the same code do not. Consider adding more comments explaining what is being done in every step of the assembly code

There is 1 instance of this issue:

File: src/AstariaRouter.sol

414       assembly {
415         let end := add(ONE_WORD, start)
416   
417         if lt(length, end) {
418           mstore(0, OUTOFBOUND_ERROR_SELECTOR)
419           revert(0, ONE_WORD)
420         }
421   
422         x := mload(add(bs, end))
423:      }

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L414-L423

[N‑23] Contracts should have full test coverage

While 100% code coverage does not guarantee that there are no bugs, it often will catch easy-to-find bugs, and will ensure that there are fewer regressions when the code invariably has to be modified. Furthermore, in order to get full coverage, code authors will often have to re-organize their code so that it is more modular, so that each component can be tested separately, which reduces interdependencies between modules and layers, and makes for code that is easier to reason about and audit.

There is 1 instance of this issue:

File: Various Files

[N‑24] Large or complicated code bases should implement fuzzing tests

Large code bases, or code with lots of inline-assembly, complicated math, or complicated interactions between multiple contracts, should implement fuzzing tests. Fuzzers such as Echidna require the test writer to come up with invariants which should not be violated under any circumstances, and the fuzzer tests various inputs and function calls to ensure that the invariants always hold. Even code with 100% code coverage can still have bugs due to the order of the operations a user performs, and fuzzers, with properly and extensively-written invariants, can close this testing gap significantly.

There is 1 instance of this issue:

File: Various Files

[N‑25] Function ordering does not follow the Solidity style guide

According to the Solidity style guide, functions should be laid out in the following order :constructor(), receive(), fallback(), external, public, internal, private, but the cases below do not follow this pattern

There are 69 instances of this issue:

File: lib/gpl/src/ERC20-Cloned.sol

/// @audit _loadERC20Slot() came earlier
35:     function balanceOf(address account) external view returns (uint256) {

/// @audit transfer() came earlier
67      function allowance(address owner, address spender)
68        external
69        view
70:       returns (uint256)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC20-Cloned.sol#L35

File: lib/gpl/src/ERC4626Router.sol

/// @audit pullToken() came earlier
24      function depositToVault(
25        IERC4626 vault,
26        address to,
27        uint256 amount,
28        uint256 minSharesOut
29:     ) external payable override returns (uint256 sharesOut) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC4626Router.sol#L24-L29

File: lib/gpl/src/ERC721.sol

/// @audit isApprovedForAll() came earlier
38:     function tokenURI(uint256 id) external view virtual returns (string memory);

/// @audit _loadERC721Slot() came earlier
52:     function ownerOf(uint256 id) public view virtual returns (address owner) {

/// @audit __initERC721() came earlier
79:     function name() public view returns (string memory) {

/// @audit symbol() came earlier
87:     function approve(address spender, uint256 id) external virtual {

/// @audit _transfer() came earlier
148     function safeTransferFrom(
149       address from,
150       address to,
151:      uint256 id

/// @audit _safeMint() came earlier
277     function onERC721Received(
278       address,
279       address,
280       uint256,
281       bytes calldata
282:    ) external virtual returns (bytes4) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC721.sol#L38

File: src/AstariaRouter.sol

/// @audit _loadRouterSlot() came earlier
224:    function feeTo() public view returns (address) {

/// @audit COLLATERAL_TOKEN() came earlier
252:    function __emergencyPause() external requiresAuth whenNotPaused {

/// @audit _file() came earlier
339:    function setNewGuardian(address _guardian) external {

/// @audit _sliceUint() came earlier
426     function validateCommitment(
427       IAstariaRouter.Commitment calldata commitment,
428       uint256 timeToSecondEpochEnd
429:    ) public view returns (ILienToken.Lien memory lien) {

/// @audit _validateCommitment() came earlier
490     function commitToLiens(IAstariaRouter.Commitment[] memory commitments)
491       public
492       whenNotPaused
493:      returns (uint256[] memory lienIds, ILienToken.Stack[] memory stack)

/// @audit commitToLiens() came earlier
522     function newVault(address delegate, address underlying)
523       external
524       whenNotPaused
525:      returns (address)

/// @audit newPublicVault() came earlier
577     function requestLienPosition(
578       IAstariaRouter.Commitment calldata params,
579       address receiver
580     )
581       external
582       whenNotPaused
583       validVault(msg.sender)
584       returns (
585         uint256,
586         ILienToken.Stack[] memory,
587:        uint256

/// @audit liquidate() came earlier
650:    function getProtocolFee(uint256 amountIn) external view returns (uint256) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L224

File: src/BeaconProxy.sol

/// @audit _fallback() came earlier
69      fallback() external payable virtual {
70:       _fallback();

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/BeaconProxy.sol#L69-L70

File: src/ClearingHouse.sol

/// @audit _getStorage() came earlier
66:     function setAuctionData(ILienToken.AuctionData calldata auctionData)

/// @audit _execute() came earlier
169     function safeTransferFrom(
170       address from, // the from is the offerer
171       address to,
172       uint256 identifier,
173       uint256 amount,
174:      bytes calldata data //empty from seaport

/// @audit safeBatchTransferFrom() came earlier
188     function onERC721Received(
189       address operator_,
190       address from_,
191       uint256 tokenId_,
192       bytes calldata data_
193:    ) external override returns (bytes4) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L66

File: src/CollateralToken.sol

/// @audit SEAPORT() came earlier
109:    function liquidatorNFTClaim(OrderParameters memory params) external {

/// @audit _loadCollateralSlot() came earlier
157     function isValidOrder(
158       bytes32 orderHash,
159       address caller,
160       address offerer,
161       bytes32 zoneHash
162:    ) external view returns (bytes4 validOrderMagicValue) {

/// @audit supportsInterface() came earlier
196:    function fileBatch(File[] calldata files) external requiresAuth {

/// @audit _file() came earlier
270     function flashAction(
271       IFlashAction receiver,
272       uint256 collateralId,
273       bytes calldata data
274:    ) external onlyOwner(collateralId) {

/// @audit _releaseToAddress() came earlier
370:    function getConduitKey() public view returns (bytes32) {

/// @audit securityHooks() came earlier
416     function getClearingHouse(uint256 collateralId)
417       external
418       view
419:      returns (ClearingHouse)

/// @audit _generateValidOrderParameters() came earlier
477     function auctionVault(AuctionVaultParams calldata params)
478       external
479       requiresAuth
480:      returns (OrderParameters memory orderParameters)

/// @audit _listUnderlyingOnSeaport() came earlier
524:    function settleAuction(uint256 collateralId) public {

/// @audit _settleAuction() came earlier
553     function onERC721Received(
554       address, /* operator_ */
555       address from_,
556       uint256 tokenId_,
557       bytes calldata // calldata data_
558:    ) external override whenNotPaused returns (bytes4) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L109

File: src/LienToken.sol

/// @audit _loadLienStorageSlot() came earlier
82:     function file(File calldata incoming) external requiresAuth {

/// @audit supportsInterface() came earlier
107     function buyoutLien(ILienToken.LienActionBuyout calldata params)
108       external
109       validateStack(params.encumber.lien.collateralId, params.encumber.stack)
110:      returns (Stack[] memory, Stack memory newStack)

/// @audit _replaceStackAtPositionWithNewLien() came earlier
246:    function getInterest(Stack calldata stack) public view returns (uint256) {

/// @audit _getInterest() came earlier
277     function stopLiens(
278       uint256 collateralId,
279       uint256 auctionWindow,
280       Stack[] calldata stack,
281       address liquidator
282:    ) external validateStack(collateralId, stack) requiresAuth {

/// @audit _stopLiens() came earlier
348     function tokenURI(uint256 tokenId)
349       public
350       view
351       override(ERC721, IERC721)
352:      returns (string memory)

/// @audit _exists() came earlier
389     function createLien(ILienToken.LienActionEncumber memory params)
390       external
391       requiresAuth
392       validateStack(params.lien.collateralId, params.stack)
393       returns (
394         uint256 lienId,
395         Stack[] memory newStack,
396:        uint256 lienSlope

/// @audit _appendStack() came earlier
497     function payDebtViaClearingHouse(
498       address token,
499       uint256 collateralId,
500       uint256 payment,
501:      AuctionStack[] memory auctionStack

/// @audit _payDebt() came earlier
531     function getAuctionData(uint256 collateralId)
532       external
533       view
534:      returns (AuctionData memory)

/// @audit validateLien() came earlier
569     function getCollateralState(uint256 collateralId)
570       external
571       view
572:      returns (bytes32)

/// @audit _getBuyout() came earlier
596     function makePayment(
597       uint256 collateralId,
598       Stack[] calldata stack,
599       uint256 amount
600     )
601       public
602       validateStack(collateralId, stack)
603:      returns (Stack[] memory newStack)

/// @audit makePayment() came earlier
608     function makePayment(
609       uint256 collateralId,
610       Stack[] calldata stack,
611       uint8 position,
612       uint256 amount
613     )
614       external
615       validateStack(collateralId, stack)
616:      returns (Stack[] memory newStack)

/// @audit _updateCollateralStateHash() came earlier
702:    function calculateSlope(Stack memory stack) public pure returns (uint256) {

/// @audit _getMaxPotentialDebtForCollateralUpToNPositions() came earlier
727     function getMaxPotentialDebtForCollateral(Stack[] memory stack, uint256 end)
728       public
729       view
730       validateStack(stack[0].lien.collateralId, stack)
731:      returns (uint256 maxPotentialDebt)

/// @audit getMaxPotentialDebtForCollateral() came earlier
742:    function getOwed(Stack memory stack) external view returns (uint88) {

/// @audit _isPublicVault() came earlier
893:    function getPayee(uint256 lienId) public view returns (address) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L82

File: src/PublicVault.sol

/// @audit _redeemFutureEpoch() came earlier
192:    function getWithdrawProxy(uint64 epoch) public view returns (WithdrawProxy) {

/// @audit _deployWithdrawProxyIfNotDeployed() came earlier
233     function mint(uint256 shares, address receiver)
234       public
235       override(ERC4626Cloned)
236       whenNotPaused
237:      returns (uint256)

/// @audit computeDomainSeparator() came earlier
275     function processEpoch() public {
276       // check to make sure epoch is over
277:      if (timeToEpochEnd() > 0) {

/// @audit _afterCommitToLien() came earlier
461:    function accrue() public returns (uint256) {

/// @audit _accrue() came earlier
479     function totalAssets()
480       public
481       view
482       virtual
483       override(ERC4626Cloned)
484:      returns (uint256)

/// @audit _totalAssets() came earlier
495     function totalSupply()
496       public
497       view
498       virtual
499       override(IERC20, ERC20Cloned)
500:      returns (uint256)

/// @audit totalSupply() came earlier
507     function claim() external {
508:      require(msg.sender == owner()); //owner is "strategist"

/// @audit _setSlope() came earlier
534:    function decreaseEpochLienCount(uint64 epoch) public onlyLienToken {

/// @audit _decreaseEpochLienCount() came earlier
546:    function getLienEpoch(uint64 end) public pure returns (uint64) {

/// @audit _increaseOpenLiens() came earlier
562:    function afterPayment(uint256 computedSlope) public onlyLienToken {

/// @audit _handleStrategistInterestReward() came earlier
611:    function LIEN_TOKEN() public view returns (ILienToken) {

/// @audit handleBuyoutLien() came earlier
632     function updateAfterLiquidationPayment(
633       LiquidationPaymentParams calldata params
634:    ) external onlyLienToken {

/// @audit _setYIntercept() came earlier
699:    function timeToEpochEnd() public view returns (uint256) {

/// @audit _timeToSecondEndIfPublic() came earlier
722:    function timeToSecondEpochEnd() public view returns (uint256) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L192

File: src/VaultImplementation.sol

/// @audit _loadVISlot() came earlier
95:     function modifyAllowList(address depositor, bool enabled) external virtual {

/// @audit domainSeparator() came earlier
170     function encodeStrategyData(
171       IAstariaRouter.StrategyDetailsParam calldata strategy,
172       bytes32 root
173:    ) external view returns (bytes memory) {

/// @audit _encodeStrategyData() came earlier
190:    function init(InitParams calldata params) external virtual {

/// @audit _beforeCommitToLien() came earlier
287     function commitToLien(
288       IAstariaRouter.Commitment calldata params,
289       address receiver
290     )
291       external
292       whenNotPaused
293:      returns (uint256 lienId, ILienToken.Stack[] memory stack, uint256 payout)

/// @audit _timeToSecondEndIfPublic() came earlier
366:    function recipient() public view returns (address) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L95

File: src/Vault.sol

/// @audit deposit() came earlier
70:     function withdraw(uint256 amount) external {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/Vault.sol#L70

File: src/WithdrawProxy.sol

/// @audit redeem() came earlier
198     function supportsInterface(bytes4 interfaceId)
199       external
200       view
201       virtual
202:      returns (bool)

/// @audit _loadSlot() came earlier
215:    function getFinalAuctionEnd() public view returns (uint256) {

/// @audit getExpected() came earlier
235:    function increaseWithdrawReserveReceived(uint256 amount) external onlyVault {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L198-L202

File: src/WithdrawVaultBase.sol

/// @audit symbol() came earlier
26:     function ROUTER() external pure returns (IAstariaRouter) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawVaultBase.sol#L26

[N‑26] Contract does not follow the Solidity style guide's suggested layout ordering

The style guide says that, within a contract, the ordering should be 1) Type declarations, 2) State variables, 3) Events, 4) Modifiers, and 5) Functions, but the contract(s) below do not follow this ordering

There are 21 instances of this issue:

File: src/AstariaRouter.sol

/// @audit function redeemFutureEpoch came earlier
196     modifier validVault(address targetVault) {
197       if (!isValidVault(targetVault)) {
198         revert InvalidVault(targetVault);
199       }
200       _;
201:    }

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L196-L201

File: src/CollateralToken.sol

/// @audit function _file came earlier
253     modifier releaseCheck(uint256 collateralId) {
254       CollateralStorage storage s = _loadCollateralSlot();
255   
256       if (s.LIEN_TOKEN.getCollateralState(collateralId) != bytes32(0)) {
257         revert InvalidCollateralState(InvalidCollateralStates.ACTIVE_LIENS);
258       }
259       if (s.collateralIdToAuction[collateralId] != bytes32(0)) {
260         revert InvalidCollateralState(InvalidCollateralStates.AUCTION_ACTIVE);
261       }
262       _;
263:    }

/// @audit function onERC721Received came earlier
602     modifier whenNotPaused() {
603       if (_loadCollateralSlot().ASTARIA_ROUTER.paused()) {
604         revert ProtocolPaused();
605       }
606       _;
607:    }

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L253-L263

File: src/interfaces/IAstariaRouter.sol

/// @audit event FileUpdated came earlier
87      enum ImplementationType {
88        PrivateVault,
89        PublicVault,
90        WithdrawProxy,
91        ClearingHouse
92:     }

/// @audit function isValidRefinance came earlier
294:    event Liquidation(uint256 collateralId, uint256 position);

/// @audit event NewVault came earlier
316     enum LienState {
317       HEALTHY,
318       AUCTION
319:    }

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IAstariaRouter.sol#L87-L92

File: src/interfaces/ICollateralToken.sol

/// @audit event ReleaseTo came earlier
74      enum FileType {
75        NotSupported,
76        AstariaRouter,
77        SecurityHook,
78        FlashEnabled,
79        Seaport
80:     }

/// @audit function liquidatorNFTClaim came earlier
175     enum InvalidCollateralStates {
176       NO_AUTHORITY,
177       NO_AUCTION,
178       FLASH_DISABLED,
179       AUCTION_ACTIVE,
180       INVALID_AUCTION_PARAMS,
181       ACTIVE_LIENS
182:    }

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/ICollateralToken.sol#L74-L80

File: src/interfaces/ILienToken.sol

/// @audit function file came earlier
294     event AddLien(
295       uint256 indexed collateralId,
296       uint8 position,
297       uint256 indexed lienId,
298       Stack stack
299:    );

/// @audit event AddLien came earlier
300     enum StackAction {
301       CLEAR,
302       ADD,
303       REMOVE,
304       REPLACE
305:    }

/// @audit event PayeeChanged came earlier
324     enum InvalidStates {
325       NO_AUTHORITY,
326       COLLATERAL_MISMATCH,
327       ASSET_MISMATCH,
328       NOT_ENOUGH_FUNDS,
329       INVALID_LIEN_ID,
330       COLLATERAL_AUCTION,
331       COLLATERAL_NOT_DEPOSITED,
332       LIEN_NO_DEBT,
333       EXPIRED_LIEN,
334       DEBT_LIMIT,
335       MAX_LIENS,
336       INVALID_HASH,
337       INVALID_LIQUIDATION_INITIAL_ASK,
338       INITIAL_ASK_EXCEEDED,
339       EMPTY_STATE,
340       PUBLIC_VAULT_RECIPIENT,
341       COLLATERAL_NOT_LIQUIDATED
342:    }

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/ILienToken.sol#L294-L299

File: src/interfaces/IPublicVault.sol

/// @audit function updateVaultAfterLiquidation came earlier
157     enum InvalidStates {
158       EPOCH_TOO_LOW,
159       EPOCH_TOO_HIGH,
160       EPOCH_NOT_OVER,
161       WITHDRAW_RESERVE_NOT_ZERO,
162       LIENS_OPEN_FOR_EPOCH_NOT_ZERO,
163       LIQUIDATION_ACCOUNTANT_FINAL_AUCTION_OPEN,
164       LIQUIDATION_ACCOUNTANT_ALREADY_DEPLOYED_FOR_EPOCH,
165       DEPOSIT_CAP_EXCEEDED
166:    }

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IPublicVault.sol#L157-L166

File: src/LienToken.sol

/// @audit function _getInterest came earlier
265     modifier validateStack(uint256 collateralId, Stack[] memory stack) {
266       LienStorage storage s = _loadLienStorageSlot();
267       bytes32 stateHash = s.collateralStateHash[collateralId];
268       if (stateHash == bytes32(0) && stack.length != 0) {
269         revert InvalidState(InvalidStates.EMPTY_STATE);
270       }
271       if (stateHash != bytes32(0) && keccak256(abi.encode(stack)) != stateHash) {
272         revert InvalidState(InvalidStates.INVALID_HASH);
273       }
274       _;
275:    }

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L265-L275

File: src/PublicVault.sol

/// @audit function _afterCommitToLien came earlier
459:    event SlopeUpdated(uint48 newSlope);

/// @audit function increaseYIntercept came earlier
679     modifier onlyLienToken() {
680       require(msg.sender == address(LIEN_TOKEN()));
681       _;
682:    }

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L459

File: src/VaultImplementation.sol

/// @audit function symbol came earlier
57      uint256 private constant VI_SLOT =
58:       uint256(keccak256("xyz.astaria.VaultImplementation.storage.location")) - 1;

/// @audit function onERC721Received came earlier
131     modifier whenNotPaused() {
132       if (ROUTER().paused()) {
133         revert InvalidRequest(InvalidRequestReason.PAUSED);
134       }
135   
136       if (_loadVISlot().isShutdown) {
137         revert InvalidRequest(InvalidRequestReason.SHUTDOWN);
138       }
139       _;
140:    }

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L57-L58

File: src/WithdrawProxy.sol

/// @audit event Claimed came earlier
49      uint256 private constant WITHDRAW_PROXY_SLOT =
50:       uint256(keccak256("xyz.astaria.WithdrawProxy.storage.location")) - 1;

/// @audit variable WITHDRAW_PROXY_SLOT came earlier
59      enum InvalidStates {
60        PROCESS_EPOCH_NOT_COMPLETE,
61        FINAL_AUCTION_NOT_OVER,
62        NOT_CLAIMED,
63        CANT_CLAIM
64:     }

/// @audit function deposit came earlier
152     modifier onlyWhenNoActiveAuction() {
153       WPStorage storage s = _loadSlot();
154       // If auction funds have been collected to the WithdrawProxy
155       // but the PublicVault hasn't claimed its share, too much money will be sent to LPs
156       if (s.finalAuctionEnd != 0) {
157         // if finalAuctionEnd is 0, no auctions were added
158         revert InvalidState(InvalidStates.NOT_CLAIMED);
159       }
160       _;
161:    }

/// @audit function getExpected came earlier
230     modifier onlyVault() {
231       require(msg.sender == VAULT(), "only vault can call");
232       _;
233:    }

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L49-L50


Excluded findings

These findings are excluded from awards calculations because there are publicly-available automated tools that find them. The valid ones appear here for completeness

Summary

Low Risk Issues

IssueInstances
[L‑01]safeApprove() is deprecated6

Total: 6 instances over 1 issues

Non-critical Issues

IssueInstances
[N‑01]require()/revert() statements should have descriptive reason strings29
[N‑02]public functions not called by the contract should be declared external instead52
[N‑03]constants should be defined rather than using magic numbers21
[N‑04]Event is missing indexed fields32

Total: 134 instances over 4 issues

Low Risk Issues

[L‑01] safeApprove() is deprecated

Deprecated in favor of safeIncreaseAllowance() and safeDecreaseAllowance(). If only setting the initial allowance to the value that means infinite, safeIncreaseAllowance() can be used instead. The function may currently work, but if a bug is found in this version of OpenZeppelin, and the version that you're forced to upgrade to no longer has this function, you'll encounter unnecessary delays in porting and testing replacement contracts.

There are 6 instances of this issue:

File: lib/gpl/src/ERC4626RouterBase.sol

/// @audit (valid but excluded finding)
21:       ERC20(vault.asset()).safeApprove(address(vault), shares);

/// @audit (valid but excluded finding)
34:       ERC20(vault.asset()).safeApprove(address(vault), amount);

/// @audit (valid but excluded finding)
48:       ERC20(address(vault)).safeApprove(address(vault), amount);

/// @audit (valid but excluded finding)
62:       ERC20(address(vault)).safeApprove(address(vault), shares);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC4626RouterBase.sol#L21

File: src/ClearingHouse.sol

/// @audit (valid but excluded finding)
148:      ERC20(paymentToken).safeApprove(

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L148

File: src/VaultImplementation.sol

/// @audit (valid but excluded finding)
334:      ERC20(asset()).safeApprove(address(ROUTER().TRANSFER_PROXY()), buyout);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L334

Non-critical Issues

[N‑01] require()/revert() statements should have descriptive reason strings

There are 29 instances of this issue:

File: src/AstariaRouter.sol

/// @audit (valid but excluded finding)
341:      require(msg.sender == s.guardian);

/// @audit (valid but excluded finding)
347:      require(msg.sender == s.guardian);

/// @audit (valid but excluded finding)
354:      require(msg.sender == s.newGuardian);

/// @audit (valid but excluded finding)
361:      require(msg.sender == address(s.guardian));

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L341

File: src/ClearingHouse.sol

/// @audit (valid but excluded finding)
72:       require(msg.sender == address(ASTARIA_ROUTER.LIEN_TOKEN()));

/// @audit (valid but excluded finding)
199:      require(msg.sender == address(ASTARIA_ROUTER.COLLATERAL_TOKEN()));

/// @audit (valid but excluded finding)
216:      require(msg.sender == address(ASTARIA_ROUTER.COLLATERAL_TOKEN()));

/// @audit (valid but excluded finding)
223:      require(msg.sender == address(ASTARIA_ROUTER.COLLATERAL_TOKEN()));

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L72

File: src/CollateralToken.sol

/// @audit (valid but excluded finding)
266:      require(ownerOf(collateralId) == msg.sender);

/// @audit (valid but excluded finding)
535:      require(msg.sender == s.clearingHouse[collateralId]);

/// @audit (valid but excluded finding)
564:        require(ERC721(msg.sender).ownerOf(tokenId_) == address(this));

/// @audit (valid but excluded finding)
598:        revert();

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L266

File: src/LienToken.sol

/// @audit (valid but excluded finding)
504       require(
505         msg.sender == address(s.COLLATERAL_TOKEN.getClearingHouse(collateralId))
506:      );

/// @audit (valid but excluded finding)
860:      require(position < length);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L504-L506

File: src/PublicVault.sol

/// @audit (valid but excluded finding)
241:        require(s.allowList[receiver]);

/// @audit (valid but excluded finding)
259:        require(s.allowList[receiver]);

/// @audit (valid but excluded finding)
508:      require(msg.sender == owner()); //owner is "strategist"

/// @audit (valid but excluded finding)
672       require(
673         currentEpoch != 0 &&
674           msg.sender == s.epochData[currentEpoch - 1].withdrawProxy
675:      );

/// @audit (valid but excluded finding)
680:      require(msg.sender == address(LIEN_TOKEN()));

/// @audit (valid but excluded finding)
687       require(
688         currentEpoch != 0 &&
689           msg.sender == s.epochData[currentEpoch - 1].withdrawProxy
690:      );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L241

File: src/VaultImplementation.sol

/// @audit (valid but excluded finding)
78:       require(msg.sender == owner()); //owner is "strategist"

/// @audit (valid but excluded finding)
96:       require(msg.sender == owner()); //owner is "strategist"

/// @audit (valid but excluded finding)
105:      require(msg.sender == owner()); //owner is "strategist"

/// @audit (valid but excluded finding)
114:      require(msg.sender == owner()); //owner is "strategist"

/// @audit (valid but excluded finding)
147:      require(msg.sender == owner()); //owner is "strategist"

/// @audit (valid but excluded finding)
191:      require(msg.sender == address(ROUTER()));

/// @audit (valid but excluded finding)
211:      require(msg.sender == owner()); //owner is "strategist"

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L78

File: src/Vault.sol

/// @audit (valid but excluded finding)
65:       require(s.allowList[msg.sender] && receiver == owner());

/// @audit (valid but excluded finding)
71:       require(msg.sender == owner());

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/Vault.sol#L65

[N‑02] public functions not called by the contract should be declared external instead

Contracts are allowed to override their parents' functions and change the visibility from external to public.

There are 52 instances of this issue:

File: src/AstariaRouter.sol

/// @audit (valid but excluded finding)
224:    function feeTo() public view returns (address) {

/// @audit (valid but excluded finding)
229:    function BEACON_PROXY_IMPLEMENTATION() public view returns (address) {

/// @audit (valid but excluded finding)
234:    function LIEN_TOKEN() public view returns (ILienToken) {

/// @audit (valid but excluded finding)
239:    function TRANSFER_PROXY() public view returns (ITransferProxy) {

/// @audit (valid but excluded finding)
273:    function file(File calldata incoming) public requiresAuth {

/// @audit (valid but excluded finding)
402:    function getAuctionWindow(bool includeBuffer) public view returns (uint256) {

/// @audit (valid but excluded finding)
426     function validateCommitment(
427       IAstariaRouter.Commitment calldata commitment,
428       uint256 timeToSecondEpochEnd
429:    ) public view returns (ILienToken.Lien memory lien) {

/// @audit (valid but excluded finding)
490     function commitToLiens(IAstariaRouter.Commitment[] memory commitments)
491       public
492       whenNotPaused
493:      returns (uint256[] memory lienIds, ILienToken.Stack[] memory stack)

/// @audit (valid but excluded finding)
544     function newPublicVault(
545       uint256 epochLength,
546       address delegate,
547       address underlying,
548       uint256 vaultFee,
549       bool allowListEnabled,
550       address[] calldata allowList,
551       uint256 depositCap
552:    ) public whenNotPaused returns (address) {

/// @audit (valid but excluded finding)
621     function liquidate(ILienToken.Stack[] memory stack, uint8 position)
622       public
623:      returns (OrderParameters memory listedOrder)

/// @audit (valid but excluded finding)
684     function isValidRefinance(
685       ILienToken.Lien calldata newLien,
686       uint8 position,
687       ILienToken.Stack[] calldata stack
688:    ) public view returns (bool) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L224

File: src/ClearingHouse.sol

/// @audit (valid but excluded finding)
169     function safeTransferFrom(
170       address from, // the from is the offerer
171       address to,
172       uint256 identifier,
173       uint256 amount,
174:      bytes calldata data //empty from seaport

/// @audit (valid but excluded finding)
180     function safeBatchTransferFrom(
181       address from,
182       address to,
183       uint256[] calldata ids,
184       uint256[] calldata amounts,
185:      bytes calldata data

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L169-L174

File: src/CollateralToken.sol

/// @audit (valid but excluded finding)
80      function initialize(
81        Authority AUTHORITY_,
82        ITransferProxy TRANSFER_PROXY_,
83        ILienToken LIEN_TOKEN_,
84        ConsiderationInterface SEAPORT_
85:     ) public initializer {

/// @audit (valid but excluded finding)
105:    function SEAPORT() public view returns (ConsiderationInterface) {

/// @audit (valid but excluded finding)
206:    function file(File calldata incoming) public requiresAuth {

/// @audit (valid but excluded finding)
331     function releaseToAddress(uint256 collateralId, address releaseTo)
332       public
333       releaseCheck(collateralId)
334:      onlyOwner(collateralId)

/// @audit (valid but excluded finding)
370:    function getConduitKey() public view returns (bytes32) {

/// @audit (valid but excluded finding)
375:    function getConduit() public view returns (address) {

/// @audit (valid but excluded finding)
412:    function securityHooks(address target) public view returns (address) {

/// @audit (valid but excluded finding)
524:    function settleAuction(uint256 collateralId) public {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L80-L85

File: src/LienToken.sol

/// @audit (valid but excluded finding)
59      function initialize(Authority _AUTHORITY, ITransferProxy _TRANSFER_PROXY)
60        public
61:       initializer

/// @audit (valid but excluded finding)
246:    function getInterest(Stack calldata stack) public view returns (uint256) {

/// @audit (valid but excluded finding)
377:    function ASTARIA_ROUTER() public view returns (IAstariaRouter) {

/// @audit (valid but excluded finding)
550     function getAmountOwingAtLiquidation(ILienToken.Stack calldata stack)
551       public
552       view
553:      returns (uint256)

/// @audit (valid but excluded finding)
577     function getBuyout(Stack calldata stack)
578       public
579       view
580:      returns (uint256 owed, uint256 buyout)

/// @audit (valid but excluded finding)
596     function makePayment(
597       uint256 collateralId,
598       Stack[] calldata stack,
599       uint256 amount
600     )
601       public
602       validateStack(collateralId, stack)
603:      returns (Stack[] memory newStack)

/// @audit (valid but excluded finding)
706     function getMaxPotentialDebtForCollateral(Stack[] memory stack)
707       public
708       view
709       validateStack(stack[0].lien.collateralId, stack)
710:      returns (uint256 maxPotentialDebt)

/// @audit (valid but excluded finding)
727     function getMaxPotentialDebtForCollateral(Stack[] memory stack, uint256 end)
728       public
729       view
730       validateStack(stack[0].lien.collateralId, stack)
731:      returns (uint256 maxPotentialDebt)

/// @audit (valid but excluded finding)
893:    function getPayee(uint256 lienId) public view returns (address) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L59-L61

File: src/PublicVault.sol

/// @audit (valid but excluded finding)
192:    function getWithdrawProxy(uint64 epoch) public view returns (WithdrawProxy) {

/// @audit (valid but excluded finding)
196:    function getCurrentEpoch() public view returns (uint64) {

/// @audit (valid but excluded finding)
200:    function getSlope() public view returns (uint256) {

/// @audit (valid but excluded finding)
204:    function getWithdrawReserve() public view returns (uint256) {

/// @audit (valid but excluded finding)
208:    function getLiquidationWithdrawRatio() public view returns (uint256) {

/// @audit (valid but excluded finding)
212:    function getYIntercept() public view returns (uint256) {

/// @audit (valid but excluded finding)
461:    function accrue() public returns (uint256) {

/// @audit (valid but excluded finding)
534:    function decreaseEpochLienCount(uint64 epoch) public onlyLienToken {

/// @audit (valid but excluded finding)
552:    function getEpochEnd(uint256 epoch) public pure returns (uint64) {

/// @audit (valid but excluded finding)
562:    function afterPayment(uint256 computedSlope) public onlyLienToken {

/// @audit (valid but excluded finding)
615     function handleBuyoutLien(BuyoutLienParams calldata params)
616       public
617:      onlyLienToken

/// @audit (valid but excluded finding)
640     function updateVaultAfterLiquidation(
641       uint256 maxAuctionWindow,
642       AfterLiquidationParams calldata params
643:    ) public onlyLienToken returns (address withdrawProxyIfNearBoundary) {

/// @audit (valid but excluded finding)
669:    function increaseYIntercept(uint256 amount) public {

/// @audit (valid but excluded finding)
684:    function decreaseYIntercept(uint256 amount) public {

/// @audit (valid but excluded finding)
722:    function timeToSecondEpochEnd() public view returns (uint256) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L192

File: src/WithdrawProxy.sol

/// @audit (valid but excluded finding)
215:    function getFinalAuctionEnd() public view returns (uint256) {

/// @audit (valid but excluded finding)
220:    function getWithdrawRatio() public view returns (uint256) {

/// @audit (valid but excluded finding)
225:    function getExpected() public view returns (uint256) {

/// @audit (valid but excluded finding)
240     function claim() public {
241:      WPStorage storage s = _loadSlot();

/// @audit (valid but excluded finding)
289     function drain(uint256 amount, address withdrawProxy)
290       public
291       onlyVault
292:      returns (uint256)

/// @audit (valid but excluded finding)
302:    function setWithdrawRatio(uint256 liquidationWithdrawRatio) public onlyVault {

/// @audit (valid but excluded finding)
306     function handleNewLiquidation(
307       uint256 newLienExpectedValue,
308       uint256 finalAuctionDelta
309:    ) public onlyVault {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L215

[N‑03] constants should be defined rather than using magic numbers

Even assembly can benefit from using readable constants instead of hex/numeric literals

There are 21 instances of this issue:

File: src/AstariaRouter.sol

/// @audit 130 - (valid but excluded finding)
110:      s.liquidationFeeNumerator = uint32(130);

/// @audit 1e15 - (valid but excluded finding)
/// @audit 5 - (valid but excluded finding)
112:      s.minInterestBPS = uint32((uint256(1e15) * 5) / (365 days));

/// @audit 45 - (valid but excluded finding)
114:      s.maxEpochLength = uint32(45 days);

/// @audit 1e16 - (valid but excluded finding)
/// @audit 200 - (valid but excluded finding)
115:      s.maxInterestRate = ((uint256(1e16) * 200) / (365 days)).safeCastTo88();

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L110

File: src/AstariaVaultBase.sol

/// @audit 20 - (valid but excluded finding)
33:       return _getArgUint8(20); //ends at 21

/// @audit 21 - (valid but excluded finding)
37:       return _getArgAddress(21); //ends at 44

/// @audit 41 - (valid but excluded finding)
47:       return _getArgAddress(41); //ends at 64

/// @audit 61 - (valid but excluded finding)
51:       return _getArgUint256(61);

/// @audit 93 - (valid but excluded finding)
55:       return _getArgUint256(93); //ends at 116

/// @audit 125 - (valid but excluded finding)
59:       return _getArgUint256(125);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaVaultBase.sol#L33

File: src/BeaconProxy.sol

/// @audit 20 - (valid but excluded finding)
62:       _delegate(_getBeacon().getImpl(_getArgUint8(20)));

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/BeaconProxy.sol#L62

File: src/ClearingHouse.sol

/// @audit 21 - (valid but excluded finding)
48:       return _getArgUint256(21);

/// @audit 20 - (valid but excluded finding)
52:       return _getArgUint8(20);

/// @audit 21 - (valid but excluded finding)
136:      uint256 collateralId = _getArgUint256(21);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L48

File: src/PublicVault.sol

/// @audit 18 - (valid but excluded finding)
103:      if (ERC20(asset()).decimals() == uint8(18)) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L103

File: src/WithdrawVaultBase.sol

/// @audit 20 - (valid but excluded finding)
31:       return _getArgUint8(20);

/// @audit 21 - (valid but excluded finding)
35:       return _getArgAddress(21);

/// @audit 41 - (valid but excluded finding)
39:       return _getArgAddress(41);

/// @audit 61 - (valid but excluded finding)
43:       return _getArgUint64(61);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawVaultBase.sol#L31

[N‑04] Event is missing indexed fields

Index event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.

There are 32 instances of this issue:

File: src/interfaces/IAstariaRouter.sol

/// @audit (valid but excluded finding)
54:     event FileUpdated(FileType what, bytes data);

/// @audit (valid but excluded finding)
294:    event Liquidation(uint256 collateralId, uint256 position);

/// @audit (valid but excluded finding)
295     event NewVault(
296       address strategist,
297       address delegate,
298       address vault,
299       uint8 vaultType
300:    );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IAstariaRouter.sol#L54

File: src/interfaces/ICollateralToken.sol

/// @audit (valid but excluded finding)
32:     event ListedOnSeaport(uint256 collateralId, Order listingOrder);

/// @audit (valid but excluded finding)
33:     event FileUpdated(FileType what, bytes data);

/// @audit (valid but excluded finding)
40      event ReleaseTo(
41        address indexed underlyingAsset,
42        uint256 assetId,
43        address indexed to
44:     );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/ICollateralToken.sol#L32

File: src/interfaces/IERC1155.sol

/// @audit (valid but excluded finding)
42      event ApprovalForAll(
43        address indexed account,
44        address indexed operator,
45        bool approved
46:     );

/// @audit (valid but excluded finding)
55:     event URI(string value, uint256 indexed id);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IERC1155.sol#L42-L46

File: src/interfaces/IERC20.sol

/// @audit (valid but excluded finding)
16:     event Transfer(address indexed from, address indexed to, uint256 value);

/// @audit (valid but excluded finding)
22:     event Approval(address indexed owner, address indexed spender, uint256 value);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IERC20.sol#L16

File: src/interfaces/IERC4626.sol

/// @audit (valid but excluded finding)
15      event Deposit(
16        address indexed sender,
17        address indexed owner,
18        uint256 assets,
19        uint256 shares
20:     );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IERC4626.sol#L15-L20

File: src/interfaces/IERC721.sol

/// @audit (valid but excluded finding)
14      event ApprovalForAll(
15        address indexed owner,
16        address indexed operator,
17        bool approved
18:     );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IERC721.sol#L14-L18

File: src/interfaces/ILienToken.sol

/// @audit (valid but excluded finding)
34:     event FileUpdated(FileType what, bytes data);

/// @audit (valid but excluded finding)
294     event AddLien(
295       uint256 indexed collateralId,
296       uint8 position,
297       uint256 indexed lienId,
298       Stack stack
299:    );

/// @audit (valid but excluded finding)
306     event LienStackUpdated(
307       uint256 indexed collateralId,
308       uint8 position,
309       StackAction action,
310       uint8 stackLength
311:    );

/// @audit (valid but excluded finding)
313:    event Payment(uint256 indexed lienId, uint256 amount);

/// @audit (valid but excluded finding)
314:    event BuyoutLien(address indexed buyer, uint256 lienId, uint256 buyout);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/ILienToken.sol#L34

File: src/interfaces/IPublicVault.sol

/// @audit (valid but excluded finding)
168:    event StrategistFee(uint88 feeInShares);

/// @audit (valid but excluded finding)
169:    event LiensOpenForEpochRemaining(uint64 epoch, uint256 liensOpenForEpoch);

/// @audit (valid but excluded finding)
170:    event YInterceptChanged(uint88 newYintercept);

/// @audit (valid but excluded finding)
171:    event WithdrawReserveTransferred(uint256 amount);

/// @audit (valid but excluded finding)
172:    event LienOpen(uint256 lienId, uint256 epoch);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IPublicVault.sol#L168

File: src/interfaces/IV3PositionManager.sol

/// @audit (valid but excluded finding)
10      event IncreaseLiquidity(
11        uint256 indexed tokenId,
12        uint128 liquidity,
13        uint256 amount0,
14        uint256 amount1
15:     );

/// @audit (valid but excluded finding)
21      event DecreaseLiquidity(
22        uint256 indexed tokenId,
23        uint128 liquidity,
24        uint256 amount0,
25        uint256 amount1
26:     );

/// @audit (valid but excluded finding)
33      event Collect(
34        uint256 indexed tokenId,
35        address recipient,
36        uint256 amount0,
37        uint256 amount1
38:     );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IV3PositionManager.sol#L10-L15

File: src/interfaces/IVaultImplementation.sol

/// @audit (valid but excluded finding)
52:     event AllowListUpdated(address, bool);

/// @audit (valid but excluded finding)
54:     event AllowListEnabled(bool);

/// @audit (valid but excluded finding)
56:     event DelegateUpdated(address);

/// @audit (valid but excluded finding)
58:     event NonceUpdated(uint256 nonce);

/// @audit (valid but excluded finding)
60:     event IncrementNonce(uint256 nonce);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IVaultImplementation.sol#L52

File: src/PublicVault.sol

/// @audit (valid but excluded finding)
459:    event SlopeUpdated(uint48 newSlope);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L459

File: src/WithdrawProxy.sol

/// @audit (valid but excluded finding)
42      event Claimed(
43        address withdrawProxy,
44        uint256 withdrawProxyAmount,
45        address publicVault,
46        uint256 publicVaultAmount
47:     );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L42-L47

#0 - c4-judge

2023-01-26T14:10:33Z

Picodes marked the issue as grade-a

Awards

36.79 USDC - $36.79

Labels

bug
G (Gas Optimization)
grade-b
G-05

External Links

Summary

Gas Optimizations

IssueInstancesTotal Gas Saved
[G‑01]Structs can be packed into fewer storage slots2-
[G‑02]Using storage instead of memory for structs/arrays saves gas28400
[G‑03]The result of function calls should be cached rather than re-calling the function3-
[G‑04]internal functions only called once can be inlined to save gas21420
[G‑05]Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement185
[G‑06]keccak256() should only need to be called on a specific string literal once3126
[G‑07]Optimize names to save gas18396
[G‑08]Use a more recent version of solidity3-
[G‑09]Remove unused local variable1-
[G‑10]++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)315
[G‑11]Splitting require() statements that use && saves gas412
[G‑12]Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead2-
[G‑13]Inverting the condition of an if-else-statement wastes gas1-
[G‑14]require() or revert() statements that check input arguments should be at the top of the function1-
[G‑15]Use custom errors rather than revert()/require() strings to save gas8-
[G‑16]Functions guaranteed to revert when called by normal users can be marked payable21441

Total: 94 instances over 16 issues with 9895 gas saved

Gas totals use lower bounds of ranges and count two iterations of each for-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions. The table above as well as its gas numbers do not include any of the excluded findings.

Gas Optimizations

[G‑01] Structs can be packed into fewer storage slots

Each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct. Subsequent reads as well as writes have smaller gas savings

There are 2 instances of this issue:

File: src/interfaces/IAstariaRouter.sol

/// @audit Variable ordering with 11 slots instead of the current 12:
///           mapping(32):strategyValidators, mapping(32):implementations, mapping(32):vaults, user-defined(20):WETH, uint88(11):maxInterestRate, address(20):COLLATERAL_TOKEN, uint32(4):auctionWindow, uint32(4):auctionWindowBuffer, uint32(4):liquidationFeeNumerator, address(20):LIEN_TOKEN, uint32(4):liquidationFeeDenominator, uint32(4):maxEpochLength, uint32(4):minEpochLength, address(20):TRANSFER_PROXY, uint32(4):protocolFeeNumerator, uint32(4):protocolFeeDenominator, uint32(4):minInterestBPS, address(20):feeTo, uint32(4):buyoutFeeNumerator, uint32(4):buyoutFeeDenominator, uint32(4):minDurationIncrease, address(20):BEACON_PROXY_IMPLEMENTATION, address(20):guardian, address(20):newGuardian
56      struct RouterStorage {
57        //slot 1
58        uint32 auctionWindow;
59        uint32 auctionWindowBuffer;
60        uint32 liquidationFeeNumerator;
61        uint32 liquidationFeeDenominator;
62        uint32 maxEpochLength;
63        uint32 minEpochLength;
64        uint32 protocolFeeNumerator;
65        uint32 protocolFeeDenominator;
66        //slot 2
67        ERC20 WETH; //20
68        ICollateralToken COLLATERAL_TOKEN; //20
69        ILienToken LIEN_TOKEN; //20
70        ITransferProxy TRANSFER_PROXY; //20
71        address feeTo; //20
72        address BEACON_PROXY_IMPLEMENTATION; //20
73        uint88 maxInterestRate; //6
74        uint32 minInterestBPS; // was uint64
75        //slot 3 +
76        address guardian; //20
77        address newGuardian; //20
78        uint32 buyoutFeeNumerator;
79        uint32 buyoutFeeDenominator;
80        uint32 minDurationIncrease;
81        mapping(uint8 => address) strategyValidators;
82        mapping(uint8 => address) implementations;
83        //A strategist can have many deployed vaults
84        mapping(address => bool) vaults;
85:     }

/// @audit Variable ordering with 2 slots instead of the current 3:
///           uint256(32):deadline, address(20):vault, uint8(1):version
101     struct StrategyDetailsParam {
102       uint8 version;
103       uint256 deadline;
104       address vault;
105:    }

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IAstariaRouter.sol#L56-L85

[G‑02] Using storage instead of memory for structs/arrays saves gas

When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct

There are 2 instances of this issue:

File: src/CollateralToken.sol

137:      Asset memory underlying = s.idToUnderlying[collateralId];

562:      Asset memory incomingAsset = s.idToUnderlying[collateralId];

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L137

[G‑03] The result of function calls should be cached rather than re-calling the function

The instances below point to the second+ call of the function within a single function

There are 3 instances of this issue:

File: src/ClearingHouse.sol

/// @audit ASTARIA_ROUTER.COLLATERAL_TOKEN() on line 162
166:      ASTARIA_ROUTER.COLLATERAL_TOKEN().settleAuction(collateralId);

/// @audit ASTARIA_ROUTER.COLLATERAL_TOKEN() on line 199
204:        ASTARIA_ROUTER.COLLATERAL_TOKEN().getConduit(),

/// @audit ASTARIA_ROUTER.COLLATERAL_TOKEN() on line 199
207:      ASTARIA_ROUTER.COLLATERAL_TOKEN().SEAPORT().validate(listings);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L166

[G‑04] internal functions only called once can be inlined to save gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

There are 21 instances of this issue:

File: src/AstariaRouter.sol

407     function _sliceUint(bytes memory bs, uint256 start)
408       internal
409       pure
410:      returns (uint256 x)

761     function _executeCommitment(
762       RouterStorage storage s,
763       IAstariaRouter.Commitment memory c
764     )
765       internal
766       returns (
767         uint256,
768         ILienToken.Stack[] memory stack,
769:        uint256 payout

788     function _transferAndDepositAssetIfAble(
789       RouterStorage storage s,
790       address tokenContract,
791:      uint256 tokenId

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L407-L410

File: src/BeaconProxy.sol

20:     function _getBeacon() internal pure returns (IBeacon) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/BeaconProxy.sol#L20

File: src/ClearingHouse.sol

114     function _execute(
115       address tokenContract, // collateral token sending the fake nft
116       address to, // buyer
117       uint256 encodedMetaData, //retrieve token address from the encoded data
118:      uint256 // space to encode whatever is needed,

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L114-L118

File: src/CollateralToken.sol

425     function _generateValidOrderParameters(
426       CollateralStorage storage s,
427       address settlementToken,
428       uint256 collateralId,
429       uint256[] memory prices,
430       uint256 maxDuration
431:    ) internal returns (OrderParameters memory orderParameters) {

502     function _listUnderlyingOnSeaport(
503       CollateralStorage storage s,
504       uint256 collateralId,
505:      Order memory listingOrder

541:    function _settleAuction(CollateralStorage storage s, uint256 collateralId)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L425-L431

File: src/LienToken.sol

122     function _buyoutLien(
123       LienStorage storage s,
124       ILienToken.LienActionBuyout calldata params
125:    ) internal returns (Stack[] memory newStack, Stack memory newLien) {

233     function _replaceStackAtPositionWithNewLien(
234       LienStorage storage s,
235       ILienToken.Stack[] calldata stack,
236       uint256 position,
237       Stack memory newLien,
238       uint256 oldLienId
239:    ) internal returns (ILienToken.Stack[] memory newStack) {

292     function _stopLiens(
293       LienStorage storage s,
294       uint256 collateralId,
295       uint256 auctionWindow,
296       Stack[] calldata stack,
297:      address liquidator

459     function _appendStack(
460       LienStorage storage s,
461       Stack[] memory stack,
462       Stack memory newSlot
463:    ) internal returns (Stack[] memory newStack) {

512     function _payDebt(
513       LienStorage storage s,
514       address token,
515       uint256 payment,
516       address payer,
517       AuctionStack[] memory stack
518:    ) internal returns (uint256 totalSpent) {

623     function _paymentAH(
624       LienStorage storage s,
625       address token,
626       AuctionStack[] memory stack,
627       uint256 position,
628       uint256 payment,
629       address payer
630:    ) internal returns (uint256) {

663     function _makePayment(
664       LienStorage storage s,
665       Stack[] calldata stack,
666       uint256 totalCapitalAvailable
667:    ) internal returns (Stack[] memory newStack) {

715     function _getMaxPotentialDebtForCollateralUpToNPositions(
716       Stack[] memory stack,
717       uint256 n
718:    ) internal pure returns (uint256 maxPotentialDebt) {

775     function _getRemainingInterest(LienStorage storage s, Stack memory stack)
776       internal
777       view
778:      returns (uint256)

855     function _removeStackPosition(Stack[] memory stack, uint8 position)
856       internal
857:      returns (Stack[] memory newStack)

911     function _setPayee(
912       LienStorage storage s,
913       uint256 lienId,
914:      address newPayee

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L122-L125

File: src/PublicVault.sol

556:    function _increaseOpenLiens(VaultData storage s, uint64 epoch) internal {

713     function _timeToSecondEndIfPublic()
714       internal
715       view
716       override
717:      returns (uint256 timeToSecondEpochEnd)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L556

[G‑05] Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement

require(a <= b); x = b - a => require(a <= b); unchecked { x = b - a }

There is 1 instance of this issue:

File: src/WithdrawProxy.sol

/// @audit if-condition on line 258
260:          (s.expected - balance).mulWadDown(1e18 - s.withdrawRatio)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L260

[G‑06] keccak256() should only need to be called on a specific string literal once

It should be saved to an immutable variable, and the variable used instead. If the hash is being used as a part of a function selector, the cast to bytes4 should also only be done once

There are 3 instances of this issue:

File: lib/gpl/src/ERC20-Cloned.sol

162             keccak256(
163               "EIP712Domain(string version,uint256 chainId,address verifyingContract)"
164:            ),

165:            keccak256("1"),

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC20-Cloned.sol#L162-L164

File: src/CollateralToken.sol

310:        ) != keccak256("FlashAction.onFlashAction")

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L310

[G‑07] Optimize names to save gas

public/external function names and public member variable names can be optimized to save gas. See this link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted

There are 18 instances of this issue:

File: lib/gpl/src/interfaces/IERC4626Router.sol

/// @audit depositToVault(), depositMax(), redeemMax()
10:   interface IERC4626Router {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/interfaces/IERC4626Router.sol#L10

File: src/AstariaRouter.sol

/// @audit initialize(), redeemFutureEpoch(), feeTo(), BEACON_PROXY_IMPLEMENTATION(), LIEN_TOKEN(), TRANSFER_PROXY(), COLLATERAL_TOKEN(), __emergencyPause(), __emergencyUnpause(), fileBatch(), file(), setNewGuardian(), __renounceGuardian(), __acceptGuardian(), fileGuardian(), getImpl(), getAuctionWindow(), validateCommitment(), commitToLiens(), newVault(), newPublicVault(), requestLienPosition(), canLiquidate(), liquidate(), getProtocolFee(), getLiquidatorFee(), getBuyoutFee(), isValidVault(), isValidRefinance()
50:   contract AstariaRouter is

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L50

File: src/AstariaVaultBase.sol

/// @audit ROUTER(), IMPL_TYPE(), START(), EPOCH_LENGTH(), VAULT_FEE(), COLLATERAL_TOKEN()
23:   abstract contract AstariaVaultBase is Clone, IAstariaVaultBase {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaVaultBase.sol#L23

File: src/ClearingHouse.sol

/// @audit ROUTER(), COLLATERAL_ID(), IMPL_TYPE(), setAuctionData(), validateOrder(), transferUnderlying(), settleLiquidatorNFTClaim()
32:   contract ClearingHouse is AmountDeriver, Clone, IERC1155, IERC721Receiver {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L32

File: src/CollateralToken.sol

/// @audit initialize(), SEAPORT(), liquidatorNFTClaim(), isValidOrder(), isValidOrderIncludingExtraData(), fileBatch(), file(), flashAction(), releaseToAddress(), getConduitKey(), getConduit(), getUnderlying(), securityHooks(), getClearingHouse(), auctionVault(), settleAuction()
63:   contract CollateralToken is

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L63

File: src/interfaces/IAstariaRouter.sol

/// @audit validateCommitment(), newPublicVault(), newVault(), feeTo(), commitToLiens(), requestLienPosition(), LIEN_TOKEN(), TRANSFER_PROXY(), BEACON_PROXY_IMPLEMENTATION(), COLLATERAL_TOKEN(), getAuctionWindow(), getProtocolFee(), getBuyoutFee(), getLiquidatorFee(), liquidate(), canLiquidate(), isValidVault(), fileBatch(), file(), setNewGuardian(), fileGuardian(), getImpl(), isValidRefinance()
28:   interface IAstariaRouter is IPausable, IBeacon {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IAstariaRouter.sol#L28

File: src/interfaces/IAstariaVaultBase.sol

/// @audit COLLATERAL_TOKEN(), START(), EPOCH_LENGTH(), VAULT_FEE()
20:   interface IAstariaVaultBase is IRouterBase {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IAstariaVaultBase.sol#L20

File: src/interfaces/ICollateralToken.sol

/// @audit fileBatch(), file(), flashAction(), securityHooks(), getConduit(), getConduitKey(), getClearingHouse(), auctionVault(), settleAuction(), SEAPORT(), getUnderlying(), releaseToAddress(), liquidatorNFTClaim()
31:   interface ICollateralToken is IERC721 {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/ICollateralToken.sol#L31

File: src/interfaces/ILienToken.sol

/// @audit validateLien(), ASTARIA_ROUTER(), COLLATERAL_TOKEN(), calculateSlope(), stopLiens(), getBuyout(), getOwed(), getOwed(), getInterest(), getCollateralState(), getAmountOwingAtLiquidation(), createLien(), buyoutLien(), payDebtViaClearingHouse(), makePayment(), makePayment(), getAuctionData(), getAuctionLiquidator(), getMaxPotentialDebtForCollateral(), getMaxPotentialDebtForCollateral(), getPayee(), file()
22:   interface ILienToken is IERC721 {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/ILienToken.sol#L22

File: src/interfaces/IPublicVault.sol

/// @audit updateAfterLiquidationPayment(), redeemFutureEpoch(), beforePayment(), decreaseEpochLienCount(), getLienEpoch(), afterPayment(), claim(), timeToEpochEnd(), timeToSecondEpochEnd(), transferWithdrawReserve(), processEpoch(), increaseYIntercept(), decreaseYIntercept(), handleBuyoutLien(), updateVaultAfterLiquidation()
19:   interface IPublicVault is IVaultImplementation {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IPublicVault.sol#L19

File: src/interfaces/IRouterBase.sol

/// @audit ROUTER(), IMPL_TYPE()
18:   interface IRouterBase {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IRouterBase.sol#L18

File: src/interfaces/IVaultImplementation.sol

/// @audit getShutdown(), incrementNonce(), commitToLien(), buyoutLien(), recipient(), setDelegate(), init(), encodeStrategyData(), domainSeparator(), modifyDepositCap(), getStrategistNonce(), STRATEGY_TYPEHASH()
21:   interface IVaultImplementation is IAstariaVaultBase, IERC165 {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IVaultImplementation.sol#L21

File: src/interfaces/IWithdrawProxy.sol

/// @audit VAULT(), CLAIMABLE_EPOCH(), setWithdrawRatio(), handleNewLiquidation(), drain(), claim(), increaseWithdrawReserveReceived(), getExpected(), getWithdrawRatio(), getFinalAuctionEnd()
21:   interface IWithdrawProxy is IRouterBase, IERC165, IERC4626 {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/interfaces/IWithdrawProxy.sol#L21

File: src/LienToken.sol

/// @audit initialize(), file(), buyoutLien(), getInterest(), stopLiens(), ASTARIA_ROUTER(), COLLATERAL_TOKEN(), createLien(), payDebtViaClearingHouse(), getAuctionData(), getAuctionLiquidator(), getAmountOwingAtLiquidation(), validateLien(), getCollateralState(), getBuyout(), makePayment(), makePayment(), calculateSlope(), getMaxPotentialDebtForCollateral(), getMaxPotentialDebtForCollateral(), getOwed(), getOwed(), getPayee()
44:   contract LienToken is ERC721, ILienToken, AuthInitializable {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L44

File: src/PublicVault.sol

/// @audit redeemFutureEpoch(), getWithdrawProxy(), getCurrentEpoch(), getSlope(), getWithdrawReserve(), getLiquidationWithdrawRatio(), getYIntercept(), processEpoch(), transferWithdrawReserve(), accrue(), claim(), beforePayment(), decreaseEpochLienCount(), getLienEpoch(), getEpochEnd(), afterPayment(), LIEN_TOKEN(), handleBuyoutLien(), updateAfterLiquidationPayment(), updateVaultAfterLiquidation(), increaseYIntercept(), decreaseYIntercept(), timeToEpochEnd(), timeToEpochEnd(), timeToSecondEpochEnd()
48:   contract PublicVault is VaultImplementation, IPublicVault, ERC4626Cloned {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L48

File: src/VaultImplementation.sol

/// @audit getStrategistNonce(), incrementNonce(), modifyDepositCap(), modifyAllowList(), disableAllowList(), enableAllowList(), getShutdown(), domainSeparator(), encodeStrategyData(), init(), setDelegate(), commitToLien(), buyoutLien(), recipient()
34:   abstract contract VaultImplementation is

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L34

File: src/WithdrawProxy.sol

/// @audit getFinalAuctionEnd(), getWithdrawRatio(), getExpected(), increaseWithdrawReserveReceived(), claim(), drain(), setWithdrawRatio(), handleNewLiquidation()
37:   contract WithdrawProxy is ERC4626Cloned, WithdrawVaultBase {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L37

File: src/WithdrawVaultBase.sol

/// @audit ROUTER(), VAULT(), CLAIMABLE_EPOCH()
21:   abstract contract WithdrawVaultBase is Clone, IWithdrawProxy {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawVaultBase.sol#L21

[G‑08] Use a more recent version of solidity

Use a solidity version of at least 0.8.0 to get overflow protection without SafeMath Use a solidity version of at least 0.8.2 to get simple compiler automatic inlining Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value

There are 3 instances of this issue:

File: lib/gpl/src/interfaces/IMulticall.sol

4:    pragma solidity >=0.7.5;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/interfaces/IMulticall.sol#L4

File: lib/gpl/src/interfaces/IUniswapV3Factory.sol

2:    pragma solidity >=0.5.0;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/interfaces/IUniswapV3Factory.sol#L2

File: lib/gpl/src/interfaces/IUniswapV3PoolState.sol

2:    pragma solidity >=0.5.0;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/interfaces/IUniswapV3PoolState.sol#L2

[G‑09] Remove unused local variable

There is 1 instance of this issue:

File: src/ClearingHouse.sol

/// @audit stack
139:      ILienToken.AuctionStack[] storage stack = s.auctionStack.stack;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L139

[G‑10] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)

Saves 5 gas per loop

There are 3 instances of this issue:

File: lib/gpl/src/ERC721.sol

136:        s._balanceOf[from]--;

223:        s._balanceOf[owner]--;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC721.sol#L136

File: src/PublicVault.sol

539:      s.epochData[epoch].liensOpenForEpoch--;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L539

[G‑11] Splitting require() statements that use && saves gas

See this issue which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper by 3 gas

There are 4 instances of this issue:

File: lib/gpl/src/ERC20-Cloned.sol

143         require(
144           recoveredAddress != address(0) && recoveredAddress == owner,
145           "INVALID_SIGNER"
146:        );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC20-Cloned.sol#L143-L146

File: src/PublicVault.sol

672       require(
673         currentEpoch != 0 &&
674           msg.sender == s.epochData[currentEpoch - 1].withdrawProxy
675:      );

687       require(
688         currentEpoch != 0 &&
689           msg.sender == s.epochData[currentEpoch - 1].withdrawProxy
690:      );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L672-L675

File: src/Vault.sol

65:       require(s.allowList[msg.sender] && receiver == owner());

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/Vault.sol#L65

[G‑12] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Each operation involving a uint8 costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8) as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed

There are 2 instances of this issue:

File: src/AstariaRouter.sol

/// @audit uint8 vaultType
725:        vaultType = uint8(ImplementationType.PublicVault);

/// @audit uint8 vaultType
727:        vaultType = uint8(ImplementationType.PrivateVault);

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L725

[G‑13] Inverting the condition of an if-else-statement wastes gas

Flipping the true and false blocks instead saves 3 gas

There is 1 instance of this issue:

File: src/CollateralToken.sol

234         if (!exists) {
235           s.CONDUIT = s.CONDUIT_CONTROLLER.createConduit(
236             s.CONDUIT_KEY,
237             address(this)
238           );
239         } else {
240           s.CONDUIT = conduit;
241:        }

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L234-L241

[G‑14] require() or revert() statements that check input arguments should be at the top of the function

Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas*) in a function that may ultimately revert in the unhappy case.

There is 1 instance of this issue:

File: lib/gpl/src/ERC721.sol

/// @audit expensive op on line 111
115:      require(to != address(0), "INVALID_RECIPIENT");

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC721.sol#L115

[G‑15] Use custom errors rather than revert()/require() strings to save gas

Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas

There are 8 instances of this issue:

File: lib/gpl/src/ERC20-Cloned.sol

143         require(
144           recoveredAddress != address(0) && recoveredAddress == owner,
145           "INVALID_SIGNER"
146:        );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC20-Cloned.sol#L143-L146

File: lib/gpl/src/ERC721.sol

53        require(
54          (owner = _loadERC721Slot()._ownerOf[id]) != address(0),
55          "NOT_MINTED"
56:       );

90        require(
91          msg.sender == owner || s.isApprovedForAll[owner][msg.sender],
92          "NOT_AUTHORIZED"
93:       );

117       require(
118         msg.sender == from ||
119           s.isApprovedForAll[from][msg.sender] ||
120           msg.sender == s.getApproved[id],
121         "NOT_AUTHORIZED"
122:      );

155       require(
156         to.code.length == 0 ||
157           ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
158           ERC721TokenReceiver.onERC721Received.selector,
159         "UNSAFE_RECIPIENT"
160:      );

171       require(
172         to.code.length == 0 ||
173           ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
174           ERC721TokenReceiver.onERC721Received.selector,
175         "UNSAFE_RECIPIENT"
176:      );

240       require(
241         to.code.length == 0 ||
242           ERC721TokenReceiver(to).onERC721Received(
243             msg.sender,
244             address(0),
245             id,
246             ""
247           ) ==
248           ERC721TokenReceiver.onERC721Received.selector,
249         "UNSAFE_RECIPIENT"
250:      );

260       require(
261         to.code.length == 0 ||
262           ERC721TokenReceiver(to).onERC721Received(
263             msg.sender,
264             address(0),
265             id,
266             data
267           ) ==
268           ERC721TokenReceiver.onERC721Received.selector,
269         "UNSAFE_RECIPIENT"
270:      );

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC721.sol#L53-L56

[G‑16] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost

There are 21 instances of this issue:

File: src/AstariaRouter.sol

252:    function __emergencyPause() external requiresAuth whenNotPaused {

259:    function __emergencyUnpause() external requiresAuth whenPaused {

263:    function fileBatch(File[] calldata files) external requiresAuth {

273:    function file(File calldata incoming) public requiresAuth {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L252

File: src/CollateralToken.sol

196:    function fileBatch(File[] calldata files) external requiresAuth {

206:    function file(File calldata incoming) public requiresAuth {

270     function flashAction(
271       IFlashAction receiver,
272       uint256 collateralId,
273       bytes calldata data
274:    ) external onlyOwner(collateralId) {

331     function releaseToAddress(uint256 collateralId, address releaseTo)
332       public
333       releaseCheck(collateralId)
334:      onlyOwner(collateralId)

477     function auctionVault(AuctionVaultParams calldata params)
478       external
479       requiresAuth
480:      returns (OrderParameters memory orderParameters)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L196

File: src/LienToken.sol

82:     function file(File calldata incoming) external requiresAuth {

277     function stopLiens(
278       uint256 collateralId,
279       uint256 auctionWindow,
280       Stack[] calldata stack,
281       address liquidator
282:    ) external validateStack(collateralId, stack) requiresAuth {

389     function createLien(ILienToken.LienActionEncumber memory params)
390       external
391       requiresAuth
392       validateStack(params.lien.collateralId, params.stack)
393       returns (
394         uint256 lienId,
395         Stack[] memory newStack,
396:        uint256 lienSlope

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L82

File: src/PublicVault.sol

515     function beforePayment(BeforePaymentParams calldata params)
516       external
517:      onlyLienToken

615     function handleBuyoutLien(BuyoutLienParams calldata params)
616       public
617:      onlyLienToken

632     function updateAfterLiquidationPayment(
633       LiquidationPaymentParams calldata params
634:    ) external onlyLienToken {

640     function updateVaultAfterLiquidation(
641       uint256 maxAuctionWindow,
642       AfterLiquidationParams calldata params
643:    ) public onlyLienToken returns (address withdrawProxyIfNearBoundary) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L515-L517

File: src/TransferProxy.sol

28      function tokenTransferFrom(
29        address token,
30        address from,
31        address to,
32        uint256 amount
33:     ) external requiresAuth {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/TransferProxy.sol#L28-L33

File: src/WithdrawProxy.sol

163     function withdraw(
164       uint256 assets,
165       address receiver,
166       address owner
167     )
168       public
169       virtual
170       override(ERC4626Cloned, IERC4626)
171       onlyWhenNoActiveAuction
172:      returns (uint256 shares)

184     function redeem(
185       uint256 shares,
186       address receiver,
187       address owner
188     )
189       public
190       virtual
191       override(ERC4626Cloned, IERC4626)
192       onlyWhenNoActiveAuction
193:      returns (uint256 assets)

289     function drain(uint256 amount, address withdrawProxy)
290       public
291       onlyVault
292:      returns (uint256)

306     function handleNewLiquidation(
307       uint256 newLienExpectedValue,
308       uint256 finalAuctionDelta
309:    ) public onlyVault {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L163-L172


Excluded findings

These findings are excluded from awards calculations because there are publicly-available automated tools that find them. The valid ones appear here for completeness

Summary

Gas Optimizations

IssueInstancesTotal Gas Saved
[G‑01]Using calldata instead of memory for read-only arguments in external functions saves gas101200
[G‑02]<array>.length should not be looked up in every loop of a for-loop1030
[G‑03]++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)525
[G‑04]Using private rather than public for constants, saves gas1-
[G‑05]Use custom errors rather than revert()/require() strings to save gas15-
[G‑06]Functions guaranteed to revert when called by normal users can be marked payable484

Total: 45 instances over 6 issues with 1339 gas saved

Gas totals use lower bounds of ranges and count two iterations of each for-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions. The table above as well as its gas numbers do not include any of the excluded findings.

Gas Optimizations

[G‑01] Using calldata instead of memory for read-only arguments in external functions saves gas

When a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution. Note that even if an interface defines a function as having memory arguments, it's still valid for implementation contracs to use calldata arguments instead.

If the array is passed to an internal function which passes the array to another internal function where the array is modified and therefore memory is used in the external call, it's still more gass-efficient to use calldata when the external function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one

Note that I've also flagged instances where the function is public but can be marked as external since it's not called by the contract, and cases where a constructor is involved

There are 10 instances of this issue:

File: src/AstariaRouter.sol

/// @audit commitments - (valid but excluded finding)
490     function commitToLiens(IAstariaRouter.Commitment[] memory commitments)
491       public
492       whenNotPaused
493:      returns (uint256[] memory lienIds, ILienToken.Stack[] memory stack)

/// @audit stack - (valid but excluded finding)
621     function liquidate(ILienToken.Stack[] memory stack, uint8 position)
622       public
623:      returns (OrderParameters memory listedOrder)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L490-L493

File: src/ClearingHouse.sol

/// @audit order - (valid but excluded finding)
197:    function validateOrder(Order memory order) external {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L197

File: src/CollateralToken.sol

/// @audit params - (valid but excluded finding)
109:    function liquidatorNFTClaim(OrderParameters memory params) external {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L109

File: src/LienToken.sol

/// @audit params - (valid but excluded finding)
389     function createLien(ILienToken.LienActionEncumber memory params)
390       external
391       requiresAuth
392       validateStack(params.lien.collateralId, params.stack)
393       returns (
394         uint256 lienId,
395         Stack[] memory newStack,
396:        uint256 lienSlope

/// @audit auctionStack - (valid but excluded finding)
497     function payDebtViaClearingHouse(
498       address token,
499       uint256 collateralId,
500       uint256 payment,
501:      AuctionStack[] memory auctionStack

/// @audit stack - (valid but excluded finding)
706     function getMaxPotentialDebtForCollateral(Stack[] memory stack)
707       public
708       view
709       validateStack(stack[0].lien.collateralId, stack)
710:      returns (uint256 maxPotentialDebt)

/// @audit stack - (valid but excluded finding)
727     function getMaxPotentialDebtForCollateral(Stack[] memory stack, uint256 end)
728       public
729       view
730       validateStack(stack[0].lien.collateralId, stack)
731:      returns (uint256 maxPotentialDebt)

/// @audit stack - (valid but excluded finding)
742:    function getOwed(Stack memory stack) external view returns (uint88) {

/// @audit stack - (valid but excluded finding)
747     function getOwed(Stack memory stack, uint256 timestamp)
748       external
749       view
750:      returns (uint88)

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L389-L396

[G‑02] <array>.length should not be looked up in every loop of a for-loop

The overheads outlined below are PER LOOP, excluding the first loop

  • storage arrays incur a Gwarmaccess (100 gas)
  • memory arrays use MLOAD (3 gas)
  • calldata arrays use CALLDATALOAD (3 gas)

Caching the length changes each of these to a DUP<N> (3 gas), and gets rid of the extra DUP<N> needed to store the stack offset

There are 10 instances of this issue:

File: src/AstariaRouter.sol

/// @audit (valid but excluded finding)
265:      for (; i < files.length; ) {

/// @audit (valid but excluded finding)
364:      for (; i < file.length; ) {

/// @audit (valid but excluded finding)
506:      for (; i < commitments.length; ) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/AstariaRouter.sol#L265

File: src/ClearingHouse.sol

/// @audit (valid but excluded finding)
96:       for (uint256 i; i < accounts.length; ) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L96

File: src/CollateralToken.sol

/// @audit (valid but excluded finding)
198:      for (; i < files.length; ) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/CollateralToken.sol#L198

File: src/LienToken.sol

/// @audit (valid but excluded finding)
304:      for (; i < stack.length; ) {

/// @audit (valid but excluded finding)
520:      for (; i < stack.length;) {

/// @audit (valid but excluded finding)
669:      for (uint256 i; i < newStack.length; ) {

/// @audit (valid but excluded finding)
734:      for (; i < stack.length; ) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/LienToken.sol#L304

File: src/VaultImplementation.sol

/// @audit (valid but excluded finding)
201:        for (; i < params.allowList.length; ) {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L201

[G‑03] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)

Saves 5 gas per loop

There are 5 instances of this issue:

File: lib/gpl/src/ERC721.sol

/// @audit (valid but excluded finding)
138:        s._balanceOf[to]++;

/// @audit (valid but excluded finding)
206:        s._balanceOf[to]++;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC721.sol#L138

File: src/PublicVault.sol

/// @audit (valid but excluded finding)
341:        s.currentEpoch++;

/// @audit (valid but excluded finding)
558:        s.epochData[epoch].liensOpenForEpoch++;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L341

File: src/VaultImplementation.sol

/// @audit (valid but excluded finding)
69:       s.strategistNonce++;

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L69

[G‑04] Using private rather than public for constants, saves gas

If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table

There is 1 instance of this issue:

File: src/VaultImplementation.sol

/// @audit (valid but excluded finding)
44      bytes32 public constant STRATEGY_TYPEHASH =
45:       keccak256("StrategyDetails(uint256 nonce,uint256 deadline,bytes32 root)");

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/VaultImplementation.sol#L44-L45

[G‑05] Use custom errors rather than revert()/require() strings to save gas

Custom errors are available from solidity version 0.8.4. Custom errors save ~50 gas each time they're hit by avoiding having to allocate and store the revert string. Not defining the strings also save deployment gas

There are 15 instances of this issue:

File: lib/gpl/src/ERC20-Cloned.sol

/// @audit (valid but excluded finding)
115:      require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC20-Cloned.sol#L115

File: lib/gpl/src/ERC4626-Cloned.sol

/// @audit (valid but excluded finding)
25:       require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");

/// @audit (valid but excluded finding)
27:       require(shares > minDepositAmount(), "VALUE_TOO_SMALL");

/// @audit (valid but excluded finding)
43:       require(assets > minDepositAmount(), "VALUE_TOO_SMALL");

/// @audit (valid but excluded finding)
94:       require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC4626-Cloned.sol#L25

File: lib/gpl/src/ERC721.sol

/// @audit (valid but excluded finding)
60:       require(owner != address(0), "ZERO_ADDRESS");

/// @audit (valid but excluded finding)
113:      require(from == s._ownerOf[id], "WRONG_FROM");

/// @audit (valid but excluded finding)
115:      require(to != address(0), "INVALID_RECIPIENT");

/// @audit (valid but excluded finding)
200:      require(to != address(0), "INVALID_RECIPIENT");

/// @audit (valid but excluded finding)
202:      require(s._ownerOf[id] == address(0), "ALREADY_MINTED");

/// @audit (valid but excluded finding)
219:      require(owner != address(0), "NOT_MINTED");

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/lib/gpl/src/ERC721.sol#L60

File: src/ClearingHouse.sol

/// @audit (valid but excluded finding)
134:      require(payment >= currentOfferPrice, "not enough funds received");

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/ClearingHouse.sol#L134

File: src/PublicVault.sol

/// @audit (valid but excluded finding)
170:      require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L170

File: src/WithdrawProxy.sol

/// @audit (valid but excluded finding)
138:      require(msg.sender == VAULT(), "only vault can mint");

/// @audit (valid but excluded finding)
231:      require(msg.sender == VAULT(), "only vault can call");

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L138

[G‑06] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost

There are 4 instances of this issue:

File: src/PublicVault.sol

/// @audit (valid but excluded finding)
534:    function decreaseEpochLienCount(uint64 epoch) public onlyLienToken {

/// @audit (valid but excluded finding)
562:    function afterPayment(uint256 computedSlope) public onlyLienToken {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/PublicVault.sol#L534

File: src/WithdrawProxy.sol

/// @audit (valid but excluded finding)
235:    function increaseWithdrawReserveReceived(uint256 amount) external onlyVault {

/// @audit (valid but excluded finding)
302:    function setWithdrawRatio(uint256 liquidationWithdrawRatio) public onlyVault {

https://github.com/code-423n4/2023-01-astaria/blob/ca120317842c882572516c571b801f3594b8c40c/src/WithdrawProxy.sol#L235

#0 - c4-judge

2023-01-25T23:28:29Z

Picodes marked the issue as grade-b

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