diff options
Diffstat (limited to 'docs')
-rw-r--r-- | docs/050-breaking-changes.rst | 5 | ||||
-rw-r--r-- | docs/frequently-asked-questions.rst | 28 | ||||
-rw-r--r-- | docs/layout-of-source-files.rst | 2 | ||||
-rw-r--r-- | docs/metadata.rst | 28 | ||||
-rw-r--r-- | docs/security-considerations.rst | 41 | ||||
-rw-r--r-- | docs/types.rst | 167 |
6 files changed, 195 insertions, 76 deletions
diff --git a/docs/050-breaking-changes.rst b/docs/050-breaking-changes.rst index 1c12daa8..b49dd1e0 100644 --- a/docs/050-breaking-changes.rst +++ b/docs/050-breaking-changes.rst @@ -107,7 +107,10 @@ For most of the topics the compiler will provide suggestions. other way around is not allowed. Converting ``address`` to ``address payable`` is possible via conversion through ``uint160``. If ``c`` is a contract, ``address(c)`` results in ``address payable`` only if ``c`` has a - payable fallback function. + payable fallback function. If you use the :ref:`withdraw pattern<withdrawal_pattern>`, + you most likely do not have to change your code because ``transfer`` + is only used on ``msg.sender`` instead of stored addresses and ``msg.sender`` + is an ``address payable``. * Conversions between ``bytesX`` and ``uintY`` of different size are now disallowed due to ``bytesX`` padding on the right and ``uintY`` padding on diff --git a/docs/frequently-asked-questions.rst b/docs/frequently-asked-questions.rst index 7b7dd0b0..a474f905 100644 --- a/docs/frequently-asked-questions.rst +++ b/docs/frequently-asked-questions.rst @@ -43,17 +43,11 @@ Can you return an array or a ``string`` from a solidity function call? Yes. See `array_receiver_and_returner.sol <https://github.com/fivedogit/solidity-baby-steps/blob/master/contracts/60_array_receiver_and_returner.sol>`_. -What is problematic, though, is returning any variably-sized data (e.g. a -variably-sized array like ``uint[]``) from a function **called from within Solidity**. -This is a limitation of the EVM and will be solved with the next protocol update. - -Returning variably-sized data as part of an external transaction or call is fine. - Is it possible to in-line initialize an array like so: ``string[] myarray = ["a", "b"];`` ========================================================================================= Yes. However it should be noted that this currently only works with statically sized memory arrays. You can even create an inline memory -array in the return statement. Pretty cool, huh? +array in the return statement. Example:: @@ -70,7 +64,7 @@ Example:: Can a contract function return a ``struct``? ============================================ -Yes, but only in ``internal`` function calls. +Yes, but only in ``internal`` function calls or if ``pragma experimental "ABIEncoderV2";`` is used. If I return an ``enum``, I only get integer values in web3.js. How to get the named values? =========================================================================================== @@ -145,7 +139,17 @@ you should always convert it to a ``bytes`` first:: Can I concatenate two strings? ============================== -You have to do it manually for now. +Yes, you can use ``abi.encodePacked``:: + + pragma solidity >=0.4.0 <0.6.0; + + library ConcatHelper { + function concat(bytes memory a, bytes memory b) + internal pure returns (bytes memory) { + return abi.encodePacked(a, b); + } + } + Why is the low-level function ``.call()`` less favorable than instantiating a contract with a variable (``ContractB b;``) and executing its functions (``b.doSomething();``)? ============================================================================================================================================================================= @@ -299,8 +303,8 @@ In this example:: Can a contract function accept a two-dimensional array? ======================================================= -This is not yet implemented for external calls and dynamic arrays - -you can only use one level of dynamic arrays. +If you want to pass two-dimensional arrays across non-internal functions, +you most likely need to use ``pragma experimental "ABIEncoderV2";``. What is the relationship between ``bytes32`` and ``string``? Why is it that ``bytes32 somevar = "stringliteral";`` works and what does the saved 32-byte hex value mean? ======================================================================================================================================================================== @@ -401,7 +405,7 @@ case in C or Java). Is it possible to return an array of strings (``string[]``) from a Solidity function? ===================================================================================== -Not yet, as this requires two levels of dynamic arrays (``string`` is a dynamic array itself). +Only when ``pragma experimental "ABIEncoderV2";`` is used. What does the following strange check do in the Custom Token contract? ====================================================================== diff --git a/docs/layout-of-source-files.rst b/docs/layout-of-source-files.rst index d89ecded..fb18f8a9 100644 --- a/docs/layout-of-source-files.rst +++ b/docs/layout-of-source-files.rst @@ -77,6 +77,8 @@ for this part of the code is still under development) and has not received as much testing as the old encoder. You can activate it using ``pragma experimental ABIEncoderV2;``. +.. _smt_checker: + SMTChecker ~~~~~~~~~~ diff --git a/docs/metadata.rst b/docs/metadata.rst index 9c4f2574..c0613809 100644 --- a/docs/metadata.rst +++ b/docs/metadata.rst @@ -93,15 +93,16 @@ explanatory purposes. } } +.. warning:: + Since the bytecode of the resulting contract contains the metadata hash, any + change to the metadata results in a change of the bytecode. This includes + changes to a filename or path, and since the metadata includes a hash of all the + sources used, a single whitespace change results in different metadata, and + different bytecode. + .. note:: Note the ABI definition above has no fixed order. It can change with compiler versions. -Since the bytecode of the resulting contract contains the metadata hash, any -change to the metadata results in a change of the bytecode. This includes -changes to a filename or path, and since the metadata includes a hash of all the -sources used, a single whitespace change results in different metadata, and -different bytecode. - Encoding of the Metadata Hash in the Bytecode ============================================= @@ -117,19 +118,28 @@ to the end of the deployed bytecode:: So in order to retrieve the data, the end of the deployed bytecode can be checked to match that pattern and use the Swarm hash to retrieve the file. +.. note:: + The compiler currently uses the "swarm version 0" hash of the metadata, + but this might change in the future, so do not rely on this sequence + to start with ``0xa1 0x65 'b' 'z' 'z' 'r' '0'``. We might also + add additional data to this CBOR structure, so the + best option is to use a proper CBOR parser. + + Usage for Automatic Interface Generation and NatSpec ==================================================== The metadata is used in the following way: A component that wants to interact -with a contract (e.g. Mist) retrieves the code of the contract, from that +with a contract (e.g. Mist or any wallet) retrieves the code of the contract, from that the Swarm hash of a file which is then retrieved. That file is JSON-decoded into a structure like above. The component can then use the ABI to automatically generate a rudimentary user interface for the contract. -Furthermore, Mist can use the userdoc to display a confirmation message to the user -whenever they interact with the contract. +Furthermore, the wallet can use the NatSpec user documentation to display a confirmation message to the user +whenever they interact with the contract, together with requesting +authorization for the transaction signature. Additional information about Ethereum Natural Specification (NatSpec) can be found `here <https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format>`_. diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index 3305c1e1..bd06276b 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -136,15 +136,16 @@ Sending and Receiving Ether - If a contract receives Ether (without a function being called), the fallback function is executed. If it does not have a fallback function, the Ether will be rejected (by throwing an exception). During the execution of the fallback function, the contract can only rely - on the "gas stipend" it is passed (2300 gas) being available to it at that time. This stipend is not enough to access storage in any way. + on the "gas stipend" it is passed (2300 gas) being available to it at that time. This stipend is not enough to modify storage + (do not take this for granted though, the stipend might change with future hard forks). To be sure that your contract can receive Ether in that way, check the gas requirements of the fallback function (for example in the "details" section in Remix). - There is a way to forward more gas to the receiving contract using ``addr.call.value(x)("")``. This is essentially the same as ``addr.transfer(x)``, only that it forwards all remaining gas and opens up the ability for the - recipient to perform more expensive actions (and it only returns a failure code - and does not automatically propagate the error). This might include calling back + recipient to perform more expensive actions (and it returns a failure code + instead of automatically propagating the error). This might include calling back into the sending contract or other state changes you might not have thought of. So it allows for great flexibility for honest users but also for malicious actors. @@ -223,6 +224,26 @@ Now someone tricks you into sending ether to the address of this attack wallet: If your wallet had checked ``msg.sender`` for authorization, it would get the address of the attack wallet, instead of the owner address. But by checking ``tx.origin``, it gets the original address that kicked off the transaction, which is still the owner address. The attack wallet instantly drains all your funds. + +Two's Complement / Underflows / Overflows +========================================= + +As in many programming languages, Solidity's integer types are not actually integers. +They resemble integers when the values are small, but behave differently if the numbers are larger. +For example, the following is true: ``uint8(255) + uint8(1) == 0``. This situation is called +an *overflow*. It occurs when an operation is performed that requires a fixed size variable +to store a number (or piece of data) that is outside the range of the variable's data type. +An *underflow* is the converse situation: ``uint8(0) - uint8(1) == 255``. + +In general, read about the limits of two's complement representation, which even has some +more special edge cases for signed numbers. + +Try to use ``require`` to limit the size of inputs to a reasonable range and use the +:ref:`SMT checker<smt_checker>` to find potential overflows, or +use a library like +`SafeMath<https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol>` +if you want all overflows to cause a revert. + Minor Details ============= @@ -246,12 +267,8 @@ implications, there might be another issue buried beneath it. Any compiler warning we issue can be silenced by slight changes to the code. -Also try to enable the "0.5.0" safety features as early as possible -by adding ``pragma experimental "v0.5.0";``. Note that in this case, -the word ``experimental`` does not mean that the safety features are in any -way risky, it is just a way to enable some features that are -not yet part of the latest version of Solidity due to backwards -compatibility. +Always use the latest version of the compiler to be notified about all recently +introduced warnings. Restrict the Amount of Ether ============================ @@ -305,6 +322,12 @@ of "failsafe" mode, which, for example, disables most of the features, hands ove control to a fixed and trusted third party or just converts the contract into a simple "give me back my money" contract. +Ask for Peer Review +=================== + +The more people examine a piece of code, the more issues are found. +Asking people to review your code also helps as a cross-check to find out whether your code +is easy to understand - a very important criterion for good smart contracts. ******************* Formal Verification diff --git a/docs/types.rst b/docs/types.rst index f9fc80ce..84c448ff 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -51,7 +51,7 @@ Operators: * Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``) * Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation) * Shift operators: ``<<`` (left shift), ``>>`` (right shift) -* Arithmetic operators: ``+``, ``-``, unary ``-``, ``*``, ``/``, ``%`` (remainder), ``**`` (exponentiation) +* Arithmetic operators: ``+``, ``-``, unary ``-``, ``*``, ``/``, ``%`` (modulo), ``**`` (exponentiation) Comparisons @@ -82,12 +82,25 @@ Addition, Subtraction and Multiplication ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Addition, subtraction and multiplication have the usual semantics. -They wrap in two's complement notation, meaning that +They wrap in two's complement representation, meaning that for example ``uint256(0) - uint256(1) == 2**256 - 1``. You have to take these overflows into account when designing safe smart contracts. -Division and Modulus -^^^^^^^^^^^^^^^^^^^^ +The expression ``-x`` is equivalent to ``(T(0) - x)`` where +``T`` is the type of ``x``. This means that ``-x`` will not be negative +if the type of ``x`` is an unsigned integer type. Also, ``-x`` can be +positive if ``x`` is negative. There is another caveat also resulting +from two's complement representation:: + + int x = -2**255; + assert(-x == x); + +This means that even if a number is negative, you cannot assume that +its negation will be positive. + + +Division +^^^^^^^^ Since the type of the result of an operation is always the type of one of the operands, division on integers always results in an integer. @@ -96,7 +109,23 @@ In Solidity, division rounds towards zero. This mean that ``int256(-5) / int256( Note that in contrast, division on :ref:`literals<rational_literals>` results in fractional values of arbitrary precision. -Division by zero and modulus with zero throws a runtime exception. +.. note:: + Division by zero causes a failing assert. + +Modulo +^^^^^^ + +The modulo operation ``a % n`` yields the remainder ``r`` after the division of the operand ``a`` +by the operand ``n``, where ``q = int(a / n)`` and ``r = a - (n * q)``. This means that modulo +results in the same sign as its left operand (or zero) and ``a % n == -(abs(a) % n)`` holds for negative ``a``: + + * ``int256(5) % int256(2) == int256(1)`` + * ``int256(5) % int256(-2) == int256(1)`` + * ``int256(-5) % int256(2) == int256(-1)`` + * ``int256(-5) % int256(-2) == int256(-1)`` + +.. note:: + Modulo with zero causes a failing assert. Exponentiation ^^^^^^^^^^^^^^ @@ -104,7 +133,8 @@ Exponentiation Exponentiation is only available for unsigned types. Please take care that the types you are using are large enough to hold the result and prepare for potential wrapping behaviour. -Note that ``0**0`` is defined by the EVM as ``1``. +.. note:: + Note that ``0**0`` is defined by the EVM as ``1``. .. index:: ! ufixed, ! fixed, ! fixed point number @@ -122,7 +152,7 @@ the type and ``N`` represents how many decimal points are available. ``M`` must Operators: * Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``) -* Arithmetic operators: ``+``, ``-``, unary ``-``, unary ``+``, ``*``, ``/``, ``%`` (remainder) +* Arithmetic operators: ``+``, ``-``, unary ``-``, ``*``, ``/``, ``%`` (modulo) .. note:: The main difference between floating point (``float`` and ``double`` in many languages, more precisely IEEE 754 numbers) and fixed point numbers is @@ -159,6 +189,13 @@ has the type ``address payable``, if ``x`` is of integer or fixed bytes type, a If ``x`` is a contract without payable fallback function, then ``address(x)`` will be of type ``address``. In external function signatures ``address`` is used for both the ``address`` and the ``address payable`` type. +.. note:: + It might very well be that you do not need to care about the distinction between ``address`` + and ``address payable`` and just use ``address`` everywhere. For example, + if you are using the :ref:`withdrawal pattern<withdrawal_pattern>`, you can (and should) store the + address itself as ``address``, because you invoke the ``transfer`` function on + ``msg.sender``, which is an ``address payable``. + Operators: * ``<=``, ``<``, ``==``, ``!=``, ``>=`` and ``>`` @@ -566,8 +603,8 @@ also accepts a payment of zero Ether, so it also is ``non-payable``. On the other hand, a ``non-payable`` function will reject Ether sent to it, so ``non-payable`` functions cannot be converted to ``payable`` functions. -If a function type variable is not initialized, calling it will result -in an exception. The same happens if you call a function after using ``delete`` +If a function type variable is not initialised, calling it results +in a failed assertion. The same happens if you call a function after using ``delete`` on it. If external function types are used outside of the context of Solidity, @@ -716,14 +753,14 @@ non-persistent area where function arguments are stored, and behaves mostly like depending on the kind of variable, function type, etc., but all complex types must now give an explicit data location. -Data locations are important because they change how assignments behave: -assignments between storage and memory and also to a state variable (even from other state variables) -always create an independent copy. -Assignments to local storage variables only assign a reference though, and -this reference always points to the state variable even if the latter is changed -in the meantime. -On the other hand, assignments from a memory stored reference type to another -memory-stored reference type do not create a copy. +Data locations are not only relevant for persistency of data, but also for the semantics of assignments: +assignments between storage and memory (or from calldata) always create an independent copy. +Assignments from memory to memory only create references. This means that changes to one memory variable +are also visible in all other memory variables that refer to the same data. +Assignments from storage to a local storage variables also only assign a reference. +In contrast, all other assignments to storage always copy. Examples for this case +are assignments to state variables or to members of local variables of storage struct type, even +if the local variable itself is just a reference. :: @@ -753,13 +790,6 @@ memory-stored reference type do not create a copy. function h(uint[] memory) public pure {} } -Summary -^^^^^^^ - -Forced data location: - - parameters (not return) of external functions: calldata - - state variables: storage - .. index:: ! array .. _arrays: @@ -768,9 +798,10 @@ Arrays ------ Arrays can have a compile-time fixed size or they can be dynamic. -For storage arrays, the element type can be arbitrary (i.e. also other -arrays, mappings or structs). For memory arrays, it cannot be a mapping and -has to be an ABI type if it is an argument of a publicly-visible function. +The are few restrictions for the element, it can also be +another array, a mapping or a struct. The general restrictions for +types apply, though, in that mappings can only be used in storage +and publicly-visible functions need parameters that are ABI types. An array of fixed size ``k`` and element type ``T`` is written as ``T[k]``, an array of dynamic size as ``T[]``. As an example, an array of 5 dynamic @@ -780,9 +811,13 @@ third dynamic array, you use ``x[2][1]`` (indices are zero-based and access works in the opposite way of the declaration, i.e. ``x[2]`` shaves off one level in the type from the right). +Accessing an array past its end causes a revert. If you want to add +new elements, you have to use ``.push()`` or increase the ``.length`` +member (see below). + Variables of type ``bytes`` and ``string`` are special arrays. A ``bytes`` is similar to ``byte[]``, -but it is packed tightly in calldata. ``string`` is equal to ``bytes`` but does not allow -length or index access (for now). +but it is packed tightly in calldata and memory. ``string`` is equal to ``bytes`` but does not allow +length or index access. So ``bytes`` should always be preferred over ``byte[]`` because it is cheaper. As a rule of thumb, use ``bytes`` for arbitrary-length raw byte data and ``string`` for arbitrary-length string (UTF-8) data. If you can limit the length to a certain @@ -802,7 +837,7 @@ The numeric index will become a required parameter for the getter. Allocating Memory Arrays ^^^^^^^^^^^^^^^^^^^^^^^^ -Creating arrays with variable length in memory can be done using the ``new`` keyword. +You can use the ``new`` keyword to create arrays with a runtime-dependent length in memory. As opposed to storage arrays, it is **not** possible to resize memory arrays (e.g. by assigning to the ``.length`` member). You either have to calculate the required size in advance or create a new memory array and copy every element. @@ -845,7 +880,7 @@ assigned to a variable right away. The type of an array literal is a memory array of fixed size whose base type is the common type of the given elements. The type of ``[1, 2, 3]`` is ``uint8[3] memory``, because the type of each of these constants is ``uint8``. -Because of that, it was necessary to convert the first element in the example +Because of that, it is necessary to convert the first element in the example above to ``uint``. Note that currently, fixed size memory arrays cannot be assigned to dynamically-sized memory arrays, i.e. the following is not possible: @@ -872,15 +907,31 @@ Members ^^^^^^^ **length**: - Arrays have a ``length`` member to read their number of elements. - Dynamically-sized arrays (only available for storage) have a read-write ``length`` member to resize the array. Increasing the length adds uninitialized elements to the array, this has *O(1)* complexity. Reducing the length performs :ref:``delete`` on each removed element and has *O(n)* complexity where *n* is the number of elements being deleted. Please note that calling ``length--`` on an empty array will set the length of the array to 2^256-1 due to ``uint256`` underflow wrapping. + Arrays have a ``length`` member that contains their number of elements. + The length of memory arrays is fixed (but dynamic, i.e. it can depend on runtime parameters) once they are created. + For dynamically-sized arrays (only available for storage), this member can be assigned to resize the array. + Accessing elements outside the current length does not automatically resize the array and instead causes a failing assertion. + Increasing the length adds new zero-initialised elements to the array. + Reducing the length performs an implicit :ref:``delete`` on each of the removed elements. **push**: - Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``push`` that can be used to append an element at the end of the array. The function returns the new length. + Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``push`` that you can use to append an element at the end of the array. The element will be zero-initialised. The function returns the new length. **pop**: - Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that can be used to remove an element from the end of the array. This will also implicitly call :ref:``delete`` on the removed element. + Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that you can use to remove an element from the end of the array. This also implicitly calls :ref:``delete`` on the removed element. .. warning:: - It is not yet possible to use arrays of arrays in external functions. + If you use ``.length--`` on an empty array, it causes an underflow and + thus sets the length to ``2**256-1``. + +.. note:: + Increasing the length of a storage array has constant gas costs because + storage is assumed to be zero-initialised, while decreasing + the length has at least linear cost (but in most cases worse than linear), + because it includes explicitly clearing the removed + elements similar to calling :ref:``delete`` on them. + +.. note:: + It is not yet possible to use arrays of arrays in external functions + (but they are supported in public functions). .. note:: In EVM versions before Byzantium, it was not possible to access @@ -898,15 +949,34 @@ Members // dynamic array of pairs (i.e. of fixed size arrays of length two). // Because of that, T[] is always a dynamic array of T, even if T // itself is an array. + // Data location for all state variables is storage. bool[2][] m_pairsOfFlags; // newPairs is stored in memory - the only possibility - // for public function arguments + // for public contract function arguments function setAllFlagPairs(bool[2][] memory newPairs) public { - // assignment to a storage array replaces the complete array + // assignment to a storage array performs a copy of ``newPairs`` and + // replaces the complete array ``m_pairsOfFlags``. m_pairsOfFlags = newPairs; } + struct StructType { + uint[] contents; + uint moreInfo; + } + StructType s; + + function f(uint[] memory c) public { + // stores a reference to ``s`` in ``g`` + StructType storage g = s; + // also changes ``s.moreInfo``. + g.moreInfo = 2; + // assigns a copy because ``g.contents`` + // is not a local variable, but a member of + // a local variable. + g.contents = c; + } + function setFlagPair(uint index, bool flagA, bool flagB) public { // access to a non-existing index will throw an exception m_pairsOfFlags[index][0] = flagA; @@ -992,7 +1062,10 @@ shown in the following example: function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) { campaignID = numCampaigns++; // campaignID is return variable - // Creates new struct and saves in storage. We leave out the mapping type. + // Creates new struct in memory and copies it to storage. + // We leave out the mapping type, because it is not valid in memory. + // If structs are copied (even from storage to storage), mapping types + // are always omitted, because they cannot be enumerated. campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0); } @@ -1022,11 +1095,12 @@ Struct types can be used inside mappings and arrays and they can itself contain mappings and arrays. It is not possible for a struct to contain a member of its own type, -although the struct itself can be the value type of a mapping member. +although the struct itself can be the value type of a mapping member +or it can contain a dynamically-sized array of its type. This restriction is necessary, as the size of the struct has to be finite. Note how in all the functions, a struct type is assigned to a local variable -(of the default storage data location). +with data location ``storage``. This does not copy the struct but only stores a reference so that assignments to members of the local variable actually write to the state. @@ -1037,7 +1111,7 @@ assigning it to a local variable, as in .. index:: !mapping Mappings -======== +-------- You declare mapping types with the syntax ``mapping(_KeyType => _ValueType)``. The ``_KeyType`` can be any elementary type. This means it can be any of @@ -1046,7 +1120,7 @@ or complex types like contract types, enums, mappings, structs and any array typ apart from ``bytes`` and ``string`` are not allowed. ``_ValueType`` can be any type, including mappings. -You can think of mappings as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_, which are virtually initialized +You can think of mappings as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_, which are virtually initialised such that every possible key exists and is mapped to a value whose byte-representation is all zeros, a type's :ref:`default value <default-value>`. The similarity ends there, the key data is not stored in a mapping, only its ``keccak256`` hash is used to look up the value. @@ -1054,8 +1128,11 @@ mapping, only its ``keccak256`` hash is used to look up the value. Because of this, mappings do not have a length or a concept of a key or value being set. -Mappings are **only** allowed for state variables (or as storage reference types -in internal functions). +Mappings can only have a data location of ``storage`` and thus +are allowed for state variables, as storage reference types +in functions, or as parameters for library functions. +They cannot be used as parameters or return parameters +of contract functions that are publicly visible. You can mark variables of mapping type as ``public`` and Solidity creates a :ref:`getter <visibility-and-getters>` for you. The ``_KeyType`` becomes a |