瀏覽代碼

Runtime error strings must be LLVM constant strings instead of dynamically allocated (#1400)

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>
Lucas Steuernagel 2 年之前
父節點
當前提交
7f599fc2e3

文件差異過大導致無法顯示
+ 58 - 1057
src/codegen/constant_folding.rs


+ 2 - 2
src/codegen/expression.rs

@@ -3697,8 +3697,8 @@ pub(crate) fn log_runtime_error(
     ns: &Namespace,
 ) {
     if report_error {
-        let error_with_loc = error_msg_with_loc(ns, reason, Some(reason_loc));
-        let expr = string_to_expr(error_with_loc + ",\n");
+        let error_with_loc = error_msg_with_loc(ns, reason.to_string(), Some(reason_loc));
+        let expr = string_to_expr(error_with_loc);
         cfg.add(vartab, Instr::Print { expr });
     }
 }

+ 6 - 10
src/codegen/mod.rs

@@ -1832,16 +1832,12 @@ impl From<&ast::Builtin> for Builtin {
     }
 }
 
-pub(super) fn error_msg_with_loc(ns: &Namespace, error: &str, loc: Option<Loc>) -> String {
-    if let Some(loc) = loc {
-        match loc {
-            Loc::File(..) => {
-                let loc_from_file = ns.loc_to_string(PathDisplay::Filename, &loc);
-                format!("runtime_error: {error} in {loc_from_file}")
-            }
-            _ => error.to_string(),
+pub(super) fn error_msg_with_loc(ns: &Namespace, error: String, loc: Option<Loc>) -> String {
+    match &loc {
+        Some(loc @ Loc::File(..)) => {
+            let loc_from_file = ns.loc_to_string(PathDisplay::Filename, loc);
+            format!("runtime_error: {error} in {loc_from_file},\n")
         }
-    } else {
-        error.to_string()
+        _ => error + ",\n",
     }
 }

+ 25 - 2
src/emit/binary.rs

@@ -13,9 +13,9 @@ use tempfile::tempdir;
 #[cfg(feature = "wasm_opt")]
 use wasm_opt::OptimizationOptions;
 
-use crate::codegen::{cfg::ReturnCode, Options};
-use crate::emit::substrate;
+use crate::codegen::{cfg::ReturnCode, error_msg_with_loc, Options};
 use crate::emit::{solana, BinaryOp, Generate};
+use crate::emit::{substrate, TargetRuntime};
 use crate::linker::link;
 use crate::Target;
 use inkwell::builder::Builder;
@@ -34,6 +34,7 @@ use inkwell::AddressSpace;
 use inkwell::IntPredicate;
 use inkwell::OptimizationLevel;
 use once_cell::sync::OnceCell;
+use solang_parser::pt;
 
 static LLVM_INIT: OnceCell<()> = OnceCell::new();
 
@@ -1051,6 +1052,28 @@ impl<'a> Binary<'a> {
             _ => unreachable!(),
         }
     }
+
+    pub(super) fn log_runtime_error<T: TargetRuntime<'a> + ?Sized>(
+        &self,
+        target: &T,
+        reason_string: String,
+        reason_loc: Option<pt::Loc>,
+        ns: &Namespace,
+    ) {
+        if !self.options.log_runtime_errors {
+            return;
+        }
+        let error_with_loc = error_msg_with_loc(ns, reason_string, reason_loc);
+        let global_string =
+            self.emit_global_string("runtime_error", error_with_loc.as_bytes(), true);
+        target.print(
+            self,
+            global_string,
+            self.context
+                .i32_type()
+                .const_int(error_with_loc.len() as u64, false),
+        );
+    }
 }
 
 /// Return the stdlib as parsed llvm module. The solidity standard library is hardcoded into

+ 6 - 24
src/emit/expression.rs

@@ -276,7 +276,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 bin.builder.position_at_end(bail_block);
 
                 // throw division by zero error should be an assert
-                target.log_runtime_error(bin, "division by zero".to_string(), Some(*loc), ns);
+                bin.log_runtime_error(target, "division by zero".to_string(), Some(*loc), ns);
                 target.assert_failure(
                     bin,
                     bin.context
@@ -373,7 +373,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 bin.builder.position_at_end(bail_block);
 
                 // throw division by zero error should be an assert
-                target.log_runtime_error(bin, "division by zero".to_string(), Some(*loc), ns);
+                bin.log_runtime_error(target, "division by zero".to_string(), Some(*loc), ns);
                 target.assert_failure(
                     bin,
                     bin.context
@@ -518,7 +518,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 bin.builder.position_at_end(bail_block);
 
                 // throw division by zero error should be an assert
-                target.log_runtime_error(bin, "division by zero".to_string(), Some(*loc), ns);
+                bin.log_runtime_error(target, "division by zero".to_string(), Some(*loc), ns);
                 target.assert_failure(
                     bin,
                     bin.context
@@ -612,7 +612,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 bin.builder.position_at_end(bail_block);
 
                 // throw division by zero error should be an assert
-                target.log_runtime_error(bin, "division by zero".to_string(), Some(*loc), ns);
+                bin.log_runtime_error(target, "division by zero".to_string(), Some(*loc), ns);
                 target.assert_failure(
                     bin,
                     bin.context
@@ -732,7 +732,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                     .build_conditional_branch(error_ret, error_block, return_block);
                 bin.builder.position_at_end(error_block);
 
-                target.log_runtime_error(bin, "math overflow".to_string(), Some(*loc), ns);
+                bin.log_runtime_error(target, "math overflow".to_string(), Some(*loc), ns);
                 target.assert_failure(
                     bin,
                     bin.context
@@ -1134,7 +1134,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 .build_conditional_branch(is_equal_to_n, cast, error);
 
             bin.builder.position_at_end(error);
-            target.log_runtime_error(bin, "bytes cast error".to_string(), Some(*loc), ns);
+            bin.log_runtime_error(target, "bytes cast error".to_string(), Some(*loc), ns);
             target.assert_failure(
                 bin,
                 bin.context
@@ -2023,21 +2023,3 @@ fn runtime_cast<'a>(
         val
     }
 }
-
-pub(crate) fn string_to_basic_value<'a>(
-    bin: &Binary<'a>,
-    ns: &Namespace,
-    input: String,
-) -> BasicValueEnum<'a> {
-    let elem = Type::Bytes(1);
-    let size = bin.context.i32_type().const_int(input.len() as u64, false);
-
-    let elem_size = bin
-        .llvm_type(&elem, ns)
-        .size_of()
-        .unwrap()
-        .const_cast(bin.context.i32_type(), false);
-
-    let init = Option::Some(input.as_bytes().to_vec());
-    bin.vector_new(size, elem_size, init.as_ref()).into()
-}

+ 1 - 1
src/emit/instructions.rs

@@ -298,7 +298,7 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
                 .build_conditional_branch(is_array_empty, error, pop);
 
             bin.builder.position_at_end(error);
-            target.log_runtime_error(bin, "pop from empty array".to_string(), Some(*loc), ns);
+            bin.log_runtime_error(target, "pop from empty array".to_string(), Some(*loc), ns);
             target.assert_failure(
                 bin,
                 bin.context

+ 6 - 6
src/emit/math.rs

@@ -12,9 +12,9 @@ use solang_parser::pt::Loc;
 /// 1- Do an unsigned multiplication first, This step will check if the generated value will fit in N bits. (unsigned overflow)
 /// 2- Get the result, and negate it if needed.
 /// 3- Check for signed overflow, by checking for an unexpected change in the sign of the result.
-fn signed_ovf_detect<'b, 'a: 'b, T: TargetRuntime<'a> + ?Sized>(
+fn signed_ovf_detect<'a, T: TargetRuntime<'a> + ?Sized>(
     target: &T,
-    bin: &Binary<'b>,
+    bin: &Binary<'a>,
     mul_ty: IntType<'a>,
     mul_bits: u32,
     left: IntValue<'a>,
@@ -23,7 +23,7 @@ fn signed_ovf_detect<'b, 'a: 'b, T: TargetRuntime<'a> + ?Sized>(
     function: FunctionValue<'a>,
     ns: &Namespace,
     loc: Loc,
-) -> IntValue<'b> {
+) -> IntValue<'a> {
     // We check for signed overflow based on the facts:
     //  - * - = +
     //  + * + = +
@@ -180,7 +180,7 @@ fn signed_ovf_detect<'b, 'a: 'b, T: TargetRuntime<'a> + ?Sized>(
 
     bin.builder.position_at_end(error_block);
 
-    target.log_runtime_error(bin, "multiplication overflow".to_string(), Some(loc), ns);
+    bin.log_runtime_error(target, "multiplication overflow".to_string(), Some(loc), ns);
     target.assert_failure(
         bin,
         bin.context
@@ -358,7 +358,7 @@ pub(super) fn multiply<'a, T: TargetRuntime<'a> + ?Sized>(
 
             bin.builder.position_at_end(error_block);
 
-            target.log_runtime_error(bin, "multiplication overflow".to_string(), Some(loc), ns);
+            bin.log_runtime_error(target, "multiplication overflow".to_string(), Some(loc), ns);
             target.assert_failure(
                 bin,
                 bin.context
@@ -587,7 +587,7 @@ pub(super) fn build_binary_op_with_overflow_check<'a, T: TargetRuntime<'a> + ?Si
 
     bin.builder.position_at_end(error_block);
 
-    target.log_runtime_error(bin, "math overflow".to_string(), Some(loc), ns);
+    bin.log_runtime_error(target, "math overflow".to_string(), Some(loc), ns);
 
     target.assert_failure(
         bin,

+ 0 - 8
src/emit/mod.rs

@@ -213,14 +213,6 @@ pub trait TargetRuntime<'a> {
     /// Prints a string
     fn print(&self, bin: &Binary, string: PointerValue, length: IntValue);
 
-    fn log_runtime_error(
-        &self,
-        bin: &Binary,
-        reason_string: String,
-        reason_loc: Option<Loc>,
-        ns: &Namespace,
-    );
-
     /// Return success without any result
     fn return_empty_abi(&self, bin: &Binary);
 

+ 7 - 28
src/emit/solana/target.rs

@@ -2,9 +2,8 @@
 
 use crate::codegen;
 use crate::codegen::cfg::{HashTy, ReturnCode};
-use crate::codegen::error_msg_with_loc;
 use crate::emit::binary::Binary;
-use crate::emit::expression::{expression, string_to_basic_value};
+use crate::emit::expression::expression;
 use crate::emit::loop_builder::LoopBuilder;
 use crate::emit::solana::SolanaTarget;
 use crate::emit::{ContractArgs, TargetRuntime, Variable};
@@ -125,8 +124,8 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
 
         binary.builder.position_at_end(bang_block);
 
-        self.log_runtime_error(
-            binary,
+        binary.log_runtime_error(
+            self,
             "storage array index out of bounds".to_string(),
             Some(loc),
             ns,
@@ -206,8 +205,8 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .build_conditional_branch(in_range, set_block, bang_block);
 
         binary.builder.position_at_end(bang_block);
-        self.log_runtime_error(
-            binary,
+        binary.log_runtime_error(
+            self,
             "storage index out of bounds".to_string(),
             Some(loc),
             ns,
@@ -466,8 +465,8 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .build_conditional_branch(in_range, retrieve_block, bang_block);
 
         binary.builder.position_at_end(bang_block);
-        self.log_runtime_error(
-            binary,
+        binary.log_runtime_error(
+            self,
             "pop from empty storage array".to_string(),
             Some(loc),
             ns,
@@ -2121,24 +2120,4 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .builder
             .build_return(Some(&binary.return_values[&ReturnCode::Success]));
     }
-
-    fn log_runtime_error(
-        &self,
-        bin: &Binary,
-        reason_string: String,
-        reason_loc: Option<Loc>,
-        ns: &Namespace,
-    ) {
-        if !bin.options.log_runtime_errors {
-            return;
-        }
-
-        let error_with_loc = error_msg_with_loc(ns, &reason_string, reason_loc);
-        let custom_error = string_to_basic_value(bin, ns, error_with_loc + ",\n");
-        self.print(
-            bin,
-            bin.vector_bytes(custom_error),
-            bin.vector_len(custom_error),
-        );
-    }
 }

+ 10 - 42
src/emit/substrate/target.rs

@@ -1,9 +1,8 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use crate::codegen::cfg::{HashTy, ReturnCode};
-use crate::codegen::error_msg_with_loc;
 use crate::emit::binary::Binary;
-use crate::emit::expression::{expression, string_to_basic_value};
+use crate::emit::expression::expression;
 use crate::emit::storage::StorageSlot;
 use crate::emit::substrate::{log_return_code, SubstrateTarget, SCRATCH_SIZE};
 use crate::emit::{ContractArgs, TargetRuntime, Variable};
@@ -352,8 +351,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
 
         binary.builder.position_at_end(bang_block);
 
-        self.log_runtime_error(
-            binary,
+        binary.log_runtime_error(
+            self,
             "storage array index out of bounds".to_string(),
             Some(loc),
             ns,
@@ -440,8 +439,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             .build_conditional_branch(in_range, retrieve_block, bang_block);
 
         binary.builder.position_at_end(bang_block);
-        self.log_runtime_error(
-            binary,
+        binary.log_runtime_error(
+            self,
             "storage index out of bounds".to_string(),
             Some(loc),
             ns,
@@ -620,8 +619,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             .build_conditional_branch(in_range, retrieve_block, bang_block);
 
         binary.builder.position_at_end(bang_block);
-        self.log_runtime_error(
-            binary,
+        binary.log_runtime_error(
+            self,
             "pop from empty storage array".to_string(),
             Some(loc),
             ns,
@@ -938,12 +937,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
 
             binary.builder.position_at_end(bail_block);
 
-            self.log_runtime_error(
-                binary,
-                "contract creation failed".to_string(),
-                Some(loc),
-                ns,
-            );
+            binary.log_runtime_error(self, "contract creation failed".to_string(), Some(loc), ns);
             self.assert_failure(
                 binary,
                 scratch_buf,
@@ -1106,7 +1100,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
 
             binary.builder.position_at_end(bail_block);
 
-            self.log_runtime_error(binary, "external call failed".to_string(), Some(loc), ns);
+            binary.log_runtime_error(self, "external call failed".to_string(), Some(loc), ns);
             self.assert_failure(
                 binary,
                 scratch_buf,
@@ -1175,7 +1169,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
 
             binary.builder.position_at_end(bail_block);
 
-            self.log_runtime_error(binary, "value transfer failure".to_string(), Some(loc), ns);
+            binary.log_runtime_error(self, "value transfer failure".to_string(), Some(loc), ns);
             self.assert_failure(binary, byte_ptr!().const_null(), i32_zero!());
 
             binary.builder.position_at_end(success_block);
@@ -1741,30 +1735,4 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         // not needed for slot-based storage chains
         unimplemented!()
     }
-
-    fn log_runtime_error(
-        &self,
-        bin: &Binary,
-        reason_string: String,
-        reason_loc: Option<Loc>,
-        ns: &Namespace,
-    ) {
-        if !bin.options.log_runtime_errors {
-            return;
-        }
-        emit_context!(bin);
-        let error_with_loc = error_msg_with_loc(ns, &reason_string, reason_loc);
-        let custom_error = string_to_basic_value(bin, ns, error_with_loc + ",\n");
-        call!(
-            "debug_message",
-            &[
-                bin.vector_bytes(custom_error).into(),
-                bin.vector_len(custom_error).into()
-            ]
-        )
-        .try_as_basic_value()
-        .left()
-        .unwrap()
-        .into_int_value();
-    }
 }

+ 900 - 0
tests/contract_testcases/solana/large_contract.sol

@@ -0,0 +1,900 @@
+
+// This is a test for issue #1367: https://github.com/hyperledger/solang/issues/1367
+// This contract used to generate so many calls to 'vector_new' for allocating error strings that it caused a LLVM ERROR: Branch target out of insn range
+import "solana";
+
+/// @title Service Registry Solana - Smart contract for registering services on the Solana chain.
+/// @dev Underlying canonical agents and components are not checked for their validity since they are set up on the L1 mainnet.
+///      The architecture is optimistic, in the sense that service owners are assumed to reference existing and relevant agents.
+/// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
+/// @author Andrey Lebedev - <andrey.lebedev@valory.xyz>
+contract ServiceRegistrySolana {
+    event OwnerUpdated(address indexed owner);
+    event BaseURIChanged(string baseURI);
+    event DrainerUpdated(address indexed drainer);
+    event Deposit(address indexed sender, uint64 amount);
+    event Refund(address indexed receiver, uint64 amount);
+    event CreateService(uint32 indexed serviceId, bytes32 configHash);
+    event UpdateService(uint32 indexed serviceId, bytes32 configHash);
+    event RegisterInstance(address indexed operator, uint32 indexed serviceId, address indexed agentInstance, uint32 agentId);
+    event CreateMultisigWithAgents(uint32 indexed serviceId, address indexed multisig);
+    event ActivateRegistration(uint32 indexed serviceId);
+    event TerminateService(uint32 indexed serviceId);
+    event OperatorSlashed(uint64 amount, address indexed operator, uint32 indexed serviceId);
+    event OperatorUnbond(address indexed operator, uint32 indexed serviceId);
+    event DeployService(uint32 indexed serviceId);
+    event Drain(address indexed drainer, uint64 amount);
+
+    enum ServiceState {
+        NonExistent,
+        PreRegistration,
+        ActiveRegistration,
+        FinishedRegistration,
+        Deployed,
+        TerminatedBonded
+    }
+
+    // Service parameters
+    struct Service {
+        address serviceOwner;
+        // Registration activation deposit
+        // This is enough for 1b+ ETH or 1e27
+        uint64 securityDeposit;
+        // Multisig address for agent instances
+        address multisig;
+        // IPFS hashes pointing to the config metadata
+        bytes32 configHash;
+        // Agent instance signers threshold: must no less than ceil((n * 2 + 1) / 3) of all the agent instances combined
+        // This number will be enough to have ((2^32 - 1) * 3 - 1) / 2, which is bigger than 6.44b
+        uint32 threshold;
+        // Total number of agent instances. We assume that the number of instances is bounded by 2^32 - 1
+        uint32 maxNumAgentInstances;
+        // Actual number of agent instances. This number is less or equal to maxNumAgentInstances
+        uint32 numAgentInstances;
+        // Service state
+        ServiceState state;
+        // Canonical agent Ids for the service. Individual agent Id is bounded by the max number of agent Id
+        uint32[] agentIds;
+        uint32[] slots;
+        uint64[] bonds;
+        address[] operators;
+        address[] agentInstances;
+        uint32[] agentIdForAgentInstances;
+    }
+
+    // The public key for the storage authority (owner) that should sign every change to the contract
+    address public owner;
+    address public escrow;
+    // Base URI
+    string public baseURI;
+    // Service counter
+    uint32 public totalSupply;
+    // Reentrancy lock
+    uint32 internal _locked = 1;
+    // To better understand the CID anatomy, please refer to: https://proto.school/anatomy-of-a-cid/05
+    // CID = <multibase_encoding>multibase_encoding(<cid-version><multicodec><multihash-algorithm><multihash-length><multihash-hash>)
+    // CID prefix = <multibase_encoding>multibase_encoding(<cid-version><multicodec><multihash-algorithm><multihash-length>)
+    // to complement the multibase_encoding(<multihash-hash>)
+    // multibase_encoding = base16 = "f"
+    // cid-version = version 1 = "0x01"
+    // multicodec = dag-pb = "0x70"
+    // multihash-algorithm = sha2-256 = "0x12"
+    // multihash-length = 256 bits = "0x20"
+    string public constant CID_PREFIX = "f01701220";
+    // The amount of funds slashed. This is enough for 1b+ ETH or 1e27
+    uint64 public slashedFunds;
+    // Drainer address: set by the government and is allowed to drain ETH funds accumulated in this contract
+    address public drainer;
+    // Service registry version number
+    string public constant VERSION = "1.0.0";
+    // Map of hash(operator address and serviceId) => agent instance bonding / escrow balance
+    mapping(bytes32 => uint64) public mapOperatorAndServiceIdOperatorBalances;
+    // Map of agent instance address => service id it is registered with and operator address that supplied the instance
+    mapping (address => address) public mapAgentInstanceOperators;
+    // Map of policy for multisig implementations
+    mapping (address => bool) public mapMultisigs;
+    // Set of services
+    Service[type(uint32).max] public services;
+
+
+    /// @dev Service registry constructor.
+    /// @param _owner Agent contract symbol.
+    /// @param _baseURI Agent registry token base URI.
+    constructor(address _owner, string memory _baseURI)
+    {
+        owner = _owner;
+        baseURI = _baseURI;
+    }
+
+    /// Requires the signature of the metadata authority.
+    function requireSigner(address authority) internal view {
+        for(uint32 i=0; i < tx.accounts.length; i++) {
+            if (tx.accounts[i].key == authority) {
+                require(tx.accounts[i].is_signer, "the authority account must sign the transaction");
+                return;
+            }
+        }
+
+        revert("The authority is missing");
+    }
+
+    /// @dev Changes the owner address.
+    /// @param newOwner Address of a new owner.
+    function changeOwner(address newOwner) external {
+        // Check for the metadata authority
+        requireSigner(owner);
+
+        // Check for the zero address
+        if (newOwner == address(0)) {
+            revert("Zero Address");
+        }
+
+        owner = newOwner;
+        emit OwnerUpdated(newOwner);
+    }
+
+    /// @dev Changes the drainer.
+    /// @param newDrainer Address of a drainer.
+    function changeDrainer(address newDrainer) external {
+        // Check for the metadata authority
+        requireSigner(owner);
+
+        // Check for the zero address
+        if (newDrainer == address(0)) {
+            revert("ZeroAddress");
+        }
+
+        drainer = newDrainer;
+        emit DrainerUpdated(newDrainer);
+    }
+
+    function transfer(uint32 serviceId, address newServiceOwner) public {
+        // Check for the service authority
+        address serviceOwner = services[serviceId].serviceOwner;
+        requireSigner(serviceOwner);
+
+        services[serviceId].serviceOwner = newServiceOwner;
+
+    }
+
+    /// @dev Going through basic initial service checks.
+    /// @param configHash IPFS hash pointing to the config metadata.
+    /// @param agentIds Canonical agent Ids.
+    /// @param slots Set of agent instances number for each agent Id.
+    /// @param bonds Corresponding set of required bonds to register an agent instance in the service.
+    function _initialChecks(
+        bytes32 configHash,
+        uint32[] memory agentIds,
+        uint32[] memory slots,
+        uint64[] memory bonds
+    ) private pure
+    {
+        // Check for the non-zero hash value
+        if (configHash == 0) {
+            revert("ZeroValue");
+        }
+
+        // Checking for non-empty arrays and correct number of values in them
+        if (agentIds.length == 0 || agentIds.length != slots.length || agentIds.length != bonds.length) {
+            revert("WrongArrayLength");
+        }
+
+        // Check for duplicate canonical agent Ids
+        uint32 lastId = 0;
+        for (uint32 i = 0; i < agentIds.length; i++) {
+            if (agentIds[i] < (lastId + 1)) {
+                revert("WrongAgentId");
+            }
+            lastId = agentIds[i];
+        }
+
+        // Check that there are no zero number of slots for a specific canonical agent id and no zero registration bond
+        for (uint32 i = 0; i < agentIds.length; i++) {
+            if (slots[i] == 0 || bonds[i] == 0) {
+                revert("ZeroValue");
+            }
+        }
+    }
+
+    /// @dev Sets the service data.
+    /// @param service A service instance to fill the data for.
+    /// @param agentIds Canonical agent Ids.
+    /// @param slots Set of agent instances number for each agent Id.
+    /// @param bonds Corresponding set of required bonds to register an agent instance in the service.
+    function _setServiceData(
+        Service memory service,
+        uint32[] memory agentIds,
+        uint32[] memory slots,
+        uint64[] memory bonds
+    ) private
+    {
+        // Security deposit
+        uint64 securityDeposit = 0;
+        // Add canonical agent Ids for the service and the slots map
+        service.agentIds = agentIds;
+        service.slots = slots;
+        service.bonds = bonds;
+        for (uint32 i = 0; i < agentIds.length; i++) {
+            service.maxNumAgentInstances += slots[i];
+            // Security deposit is the maximum of the canonical agent registration bond
+            if (bonds[i] > securityDeposit) {
+                securityDeposit = bonds[i];
+            }
+        }
+        service.securityDeposit = securityDeposit;
+
+        // Check for the correct threshold: no less than ceil((n * 2 + 1) / 3) of all the agent instances combined
+        uint32 checkThreshold = service.maxNumAgentInstances * 2 + 1;
+        if (checkThreshold % 3 == 0) {
+            checkThreshold = checkThreshold / 3;
+        } else {
+            checkThreshold = checkThreshold / 3 + 1;
+        }
+        if (service.threshold < checkThreshold || service.threshold > service.maxNumAgentInstances) {
+            revert("WrongThreshold");
+        }
+    }
+
+    /// @dev Creates a new service.
+    /// @notice If agentIds are not sorted in ascending order then the function that executes initial checks gets reverted.
+    /// @param serviceOwner Individual that creates and controls a service.
+    /// @param configHash IPFS hash pointing to the config metadata.
+    /// @param agentIds Canonical agent Ids in a sorted ascending order.
+    /// @param slots Set of agent instances number for each agent Id.
+    /// @param bonds Corresponding set of required bonds to register an agent instance in the service.
+    /// @param threshold Signers threshold for a multisig composed by agent instances.
+    /// @return serviceId Created service Id.
+    function create(
+        address serviceOwner,
+        bytes32 configHash,
+        uint32[] memory agentIds,
+        uint32[] memory slots,
+        uint64[] memory bonds,
+        uint32 threshold
+    ) external returns (uint32 serviceId)
+    {
+        // Reentrancy guard
+        if (_locked > 1) {
+            revert("ReentrancyGuard");
+        }
+        _locked = 2;
+
+        // Check for the non-empty service owner address
+        if (serviceOwner == address(0)) {
+            revert("ZeroAddress");
+        }
+
+        // Execute initial checks
+        _initialChecks(configHash, agentIds, slots, bonds);
+
+        // Create a new service Id
+        serviceId = totalSupply;
+        serviceId++;
+
+        // Set high-level data components of the service instance
+        Service memory service;
+        // Updating high-level data components of the service
+        service.threshold = threshold;
+        // Assigning the initial hash
+        service.configHash = configHash;
+        // Set the initial service state
+        service.state = ServiceState.PreRegistration;
+
+        // Set service data
+        _setServiceData(service, agentIds, slots, bonds);
+
+        // Mint the service instance to the service owner and record the service structure
+        service.serviceOwner = serviceOwner;
+
+        services[serviceId] = service;
+        totalSupply = serviceId;
+
+        emit CreateService(serviceId, configHash);
+
+        _locked = 1;
+    }
+
+    /// @dev Updates a service in a CRUD way.
+    /// @param configHash IPFS hash pointing to the config metadata.
+    /// @param agentIds Canonical agent Ids in a sorted ascending order.
+    /// @notice If agentIds are not sorted in ascending order then the function that executes initial checks gets reverted.
+    /// @param slots Set of agent instances number for each agent Id.
+    /// @param bonds Corresponding set of required bonds to register an agent instance in the service.
+    /// @param threshold Signers threshold for a multisig composed by agent instances.
+    /// @param serviceId Service Id to be updated.
+    /// @return success True, if function executed successfully.
+    function update(
+        bytes32 configHash,
+        uint32[] memory agentIds,
+        uint32[] memory slots,
+        uint64[] memory bonds,
+        uint32 threshold,
+        uint32 serviceId
+    ) external returns (bool success)
+    {
+        Service memory service = services[serviceId];
+
+        // Check for the service authority
+        address serviceOwner = service.serviceOwner;
+        requireSigner(serviceOwner);
+
+        if (service.state != ServiceState.PreRegistration) {
+            revert("WrongServiceState");
+        }
+
+        // Execute initial checks
+        _initialChecks(configHash, agentIds, slots, bonds);
+
+        // Updating high-level data components of the service
+        service.threshold = threshold;
+        service.maxNumAgentInstances = 0;
+
+        // Check if the previous hash is the same / hash was not updated
+        bytes32 lastConfigHash = service.configHash;
+        if (lastConfigHash != configHash) {
+            service.configHash = configHash;
+        }
+
+        // Set service data and record the modified service struct
+        _setServiceData(service, agentIds, slots, bonds);
+        services[serviceId] = service;
+
+        emit UpdateService(serviceId, configHash);
+        success = true;
+    }
+
+    /// @dev Activates the service.
+    /// @param serviceId Correspondent service Id.
+    /// @return success True, if function executed successfully.
+    function activateRegistration(uint32 serviceId) external payable returns (bool success)
+    {
+        Service storage service = services[serviceId];
+
+        // Check for the service authority
+        address serviceOwner = service.serviceOwner;
+        requireSigner(serviceOwner);
+
+        // Service must be inactive
+        if (service.state != ServiceState.PreRegistration) {
+            revert("ServiceMustBeInactive");
+        }
+
+        // TODO: Need to check that the balance of the escrow for the serviceOwner has enough balance
+//        if (msg.value != service.securityDeposit) {
+//            revert IncorrectRegistrationDepositValue(msg.value, service.securityDeposit, serviceId);
+//        }
+
+        // Activate the agent instance registration
+        service.state = ServiceState.ActiveRegistration;
+
+        emit ActivateRegistration(serviceId);
+        success = true;
+    }
+
+    /// @dev Registers agent instances.
+    /// @param operator Address of the operator.
+    /// @param serviceId Service Id to register agent instances for.
+    /// @param agentInstances Agent instance addresses.
+    /// @param agentIds Canonical Ids of the agent correspondent to the agent instance.
+    /// @return success True, if function executed successfully.
+    function registerAgents(
+        address operator,
+        uint32 serviceId,
+        address[] memory agentInstances,
+        uint32[] memory agentIds
+    ) external payable returns (bool success)
+    {
+        // Check if the length of canonical agent instance addresses array and ids array have the same length
+        if (agentInstances.length != agentIds.length) {
+            revert("WrongArrayLength");
+        }
+
+        Service storage service = services[serviceId];
+        // The service has to be active to register agents
+        if (service.state != ServiceState.ActiveRegistration) {
+            revert("WrongServiceState");
+        }
+
+        // Check for the sufficient amount of bond fee is provided
+        uint32 numAgents = agentInstances.length;
+        uint64 totalBond = 0;
+        uint32[] memory serviceAgentIds = service.agentIds;
+        for (uint32 i = 0; i < numAgents; ++i) {
+            // Check if canonical agent Id exists in the service
+            uint32 idx = 0;
+            for (; idx < serviceAgentIds.length; ++idx) {
+                if (serviceAgentIds[idx] == agentIds[i]) {
+                    break;
+                }
+            }
+            // If the index reached the length of service agent Ids, the agent Id does not exist in the service
+            if (idx == service.agentIds.length) {
+                revert("AgentNotInService");
+            }
+            totalBond += service.bonds[idx];
+        }
+        // TODO: Check the escrow balance
+//        if (msg.value != totalBond) {
+//            revert("IncorrectAgentBondingValue");
+//        }
+
+        // Operator address must not be used as an agent instance anywhere else
+        if (mapAgentInstanceOperators[operator] != address(0)) {
+            revert("WrongOperator");
+        }
+
+        for (uint32 i = 0; i < numAgents; ++i) {
+            address agentInstance = agentInstances[i];
+            uint32 agentId = agentIds[i];
+
+            // Operator address must be different from agent instance one
+            if (operator == agentInstance) {
+                revert("WrongOperator");
+            }
+
+            // Check if the agent instance is already engaged with another service
+            if (mapAgentInstanceOperators[agentInstance] != address(0)) {
+                revert("AgentInstanceRegistered");
+            }
+
+            // Check if there is an empty slot for the agent instance in this specific service
+            uint32 numRegAgentInstances = 0;
+            for (uint32 j = 0; j < service.agentIdForAgentInstances.length; ++j) {
+                if (service.agentIdForAgentInstances[j] == agentId) {
+                    numRegAgentInstances++;
+                }
+            }
+            if (numRegAgentInstances == service.slots[i]) {
+                revert("AgentInstancesSlotsFilled");
+            }
+
+            // Copy new operator and agent instance information to the new one
+            address[] memory oldOperators = service.operators;
+            address[] memory oldAgentInstances = service.agentInstances;
+            uint32[] memory oldAgentIdForAgentInstances = service.agentIdForAgentInstances;
+            address[] memory newOperators = new address[](oldOperators.length + 1);
+            address[] memory newAgentInstances = new address[](oldOperators.length + 1);
+            uint32[] memory newAgentIdForAgentInstances = new uint32[](oldOperators.length + 1);
+            for (uint32 j = 0; j < oldOperators.length; ++j) {
+                newOperators[j] = oldOperators[j];
+                newAgentInstances[j] = oldAgentInstances[j];
+                newAgentIdForAgentInstances[j] = oldAgentIdForAgentInstances[j];
+            }
+
+            // Add agent instance and operator and set the instance engagement
+            newOperators[oldOperators.length] = operator;
+            newAgentInstances[oldOperators.length] = agentInstance;
+            newAgentIdForAgentInstances[oldOperators.length] = agentId;
+            // Update service arrays
+            service.operators = newOperators;
+            service.agentInstances = newAgentInstances;
+            service.agentIdForAgentInstances = newAgentIdForAgentInstances;
+            // Increase the total number of agent instances in the service
+            service.numAgentInstances++;
+            mapAgentInstanceOperators[agentInstance] = operator;
+
+            emit RegisterInstance(operator, serviceId, agentInstance, agentId);
+        }
+
+        // If the service agent instance capacity is reached, the service becomes finished-registration
+        if (service.numAgentInstances == service.maxNumAgentInstances) {
+            service.state = ServiceState.FinishedRegistration;
+        }
+
+        // Update operator's bonding balance
+        bytes32 operatorServiceIdHash = keccak256(abi.encode(operator, serviceId));
+        mapOperatorAndServiceIdOperatorBalances[operatorServiceIdHash] += totalBond;
+
+        emit Deposit(operator, totalBond);
+        success = true;
+    }
+
+    /// @dev Creates multisig instance controlled by the set of service agent instances and deploys the service.
+    /// @param serviceId Correspondent service Id.
+    /// @param multisigImplementation Multisig implementation address.
+    /// @param data Data payload for the multisig creation.
+    /// @return multisig Address of the created multisig.
+    function deploy(
+        uint32 serviceId,
+        address multisigImplementation,
+        bytes memory data
+    ) external returns (address multisig)
+    {
+        // Reentrancy guard
+        if (_locked > 1) {
+            revert("ReentrancyGuard");
+        }
+        _locked = 2;
+
+        Service storage service = services[serviceId];
+
+        // Check for the service authority
+        address serviceOwner = service.serviceOwner;
+        requireSigner(serviceOwner);
+
+        // Check for the whitelisted multisig implementation
+        if (!mapMultisigs[multisigImplementation]) {
+            revert("UnauthorizedMultisig");
+        }
+
+        if (service.state != ServiceState.FinishedRegistration) {
+            revert("WrongServiceState");
+        }
+
+        // Get all agent instances for the multisig
+        address[] memory agentInstances = service.agentInstances;
+
+        // TODO: Understand the multisig workflow
+        // Create a multisig with agent instances
+        multisig = address(0);//IMultisig(multisigImplementation).create(agentInstances, service.threshold, data);
+
+        service.multisig = multisig;
+        service.state = ServiceState.Deployed;
+
+        emit CreateMultisigWithAgents(serviceId, multisig);
+        emit DeployService(serviceId);
+
+        _locked = 1;
+    }
+
+    /// @dev Slashes a specified agent instance.
+    /// @param agentInstances Agent instances to slash.
+    /// @param amounts Correspondent amounts to slash.
+    /// @param serviceId Service Id.
+    /// @return success True, if function executed successfully.
+    function slash(address[] memory agentInstances, uint64[] memory amounts, uint32 serviceId) external
+        returns (bool success)
+    {
+        // Check if the service is deployed
+        // Since we do not kill (burn) services, we want this check to happen in a right service state.
+        // If the service is deployed, it definitely exists and is running. We do not want this function to be abused
+        // when the service was deployed, then terminated, then in a sleep mode or before next deployment somebody
+        // could use this function and try to slash operators.
+        Service storage service = services[serviceId];
+        if (service.state != ServiceState.Deployed) {
+            revert("WrongServiceState");
+        }
+
+        // Check for the array size
+        if (agentInstances.length != amounts.length) {
+            revert("WrongArrayLength");
+        }
+
+        // Only the multisig of a correspondent address can slash its agent instances
+        requireSigner(service.multisig);
+
+        // Loop over each agent instance
+        uint32 numInstancesToSlash = agentInstances.length;
+        for (uint32 i = 0; i < numInstancesToSlash; ++i) {
+            // Get the service Id from the agentInstance map
+            address operator = mapAgentInstanceOperators[agentInstances[i]];
+            // Slash the balance of the operator, make sure it does not go below zero
+            bytes32 operatorServiceIdHash = keccak256(abi.encode(operator, serviceId));
+            uint64 balance = mapOperatorAndServiceIdOperatorBalances[operatorServiceIdHash];
+            if ((amounts[i] + 1) > balance) {
+                // We cannot add to the slashed amount more than the balance of the operator
+                slashedFunds += balance;
+                balance = 0;
+            } else {
+                slashedFunds += amounts[i];
+                balance -= amounts[i];
+            }
+            mapOperatorAndServiceIdOperatorBalances[operatorServiceIdHash] = balance;
+
+            emit OperatorSlashed(amounts[i], operator, serviceId);
+        }
+        success = true;
+    }
+
+    /// @dev Terminates the service.
+    /// @param serviceId Service Id to be updated.
+    /// @return success True, if function executed successfully.
+    /// @return refund Refund to return to the service owner.
+    function terminate(uint32 serviceId) external returns (bool success, uint64 refund)
+    {
+        // Reentrancy guard
+        if (_locked > 1) {
+            revert("ReentrancyGuard");
+        }
+        _locked = 2;
+
+        Service storage service = services[serviceId];
+
+        // Check for the service authority
+        address serviceOwner = service.serviceOwner;
+        requireSigner(serviceOwner);
+
+        // Check if the service is already terminated
+        if (service.state == ServiceState.PreRegistration || service.state == ServiceState.TerminatedBonded) {
+            revert("WrongServiceState");
+        }
+        // Define the state of the service depending on the number of bonded agent instances
+        if (service.numAgentInstances > 0) {
+            service.state = ServiceState.TerminatedBonded;
+        } else {
+            service.state = ServiceState.PreRegistration;
+        }
+
+        // Return registration deposit back to the service owner
+        refund = service.securityDeposit;
+        // TODO: Figure out the escrow release
+//        // By design, the refund is always a non-zero value, so no check is needed here fo that
+//        (bool result, ) = serviceOwner.call{value: refund}("");
+//        if (!result) {
+//            revert TransferFailed(address(0), address(this), serviceOwner, refund);
+//        }
+
+        emit Refund(serviceOwner, refund);
+        emit TerminateService(serviceId);
+        success = true;
+
+        _locked = 1;
+    }
+
+    /// @dev Unbonds agent instances of the operator from the service.
+    /// @param operator Operator of agent instances.
+    /// @param serviceId Service Id.
+    /// @return success True, if function executed successfully.
+    /// @return refund The amount of refund returned to the operator.
+    function unbond(address operator, uint32 serviceId) external returns (bool success, uint64 refund) {
+        // Reentrancy guard
+        if (_locked > 1) {
+            revert("ReentrancyGuard");
+        }
+        _locked = 2;
+
+        // Checks if the operator address is not zero
+        if (operator == address(0)) {
+            revert("ZeroAddress");
+        }
+
+        Service storage service = services[serviceId];
+        // Service can only be in the terminated-bonded state or expired-registration in order to proceed
+        if (service.state != ServiceState.TerminatedBonded) {
+            revert("WrongServiceState");
+        }
+
+        // Check for the operator and unbond all its agent instances
+        uint32 numAgentsUnbond = 0;
+        for (uint32 j = 0; j < service.operators.length; ++j) {
+            if (operator == service.operators[j]) {
+                // Get the agent Id of the agent instance
+                uint32 agentId = service.agentIdForAgentInstances[j];
+                // Remove the operator from the set of service operators
+                service.operators[j] = address(0);
+                numAgentsUnbond++;
+
+                // Add the bond associated with the agent Id to the refund
+                for (uint32 idx = 0; idx < service.agentIds.length; ++idx) {
+                    if (service.agentIds[idx] == agentId) {
+                        refund += uint64(service.bonds[idx]);
+                        break;
+                    }
+                }
+            }
+        }
+        if (numAgentsUnbond == 0) {
+            revert("OperatorHasNoInstances");
+        }
+
+        // Subtract number of unbonded agent instances
+        service.numAgentInstances -= numAgentsUnbond;
+        // When number of instances is equal to zero, all the operators have unbonded and the service is moved into
+        // the PreRegistration state, from where it can be updated / start registration / get deployed again
+        if (service.numAgentInstances == 0) {
+            delete service.operators;
+            delete service.agentInstances;
+            delete service.agentIdForAgentInstances;
+            service.state = ServiceState.PreRegistration;
+        }
+        // else condition is redundant here, since the service is either in the TerminatedBonded state, or moved
+        // into the PreRegistration state and unbonding is not possible before the new TerminatedBonded state is reached
+
+        // Calculate the refund
+        bytes32 operatorServiceIdHash = keccak256(abi.encode(operator, serviceId));
+        uint64 balance = mapOperatorAndServiceIdOperatorBalances[operatorServiceIdHash];
+        // This situation is possible if the operator was slashed for the agent instance misbehavior
+        if (refund > balance) {
+            refund = balance;
+        }
+
+        // Refund the operator
+        if (refund > 0) {
+            // Operator's balance is essentially zero after the refund
+            mapOperatorAndServiceIdOperatorBalances[operatorServiceIdHash] = 0;
+            // TODO: Figure out the escrow release
+//            // Send the refund
+//            (bool result, ) = operator.call{value: refund}("");
+//            if (!result) {
+//                revert("TransferFailed");
+//            }
+            emit Refund(operator, refund);
+        }
+
+        emit OperatorUnbond(operator, serviceId);
+        success = true;
+
+        _locked = 1;
+    }
+
+    /// @dev Gets the service instance.
+    /// @param serviceId Service Id.
+    /// @return service Corresponding Service struct.
+    function getService(uint32 serviceId) external view returns (Service memory service) {
+        service = services[serviceId];
+    }
+
+    /// @dev Gets service agent parameters: number of agent instances (slots) and a bond amount.
+    /// @param serviceId Service Id.
+    /// @return numAgentIds Number of canonical agent Ids in the service.
+    /// @return slots Set of agent instances number for each agent Id.
+    /// @return bonds Corresponding set of required bonds to register an agent instance in the service.
+    function getAgentParams(uint32 serviceId) external view
+        returns (uint32 numAgentIds, uint32[] memory slots, uint64[] memory bonds)
+    {
+        Service memory service = services[serviceId];
+        numAgentIds = service.agentIds.length;
+        slots = new uint32[](numAgentIds);
+        bonds = new uint64[](numAgentIds);
+        for (uint32 i = 0; i < numAgentIds; ++i) {
+            slots[i] = service.slots[i];
+            bonds[i] = service.bonds[i];
+        }
+    }
+
+//    /// @dev Lists all the instances of a given canonical agent Id if the service.
+//    /// @param serviceId Service Id.
+//    /// @param agentId Canonical agent Id.
+//    /// @return numAgentInstances Number of agent instances.
+//    /// @return agentInstances Set of agent instances for a specified canonical agent Id.
+//    function getInstancesForAgentId(uint32 serviceId, uint32 agentId) external view
+//        returns (uint32 numAgentInstances, address[] memory agentInstances)
+//    {
+//        numAgentInstances = mapServiceAndAgentIdAgentInstances[serviceId][agentId].length;
+//        agentInstances = new address[](numAgentInstances);
+//        for (uint32 i = 0; i < numAgentInstances; i++) {
+//            agentInstances[i] = mapServiceAndAgentIdAgentInstances[serviceId][agentId][i];
+//        }
+//    }
+
+    /// @dev Gets service agent instances.
+    /// @param serviceId ServiceId.
+    /// @return numAgentInstances Number of agent instances.
+    /// @return agentInstances Pre-allocated list of agent instance addresses.
+    function getAgentInstances(uint32 serviceId) external view
+        returns (uint32 numAgentInstances, address[] memory agentInstances)
+    {
+        Service memory service = services[serviceId];
+        agentInstances = service.agentInstances;
+        numAgentInstances = agentInstances.length;
+    }
+
+    /// @dev Gets the operator's balance in a specific service.
+    /// @param operator Operator address.
+    /// @param serviceId Service Id.
+    /// @return balance The balance of the operator.
+    function getOperatorBalance(address operator, uint32 serviceId) external view returns (uint64 balance)
+    {
+        bytes32 operatorServiceIdHash = keccak256(abi.encode(operator, serviceId));
+        balance = mapOperatorAndServiceIdOperatorBalances[operatorServiceIdHash];
+    }
+
+    /// @dev Controls multisig implementation address permission.
+    /// @param multisig Address of a multisig implementation.
+    /// @param permission Grant or revoke permission.
+    /// @return success True, if function executed successfully.
+    function changeMultisigPermission(address multisig, bool permission) external returns (bool success) {
+        // Check for the contract authority
+        requireSigner(owner);
+
+        if (multisig == address(0)) {
+            revert("ZeroAddress");
+        }
+        mapMultisigs[multisig] = permission;
+        success = true;
+    }
+
+    /// @dev Drains slashed funds.
+    /// @return amount Drained amount.
+    function drain() external returns (uint64 amount) {
+        // Reentrancy guard
+        if (_locked > 1) {
+            revert("ReentrancyGuard");
+        }
+        _locked = 2;
+
+        // Check for the drainer address
+        requireSigner(drainer);
+
+        // Drain the slashed funds
+        amount = slashedFunds;
+        if (amount > 0) {
+            slashedFunds = 0;
+            // TODO: Figure out the amount send
+            // Send the amount
+//            (bool result, ) = msg.sender.call{value: amount}("");
+//            if (!result) {
+//                revert TransferFailed(address(0), address(this), msg.sender, amount);
+//            }
+            emit Drain(drainer, amount);
+        }
+
+        _locked = 1;
+    }
+
+    function ownerOf(uint32 serviceId) public view returns (address) {
+        return services[serviceId].serviceOwner;
+    }
+    
+    /// @dev Checks for the service existence.
+    /// @notice Service counter starts from 1.
+    /// @param serviceId Service Id.
+    /// @return true if the service exists, false otherwise.
+    function exists(uint32 serviceId) public view returns (bool) {
+        return serviceId > 0 && serviceId < (totalSupply + 1);
+    }
+
+    /// @dev Sets service base URI.
+    /// @param bURI Base URI string.
+    function setBaseURI(string memory bURI) external {
+        requireSigner(owner);
+
+        // Check for the zero value
+        if (bytes(bURI).length == 0) {
+            revert("Zero Value");
+        }
+
+        baseURI = bURI;
+        emit BaseURIChanged(bURI);
+    }
+
+    /// @dev Gets the valid service Id from the provided index.
+    /// @notice Service counter starts from 1.
+    /// @param id Service counter.
+    /// @return serviceId Service Id.
+    function tokenByIndex(uint32 id) external view returns (uint32 serviceId) {
+        serviceId = id + 1;
+        if (serviceId > totalSupply) {
+            revert("Overflow");
+        }
+    }
+
+    // Open sourced from: https://stackoverflow.com/questions/67893318/solidity-how-to-represent-bytes32-as-string
+    /// @dev Converts bytes16 input data to hex16.
+    /// @notice This method converts bytes into the same bytes-character hex16 representation.
+    /// @param data bytes16 input data.
+    /// @return result hex16 conversion from the input bytes16 data.
+    function _toHex16(bytes16 data) internal pure returns (bytes32 result) {
+        result = bytes32 (data) & 0xFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000 |
+        (bytes32 (data) & 0x0000000000000000FFFFFFFFFFFFFFFF00000000000000000000000000000000) >> 64;
+        result = result & 0xFFFFFFFF000000000000000000000000FFFFFFFF000000000000000000000000 |
+        (result & 0x00000000FFFFFFFF000000000000000000000000FFFFFFFF0000000000000000) >> 32;
+        result = result & 0xFFFF000000000000FFFF000000000000FFFF000000000000FFFF000000000000 |
+        (result & 0x0000FFFF000000000000FFFF000000000000FFFF000000000000FFFF00000000) >> 16;
+        result = result & 0xFF000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000 |
+        (result & 0x00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000) >> 8;
+        result = (result & 0xF000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000) >> 4 |
+        (result & 0x0F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F00) >> 8;
+        result = bytes32 (0x3030303030303030303030303030303030303030303030303030303030303030 +
+        uint256 (result) +
+            (uint256 (result) + 0x0606060606060606060606060606060606060606060606060606060606060606 >> 4 &
+            0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F) * 39);
+    }
+
+    /// @dev Returns service token URI.
+    /// @notice Expected multicodec: dag-pb; hashing function: sha2-256, with base16 encoding and leading CID_PREFIX removed.
+    /// @param serviceId Service Id.
+    /// @return Service token URI string.
+    function tokenURI(uint32 serviceId) public view returns (string memory) {
+        bytes32 serviceHash = services[serviceId].configHash;
+        // Parse 2 parts of bytes32 into left and right hex16 representation, and concatenate into string
+        // adding the base URI and a cid prefix for the full base16 multibase prefix IPFS hash representation
+        return string(abi.encodePacked(baseURI, CID_PREFIX, _toHex16(bytes16(serviceHash)),
+            _toHex16(bytes16(serviceHash << 128))));
+    }
+}
+
+// ---- Expect: diagnostics ----
+// warning: 204:5-209:14: function can be declared 'pure'
+// warning: 500:22-26: function parameter 'data' is unused
+// warning: 867:31-35: function parameter 'data' is unused

部分文件因文件數量過多而無法顯示