Skip to content

Instantly share code, notes, and snippets.

@Picodes
Created December 1, 2022 22:33
Show Gist options
  • Save Picodes/4d46aae8efe6935f99beba012aa17876 to your computer and use it in GitHub Desktop.
Save Picodes/4d46aae8efe6935f99beba012aa17876 to your computer and use it in GitHub Desktop.

Report - 1

Gas Optimizations

Issue Instances
GAS-1 Use assembly to check for address(0) 1
GAS-2 Using bools for storage incurs overhead 2
GAS-3 Cache array length outside of loop 5
GAS-4 State variables should be cached in stack variables rather than re-reading them from storage 6
GAS-5 Use calldata instead of memory for function arguments that do not get mutated 2
GAS-6 Use Custom Errors 20
GAS-7 Don't initialize variables with default value 4
GAS-8 ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too) 18
GAS-9 Use != 0 instead of > 0 for unsigned integer comparison 18
GAS-10 internal functions not called by the contract should be removed 32

[GAS-1] Use assembly to check for address(0)

Saves 6 gas per instance

Instances (1):

File: models/Factory.sol

41:         require(msg.sender == owner && _owner != address(0), "Factory:NOT_ALLOWED");

[GAS-2] Using bools for storage incurs overhead

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 (2):

File: libraries/BinMap.sol

75:     function nextActive(mapping(int32 => uint256) storage binMap, int32 tick, bool isRight) internal view returns (int32 nextTick) {
File: models/Factory.sol

18:     mapping(IPool => bool) public override isFactoryPool;

[GAS-3] Cache array length outside of loop

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 (5):

File: models/Pool.sol

127:         for (uint256 i; i < params.length; i++) {

180:         for (uint256 i; i < binIds.length; i++) {

193:         for (uint256 i; i < params.length; i++) {

224:         for (uint256 i; i < params.length; i++) {
File: models/PoolInspector.sol

101:         for (uint256 i; i < bins.length; i++) {

[GAS-4] State variables should be cached in stack variables rather than re-reading them from storage

The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.

Saves 100 gas per instance

Instances (6):

File: libraries/BinMap.sol

68:                 offset += uint16(uint32(NUMBER_OF_KINDS_32));
File: models/Factory.sol

80:         emit PoolCreated(address(pool), _fee, _tickSpacing, _activeTick, _lookback, protocolFeeRatio, _tokenA, _tokenB);
File: models/Pool.sol

164:         tokenAAmount = Math.toScale(tokenAAmount, tokenAScale, true);

165:         tokenBAmount = Math.toScale(tokenBAmount, tokenBScale, true);

518:         sqrtUpperTickPrice = BinMath.tickSqrtPrice(tickSpacing, tick + 1);
File: models/PositionMetadata.sol

22:         return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";

[GAS-5] Use calldata instead of memory for function arguments that do not get mutated

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 (2):

File: models/PositionMetadata.sol

13:     constructor(string memory _baseURI) {

17:     function setBaseURI(string memory _baseURI) external onlyOwner {

[GAS-6] Use Custom Errors

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 (20):

File: libraries/Bin.sol

85:         require(deltaLpToken != 0, "L");

140:             require(activeBin.state.mergeId == 0, "N");
File: libraries/BinMath.sol

94:         require(tick <= MAX_TICK, "X");
File: libraries/Cast.sol

6:         require((y = uint128(x)) == x, "C");
File: libraries/SafeERC20Min.sol

18:             require(abi.decode(returndata, (bool)), "T");
File: models/Factory.sol

27:         require(_protocolFeeRatio <= ONE_3_DECIMAL_SCALE, "Factory:PROTOCOL_FEE_CANNOT_EXCEED_ONE");

34:         require(msg.sender == owner, "Factory:NOT_ALLOWED");

35:         require(_protocolFeeRatio <= ONE_3_DECIMAL_SCALE, "Factory:PROTOCOL_FEE_CANNOT_EXCEED_ONE");

41:         require(msg.sender == owner && _owner != address(0), "Factory:NOT_ALLOWED");

59:         require(_tokenA < _tokenB, "Factory:TOKENS_MUST_BE_SORTED_BY_ADDRESS");

60:         require(_fee > 0 && _fee < 1e18, "Factory:FEE_OUT_OF_BOUNDS");

61:         require(_tickSpacing > 0, "Factory:TICK_SPACING_OUT_OF_BOUNDS");

62:         require(_lookback >= 3600 && _lookback <= uint16(type(int16).max), "Factory:LOOKBACK_OUT_OF_BOUNDS");

64:         require(pools[_fee][_tickSpacing][_lookback][_tokenA][_tokenB] == IPool(address(0)), "Factory:POOL_ALREADY_EXISTS");
File: models/Pool.sol

87:         require(msg.sender == position.ownerOf(tokenId) || msg.sender == position.getApproved(tokenId), "P");

93:         require((currentState.status & LOCKED == 0) && (allowInEmergency || (currentState.status & EMERGENCY == 0)), "E");

168:         require(previousABalance + tokenAAmount <= _tokenABalance() && previousBBalance + tokenBAmount <= _tokenBBalance(), "A");

303:         require(previousBalance + amountIn <= (tokenAIn ? _tokenABalance() : _tokenBBalance()), "S");
File: models/PoolInspector.sol

67:                 revert("Invalid Swap");
File: models/Position.sol

43:         require(_exists(tokenId), "Invalid Token ID");

[GAS-7] Don't initialize variables with default value

Instances (4):

File: libraries/Constants.sol

6: uint256 constant MERGED_LP_BALANCE_TOKEN_ID = 0;
File: models/Pool.sol

31:     uint8 constant NO_EMERGENCY_UNLOCKED = 0;
File: models/PoolInspector.sol

29:         uint128 activeCounter = 0;

80:         for (uint8 i = 0; i < NUMBER_OF_KINDS; i++) {

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

Saves 5 gas per loop

Instances (18):

File: models/Pool.sol

127:         for (uint256 i; i < params.length; i++) {

180:         for (uint256 i; i < binIds.length; i++) {

193:         for (uint256 i; i < params.length; i++) {

224:         for (uint256 i; i < params.length; i++) {

380:         for (uint256 j; j < 2; j++) {

389:             for (uint256 i; i <= moveData.binCounter; i++) {

396:                     moveData.mergeBinCounter++;

409:                     moveData.mergeBinCounter++;

414:             for (uint256 i; i < moveData.mergeBinCounter; i++) {

523:         for (uint256 i; i < NUMBER_OF_KINDS; i++) {

530:                 output.counter++;

540:         for (uint256 i; i < NUMBER_OF_KINDS; i++) {

653:             for (uint256 i; i < swapData.counter; i++) {
File: models/PoolInspector.sol

30:         for (uint128 i = startBinIndex; i < binCounter; i++) {

34:                 activeCounter++;

49:             depth++;

80:         for (uint8 i = 0; i < NUMBER_OF_KINDS; i++) {

101:         for (uint256 i; i < bins.length; i++) {

[GAS-9] Use != 0 instead of > 0 for unsigned integer comparison

Instances (18):

File: libraries/Bin.sol

42:             if (_existingReserveA > 0) {

45:             if (deltaBOptimal > _deltaB && _existingReserveB > 0) {
File: libraries/BinMath.sol

52:             if (x & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF > 0) {

57:             if (x & 0xFFFFFFFFFFFFFFFF > 0) {

62:             if (x & 0xFFFFFFFF > 0) {

67:             if (x & 0xFFFF > 0) {

72:             if (x & 0xFF > 0) {

77:             if (x & 0xF > 0) {

82:             if (x & 0x3 > 0) {

87:             if (x & 0x1 > 0) result -= 1;

115:         if (_tick > 0) ratio = type(uint256).max / ratio;
File: libraries/SafeERC20Min.sol

17:         if (returndata.length > 0) {
File: models/Factory.sol

60:         require(_fee > 0 && _fee < 1e18, "Factory:FEE_OUT_OF_BOUNDS");

61:         require(_tickSpacing > 0, "Factory:TICK_SPACING_OUT_OF_BOUNDS");
File: models/Pool.sol

277:         while (delta.excess > 0) {

659:                     swapData.currentReserveB > 0 ? bin.state.reserveB : bin.state.reserveA,

660:                     swapData.currentReserveB > 0 ? swapData.currentReserveB : swapData.currentReserveA
File: models/PositionMetadata.sol

22:         return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";

[GAS-10] internal functions not called by the contract should be removed

If the functions are required by an interface, the contract should inherit from that interface and use the override keyword

Instances (32):

File: libraries/Bin.sol

56:     function adjustAB(Bin.Instance storage self, bool tokenAIn, Delta.Instance memory delta, uint256 thisBinAmount, uint256 totalAmount) internal {

75:     function addLiquidity(

126:     function removeLiquidity(
File: libraries/BinMap.sol

25:     function putTypeAtTick(mapping(int32 => uint256) storage binMap, uint8 kind, int32 tick) internal {

30:     function removeTypeAtTick(mapping(int32 => uint256) storage binMap, uint8 kind, int32 tick) internal {

35:     function getKindsAtTick(mapping(int32 => uint256) storage binMap, int32 tick) internal view returns (uint256 word) {

41:     function getKindsAtTickRange(mapping(int32 => uint256) storage binMap, int32 startTick, int32 endTick, uint256 kindMask) internal view returns (Active[] memory activeList, uint256 counter) {

75:     function nextActive(mapping(int32 => uint256) storage binMap, int32 tick, bool isRight) internal view returns (int32 nextTick) {
File: libraries/BinMath.sol

12:     function msb(uint256 x) internal pure returns (uint8 result) {

48:     function lsb(uint256 x) internal pure returns (uint8 result) {

91:     function tickSqrtPrice(uint256 tickSpacing, int32 _tick) internal pure returns (uint256 _result) {

134:     function getTickSqrtPriceAndL(uint256 _reserveA, uint256 _reserveB, uint256 _sqrtLowerTickPrice, uint256 _sqrtUpperTickPrice) internal pure returns (uint256 sqrtPrice, uint256 liquidity) {
File: libraries/Cast.sol

5:     function toUint128(uint256 x) internal pure returns (uint128 y) {
File: libraries/Delta.sol

22:     function combine(Instance memory self, Instance memory delta) internal pure {

34:     function pastMaxPrice(Instance memory self) internal pure {

38:     function sqrtEdgePrice(Instance memory self) internal pure returns (uint256 edge) {

42:     function noSwapReset(Instance memory self) internal pure {
File: libraries/Math.sol

8:     function max(uint256 x, uint256 y) internal pure returns (uint256) {

12:     function min(uint256 x, uint256 y) internal pure returns (uint256) {

16:     function max(int256 x, int256 y) internal pure returns (int256) {

20:     function min(int256 x, int256 y) internal pure returns (int256) {

25:     function clip128(uint128 x, uint128 y) internal pure returns (uint128) {

29:     function clip(uint256 x, uint256 y) internal pure returns (uint256) {

33:     function mulDiv(uint256 x, uint256 y, uint256 k, bool ceil) internal pure returns (uint256) {

38:     function scale(uint8 decimals) internal pure returns (uint256) {

48:     function toScale(uint256 amount, uint256 scaleFactor, bool ceil) internal pure returns (uint256) {

58:     function fromScale(uint256 amount, uint256 scaleFactor) internal pure returns (uint256) {

68:     function abs32(int32 x) internal pure returns (uint32) {
File: libraries/SafeERC20Min.sol

11:     function safeTransfer(IERC20 token, address to, uint256 value) internal {
File: libraries/Twa.sol

11:     function updateValue(IPool.TwaState storage self, int256 value) internal {

17:     function floor(IPool.TwaState storage self) internal view returns (int32) {

35:     function getTwaFloor(IPool.TwaState storage self) internal view returns (int32) {

Non Critical Issues

Issue Instances
NC-1 require() / revert() statements should have descriptive reason strings 3
NC-2 Event is missing indexed fields 12
NC-3 Functions not used internally could be marked external 2

[NC-1] require() / revert() statements should have descriptive reason strings

Instances (3):

File: models/Pool.sol

129:             require(param.kind < NUMBER_OF_KINDS);

333:         require(_protocolFeeRatio <= ONE_3_DECIMAL_SCALE);

342:         require(msg.sender == factory.owner());

[NC-2] 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.

Instances (12):

File: interfaces/IFactory.sol

9:     event PoolCreated(address poolAddress, uint256 fee, uint256 tickSpacing, int32 activeTick, uint32 lookback, uint16 protocolFeeRatio, IERC20 tokenA, IERC20 tokenB);

10:     event SetFactoryProtocolFeeRatio(uint16 protocolFeeRatio);

11:     event SetFactoryOwner(address owner);
File: interfaces/IPool.sol

8:     event Swap(address indexed sender, address indexed recipient, bool tokenAIn, bool exactOutput, uint256 amountIn, uint256 amountOut, int32 activeTick);

10:     event AddLiquidity(address indexed sender, uint256 indexed tokenId, BinDelta[] binDeltas);

12:     event MigrateBinsUpStack(address indexed sender, uint128[] binIds, uint32 maxRecursion);

14:     event TransferLiquidity(uint256 fromTokenId, uint256 toTokenId, RemoveLiquidityParams[] params);

18:     event BinMerged(uint128 indexed binId, uint128 reserveA, uint128 reserveB, uint128 mergeId);

20:     event BinMoved(uint128 indexed binId, int128 previousTick, int128 newTick);

22:     event ProtocolFeeCollected(uint256 protocolFee, bool isTokenA);

24:     event SetProtocolFeeRatio(uint256 protocolFee);
File: interfaces/IPosition.sol

8:     event SetMetadata(IPositionMetadata metadata);

[NC-3] Functions not used internally could be marked external

Instances (2):

File: models/PoolInspector.sol

45:     function getBinDepth(IPool pool, uint128 binId) public view returns (uint256 depth) {
File: models/Position.sol

38:     function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable, IERC165) returns (bool) {

Report - 2

Gas Optimizations

Issue Instances
GAS-1 Use selfbalance() instead of address(this).balance 3
GAS-2 Use assembly to check for address(0) 4
GAS-3 Cache array length outside of loop 1
GAS-4 State variables should be cached in stack variables rather than re-reading them from storage 2
GAS-5 Use Custom Errors 26
GAS-6 Don't initialize variables with default value 1
GAS-7 ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too) 1
GAS-8 Use != 0 instead of > 0 for unsigned integer comparison 10
GAS-9 internal functions not called by the contract should be removed 12

[GAS-1] Use selfbalance() instead of address(this).balance

Use assembly when getting a contract's balance of ETH.

You can use selfbalance() instead of address(this).balance when getting your contract's balance of ETH to save gas. Additionally, you can use balance(address) instead of address.balance() when getting an external contract's balance of ETH.

Saves 15 gas when checking internal balance, 6 for external

Instances (3):

File: Router.sol

81:         if (address(this).balance > 0) TransferHelper.safeTransferETH(msg.sender, address(this).balance);

81:         if (address(this).balance > 0) TransferHelper.safeTransferETH(msg.sender, address(this).balance);

89:         if (IWETH9(address(token)) == WETH9 && address(this).balance >= value) {

[GAS-2] Use assembly to check for address(0)

Saves 6 gas per instance

Instances (4):

File: Router.sol

122:         if (recipient == address(0)) recipient = address(this);

170:         if (recipient == address(0)) recipient = address(this);

271:         if (address(pool) == address(0)) {

306:         if (recipient == address(0)) recipient = address(this);

[GAS-3] Cache array length outside of loop

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 (1):

File: libraries/Multicall.sol

13:         for (uint256 i = 0; i < data.length; i++) {

[GAS-4] State variables should be cached in stack variables rather than re-reading them from storage

The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.

Saves 100 gas per instance

Instances (2):

File: Router.sol

224:                 tokenId = IPosition(position).tokenOfOwnerByIndex(msg.sender, 0);

272:             pool = IFactory(factory).create(poolParams.fee, poolParams.tickSpacing, poolParams.lookback, poolParams.activeTick, poolParams.tokenA, poolParams.tokenB);

[GAS-5] Use Custom Errors

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 (26):

File: Router.sol

55:         require(IWETH9(msg.sender) == WETH9, "Not WETH9");

61:         require(balanceWETH9 >= amountMinimum, "Insufficient WETH9");

72:         require(balanceToken >= amountMinimum, "Insufficient token");

100:         require(amountToPay > 0 && amountOut > 0, "In or Out Amount is Zero");

101:         require(factory.isFactoryPool(IPool(msg.sender)), "Must call from a Factory Pool");

139:         require(amountOut >= params.amountOutMinimum, "Too little received");

165:         require(amountOut >= params.amountOutMinimum, "Too little received");

177:         require(amountOutReceived == amountOut, "Requested amount not available");

188:         require(amountIn <= params.amountInMaximum, "Too much requested");

197:         require(amountIn <= params.amountInMaximum, "Too much requested");

234:         require(tokenAAmount >= minTokenAAmount && tokenBAmount >= minTokenBAmount, "Too little added");

262:         require(activeTick >= minActiveTick && activeTick <= maxActiveTick, "activeTick not in range");

304:         require(msg.sender == position.ownerOf(tokenId), "P");

309:         require(tokenAAmount >= minTokenAAmount && tokenBAmount >= minTokenBAmount, "Too little removed");
File: libraries/BytesLib.sol

13:         require(_length + 31 >= _length, "slice_overflow");

14:         require(_start + _length >= _start, "slice_overflow");

15:         require(_bytes.length >= _start + _length, "slice_outOfBounds");

75:         require(_start + 20 >= _start, "toAddress_overflow");

76:         require(_bytes.length >= _start + 20, "toAddress_outOfBounds");

87:         require(_start + 3 >= _start, "toUint24_overflow");

88:         require(_bytes.length >= _start + 3, "toUint24_outOfBounds");
File: libraries/Deadline.sol

6:         require(block.timestamp <= deadline, "Transaction too old");
File: libraries/TransferHelper.sol

15:         require(success && (data.length == 0 || abi.decode(data, (bool))), "STF");

25:         require(success && (data.length == 0 || abi.decode(data, (bool))), "ST");

35:         require(success && (data.length == 0 || abi.decode(data, (bool))), "SA");

44:         require(success, "STE");

[GAS-6] Don't initialize variables with default value

Instances (1):

File: libraries/Multicall.sol

13:         for (uint256 i = 0; i < data.length; i++) {

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

Saves 5 gas per loop

Instances (1):

File: libraries/Multicall.sol

13:         for (uint256 i = 0; i < data.length; i++) {

[GAS-8] Use != 0 instead of > 0 for unsigned integer comparison

Instances (10):

File: Router.sol

63:         if (balanceWETH9 > 0) {

74:         if (balanceToken > 0) {

81:         if (address(this).balance > 0) TransferHelper.safeTransferETH(msg.sender, address(this).balance);

100:         require(amountToPay > 0 && amountOut > 0, "In or Out Amount is Zero");

100:         require(amountToPay > 0 && amountOut > 0, "In or Out Amount is Zero");
File: interfaces/IMulticall.sol

2: pragma solidity >=0.7.5;
File: interfaces/ISelfPermit.sol

2: pragma solidity >=0.7.5;
File: interfaces/external/IERC20PermitAllowed.sol

2: pragma solidity >=0.5.0;
File: libraries/SelfPermit.sol

2: pragma solidity >=0.5.0;
File: libraries/TransferHelper.sol

2: pragma solidity >=0.6.0;

[GAS-9] internal functions not called by the contract should be removed

If the functions are required by an interface, the contract should inherit from that interface and use the override keyword

Instances (12):

File: libraries/BytesLib.sol

12:     function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) {

74:     function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {

86:     function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) {
File: libraries/Path.sol

27:     function hasMultiplePools(bytes memory path) internal pure returns (bool) {

34:     function numPools(bytes memory path) internal pure returns (uint256) {

44:     function decodeFirstPool(bytes memory path) internal pure returns (IERC20 tokenIn, IERC20 tokenOut, IPool pool) {

53:     function getFirstPool(bytes memory path) internal pure returns (bytes memory) {

60:     function skipToken(bytes memory path) internal pure returns (bytes memory) {
File: libraries/TransferHelper.sol

13:     function safeTransferFrom(address token, address from, address to, uint256 value) internal {

23:     function safeTransfer(address token, address to, uint256 value) internal {

33:     function safeApprove(address token, address to, uint256 value) internal {

42:     function safeTransferETH(address to, uint256 value) internal {

Non Critical Issues

Issue Instances
NC-1 require() / revert() statements should have descriptive reason strings 3
NC-2 Constants should be defined rather than using magic numbers 1
NC-3 Functions not used internally could be marked external 3

[NC-1] require() / revert() statements should have descriptive reason strings

Instances (3):

File: Router.sol

106:         require(msg.sender == address(pool));

205:         require(factory.isFactoryPool(IPool(msg.sender)));

206:         require(msg.sender == address(data.pool));

[NC-2] Constants should be defined rather than using magic numbers

Instances (1):

File: libraries/BytesLib.sol

58:                 mstore(0x40, and(add(mc, 31), not(31)))

[NC-3] Functions not used internally could be marked external

Instances (3):

File: Router.sol

59:     function unwrapWETH9(uint256 amountMinimum, address recipient) public payable override {

70:     function sweepToken(IERC20 token, uint256 amountMinimum, address recipient) public payable {
File: libraries/Multicall.sol

11:     function multicall(bytes[] calldata data) public payable override returns (bytes[] memory results) {

Low Issues

Issue Instances
L-1 Do not use deprecated library functions 1
L-2 Unsafe ERC20 operation(s) 1
L-3 Unspecific compiler version pragma 5

[L-1] Do not use deprecated library functions

Instances (1):

File: libraries/TransferHelper.sol

33:     function safeApprove(address token, address to, uint256 value) internal {

[L-2] Unsafe ERC20 operation(s)

Instances (1):

File: Router.sol

91:             WETH9.transfer(recipient, value);

[L-3] Unspecific compiler version pragma

Instances (5):

File: interfaces/IMulticall.sol

2: pragma solidity >=0.7.5;
File: interfaces/ISelfPermit.sol

2: pragma solidity >=0.7.5;
File: interfaces/external/IERC20PermitAllowed.sol

2: pragma solidity >=0.5.0;
File: libraries/SelfPermit.sol

2: pragma solidity >=0.5.0;
File: libraries/TransferHelper.sol

2: pragma solidity >=0.6.0;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment