Issue | Instances | |
---|---|---|
GAS-1 | Use assembly to check for address(0) |
43 |
GAS-2 | Using bools for storage incurs overhead | 4 |
GAS-3 | Cache array length outside of loop | 12 |
GAS-4 | Use calldata instead of memory for function arguments that do not get mutated | 21 |
GAS-5 | Use Custom Errors | 15 |
GAS-6 | Don't initialize variables with default value | 3 |
GAS-7 | Functions guaranteed to revert when called by normal users can be marked payable |
4 |
GAS-8 | ++i costs less gas than i++ , especially when it's used in for -loops (--i /i-- too) |
5 |
GAS-9 | Using private rather than public for constants, saves gas |
1 |
GAS-10 | Use != 0 instead of > 0 for unsigned integer comparison | 17 |
GAS-11 | internal functions not called by the contract should be removed |
1 |
Saves 6 gas per instance
Instances (43):
File: lib/gpl/src/ERC721.sol
54: (owner = _loadERC721Slot()._ownerOf[id]) != address(0),
60: require(owner != address(0), "ZERO_ADDRESS");
115: require(to != address(0), "INVALID_RECIPIENT");
200: require(to != address(0), "INVALID_RECIPIENT");
202: require(s._ownerOf[id] == address(0), "ALREADY_MINTED");
219: require(owner != address(0), "NOT_MINTED");
File: src/AstariaRouter.sol
342: s.newGuardian = _guardian;
349: s.newGuardian = address(0);
379: } else if (what == FileType.TransferProxy) {
384: revert UnsupportedFile();
396: impl = _loadRouterSlot().implementations[implType];
402: function getAuctionWindow(bool includeBuffer) public view returns (uint256) {
418: mstore(0, OUTOFBOUND_ERROR_SELECTOR)
459: revert InvalidCommitmentState(CommitmentState.INVALID_RATE);
476: if (timeToSecondEpochEnd > 0 && details.duration > timeToSecondEpochEnd) {
File: src/CollateralToken.sol
134: revert InvalidCollateralState(InvalidCollateralStates.AUCTION_ACTIVE);
134: revert InvalidCollateralState(InvalidCollateralStates.AUCTION_ACTIVE);
243: address(s.CONDUIT),
274: ) external onlyOwner(collateralId) {
278: (addr, tokenId) = getUnderlying(collateralId);
311: ) {
336: CollateralStorage storage s = _loadCollateralSlot();
541: function _settleAuction(CollateralStorage storage s, uint256 collateralId)
576: s.clearingHouse[collateralId] = clearingHouse;
581: tokenId_
File: src/LienToken.sol
286: auctionWindow,
294: uint256 collateralId,
342: auctionData.endAmount = uint88(1000 wei);
404: newStack = _appendStack(s, params.stack, newStackSlot);
564: if (!_exists(lienId)) {
920:
File: src/PublicVault.sol
235: override(ERC4626Cloned)
312: uint256 proxySupply = currentWithdrawProxy.totalSupply();
325: .safeCastTo88();
376: withdrawBalance = s.withdrawReserve;
385: WithdrawProxy(currentWithdrawProxy).increaseWithdrawReserveReceived(
413: function _beforeCommitToLien(IAstariaRouter.Commitment calldata params)
444: VaultData storage s = _loadStorageSlot();
619: VaultData storage s = _loadStorageSlot();
File: src/VaultImplementation.sol
213: s.delegate = delegate_;
281: * Origination consists of a few phases: pre-commitment validation, lien token issuance, strategist reward, and after commitment actions
411:
File: src/WithdrawProxy.sol
289: function drain(uint256 amount, address withdrawProxy)
Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past. See source.
Instances (4):
File: lib/gpl/src/ERC721.sol
23: mapping(address => mapping(address => bool)) isApprovedForAll;
File: src/interfaces/IAstariaRouter.sol
84: mapping(address => bool) vaults;
File: src/interfaces/ICollateralToken.sol
60: mapping(address => bool) flashEnabled;
File: src/interfaces/IVaultImplementation.sol
49: mapping(address => bool) allowList;
If not cached, the solidity compiler will always read the length of the array during each iteration. That is, if it is a storage array, this is an extra sload operation (100 additional extra gas for each iteration except for the first) and if it is a memory array, this is an extra mload operation (3 additional gas for each iteration except for the first).
Instances (12):
File: src/AstariaRouter.sol
265: for (; i < files.length; ) {
364: for (; i < file.length; ) {
506: for (; i < commitments.length; ) {
File: src/ClearingHouse.sol
96: for (uint256 i; i < accounts.length; ) {
File: src/CollateralToken.sol
198: for (; i < files.length; ) {
File: src/LienToken.sol
153: for (uint256 i = params.encumber.stack.length; i > 0; ) {
304: for (; i < stack.length; ) {
472: for (uint256 i = stack.length; i > 0; ) {
520: for (; i < stack.length;) {
669: for (uint256 i; i < newStack.length; ) {
734: for (; i < stack.length; ) {
File: src/VaultImplementation.sol
201: for (; i < params.allowList.length; ) {
Mark data types as calldata
instead of memory
where possible. This makes it so that the data is not automatically loaded into memory. If the data passed into the function does not need to be changed (like updating values in an array), it can be passed in as calldata
. The one exception to this is if the argument must later be passed into another function that takes an argument that specifies memory
storage.
Instances (21):
File: src/AstariaRouter.sol
510: uint256 payout;
629: RouterStorage storage s = _loadRouterSlot();
638: emit Liquidation(stack[position].lien.collateralId, position);
File: src/ClearingHouse.sol
211: address tokenContract,
File: src/CollateralToken.sol
124: s.collateralIdToAuction[collateralId] != keccak256(abi.encode(params))
File: src/LienToken.sol
406: abi.encode(newStack)
520: for (; i < stack.length;) {
582: return _getBuyout(_loadLienStorageSlot(), stack);
718: ) internal pure returns (uint256 maxPotentialDebt) {
727: function getMaxPotentialDebtForCollateral(Stack[] memory stack, uint256 end)
749: view
761: function _getOwed(Stack memory stack, uint256 timestamp)
766: return stack.point.amount + _getInterest(stack, timestamp).safeCastTo88();
File: src/interfaces/IAstariaRouter.sol
199: function LIEN_TOKEN() external view returns (ILienToken);
File: src/interfaces/ICollateralToken.sol
186: error FlashActionNFTNotReturned();
File: src/interfaces/ILienToken.sol
200: * @notice Called by the ClearingHouse (through Seaport) to pay back debt with auction funds.
212: * @notice Make a payment for the debt against a CollateralToken.
227: ) external returns (Stack[] memory newStack);
245: * @notice Retrieves the AuctionData for a CollateralToken (The liquidator address and the AuctionStack).
282: * @notice Retrieve the payee (address that receives payments and auction funds) for a specified Lien.
292: function file(File calldata file) external;
Source Instead of using error strings, to reduce deployment and runtime cost, you should use Custom Errors. This would save both deployment and runtime cost.
Instances (15):
File: lib/gpl/src/ERC4626-Cloned.sol
25: require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");
27: require(shares > minDepositAmount(), "VALUE_TOO_SMALL");
43: require(assets > minDepositAmount(), "VALUE_TOO_SMALL");
94: require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");
File: lib/gpl/src/ERC721.sol
60: require(owner != address(0), "ZERO_ADDRESS");
113: require(from == s._ownerOf[id], "WRONG_FROM");
115: require(to != address(0), "INVALID_RECIPIENT");
200: require(to != address(0), "INVALID_RECIPIENT");
202: require(s._ownerOf[id] == address(0), "ALREADY_MINTED");
219: require(owner != address(0), "NOT_MINTED");
File: src/AstariaRouter.sol
398: revert("unsupported/impl");
File: src/ClearingHouse.sol
134: require(payment >= currentOfferPrice, "not enough funds received");
File: src/PublicVault.sol
170: require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");
File: src/WithdrawProxy.sol
138: require(msg.sender == VAULT(), "only vault can mint");
231: require(msg.sender == VAULT(), "only vault can call");
Instances (3):
File: src/LienToken.sol
152: uint256 potentialDebt = 0;
636: uint256 remaining = 0;
File: src/WithdrawProxy.sol
254: uint256 transferAmount = 0;
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.
Instances (4):
File: src/PublicVault.sol
534: function decreaseEpochLienCount(uint64 epoch) public onlyLienToken {
562: function afterPayment(uint256 computedSlope) public onlyLienToken {
File: src/WithdrawProxy.sol
235: function increaseWithdrawReserveReceived(uint256 amount) external onlyVault {
302: function setWithdrawRatio(uint256 liquidationWithdrawRatio) public onlyVault {
Saves 5 gas per loop
Instances (5):
File: lib/gpl/src/ERC721.sol
138: s._balanceOf[to]++;
206: s._balanceOf[to]++;
File: src/PublicVault.sol
341: s.currentEpoch++;
558: s.epochData[epoch].liensOpenForEpoch++;
File: src/VaultImplementation.sol
69: s.strategistNonce++;
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
Instances (1):
File: src/VaultImplementation.sol
44: bytes32 public constant STRATEGY_TYPEHASH =
Instances (17):
File: lib/gpl/src/ERC4626-Cloned.sol
2: pragma solidity >=0.8.16;
File: lib/gpl/src/interfaces/IMulticall.sol
4: pragma solidity >=0.7.5;
File: lib/gpl/src/interfaces/IUniswapV3Factory.sol
2: pragma solidity >=0.5.0;
File: lib/gpl/src/interfaces/IUniswapV3PoolState.sol
2: pragma solidity >=0.5.0;
File: src/AstariaRouter.sol
476: if (timeToSecondEpochEnd > 0 && details.duration > timeToSecondEpochEnd) {
File: src/ClearingHouse.sol
160: if (ERC20(paymentToken).balanceOf(address(this)) > 0) {
File: src/LienToken.sol
153: for (uint256 i = params.encumber.stack.length; i > 0; ) {
438: if (params.stack.length > 0) {
472: for (uint256 i = stack.length; i > 0; ) {
491: stack.length > 0 && potentialDebt > newSlot.lien.details.maxPotentialDebt
642: if (payment > 0)
File: src/PublicVault.sol
277: if (timeToEpochEnd() > 0) {
282: if (s.withdrawReserve > 0) {
303: if (s.epochData[s.currentEpoch].liensOpenForEpoch > 0) {
393: s.withdrawReserve > 0 &&
636: if (params.remaining > 0)
File: src/WithdrawProxy.sol
280: if (balance > 0) {
If the functions are required by an interface, the contract should inherit from that interface and use the override
keyword
Instances (1):
File: lib/gpl/src/ERC721.sol
69: function __initERC721(string memory _name, string memory _symbol) internal {
Issue | Instances | |
---|---|---|
NC-1 | require() / revert() statements should have descriptive reason strings |
32 |
NC-2 | Return values of approve() not checked |
1 |
NC-3 | Event is missing indexed fields |
30 |
NC-4 | Constants should be defined rather than using magic numbers | 19 |
NC-5 | Functions not used internally could be marked external | 83 |
Instances (32):
File: src/AstariaRouter.sol
360: RouterStorage storage s = _loadRouterSlot();
365: FileType what = file[i].what;
369: if (addr == address(0)) revert InvalidFileData();
373: if (addr == address(0)) revert InvalidFileData();
579: address receiver
587: uint256
File: src/ClearingHouse.sol
93: returns (uint256[] memory output)
215: IAstariaRouter ASTARIA_ROUTER = IAstariaRouter(_getArgAddress(0));
230: );
233:
File: src/CollateralToken.sol
285: s.LIEN_TOKEN.getCollateralState(collateralId) == bytes32("ACTIVE_AUCTION")
553: function onERC721Received(
578: ERC721(msg.sender).safeTransferFrom(
File: src/LienToken.sol
523: spent = _paymentAH(s, token, stack, i, payment, payer);
884: internal
File: src/PublicVault.sol
262: uint256 assets = totalAssets();
280: VaultData storage s = _loadStorageSlot();
526: _handleStrategistInterestReward(s, params.interestOwed, params.amount);
691: _setYIntercept(s, s.yIntercept - amount);
696: emit YInterceptChanged(s.yIntercept);
703: function timeToEpochEnd(uint256 epoch) public view returns (uint256) {
File: src/Vault.sol
80: function enableAllowList() external pure override(VaultImplementation) {
85: function modifyAllowList(address, bool)
File: src/VaultImplementation.sol
96: require(msg.sender == owner()); //owner is "strategist"
114: require(msg.sender == owner()); //owner is "strategist"
124: address, // from_
133: revert InvalidRequest(InvalidRequestReason.PAUSED);
168: * @param amount The amount of the token
211: require(msg.sender == owner()); //owner is "strategist"
222: * if a user, then ensure that the user is approved to borrow and is also receiving the funds.
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.
344: commitment: incomingTerms,
Not all IERC20 implementations revert()
when there's a failure in approve()
. The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually approving anything
Instances (1):
File: src/ClearingHouse.sol
203: ERC721(order.parameters.offer[0].token).approve(
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.
Instances (30):
File: src/PublicVault.sol
477: * @return The implied value for this PublicVault.
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.
File: src/interfaces/IAstariaRouter.sol
72: address BEACON_PROXY_IMPLEMENTATION; //20
310: error InvalidCollateralState(CollateralStates);
312: error InvalidStrategy(uint16);
File: src/interfaces/ICollateralToken.sol
54: IAstariaRouter ASTARIA_ROUTER;
55: ConsiderationInterface SEAPORT;
60: mapping(address => bool) flashEnabled;
File: src/interfaces/IERC1155.sol
42: event ApprovalForAll(
55: event URI(string value, uint256 indexed id);
File: src/interfaces/IERC4626.sol
15: event Deposit(
File: src/interfaces/IERC721.sol
14: event ApprovalForAll(
File: src/interfaces/ILienToken.sol
55: uint256 duration;
315: event PayeeChanged(uint256 indexed lienId, address indexed payee);
320: error InvalidTerms();
328: NOT_ENOUGH_FUNDS,
330: COLLATERAL_AUCTION,
File: src/interfaces/IPublicVault.sol
174:
174:
174:
174:
174:
File: src/interfaces/IV3PositionManager.sol
10: event IncreaseLiquidity(
21: event DecreaseLiquidity(
33: event Collect(
File: src/interfaces/IVaultImplementation.sol
78: uint256 payout
81: function buyoutLien(
82: ILienToken.Stack[] calldata stack,
84: IAstariaRouter.Commitment calldata incomingTerms
84: IAstariaRouter.Commitment calldata incomingTerms
Instances (19):
File: src/AstariaRouter.sol
110: s.liquidationFeeNumerator = uint32(130);
112: s.minInterestBPS = uint32((uint256(1e15) * 5) / (365 days));
114: s.maxEpochLength = uint32(45 days);
115: s.maxInterestRate = ((uint256(1e16) * 200) / (365 days)).safeCastTo88();
File: src/AstariaVaultBase.sol
33: return _getArgUint8(20); //ends at 21
37: return _getArgAddress(21); //ends at 44
47: return _getArgAddress(41); //ends at 64
51: return _getArgUint256(61);
55: return _getArgUint256(93); //ends at 116
59: return _getArgUint256(125);
File: src/BeaconProxy.sol
62: _delegate(_getBeacon().getImpl(_getArgUint8(20)));
File: src/ClearingHouse.sol
48: return _getArgUint256(21);
52: return _getArgUint8(20);
136: uint256 collateralId = _getArgUint256(21);
File: src/PublicVault.sol
103: if (ERC20(asset()).decimals() == uint8(18)) {
File: src/WithdrawVaultBase.sol
31: return _getArgUint8(20);
35: return _getArgAddress(21);
39: return _getArgAddress(41);
43: return _getArgUint64(61);
Instances (83):
File: lib/gpl/src/ERC4626Router.sol
35: function depositMax(
49: function redeemMax(
File: lib/gpl/src/ERC721.sol
26: function getApproved(uint256 tokenId) public view returns (address) {
30: function isApprovedForAll(address owner, address operator)
79: function name() public view returns (string memory) {
83: function symbol() public view returns (string memory) {
File: src/AstariaRouter.sol
225: RouterStorage storage s = _loadRouterSlot();
240: RouterStorage storage s = _loadRouterSlot();
245: RouterStorage storage s = _loadRouterSlot();
251: */
257: * @dev Disables _pause, un-freezing functions with the whenNotPaused modifier.
263: function fileBatch(File[] calldata files) external requiresAuth {
288: } else if (what == FileType.LiquidationFee) {
422: x := mload(add(bs, end))
439: if (block.timestamp > commitment.lienRequest.strategy.deadline) {
508: commitments[i].lienRequest.stack = stack;
561: IPublicVault.InvalidStates.EPOCH_TOO_HIGH
638: emit Liquidation(stack[position].lien.collateralId, position);
698: newLien.details.duration + block.timestamp >=
File: src/AstariaVaultBase.sol
55: return _getArgUint256(93); //ends at 116
59: return _getArgUint256(125);
66:
66:
66:
66:
File: src/ClearingHouse.sol
66: function setAuctionData(ILienToken.AuctionData calldata auctionData)
72: require(msg.sender == address(ASTARIA_ROUTER.LIEN_TOKEN()));
191: uint256 tokenId_,
199: require(msg.sender == address(ASTARIA_ROUTER.COLLATERAL_TOKEN()));
File: src/CollateralToken.sol
93: bytes32 CONDUIT_KEY = Bytes32AddressLib.fillLast12Bytes(address(this));
119: ) {
210: function _file(File calldata incoming) internal {
219: (address target, address hook) = abi.decode(data, (address, address));
346: }
385: function getUnderlying(uint256 collateralId)
390: Asset memory underlying = _loadCollateralSlot().idToUnderlying[
431: ) internal returns (OrderParameters memory orderParameters) {
537: delete s.idToUnderlying[collateralId];
File: src/LienToken.sol
82: function file(File calldata incoming) external requiresAuth {
113: revert InvalidState(InvalidStates.EXPIRED_LIEN);
262: return (delta_t * stack.lien.details.rate).mulWadDown(stack.point.amount);
370: revert InvalidState(InvalidStates.COLLATERAL_AUCTION);
377: function ASTARIA_ROUTER() public view returns (IAstariaRouter) {
392: validateStack(params.lien.collateralId, params.stack)
399: LienStorage storage s = _loadLienStorageSlot();
569: function getCollateralState(uint256 collateralId)
599: uint256 amount
619: (newStack, ) = _payment(s, stack, position, amount, msg.sender);
721: unchecked {
747: function getOwed(Stack memory stack, uint256 timestamp)
914: address newPayee
File: src/PublicVault.sol
208: function getLiquidationWithdrawRatio() public view returns (uint256) {
212: function getYIntercept() public view returns (uint256) {
216: function _deployWithdrawProxyIfNotDeployed(VaultData storage s, uint64 epoch)
219: if (s.epochData[epoch].withdrawProxy == address(0)) {
221: IAstariaRouter(ROUTER()).BEACON_PROXY_IMPLEMENTATION(),
224: uint8(IAstariaRouter.ImplementationType.WithdrawProxy),
252: public
272: return super.domainSeparator();
363: return;
477: * @return The implied value for this PublicVault.
549: .safeCastTo64();
571: * @notice After-deposit hook to update the yIntercept of the PublicVault to reflect a capital contribution.
575: function afterDeposit(uint256 assets, uint256 shares)
632: function updateAfterLiquidationPayment(
657: uint256 timeToEnd = timeToEpochEnd(lienEpoch);
686: uint64 currentEpoch = s.currentEpoch;
699: function timeToEpochEnd() public view returns (uint256) {
726:
File: src/WithdrawProxy.sol
103: function name()
114: * @notice Public view function to return the symbol of this WithdrawProxy.
124: string(abi.encodePacked("AST-W", VAULT(), "-", ERC20(asset()).symbol()));
135: override(ERC4626Cloned, IERC4626)
235: function increaseWithdrawReserveReceived(uint256 amount) external onlyVault {
240: function claim() public {
244: revert InvalidState(InvalidStates.CANT_CLAIM);
256: s.withdrawReserveReceived; // will never underflow because withdrawReserveReceived is always increased by the transfer amount from the PublicVault
307: uint256 newLienExpectedValue,
315: if (auctionEnd > s.finalAuctionEnd) s.finalAuctionEnd = auctionEnd;
319:
File: src/WithdrawVaultBase.sol
46:
46:
46:
Issue | Instances | |
---|---|---|
L-1 | Do not use deprecated library functions | 6 |
L-2 | Unsafe ERC20 operation(s) | 2 |
L-3 | Unspecific compiler version pragma | 4 |
Instances (6):
File: lib/gpl/src/ERC4626RouterBase.sol
21: ERC20(vault.asset()).safeApprove(address(vault), shares);
34: ERC20(vault.asset()).safeApprove(address(vault), amount);
48: ERC20(address(vault)).safeApprove(address(vault), amount);
62: ERC20(address(vault)).safeApprove(address(vault), shares);
File: src/ClearingHouse.sol
148: ERC20(paymentToken).safeApprove(
File: src/VaultImplementation.sol
334: ERC20(asset()).safeApprove(address(ROUTER().TRANSFER_PROXY()), buyout);
Instances (2):
File: src/ClearingHouse.sol
203: ERC721(order.parameters.offer[0].token).approve(
File: src/LienToken.sol
374: super.transferFrom(from, to, id);
Instances (4):
File: lib/gpl/src/ERC4626-Cloned.sol
2: pragma solidity >=0.8.16;
File: lib/gpl/src/interfaces/IMulticall.sol
4: pragma solidity >=0.7.5;
File: lib/gpl/src/interfaces/IUniswapV3Factory.sol
2: pragma solidity >=0.5.0;
File: lib/gpl/src/interfaces/IUniswapV3PoolState.sol
2: pragma solidity >=0.5.0;
Issue | Instances | |
---|---|---|
M-1 | Centralization Risk for trusted owners | 16 |
Contracts have owners with privileged rights to perform admin tasks and need to be trusted to not perform malicious updates or drain funds.
Instances (16):
File: src/AstariaRouter.sol
94: __initAuth(msg.sender, address(_AUTHORITY));
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 {
File: src/CollateralToken.sol
86: __initAuth(msg.sender, address(AUTHORITY_));
196: function fileBatch(File[] calldata files) external requiresAuth {
206: function file(File calldata incoming) public requiresAuth {
File: src/LienToken.sol
18: import {Auth, Authority} from "solmate/auth/Auth.sol";
63: __initAuth(msg.sender, address(_AUTHORITY));
82: function file(File calldata incoming) external requiresAuth {
282: ) external validateStack(collateralId, stack) requiresAuth {
File: src/TransferProxy.sol
16: import {Auth, Authority} from "solmate/auth/Auth.sol";
21: contract TransferProxy is Auth, ITransferProxy {
24: constructor(Authority _AUTHORITY) Auth(msg.sender, _AUTHORITY) {
33: ) external requiresAuth {