Platform: Code4rena
Start Date: 12/08/2022
Pot Size: $50,000 USDC
Total HM: 15
Participants: 120
Period: 5 days
Judge: Justin Goro
Total Solo HM: 6
Id: 153
League: ETH
Rank: 32/120
Findings: 2
Award: $84.56
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0x1f8b
Also found by: 0x52, 0xA5DF, 0xDjango, 0xNazgul, 0xNineDec, 0xSmartContract, 0xmatt, 0xsolstars, Aymen0909, Bnke0x0, CertoraInc, Chom, CodingNameKiki, Deivitto, Dravee, ElKu, EthLedger, Funen, IllIllI, JC, Junnon, Lambda, LeoS, MiloTruck, Noah3o6, PaludoX0, ReyAdmirado, Rohan16, RoiEvenHaim, Rolezn, SaharAP, Sm4rty, SooYa, The_GUILD, TomJ, Waze, Yiko, _Adam, __141345__, a12jmx, ak1, asutorufos, auditor0517, ayeslick, ballx, beelzebufo, berndartmueller, bin2chen, brgltd, c3phas, cRat1st0s, cccz, cryptonue, cryptphi, d3e4, delfin454000, dipp, djxploit, durianSausage, dy, erictee, fatherOfBlocks, gogo, gzeon, hyh, ignacio, kyteg, ladboy233, medikko, mics, minhquanym, oyc_109, pfapostol, rbserver, reassor, ret2basic, robee, sach1r0, simon135, sryysryy, tabish, yac, yash90, zzzitron
48.2239 USDC - $48.22
File: FraxlendPairDeployer.sol Line 104
CIRCUIT_BREAKER_ADDRESS = _circuitBreaker;
File: FraxlendPairDeployer.sol Line 105
COMPTROLLER_ADDRESS = _comptroller;
File: FraxlendPairDeployer.sol Line 106
TIME_LOCK_ADDRESS = _timelock;
File: FraxlendPairDeployer.sol Line 107
FRAXLEND_WHITELIST_ADDRESS = _fraxlendWhitelist;
There are several occurrences of literal values with unexplained meaning .Literal values in the codebase without an explained meaning make the code harder to read, understand and maintain, thus hindering the experience of developers, auditors and external contributors alike.
Developers should define a constant variable for every magic value used , giving it a clear and self-explanatory name.
File: VariableInterestRate.sol Line 69 1e18
uint256 _deltaUtilization = ((MIN_UTIL - _utilization) * 1e18) / MIN_UTIL;
File: VariableInterestRate.sol Line 76 1e18
uint256 _deltaUtilization = ((_utilization - MAX_UTIL) * 1e18) / (UTIL_PREC - MAX_UTIL);
File: FraxlendPairCore.sol Line 194 9000
dirtyLiquidationFee = (_liquidationFee * 9000) / LIQ_PRECISION; // 90% of clean fee
File: FraxlendPairCore.sol Line 468 1e18
_interestEarned = (_deltaTime * _totalBorrow.amount * _currentRateInfo.ratePerSec) / 1e18;
File: FraxlendPairCore.sol Line 522 1e36
uint256 _price = uint256(1e36);
File: FraxlendPair.sol Line 105
_assetsPerUnitShare = totalAsset.toAmount(1e18, false);
Contracts should be deployed with the same compiler version and flags that they have been tested the most with. Locking the pragma helps ensure that contracts do not accidentally get deployed using, for example, the latest compiler which may have higher risks of undiscovered bugs. Contracts may also be deployed by others and the pragma indicates the compiler version intended by the original authors.
File: LinearInterestRate.sol Line 2
pragma solidity ^0.8.15;
Other instances File: FraxlendWhitelist.sol Line 2
File: FraxlendPairConstants.sol Line 2
File: FraxlendPairCore.sol Line 2
File: FraxlendPair.sol Line 2
File: FraxlendPairDeployer.sol Line 2
File: FraxlendPairCore.sol Line 245-285 Function has been truncated
function initialize( string calldata _name, address[] calldata _approvedBorrowers, address[] calldata _approvedLenders, bytes calldata _rateInitCallData ) external {
File: FraxlendPairDeployer.sol Line 57
address public CIRCUIT_BREAKER_ADDRESS;
File: FraxlendPairDeployer.sol Line 58
address public COMPTROLLER_ADDRESS;
File: FraxlendPairDeployer.sol Line 59
address public TIME_LOCK_ADDRESS;
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0xA5DF, 0xDjango, 0xNazgul, 0xSmartContract, 0xackermann, 0xbepresent, 0xc0ffEE, 0xkatana, 2997ms, Amithuddar, Aymen0909, Bnke0x0, Chinmay, Chom, CodingNameKiki, Deivitto, Diraco, Dravee, ElKu, EthLedger, Fitraldys, Funen, IgnacioB, JC, Junnon, Lambda, LeoS, Metatron, MiloTruck, Noah3o6, NoamYakov, PaludoX0, Randyyy, ReyAdmirado, Rohan16, Rolezn, Ruhum, SaharAP, Sm4rty, SooYa, TomJ, Tomio, Waze, Yiko, _Adam, __141345__, a12jmx, ajtra, ak1, asutorufos, ballx, brgltd, c3phas, cRat1st0s, carlitox477, chrisdior4, d3e4, delfin454000, dharma09, djxploit, durianSausage, erictee, fatherOfBlocks, find_a_bug, flyx, francoHacker, gerdusx, gogo, gzeon, hakerbaya, ignacio, jag, kyteg, ladboy233, ltyu, m_Rassska, medikko, mics, mrpathfindr, newfork01, nxrblsrpr, oyc_109, pfapostol, rbserver, reassor, ret2basic, robee, sach1r0, saian, simon135, sryysryy, zeesaw
36.3433 USDC - $36.34
Use immutable if you want to assign a permanent value at construction. Use constants if you already know the permanent value. Both get directly embedded in bytecode, saving SLOAD. Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmacces (100 gas) with a PUSH32 (3 gas).
File: FraxlendPairDeployer.sol Line 57
address public CIRCUIT_BREAKER_ADDRESS;
File: FraxlendPairDeployer.sol Line 58
address public COMPTROLLER_ADDRESS;
File: FraxlendPairDeployer.sol Line 59
address public TIME_LOCK_ADDRESS;
The compiler does not reserve a storage slot for these variables, and every occurrence is replaced by the respective value. Compared to regular state variables, the gas costs of constant variables are much lower. For a constant variable, the expression assigned to it is copied to all the places where it is accessed and also re-evaluated each time. This allows for local optimizations
As evident from the comment , the following are meant to be constants but they were not declared using the constant keyword, as a result everytime they are accessed they still incur an SLOAD
File: FraxlendPairDeployer.sol Line 49
uint256 public DEFAULT_MAX_LTV = 75000; // 75% with 1e5 precision
File: FraxlendPairDeployer.sol Line 50
uint256 public GLOBAL_MAX_LTV = 1e8; // 1000x (100,000%) with 1e5 precision, protects from rounding errors in LTV calc
File: FraxlendPairDeployer.sol Line 51
uint256 public DEFAULT_LIQ_FEE = 10000; // 10% with 1e5 precision
With the keyword constant missing this are just normal state variables thus an SLOAD is incurred everytime they are accessed.
The code can be optimized by minimizing the number of SLOADs.
SLOADs are expensive (100 gas after the 1st one) compared to MLOADs/MSTOREs (3 gas each). Storage values read multiple times should instead be cached in memory the first time (costing 1 SLOAD) and then read from this cache to avoid multiple SLOADs.
NB: The function has been truncated where neccessary to just show affected parts of the code
FIle: FraxlendPairDeployer.sol Line 310-343
**Note: from the comments(at declaration) this variables were meant to be constants but the keyword constant
was never used.
function deploy(bytes memory _configData) external returns (address _pairAddress) { _pairAddress = _deployFirst( keccak256(abi.encodePacked("public")), _configData, abi.encode(CIRCUIT_BREAKER_ADDRESS, COMPTROLLER_ADDRESS, TIME_LOCK_ADDRESS, FRAXLEND_WHITELIST_ADDRESS), DEFAULT_MAX_LTV, //@audit: SLOAD 1 for DEFAULT_MAX_LTV DEFAULT_LIQ_FEE, //@audit: SLOAD 1 for DEFAULT_LIQ_FEE 0, 0, false, false ); _logDeploy(_name, _pairAddress, _configData, DEFAULT_MAX_LTV, DEFAULT_LIQ_FEE, 0);//@audit: SLOAD 2 for DEFAULT_MAX_LTV & DEFAULT_LIQ_FEE }
DEFAULT_MAX_LTV: SLOAD 1 Line 332 DEFAULT_MAX_LTV: SLOAD 2 Line 342
DEFAULT_LIQ_FEE: SLOAD 1 Line 333 DEFAULT_LIQ_FEE: SLOAD 2 Line 342
When arguments are read-only on external functions, the data location should be calldata:
File: FraxlendPairCore.sol Line 1062
_path
is only being read as such we can declare it as calldata
function leveragedPosition( address _swapperAddress, uint256 _borrowAmount, uint256 _initialCollateralAmount, uint256 _amountCollateralOutMin, address[] memory _path ) external
File: FraxlendPairDeployer.sol Line 398-411
function globalPause(address[] memory _addresses) external returns (address[] memory _updatedAddresses) { require(msg.sender == CIRCUIT_BREAKER_ADDRESS, "Circuit Breaker only"); address _pairAddress; uint256 _lengthOfArray = _addresses.length; for (uint256 i = 0; i < _lengthOfArray; ) { _pairAddress = _addresses[i]; try IFraxlendPair(_pairAddress).pause() { _updatedAddresses[i] = _addresses[i]; } catch {} unchecked { i = i + 1; } } }
address[] memory _addresses
should be modified to address[] calldata _addresses
File: FraxlendPairDeployer.sol Line 355
_name,_configData,_approvedBorrowers,_approvedLenders
should all be declared using calldata
function deployCustom( string memory _name, bytes memory _configData, uint256 _maxLTV, uint256 _liquidationFee, uint256 _maturityDate, uint256 _penaltyRate, address[] memory _approvedBorrowers, address[] memory _approvedLenders ) external returns (address _pairAddress) {
File: FraxlendPairDeployer.sol Line 310
_configData
should be calldata
function deploy(bytes memory _configData) external returns (address _pairAddress) {
Booleans are more expensive than uint256 or any type that takes up a full word because each write operation emits an extra SLOAD to first read the slot's contents, replace the bits taken up by the boolean, and then write back. This is the compiler's defense against contract upgrades and pointer aliasing, and it cannot be disabled.
See source 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
Instances affected include File: FraxlendWhitelist.sol Line 32
mapping(address => bool) public oracleContractWhitelist;
File: FraxlendWhitelist.sol Line 35
mapping(address => bool) public rateContractWhitelist;
File: FraxlendWhitelist.sol Line 38
mapping(address => bool) public fraxlendDeployerWhitelist;
File: FraxlendPairCore.sol Line 78
mapping(address => bool) public swappers; // approved swapper addresses
File:FraxlendPairCore.sol Line 134
mapping(address => bool) public approvedBorrowers;
File: FraxlendPairCore.sol Line 137
mapping(address => bool) public approvedLenders;
File: FraxlendPairDeployer.sol Line 94
mapping(address => bool) public deployedPairCustomStatusByAddress;
If needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table.
File: FraxlendPairDeployer.sol Line 49
uint256 public DEFAULT_MAX_LTV = 75000; // 75% with 1e5 precision
File: FraxlendPairCore.sol Line 773
totalCollateral += _collateralAmount;
File: FraxlendPairCore.sol Line 772
userCollateralBalance[_borrower] += _collateralAmount;
File: FraxlendPairCore.sol Line 815
totalCollateral -= _collateralAmount;
File: FraxlendPairCore.sol Line 1008
totalAsset.amount -= _amountToAdjust;
File: FraxlendPairCore.sol Line 867
userBorrowShares[_borrower] -= _shares;
File: FraxlendPairCore.sol Line 813
userCollateralBalance[_borrower] -= _collateralAmount;
File: FraxlendPairCore.sol Line 724
userBorrowShares[msg.sender] += _sharesAdded;
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.
Use a larger size then downcast where needed See source
File: FraxlendPairCore.sol Line 707
uint128 _borrowAmount
function _borrowAsset(uint128 _borrowAmount, address _receiver) internal returns (uint256 _sharesAdded) {
File: FraxlendPairCore.sol Line 559-564
uint128 _amount
and uint128 _shares
function _deposit( VaultAccount memory _totalAsset, uint128 _amount, uint128 _shares, address _receiver ) internal {
File: FraxlendPairCore.sol Line 623-629
uint128 _amountToReturn
and uint128 _shares
function _redeem( VaultAccount memory _totalAsset, uint128 _amountToReturn, uint128 _shares, address _receiver, address _owner ) internal {
File: FraxlendPairCore.sol Line 855-858
uint128 _amountToRepay
and uint128 _shares
function _repayAsset( VaultAccount memory _totalBorrow, uint128 _amountToRepay, uint128 _shares,
File: FraxlendPairCore.sol Line 950
uint128 _sharesToLiquidate
function liquidateClean( uint128 _sharesToLiquidate, uint256 _deadline, address _borrower
File: FraxlendPair.sol Line 215
function changeFee(uint32 _newFee) external whenNotPaused {
File: FraxlendPair.sol Line 234
function withdrawFees(uint128 _shares, address _recipient) external onlyOwner returns (uint256 _amountToTransfer) {
Instead of using the && operator in a single require statement to check multiple conditions,using multiple require statements with 1 condition per require statement will save 8 GAS per && The gas difference would only be realized if the revert condition is realized(met).
File: LinearInterestRate.sol Line 57-60
require( _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT, "LinearInterestRate: _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT" );
File: LinearInterestRate.sol Line 61-64
require( _maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT, "LinearInterestRate: _maxInterest <= MAX_INT && _vertexInterest <= _maxInterest && _maxInterest > MIN_INT" );
File: LinearInterestRate.sol Line 65-68
require( _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0, "LinearInterestRate: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0" );
Proof The following tests were carried out in remix with both optimization turned on and off
function multiple (uint a) public pure returns (uint){ require ( a > 1 && a < 5, "Initialized"); return a + 2; }
Execution cost 21617 with optimization and using && 21976 without optimization and using &&
After splitting the require statement
function multiple(uint a) public pure returns (uint){ require (a > 1 ,"Initialized"); require (a < 5 , "Initialized"); return a + 2; }
Execution cost 21609 with optimization and split require 21968 without optimization and using split require
!= 0 costs less gas compared to > 0 for unsigned integers in require statements with the optimizer enabled (6 gas)
For uints the minimum value would be 0 and never a negative value. Since it cannot be a negative value, then the check > 0 is essentially checking that the value is not equal to 0 therefore >0 can be replaced with !=0 which saves gas.
I suggest changing > 0 with != 0 here:
File: LinearInterestRate.sol Line 65-68
_vertexUtilization > 0
require(_vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0 "LinearInterestRate: _vertexUtilization < MAX_VERTEX_UTIL && _vertexUtilization > 0");
Solidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isn’t possible (as an example, when a comparison is made before the arithmetic operation), some gas can be saved by using an unchecked block see resource
File: VariableInterestRate.sol Line 69
uint256 _deltaUtilization = ((MIN_UTIL - _utilization) * 1e18) / MIN_UTIL;
The operation MIN_UTIL - _utilization
cannot underflow due to the check on Line 68 which ensures that MIN_UTIL
is greater than _utilization
before perfoming the subtraction operation
File: VariableInterestRate.sol Line 76
uint256 _deltaUtilization = ((_utilization - MAX_UTIL) * 1e18) / (UTIL_PREC - MAX_UTIL);
The operation _utilization - MAX_UTIL
cannot underflow due to the check on Line 75 which ensures that _utilization
is greater than MAX_UTIL
before perfoming the subtraction operation
The majority of Solidity for loops increment a uint256 variable that starts at 0. These increment operations never need to be checked for over/underflow because the variable will never reach the max number of uint256 (will run out of gas long before that happens). The default over/underflow check wastes gas in every iteration of virtually every for loop .
Affected code File: FraxlendWhitelist.sol Line 50-55
function setOracleContractWhitelist(address[] calldata _addresses, bool _bool) external onlyOwner { for (uint256 i = 0; i < _addresses.length; i++) { oracleContractWhitelist[_addresses[i]] = _bool; emit SetOracleWhitelist(_addresses[i], _bool); } }
The above should be modified to:
function setOracleContractWhitelist(address[] calldata _addresses, bool _bool) external onlyOwner { for (uint256 i = 0; i < _addresses.length;) { oracleContractWhitelist[_addresses[i]] = _bool; emit SetOracleWhitelist(_addresses[i], _bool); unchecked{ ++i; } } }
Other Instances to modify File: FraxlendWhitelist.sol Line 65-70
File: FraxlendWhitelist.sol Line 81
File: FraxlendPairCore.sol Line 265
File: FraxlendPairCore.sol Line 270
File: FraxlendPair.sol Line 289
File: FraxlendPair.sol Line 308
Reading array length at each iteration of the loop takes 6 gas (3 for mload and 3 to place memory_offset) in the stack.
The solidity compiler will always read the length of the array during each iteration. That is,
1.if it is a storage array, this is an extra sload operation (100 additional extra gas (EIP-2929 2) for each iteration except for the first) 2.if it is a memory array, this is an extra mload operation (3 additional gas for each iteration except for the first) 3.if it is a calldata array, this is an extra calldataload operation (3 additional gas for each iteration except for the first)
This extra costs can be avoided by caching the array length (in stack): When reading the length of an array, sload or mload or calldataload operation is only called once and subsequently replaced by a cheap dupN instruction. Even though mload , calldataload and dupN have the same gas cost, mload and calldataload needs an additional dupN to put the offset in the stack, i.e., an extra 3 gas. which brings this to 6 gas
Here, I suggest storing the array’s length in a variable before the for-loop, and use it instead:
File: FraxlendWhitelist.sol Line 50-55
function setOracleContractWhitelist(address[] calldata _addresses, bool _bool) external onlyOwner { for (uint256 i = 0; i < _addresses.length; i++) { oracleContractWhitelist[_addresses[i]] = _bool; emit SetOracleWhitelist(_addresses[i], _bool); } }
The above should be modified to
function setOracleContractWhitelist(address[] calldata _addresses, bool _bool) external onlyOwner { uint256 length = _addresses.length; for (uint256 i = 0; i < length; i++) { oracleContractWhitelist[_addresses[i]] = _bool; emit SetOracleWhitelist(_addresses[i], _bool); } }
Other instances to modify File: FraxlendWhitelist.sol Line 66
File: FraxlendWhitelist.sol Line 81
File: FraxlendPairCore.sol Line 265
File: FraxlendPairCore.sol Line 270
File: FraxlendPair.sol Line 289
File: FraxlendPair.sol Line 308
++i costs less gas compared to i++ or i += 1 for unsigned integer, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled.
i++ increments i and returns the initial value of i. Which means:
uint i = 1; i++; // == 1 but i == 2
But ++i returns the actual incremented value:
uint i = 1; ++i; // == 2 and i == 2 too, so no need for a temporary variable
In the first case, the compiler has to create a temporary variable (when used) for returning 1 instead of 2
Instances include: File: FraxlendWhitelist.sol Line 51
for (uint256 i = 0; i < _addresses.length; i++) {
File: FraxlendPairDeployer.sol Line 158
File: FraxlendWhitelist.sol Line 66
File: FraxlendWhitelist.sol Line 81
File: FraxlendPair.sol Line 289
File: FraxlendPair.sol Line 308
Every reason string takes at least 32 bytes so make sure your string fits in 32 bytes or it will become more expensive.Each extra chunk of byetes past the original 32 incurs an MSTORE which costs 3 gas
Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition is met. Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.
File: LinearInterestRate.sol Line 57-60
require( _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT, "LinearInterestRate: _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT" );
Other instances to modify File: LinearInterestRate.sol Line 61-64
File: LinearInterestRate.sol Line 65-68
File: FraxlendPairDeployer.sol Line 205
FIle: FraxlendPairDeployer.sol Line 228
File: FraxlendPairDeployer.sol Line 253
File: FraxlendPairDeployer.sol Line 365,366
I suggest shortening the revert strings to fit in 32 bytes, or using custom errors.
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) 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
Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries). see Source
File: LinearInterestRate.sol Line 57-60
require( _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT, "LinearInterestRate: _minInterest < MAX_INT && _minInterest <= _vertexInterest && _minInterest >= MIN_INT" );
Other instances to modify File: LinearInterestRate.sol Line 61-64
File: LinearInterestRate.sol Line 65-68
File: FraxlendPairDeployer.sol Line 205
FIle: FraxlendPairDeployer.sol Line 228
File: FraxlendPairDeployer.sol Line 253
File: FraxlendPairDeployer.sol Line 365,366
Proof of custom errors optimizations
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testFailGas() public { c0.stringErrorMessage(); c1.customErrorMessage(); } } contract Contract0 { function stringErrorMessage() public { bool check = false; require(check, "error message"); } } contract Contract1 { error CustomError(); function customErrorMessage() public { bool check = false; if (!check) { revert CustomError(); } } }
Gas comparisons
╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ │ Contract0 contract ┆ ┆ ┆ ┆ ┆ │ ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 34087 ┆ 200 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ stringErrorMessage ┆ 218 ┆ 218 ┆ 218 ┆ 218 ┆ 1 │ ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ │ Contract1 contract ┆ ┆ ┆ ┆ ┆ │ ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ 26881 ┆ 164 ┆ ┆ ┆ ┆ │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ │ customErrorMessage ┆ 161 ┆ 161 ┆ 161 ┆ 161 ┆ 1 │ ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯