๐Ÿ”Security

List of methods on how we are making sure even Krystalโ€™s devs wonโ€™t be able to exploit the system and take userโ€™s funds

Access Control

#limit_fee #pause #multi_sig

The smart contract V3 Automation is operated and guarded by 3 different roles Admin, Withdrawer, and Operator

  • What can Admin role do?

    • Assign a wallet to be Operator or Withdrawer

    • Pause the smart contract to stop executing orders

    • Setting the ceiling fee taken in one execution

    โ†’ This role is maintained by a multi-sig wallet which requires 5/6 signatures ๐Ÿ”ฐ

  • What can Withdrawer role do?

    • Withdraw the collected fee in the smart contract

    โ†’ Withdrawers cannot touch any of the LP positions โ†’ This role is maintained by a multi-sig wallet and a different set of approvals to reduce the risk.

    โ†’ It can be changed to other wallets by the Admin role

  • What can Operator role do?

    • The only role to manage usersโ€™ positions on behalf of them, such as depositing, withdrawing, collect fees from the pool.

    โ†’ Limited set of actions that they could perform for rebalancing only. Things are wrapped in specific transactions (a.k.a. rebalance) to ensure this role cannot interact with the fund freely. โ†’ This role is run by multiple EoA wallets to serve multiple orders at once โ†’ It can be added/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 the Operator manages user position on their behalf, it can deposit, withdraw & collect liquidity assets, and swap. To make sure each execution follows the userโ€™s intention, Krystal will ask users to sign their order off-chain, and then verify this signature once again on the smart contract.

  • Users will be asked to sign their order config following the EIP-712: Typed structured data hashing and signing, this signature will be kept by Krystal

    • Example

      {
          "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 Operator calls V3 Automation smart contract to perform the function execute, it will send the signature along with the order config. The smart contract will verify if the signer is the position-owner.

    • Code

      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 would make sure users have full access and control over their positions and automation, even when Krystal server is inaccessible.

Even though Krystal allows users to cancel orders off-chain, this system can malfunction, e.g.: failing to handle cancel requests. In that case, users can call the smart contract directly with the method cancelOrder using the order config and signature to cancel the order. When this happens, the Operator cannot execute the order anymore.

โœ… 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 directly using the method setApprovalForAll(V3 Automation Address, false)to revoke the permission granted for V3 Automation, and then Krystal cannot execute their orders anymore.

Last updated