πŸ›‘οΈSecurity

Security is always our top priority at Krystal. We've implemented comprehensive external and internal security measures to ensure that our users can enjoy using Krystal with complete peace of mind.

External Audit

We recently conducted a thorough audit with Code4rena, involving 1,137 lines of code, 6 security researchers, and 10 days of rigorous analysis. Five medium-severity and five low-severity issues were identified by the researchers and resolved by Krystal team. Below is a summary for medium-severity issues found and actions:

RiskSeverityExplanationAction

Wrong logic in Auto-Compound: Doesn’t allow for Swap token

Medium

Auto-Compound action allows for compounding your gains into liquidity. It additionally allows for swaps in the middle. There is a faulty condition though, which is never effective.

Fixed

The signatures are replayable

Medium

User signed orders can be replayed

The onlyRole(OPERATOR_ROLE) privilege required to exploit this in the smart contract mitigates the risk. No further action needed.

_deductFees() is incompatible with tokens that revert on zero value transfers

Medium

All main functionality risks reverting with tokens that revert on zero value transfers, via a transfer in Common._deductFees().

Fixed

The Protocol breaks the Allowance Mechanism of the NFTs

Medium

The approved entities may lose the right to control the NFT after making a transaction

There are 2 options to authorize Automation: : setApprovalForAll or onERC721Received. Krystal is already using onERC721Received - the safer option for users. No further action needed.

Swapping logic would be broken for some supported tokens

Medium

Swapping logic would be broken for some supported tokens

Fixed

The detailed report can be found in the link below:

Internal Measures

We've implemented four key measures to ensure that even Krystal's developers cannot exploit the system or access user funds.

Access Control

#limit_fee #pause #multi_sig

The Krystal V3 Automation smart contract is operated and guarded by three distinct roles: Admin, Withdrawer, and Operator. Each role has specific responsibilities and limitations, enhancing the overall safety and integrity of the system.

  • The Admin role has the following capabilities:

    • Assign a wallet to be Operator or Withdrawer

    • Pause the smart contract to halt the executing orders

    • Set the ceiling fee taken in one execution

    This role is maintained by a multi-sig wallet, requiring 5 out of 6 signatures to approve any action.

  • The Withdrawer role has one capability:

    • Withdraw the collected fee in the smart contract

    Withdrawers cannot access any of the LP positions. This role is also managed by a multi-sig wallet with a different set of approvals to mitigate risks. Additionally, the Admin role can change the assigned wallets for this role.

  • The Operator role has one capability:

    • Manage users’ positions on their behalf, including depositing, withdrawing, and collecting fees from the pool

    Operators are restricted to a limited set of actions necessary for rebalancing only. All actions are encapsulated in specific transactions (rebalances) to prevent unrestricted access to funds. This role is operated by multiple Externally Owned Accounts (EOA) to handle multiple orders simultaneously. Operators can be added or removed by the Admin role.

Sign & Verify

#verification

The smart contracts only run the settings by the owner; even Krystal devs won’t be able to change those settings

Since Operator would deposit, withdraw & collect liquidity assets, and swap according to the user’s intentions on their behalf, Krystal will ask users to sign their order off-chain and then verify this signature once again on the smart contract.

  • Users will sign their order configurations using EIP-712: Typed structured data hashing and signing. These signatures will be securely stored by Krystal:

{
    "chainId": 42161,
    "nfpmAddress": "0xc36442b4a4522e871399cd717abdd847ab11fe88",
    "tokenId": "2008812",
    "orderType": "ORDER_TYPE_REBALANCE",
    "config": {
        "rangeOrderConfig": {
            "action": {
                "maxGasProportion": "0",
                "swapSlippage": "0",
                "withdrawSlippage": "0"
            },
            "condition": {
                "gteTickAbsolute": 0,
                "lteTickAbsolute": 0,
                "zeroToOne": false
            }
        },
        "rebalanceConfig": {
            "autoCompound": {
                "action": {
                    "feeToPrincipalRatioThreshold": "922337203685477632",
                    "maxGasProportion": "970925927575628544"
                }
            },
            "rebalanceAction": {
                "liquiditySlippage": "184467440737095520",
                "maxGasProportion": "970925927575628544",
                "priceOffsetAction": {
                    "baseToken": 0,
                    "priceLowerOffset": "0",
                    "priceUpperOffset": "0"
                },
                "swapSlippage": "184467440737095520",
                "tickOffsetAction": {
                    "tickLowerOffset": 102,
                    "tickUpperOffset": 100
                },
                "tokenRatioAction": {
                    "tickWidth": 0,
                    "token0Ratio": "0"
                },
                "type": "ACTION_TYPE_PERCENTAGE"
            },
            "rebalanceCondition": {
                "priceOffsetCondition": {
                    "baseToken": 0,
                    "gtePriceOffset": "0",
                    "ltePriceOffset": "0"
                },
                "sqrtPriceX96": "79214759379423715127847",
                "tickOffsetCondition": {
                    "gteTickOffset": 4,
                    "lteTickOffset": 7
                },
                "timeBuffer": 3600,
                "tokenRatioCondition": {
                    "gteToken0Ratio": "0",
                    "lteToken0Ratio": "0"
                },
                "type": "CONDITION_TYPE_PERCENTAGE"
            },
            "recurring": true
        }
    },
    "signatureTime": 1713927796
}
  • When the Operator calls the V3 Automation smart contract to execute a function, it includes the signature and order configuration. The smart contract then verifies that the signer matches the position owner.:

function execute(ExecuteParams calldata params) public payable onlyRole(OPERATOR_ROLE) whenNotPaused() {
        address userAddress = _recover(params.userConfig, params.orderSignature);
        require(userAddress == params.userAddress);
        _execute(params);
 }

Canceling orders

#cancel

Smart contract settings ensure that users retain full control over their positions and automation, even if Krystal’s servers are inaccessible.

In case of system malfunctions or failures, users can directly interact with the smart contract. By using the cancelOrder method with the order configuration and signature, users can cancel their orders, preventing the Operator from executing them.

In any case, the fund is safe on-chain even when our server is down or malfunctioning.


function cancelOrder(Order calldata order, bytes calldata orderSignature) external {
    _validateOrder(order, orderSignature, msg.sender);
    _cancelledOrder[keccak256(orderSignature)] = true;
    emit CancelOrder(msg.sender, order, orderSignature);
}

function isOrderCancelled(bytes calldata orderSignature) external view returns (bool) {
    return _cancelledOrder[keccak256(orderSignature)];
}

function _validateOrder(Order memory order, bytes memory orderSignature, address actor) internal view {
    address userAddress = _recover(order, orderSignature);
    require(userAddress == actor);
    if (_cancelledOrder[keccak256(orderSignature)]) {
        revert OrderCancelled();
    }
}

Revoke

#revoke

Users can call the NonfungiblePositionManager smart contract using the setApprovalForAll(V3 Automation Address, false) method to revoke permission for V3 Automation. Once revoked, Krystal will no longer be able to execute their orders.

Last updated