diff --git a/API.md b/API.md index 51e35f69..e378b10a 100644 --- a/API.md +++ b/API.md @@ -2,6 +2,8 @@ TVM Solidity compiler expands Solidity language with different API functions to facilitate TVM contract development. +When deploying contracts, you should use the latest released version of Solidity. Apart from exceptional cases, only the latest version receives security fixes. Furthermore, breaking changes, as well as new features, are introduced regularly. We currently use a 0.y.z version number to indicate this fast pace of change. + ## Table of Contents * [Compiler version](#compiler-version) @@ -33,10 +35,7 @@ TVM Solidity compiler expands Solidity language with different API functions to * [\.loadInt() and \.loadIntQ()](#tvmsliceloadint-and-tvmsliceloadintq) * [\.loadUint() and \.loadUintQ()](#tvmsliceloaduint-and-tvmsliceloaduintq) * [Load little-endian integers](#load-little-endian-integers) - * [\.loadTons()](#tvmsliceloadtons) * [\.loadSlice() and \.loadSliceQ()](#tvmsliceloadslice-and-tvmsliceloadsliceq) - * [\.loadFunctionParams()](#tvmsliceloadfunctionparams) - * [\.loadStateVars()](#tvmsliceloadstatevars) * [\.skip()](#tvmsliceskip) * [\.loadZeroes(), \.loadOnes() and \.loadSame()](#tvmsliceloadzeroes-tvmsliceloadones-and-tvmsliceloadsame) * [TvmSlice preload primitives](#tvmslice-preload-primitives) @@ -64,7 +63,6 @@ TVM Solidity compiler expands Solidity language with different API functions to * [\.storeUint()](#tvmbuilderstoreuint) * [Store little-endian integers](#store-little-endian-integers) * [\.storeRef()](#tvmbuilderstoreref) - * [\.storeTons()](#tvmbuilderstoretons) * [optional(T)](#optionalt) * [constructing an optional](#constructing-an-optional) * [\.hasValue()](#optionalthasvalue) @@ -88,6 +86,7 @@ TVM Solidity compiler expands Solidity language with different API functions to * [unchecked block](#unchecked-block) * [Changes and extensions in Solidity types](#changes-and-extensions-in-solidity-types) * [Integers](#integers) + * [\.cast()](#integercast) * [bitSize() and uBitSize()](#bitsize-and-ubitsize) * [varInt and varUint](#varint-and-varuint) * [struct](#struct) @@ -124,7 +123,7 @@ TVM Solidity compiler expands Solidity language with different API functions to * [Object creating](#object-creating) * [constructor()](#constructor) * [address.makeAddrStd()](#addressmakeaddrstd) - * [address.makeAddrNone()](#addressmakeaddrnone) + * [address.addrNone](#addressaddrnone) * [address.makeAddrExtern()](#addressmakeaddrextern) * [Members](#members) * [\.wid](#addresswid) @@ -159,6 +158,7 @@ TVM Solidity compiler expands Solidity language with different API functions to * [\.keys() \.values()](#mappingkeys-mappingvalues) * [Fixed point number](#fixed-point-number) * [Function type](#function-type) + * [User-defined type](#user-defined-type) * [require, revert](#require-revert) * [require](#require) * [revert](#revert) @@ -176,6 +176,7 @@ TVM Solidity compiler expands Solidity language with different API functions to * [Decoding state variables](#decoding-state-variables) * [Keyword `constant`](#keyword-constant) * [Keyword `static`](#keyword-static) + * [Keyword `nostorage`](#keyword-nostorage) * [Keyword `public`](#keyword-public) * [Special contract functions](#special-contract-functions) * [receive](#receive) @@ -230,10 +231,6 @@ TVM Solidity compiler expands Solidity language with different API functions to * [tvm.hash()](#tvmhash) * [tvm.checkSign()](#tvmchecksign) * [Deploy contract from contract](#deploy-contract-from-contract) - * [tvm.insertPubkey()](#tvminsertpubkey) - * [tvm.buildStateInit()](#tvmbuildstateinit) - * [tvm.buildDataInit()](#tvmbuilddatainit) - * [tvm.stateInitHash()](#tvmstateinithash) * [Deploy via new](#deploy-via-new) * [`stateInit` option usage](#stateinit-option-usage) * [`code` option usage](#code-option-usage) @@ -242,17 +239,11 @@ TVM Solidity compiler expands Solidity language with different API functions to * [New contract address problem](#new-contract-address-problem) * [Misc functions from `tvm`](#misc-functions-from-tvm) * [tvm.code()](#tvmcode) - * [tvm.codeSalt()](#tvmcodesalt) - * [tvm.setCodeSalt()](#tvmsetcodesalt) * [tvm.pubkey()](#tvmpubkey) * [tvm.setPubkey()](#tvmsetpubkey) * [tvm.setCurrentCode()](#tvmsetcurrentcode) * [tvm.resetStorage()](#tvmresetstorage) - * [tvm.functionId()](#tvmfunctionid) - * [tvm.encodeBody()](#tvmencodebody) * [tvm.exit() and tvm.exit1()](#tvmexit-and-tvmexit1) - * [tvm.buildExtMsg()](#tvmbuildextmsg) - * [tvm.buildIntMsg()](#tvmbuildintmsg) * [tvm.sendrawmsg()](#tvmsendrawmsg) * [**math** namespace](#math-namespace) * [math.min() math.max()](#mathmin-mathmax) @@ -277,6 +268,18 @@ TVM Solidity compiler expands Solidity language with different API functions to * [rnd.shuffle](#rndshuffle) * [**abi** namespace](#abi-namespace) * [abi.encode(), abi.decode()](#abiencode-abidecode) + * [abi.encodeData()](#abiencodedata) + * [abi.encodeOldDataInit()](#abiencodeolddatainit) + * [abi.decodeData()](#abidecodedata) + * [abi.encodeStateInit()](#abiencodestateinit) + * [abi.stateInitHash()](#abistateinithash) + * [abi.encodeBody()](#abiencodebody) + * [abi.decodeFunctionParams()](#abidecodefunctionparams) + * [abi.codeSalt()](#abicodesalt) + * [abi.setCodeSalt()](#abisetcodesalt) + * [abi.functionId()](#abifunctionid) + * [abi.encodeExtMsg()](#abiencodeextmsg) + * [abi.encodeIntMsg()](#abiencodeintmsg) * [**gosh** namespace](#gosh-namespace) * [gosh.diff and gosh.zipDiff](#goshdiff-and-goshzipdiff) * [gosh.applyPatch, gosh.applyPatchQ, gosh.applyZipPatch, gosh.applyZipPatchQ, gosh.applyZipBinPatch and gosh.applyZipBinPatchQ](#goshapplypatch-goshapplypatchq-goshapplyzippatch-goshapplyzippatchq-goshapplyzipbinpatch-and-goshapplyzipbinpatchq) @@ -298,7 +301,7 @@ TVM Solidity compiler expands Solidity language with different API functions to ### Compiler version -TVM Solidity compiler add its current version to the generated code. This version can be obtained: +TVM Solidity compiler adds its current version to the generated code. This version can be obtained: 1) using [tvm_linker](https://github.com/tonlabs/TVM-linker#2-decoding-of-boc-messages-prepared-externally) from a `*.tvc` file: @@ -734,21 +737,13 @@ Loads a cell from the `TvmSlice` reference and converts it into a `TvmSlice`. (2) Same as (1) but returns `null` if it's impossible to load the integer. -###### \.loadTons() - -```TVMSolidity -.loadTons() returns (uint128); -``` - -Loads (deserializes) **VarUInteger 16** and returns an unsigned 128-bit integer. See [TL-B scheme][3]. - ###### \.loadSlice() and \.loadSliceQ() ```TVMSolidity (1) .loadSlice(uint10 bits) returns (TvmSlice); (2) -.loadSlice(uint10 bits, uint refs) returns (TvmSlice); +.loadSlice(uint10 bits, uint2 refs) returns (TvmSlice); (3) .loadSliceQ(uint10 bits) returns (optional(TvmSlice)); (4) @@ -761,66 +756,6 @@ Loads (deserializes) **VarUInteger 16** and returns an unsigned 128-bit integer. (3) and (4) are same as (1) and (2) but return `optional` type. -###### \.loadFunctionParams() - -```TVMSolidity -// Loads parameters of the public/external function without "responsible" attribute -.loadFunctionParams(functionName) returns (TypeA /*a*/, TypeB /*b*/, ...); - -// Loads parameters of the public/external function with "responsible" attribute -.loadFunctionParams(functionName) returns (uint32 callbackFunctionId, TypeA /*a*/, TypeB /*b*/, ...); - -// Loads constructor parameters -.loadFunctionParams(ContractName) returns (TypeA /*a*/, TypeB /*b*/, ...); -``` - -Loads parameters of the function or constructor (if contract type is provided). This function is usually used in -**[onBounce](#onbounce)** function. - -See example of how to use **onBounce** function: - -* [onBounceHandler](https://github.com/tonlabs/samples/blob/master/solidity/16_onBounceHandler.sol) - -###### \.loadStateVars() - -```TVMSolidity -.loadStateVars(ContractName) returns (uint256 /*pubkey*/, uint64 /*timestamp*/, bool /*constructorFlag*/, Type1 /*var1*/, Type2 /*var2*/, ...); -``` - -Loads state variables from `slice` that is obtained from the field `data` of `stateInit` - -Example: - -``` -contract A { - uint a = 111; - uint b = 22; - uint c = 3; - uint d = 44; - address e = address(12); - address f; -} - -contract B { - function f(TvmCell data) public pure { - TvmSlice s = data.toSlice(); - (uint256 pubkey, uint64 timestamp, bool flag, - uint a, uint b, uint c, uint d, address e, address f) = s.loadStateVars(A); - - // pubkey - pubkey of the contract A - // timestamp - timestamp that used for replay protection - // flag - always is equal to true - // a == 111 - // b == 22 - // c == 3 - // d == 44 - // e == address(12) - // f == address(0) - // s.empty() - } -} -``` - ###### \.skip() ```TVMSolidity @@ -1138,20 +1073,6 @@ Stores the little-endian integer. Stores `TvmBuilder b`/`TvmCell c`/`TvmSlice s` in the reference of the `TvmBuilder`. -##### \.storeTons() - -```TVMSolidity -.storeTons(uint128 value); -``` - -Stores (serializes) an integer **value** and stores it in the `TvmBuilder` as -**VarUInteger 16**. See [TL-B scheme][3]. - -See example of how to work with TVM specific types: - -* [Message_construction](https://github.com/tonlabs/samples/blob/master/solidity/15_MessageSender.sol) -* [Message_parsing](https://github.com/tonlabs/samples/blob/master/solidity/15_MessageReceiver.sol) - #### optional(T) The template optional type manages an optional contained value, i.e. a value that may or may not be present. @@ -1360,28 +1281,18 @@ for ((, uint value) : map) { // key is omitted #### repeat -Allows repeating block of code several times. -A `repeat` loop evaluates the expression only one time. -This expression must have an unsigned integer type. +```TVMSolidity +repeat (exression) statement +``` + +Allows repeating the block of code several times. `exression` evaluates only one time. ```TVMSolidity uint a = 0; repeat(10) { - a ++; + ++a; } // a == 10 - -// Despite a is changed in the cycle, code block will be repeated 10 times (10 is the initial value of a) -repeat(a) { - a += 2; -} -// a == 30 - -a = 11; -repeat(a - 1) { - a -= 1; -} -// a == 1 ``` #### try-catch @@ -1488,6 +1399,41 @@ Operators: * Shift operators: ``<<`` (left shift), ``>>`` (right shift) * Arithmetic operators: ``+``, ``-``, unary ``-``, ``*``, ``/``, ``%`` (modulo), ``**`` (exponentiation) +##### \.cast() + +```TVMSolidity +.cast(T) returns (T) +``` + +Convert `` to T type. `T` is integer type. Type of `` and `T` must have same sign or bit-size. Never throws an exception. For example: + +```TVMSolidity +uint8 a = 255; +uint4 b = a.cast(uint4); // b == 15 + +uint8 a = 255; +int8 b = a.cast(int8); // b == -1 + +uint8 a = 255; +int4 b = a.cast(uint4).cast(int4); // b == -1 + +uint8 a = 255; +// int4 b = a.cast(int4); // compilation fail +``` + +**Note:** conversion via `T(x)` throws an exception if `x` does not fit into `T` type. For example: + +```TVMSolidity +uint8 a = 10; +uint4 b = uint4(a); // OK, a == 10 + +uint8 a = 100; +uint4 b = uint4(a); // throws an exception because type(uint4).max == 15 + +int8 a = -1; +uint8 b = uint8(a); // throws an exception because type(uint8).min == 0 +``` + ##### bitSize() and uBitSize() ```TVMSolidity @@ -1516,12 +1462,14 @@ uint16 s = uBitSize(0); // s == 0 #### varInt and varUint -`varInt`/`varInt16`/`varInt32`/`varUint`/`varUint16`/`varUint32` are kinds of [Integer](#integers) +`varInt`/`varInt16`/`varInt32`/`varUint`/`varUint16`/`coins`/`varUint32` are kinds of [Integer](#integers) types. But they are serialized/deserialized according to [their TLB schemes](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb#L112). These schemes are effective if you want to store or send integers, and they usually have small size. Use these types only if you are sure. `varInt` is equal to `varInt32`, `varInt32` - `int248` , `varInt16` - `int120`. `varUint` is equal to `varUint32`, `varUint32` - `uint248` , `varUint16` - `uint120`. +`coins` is an alias for `varUint16`. + Example: ```TVMSolidity @@ -1755,17 +1703,32 @@ Returns byte length of the `string` data. ##### \.substr() ```TVMSolidity -.substr(uint from[, uint count]) returns (string); +(1) +.substr(uint pos) returns (string); +(2) +.substr(uint pos, uint count) returns (string); +``` + +(1) Returns a string that is a substring of this string. The substring begins with the character at the specified `pos` and extends to the end of this string. + +(2) Returns a string that is a substring of this string. The substring begins with the character at the specified `pos` and copies at most `count` characters from the source string. If the string is shorter, as many characters as possible are used. + +Throws an exception if `pos > string.byteLength()`. + +```TVMSolidity +string text = "0123456789"; +string a = text.substr(1, 2); // a = "12" +string b = text.substr(6); // b = "6789" +string c = text.substr(6, 100); // c = "6789" +string d = text.substr(777); // throws an exception ``` -Returns the substring starting from the byte with number **from** with byte length **count**. -**Note**: if count is not set, then the new `string` will be cut from the **from** byte to the end -of the string. +**Note:** if [Array Slices](https://docs.soliditylang.org/en/latest/types.html#array-slices) are used (they are written as `x[start:end]`), then `0 <= start <= end <= byteLength()`. ```TVMSolidity -string long = "0123456789"; -string a = long.substr(1, 2); // a = "12" -string b = long.substr(6); // b = "6789" +string str = "01234567890"; +string c = text.substr(6, 100); // c = "6789" +string d = text[6:106]; // throws an exception ``` ##### \.append() @@ -1857,13 +1820,13 @@ string b = s.toLowerCase(); // b == "hello" format(string template, TypeA a, TypeB b, ...) returns (string); ``` -Builds a `string` with arbitrary parameters. Empty placeholder {} can be filled with integer +Builds a `string` with arbitrary parameters. Empty placeholder `{}` can be filled with integer (in decimal view), address, string or fixed point number. Fixed point number is printed based on its type (`fixedMxN` is printed with `N` digits after dot). Placeholder should be specified in such formats: + * `"{}"` - empty placeholder + * `"{:[0]{"x","d","X","t"}}"` - placeholder for integers. Fills num with 0 if format starts with "0". -* `"{}"` - empty placeholder -* `"{:[0]{"x","d","X","t"}}"` - placeholder for integers. Fills num with 0 if format starts with "0". Formats integer to have specified `width`. Can format integers in decimal ("d" postfix), lower hex ("x") or upper hex ("X") form. Format "t" prints number (in nanoevers) as a fixed point sum. @@ -1912,6 +1875,7 @@ res = stoi("123"); // res ==123 string hexstr = "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF123456789ABCDE"; res = stoi(hexstr); // res == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF123456789ABCDE res = stoi("0xag"); // res == null +res = stoi(""); // res == null ``` ##### string conversion @@ -1942,7 +1906,7 @@ uint address_value; address addrStd = address(address_value); ``` -Constructs an `address` of type **addr_std** with zero workchain id and given address value. +Constructs `address` of type **addr_std** with zero workchain id and given address value. ##### address.makeAddrStd() @@ -1952,15 +1916,15 @@ uint address; address addrStd = address.makeAddrStd(wid, address); ``` -Constructs an `address` of type **addr_std** with given workchain id **wid** and value **address_value**. +Constructs `address` of type **addr_std** with given workchain id **wid** and value **address_value**. -##### address.makeAddrNone() +##### address.addrNone ```TVMSolidity -address addrNone = address.makeAddrNone(); +address addrNone = address.addrNone; ``` -Constructs an `address` of type **addr_none**. +Constructs `address` of type **addr_none**. ##### address.makeAddrExtern() @@ -1970,7 +1934,7 @@ uint bitCnt; address addrExtern = address.makeAddrExtern(addrNumber, bitCnt); ``` -Constructs an `address` of type **addr_extern** with given **value** with **bitCnt** bit-length. +Constructs `address` of type **addr_extern** with given **value** with **bitCnt** bit-length. ##### Members @@ -1993,7 +1957,7 @@ Returns the `address` value of **addr_std** or **addr_var** if **addr_var** has ##### \.balance ```TVMSolidity -address(this).balance returns (uint128); +address(this).balance returns (varUint16); ``` Returns balance of the current contract account in nanoevers. @@ -2074,12 +2038,12 @@ Example: ##### \.transfer() ```TVMSolidity -
.transfer(uint128 value, bool bounce, uint16 flag, TvmCell body, mapping(uint32 => varUint32) currencies, TvmCell stateInit); +
.transfer(varUint16 value, bool bounce, uint16 flag, TvmCell body, mapping(uint32 => varUint32) currencies, TvmCell stateInit); ``` Sends an internal outbound message to the `address`. Function parameters: -* `value` (`uint128`) - amount of nanoevers sent attached to the message. Note: the sent value is +* `value` (`varUint16`) - amount of nanoevers sent attached to the message. Note: the sent value is withdrawn from the contract's balance even if the contract has been called by internal inbound message. * `currencies` (`mapping(uint32 => varUint32)`) - additional currencies attached to the message. Defaults to an empty set. @@ -2431,6 +2395,14 @@ function process(int a, int b, uint8 mode) public returns (int) { } ``` +#### User-defined type + +A user-defined value type allows creating a zero cost abstraction over an elementary value type. This is similar to an alias, but with stricter type requirements. + +More information can be found [here](https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types). + +You can define an operator for user-defined type. See [here](https://docs.soliditylang.org/en/latest/contracts.html#using-for). + #### require, revert In case of exception state variables of the contract are reverted to the state before @@ -2732,7 +2704,7 @@ Defines that code is compiled with special selector that is needed to upgrade Fu You can decode state variables using tonos-cli. See `tonos-cli decode account --help`. -See also: [\.loadStateVars()](#tvmsliceloadstatevars). +See also: [abi.decodeData()](#abidecodedata). #### Keyword `constant` @@ -2766,6 +2738,22 @@ See also: * [`code` option usage](#code-option-usage) * [New contract address problem](#new-contract-address-problem) +#### Keyword `nostorage` + +`nostorage` state variables are not saved in the contract's storage. They have default values at the +beginning of each transaction. + +```TVMSolidity +contract C { + uint nostorage m_value; + function f() public { + // here m_value == 0 + ++m_value; + // here m_value == 1 + } +} +``` + #### Keyword `public` For each public state variable, a getter function is generated. Generated @@ -2885,7 +2873,7 @@ contract ContractB { } TvmBuilder b; - uint32 id = tvm.functionId(ContractA.f); + uint32 id = abi.functionId(ContractA.f); b.store(id, uint(2)); // ContractA's fallback function won't be called because the message body doesn't contain // the second ContractA.f's parameter. It will cause cell underflow exception in ContractA. @@ -3394,9 +3382,8 @@ uint8 refs = b.refs(); // refs == 1 The expression `type(T)` can be used to retrieve information about the type T. -The following properties are available for an integer, variable integer and enum type `T`: +The following properties are available for an integer, [variable integer](#varint-and-varuint) and enum type `T`: * `type(T).min` - the smallest value representable by type `T`. - * `type(T).max` - the largest value representable by type `T`. #### **msg** namespace @@ -3416,7 +3403,7 @@ Returns: ##### msg.value ```TVMSolidity -msg.value (uint128) +msg.value (varUint16) ``` Returns: @@ -3777,6 +3764,8 @@ tvm.initCodeHash() returns (uint256 hash) Returns the initial code hash that contract had when it was deployed. +[Capabilities](#tvm-capabilities) required: `CapInitCodeHash`. + ##### Hashing and cryptography ##### tvm.hash() @@ -3848,143 +3837,6 @@ bool signatureIsValid = tvm.checkSign(data, signature, pubkey); ##### Deploy contract from contract -##### tvm.insertPubkey() - -```TVMSolidity -tvm.insertPubkey(TvmCell stateInit, uint256 pubkey) returns (TvmCell); -``` - -Inserts a public key into the `stateInit` data field. If the `stateInit` has wrong format, then throws an exception. - -##### tvm.buildStateInit() - -```TVMSolidity -// 1) -tvm.buildStateInit(TvmCell code, TvmCell data) returns (TvmCell stateInit); -// 2) -tvm.buildStateInit(TvmCell code, TvmCell data, uint8 splitDepth) returns (TvmCell stateInit); -// 3) -tvm.buildStateInit({code: TvmCell code, data: TvmCell data, splitDepth: uint8 splitDepth, - pubkey: uint256 pubkey, contr: contract Contract, varInit: {VarName0: varValue0, ...}}); -``` - -Generates a `StateInit` ([TBLKCH][2] - 3.1.7.) from `code` and `data` `TvmCell`s. -Member `splitDepth` of the tree of cell `StateInit`: - -1) is not set. Has no value. -2) is set. `0 <= splitDepth <= 31` -3) Arguments can also be set with names. - -List of possible names: -* `code` (`TvmCell`) - defines the code field of the `StateInit`. Must be specified. -* `data` (`TvmCell`) - defines the data field of the `StateInit`. Conflicts with `pubkey` and -`varInit`. Can be omitted, in this case data field would be built from `pubkey` and `varInit`. -* `splitDepth` (`uint8`) - splitting depth. `0 <= splitDepth <= 31`. Can be omitted. By default, -it has no value. -* `pubkey` (`uint256`) - defines the public key of the new contract. Conflicts with `data`. -Can be omitted, default value is 0. -* `varInit` (`initializer list`) - used to set [static](#keyword-static) variables of the contract. -Conflicts with `data` and requires `contr` to be set. Can be omitted. -* `contr` (`contract`) - defines the contract whose `StateInit` is being built. Mandatory to be set if the -option `varInit` is specified. - -Examples of this function usage: - -```TvmSolidity -contract A { - uint static var0; - address static var1; -} - -contract C { - - function f() public pure { - TvmCell code; - TvmCell data; - uint8 depth; - TvmCell stateInit = tvm.buildStateInit(code, data); - stateInit = tvm.buildStateInit(code, data, depth); - } - - function f1() public pure { - TvmCell code; - TvmCell data; - uint8 depth; - uint pubkey; - uint var0; - address var1; - - TvmCell stateInit1 = tvm.buildStateInit({code: code, data: data, splitDepth: depth}); - stateInit1 = tvm.buildStateInit({code: code, splitDepth: depth, varInit: {var0: var0, var1: var1}, pubkey: pubkey, contr: A}); - stateInit1 = tvm.buildStateInit({varInit: {var0: var0, var1: var1}, pubkey: pubkey, contr: A, code: code, splitDepth: depth}); - stateInit1 = tvm.buildStateInit({contr: A, varInit: {var0: var0, var1: var1}, pubkey: pubkey, code: code, splitDepth: depth}); - stateInit1 = tvm.buildStateInit({contr: A, varInit: {var0: var0, var1: var1}, pubkey: pubkey, code: code}); - stateInit1 = tvm.buildStateInit({contr: A, varInit: {var0: var0, var1: var1}, code: code, splitDepth: depth}); - stateInit1 = tvm.buildStateInit({pubkey: pubkey, code: code, splitDepth: depth}); - stateInit1 = tvm.buildStateInit({code: code, splitDepth: depth}); - stateInit1 = tvm.buildStateInit({code: code}); - } -} -``` - -##### tvm.buildDataInit() - -```TVMSolidity -tvm.buildDataInit({pubkey: uint256 pubkey, contr: contract Contract, varInit: {VarName0: varValue0, ...}}); -``` - -Generates `data` field of the `StateInit` ([TBLKCH][2] - 3.1.7.). Parameters are the same as in -[tvm.buildStateInit()](#tvmbuildstateinit). - -```TVMSolidity -// SimpleWallet.sol -contract SimpleWallet { - uint static value; - // ... -} - -// Usage -TvmCell data = tvm.buildDataInit({ - contr: SimpleWallet, - varInit: {mulRes: 12345}, - pubkey: 0x3f82435f2bd40915c28f56d3c2f07af4108931ae8bf1ca9403dcf77d96250827 -}); -TvmCell code = ...; -TvmCell stateInit = tvm.buildStateInit({code: code, data: data}); - -// Note, the code above can be simplified to: -TvmCell stateInit = tvm.buildStateInit({ - code: code, - contr: SimpleWallet, - varInit: {mulRes: 12345}, - pubkey: 0x3f82435f2bd40915c28f56d3c2f07af4108931ae8bf1ca9403dcf77d96250827 -}); -``` - -##### tvm.stateInitHash() - -```TVMSolidity -tvm.stateInitHash(uint256 codeHash, uint256 dataHash, uint16 codeDepth, uint16 dataDepth) returns (uint256); -``` - -Calculates hash of the stateInit for given code and data specifications. -[Capabilities](#tvm-capabilities) required: `CapInitCodeHash`. - -Example: - -```TVMSolidity -TvmCell code = ...; -TvmCell data = ...; -uint codeHash = tvm.hash(code); -uint dataHash = tvm.hash(data); -uint16 codeDepth = code.depth(); -uint16 dataDepth = data.depth(); -uint256 hash = tvm.stateInitHash(codeHash, dataHash, codeDepth, dataDepth); -``` - -See also [internal doc](https://github.com/tonlabs/TON-Solidity-Compiler/blob/master/docs/internal/stateInit_hash.md) to read more about this -function mechanics. - ##### Deploy via new Either `code` or `stateInit` option must be set when you deploy a contract @@ -4056,7 +3908,7 @@ address newWallet = new SimpleWallet{ The following options can be used with both `stateInit` and `code`: -* `value` (`uint128`) - funds attached to the outbound internal message, that creates new account. +* `value` (`varUint16`) - funds attached to the outbound internal message, that creates new account. This value must be set. * `currencies` (`mapping(uint32 => varUint32)`) - currencies attached to the outbound internal message. Defaults to an empty set. @@ -4124,22 +3976,6 @@ Returns contract's code. [Capabilities](#tvm-capabilities) required: `CapMycode` See [SelfDeployer](https://github.com/tonlabs/samples/blob/master/solidity/21_self_deploy.sol). -##### tvm.codeSalt() - -```TVMSolidity -tvm.codeSalt(TvmCell code) returns (optional(TvmCell) optSalt); -``` - -If **code** contains salt, then **optSalt** contains one. Otherwise, **optSalt** doesn't contain any value. - -##### tvm.setCodeSalt() - -```TVMSolidity -tvm.setCodeSalt(TvmCell code, TvmCell salt) returns (TvmCell newCode); -``` - -Inserts **salt** into **code** and returns new code **newCode**. - ##### tvm.pubkey() ```TVMSolidity @@ -4179,268 +4015,35 @@ tvm.resetStorage(); Resets all state variables to their default values. -##### tvm.functionId() +##### tvm.exit() and tvm.exit1() ```TVMSolidity -// id of public function -tvm.functionId(functionName) returns (uint32); - -// id of public constructor -tvm.functionId(ContractName) returns (uint32); +tvm.exit(); +tvm.exit1(); ``` -Returns the function id (uint32) of a public/external function or constructor. +Functions are used to save state variables and quickly terminate execution of the smart contract. +Exit code of the compute phase is equal to zero/one for `tvm.exit`/`tvm.exit1`. Example: ```TVMSolidity -contract MyContract { - constructor(uint a) public { - } - /*...*/ +function g0(uint a) private { + if (a == 0) { + tvm.exit(); } + //... +} - function f() public pure returns (uint) { - /*...*/ +function g1(uint a) private { + if (a == 0) { + tvm.exit1(); } + //... +} +``` - function getConstructorID() public pure returns (uint32) { - uint32 functionId = tvm.functionId(MyContract); - return functionId; - } - - function getFuncID() public pure returns (uint32) { - uint32 functionId = tvm.functionId(f); - return functionId; - } -} -``` - -See example of how to use this function: - -* [onBounceHandler](https://github.com/tonlabs/samples/blob/master/solidity/16_onBounceHandler.sol) - -##### tvm.encodeBody() - -```TVMSolidity -tvm.encodeBody(function, arg0, arg1, arg2, ...) returns (TvmCell); -tvm.encodeBody(function, callbackFunction, arg0, arg1, arg2, ...) returns (TvmCell); -tvm.encodeBody(contract, arg0, arg1, arg2, ...) returns (TvmCell); -``` - -Constructs a message body for the function call. Body can be used as a payload for [\.transfer()](#addresstransfer). -If the **function** is `responsible`, **callbackFunction** must be set. - -Example: - -```TVMSolidity -contract Remote { - constructor(uint x, uint y, uint z) public { /* */ } - function func(uint256 num, int64 num2) public pure { /* */ } - function getCost(uint256 num) public responsible pure returns (uint128) { /* */ } -} - -// deploy the contract -TvmCell body = tvm.encodeBody(Remote, 100, 200, 300); -addr.transfer({value: 10 ever, body: body, stateInit: stateInit }); - -// call the function -TvmCell body = tvm.encodeBody(Remote.func, 123, -654); -addr.transfer({value: 10 ever, body: body}); - -// call the responsible function -TvmCell body = tvm.encodeBody(Remote.getCost, onGetCost, 105); -addr.transfer({value: 10 ever, body: body}); -``` - -See also: - -* [External function calls](#external-function-calls) -* [\.loadFunctionParams()](#tvmsliceloadfunctionparams) -* [tvm.buildIntMsg()](#tvmbuildintmsg) - -##### tvm.exit() and tvm.exit1() - -```TVMSolidity -tvm.exit(); -tvm.exit1(); -``` - -Functions are used to save state variables and quickly terminate execution of the smart contract. -Exit codes are equal to zero and one for `tvm.exit` and `tvm.exit1` respectively. - -Example: - -```TVMSolidity -function g0(uint a) private { - if (a == 0) { - tvm.exit(); - } - //... -} - -function g1(uint a) private { - if (a == 0) { - tvm.exit1(); - } - //... -} -``` - -##### tvm.buildExtMsg() - -```TVMSolidity -tvm.buildExtMsg({ - dest: address, - time: uint64, - expire: uint32, - call: {functionIdentifier [, list of function arguments]}, - sign: bool, - pubkey: optional(uint256), - callbackId: (uint32 | functionIdentifier), - onErrorId: (uint32 | functionIdentifier), - stateInit: TvmCell, - signBoxHandle: optional(uint32), - abiVer: uint8, - flags: uint8 -}) -returns (TvmCell); -``` - -Function should be used only offchain and intended to be used only in debot contracts. -Allows creating an external inbound message, that calls the **func** function of the -contract on address **destination** with specified function arguments. - -Mandatory parameters that are used to form a src field that is used for debots: - -* `callbackId` - identifier of the callback function. -* `onErrorId` - identifier of the function that is called in case of an error. -* `signBoxHandle` - handle of the sign box entity, that engine will use to sign the message. - -These parameters are stored in addr_extern and placed to the src field of the message. -Message is of type [ext_in_msg_info](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L127) -and src address is of type [addr_extern](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L101) -but stores special data: - -* callback id - 32 bits; -* on error id - 32 bits; -* abi version - 8 bits; Can be specified manually and contain full abi version in little endian half bytes (e.g. version = "2.3" -> abiVer: 0x32) -* header mask - 3 bits in such order: time, expire, pubkey; -* optional value signBoxHandle - 1 bit (whether value exists) + \[32 bits\]; -* control flags byte - 8 bits. - Currently used bits: - 1 - override time (dengine will replace time value with current time) - 2 - override exp (dengine will replace time value with actual expire value) - 4 - async call (dengine must send message and don't wait for the result) - -Other function parameters define fields of the message: - -* `time` - message creation timestamp. Used for replay attack protection, encoded as 64 bit Unix -time in milliseconds. -* `expire` - Unix time (in seconds, 32 bit) after that message should not be processed by contract. -* `pubkey` - public key from key pair used for signing the message body. This parameter is optional -and can be omitted. -* `sign` - constant bool flag that shows whether message should contain signature. If set to -**true**, message is generated with signature field filled with zeroes. This parameter is optional -and can be omitted (in this case is equal to **false**). - -User can also attach stateInit to the message using `stateInit` parameter. - -Function throws an exception with code 64 if function is called with wrong parameters (pubkey is set -and has value, but sign is false or omitted). - -Example: - -```TVMSolidity - -interface Foo { - function bar(uint a, uint b) external pure; -} - -contract Test { - - TvmCell public m_cell; - - function generate0() public { - tvm.accept(); - address addr = address.makeAddrStd(0, 0x0123456789012345678901234567890123456789012345678901234567890123); - m_cell = tvm.buildExtMsg({callbackId: 0, onErrorId: 0, dest: addr, time: 0x123, expire: 0x12345, call: {Foo.bar, 111, 88}}); - } - - function generate1() public { - tvm.accept(); - optional(uint) pubkey; - address addr = address.makeAddrStd(0, 0x0123456789012345678901234567890123456789012345678901234567890123); - m_cell = tvm.buildExtMsg({callbackId: 0, onErrorId: 0, dest: addr, time: 0x123, expire: 0x12345, call: {Foo.bar, 111, 88}, pubkey: pubkey}); - } - - function generate2() public { - tvm.accept(); - optional(uint) pubkey; - pubkey.set(0x95c06aa743d1f9000dd64b75498f106af4b7e7444234d7de67ea26988f6181df); - address addr = address.makeAddrStd(0, 0x0123456789012345678901234567890123456789012345678901234567890123); - optional(uint32) signBox; - signBox.set(0x12333112); - m_cell = tvm.buildExtMsg({callbackId: 0, onErrorId: 0, dest: addr, time: 0x1771c58ef9a, expire: 0x600741e4, call: {Foo.bar, 111, 88}, pubkey: pubkey, sign: true, signBoxHandle: signBox}); - } - -} -``` - -External inbound message can also be built and sent with construction similar to remote contract -call. It requires suffix ".extMsg" and call options similar to `buildExtMsg` function call. -**Note**: this type of call should be used only offchain in debot contracts. - -```TVMSolidity -interface Foo { - function bar(uint a, uint b) external pure; -} - -contract Test { - - function test7() public { - address addr = address.makeAddrStd(0, 0x0123456789012345678901234567890123456789012345678901234567890123); - Foo(addr).bar{expire: 0x12345, time: 0x123}(123, 45).extMsg; - optional(uint) pubkey; - optional(uint32) signBox; - Foo(addr).bar{expire: 0x12345, time: 0x123, pubkey: pubkey}(123, 45).extMsg; - Foo(addr).bar{expire: 0x12345, time: 0x123, pubkey: pubkey, sign: true}(123, 45).extMsg; - pubkey.set(0x95c06aa743d1f9000dd64b75498f106af4b7e7444234d7de67ea26988f6181df); - Foo(addr).bar{expire: 0x12345, time: 0x123, pubkey: pubkey, sign: true}(123, 45).extMsg; - Foo(addr).bar{expire: 0x12345, time: 0x123, sign: true, signBoxHandle: signBox}(123, 45).extMsg; - Foo(addr).bar{expire: 0x12345, time: 0x123, sign: true, signBoxHandle: signBox, abiVer: 0x32, flags: 0x07}(123, 45).extMsg; - } -} -``` - -##### tvm.buildIntMsg() - -```TVMSolidity -tvm.buildIntMsg({ - dest: address, - value: uint128, - call: {function, [callbackFunction,] arg0, arg1, arg2, ...}, - bounce: bool, - currencies: mapping(uint32 => varUint32), - stateInit: TvmCell -}) -returns (TvmCell); -``` - -Generates an internal outbound message that contains a function call. The result `TvmCell` can be used to send a -message using [tvm.sendrawmsg()](#tvmsendrawmsg). If the `function` is `responsible`, then -`callbackFunction` parameter must be set. - -`dest`, `value` and `call` parameters are mandatory. Another parameters can be omitted. See -[\.transfer()](#addresstransfer) where these options and their default values are -described. - -See also: - -* sample [22_sender.sol](https://github.com/tonlabs/samples/blob/master/solidity/22_sender.sol) -* [tvm.encodeBody()](#tvmencodebody) - -#### tvm.sendrawmsg() +#### tvm.sendrawmsg() ```TVMSolidity tvm.sendrawmsg(TvmCell msg, uint8 flag); @@ -4448,7 +4051,7 @@ tvm.sendrawmsg(TvmCell msg, uint8 flag); Send the internal/external message `msg` with `flag`. It's a wrapper for opcode `SENDRAWMSG` ([TVM][1] - A.11.10). -Internal message `msg` can be generated by [tvm.buildIntMsg()](#tvmbuildintmsg). +Internal message `msg` can be generated by [abi.encodeIntMsg()](#abiencodeintmsg). Possible values of `flag` are described here: [\.transfer()](#addresstransfer). **Note:** make sure that `msg` has a correct format and follows the [TL-B scheme][3] of `Message X`. @@ -4494,17 +4097,19 @@ Example: ##### math.abs() ```TVMSolidity -math.abs(T val) returns (T); +math.abs(T1 val) returns (T2); ``` -Computes the absolute value of the given integer. +Computes the absolute value of the given integer. Throws an exception if absolute value of `val` does not fit into T2. Example: ```TVMSolidity -int a = math.abs(-100); // a == 100 -int b = -100; -int c = math.abs(b); // c == 100 +int256 a = -100; +uint255 b = math.abs(a); // b == 100 + +int256 a = type(int256).min; +uint255 b = math.abs(a); // throws an exception ``` ##### math.modpow2() @@ -4631,7 +4236,7 @@ int8 sign = math.sign(0); // sign == 0 ##### tx.logicaltime ```TVMSolidity -tx.logicaltime returns (uint64); +tx.logicaltime (uint64); ``` Returns the logical time of the current transaction. @@ -4639,7 +4244,7 @@ Returns the logical time of the current transaction. ##### tx.storageFee ```TVMSolidity -tx.storageFee returns (uint120); +tx.storageFee (varUint16); ``` Returns the storage fee paid in the current transaction. [Capabilities](#tvm-capabilities) required: `CapStorageFeeToTvm`. @@ -4649,7 +4254,7 @@ Returns the storage fee paid in the current transaction. [Capabilities](#tvm-cap ##### block.timestamp ```TVMSolidity -block.timestamp returns (uint32); +block.timestamp (uint32); ``` Returns the current Unix time. Unix time is the same for the all transactions from one block. @@ -4657,7 +4262,7 @@ Returns the current Unix time. Unix time is the same for the all transactions fr ##### block.logicaltime ```TVMSolidity -block.logicaltime returns (uint64); +block.logicaltime (uint64); ``` Returns the starting logical time of the current block. @@ -4766,6 +4371,495 @@ TvmCell cell = abi.encode(uint(1), uint(2), uint(3), uint(4)); // d == 4 ``` +##### abi.encodeData() + +```TVMSolidity +abi.encodeData({uint256 pubkey, contract Contract, varInit: {VarName0: varValue0, ...}}); +``` + +Generates `data` field of the `StateInit` ([TBLKCH][2] - 3.1.7.). Parameters are the same as in +[abi.encodeStateInit()](#abiencodestateinit). + +```TVMSolidity +// SimpleWallet.sol +contract SimpleWallet { + uint static m_id; + address static m_creator; + // ... +} + +// Usage +TvmCell data = abi.encodeData({ + contr: SimpleWallet, + varInit: {m_id: 1, m_creator: address(this)}, + pubkey: 0x3f82435f2bd40915c28f56d3c2f07af4108931ae8bf1ca9403dcf77d96250827 +}); +TvmCell code = ...; +TvmCell stateInit = abi.encodeStateInit({code: code, data: data}); + +// Note, the code above can be simplified to: +TvmCell stateInit = abi.encodeStateInit({ + code: code, + contr: SimpleWallet, + varInit: {m_id: 1, m_creator: address(this)}, + pubkey: 0x3f82435f2bd40915c28f56d3c2f07af4108931ae8bf1ca9403dcf77d96250827 +}); +``` + +##### abi.encodeOldDataInit() + +```TVMSolidity +abi.encodeOldDataInit() returns (TvmCell); +``` + +Same as [abi.encodeData()](#abiencodedata) but generate data in the format that was used in the compiler < 0.72.0. This function can be used to deploy old contracts (that was compiled < 0.72.0) from new ones. Example: + +File with old contracts that was compiled by compiler < 0.72.0: +```TVMSolidity +pragma ever-solidity >= 0.72.0; // set new version + +contract SimpleContractA { + uint static m_x0; + uint static m_x1; +} + +// Remove all code from old contracts but state variables and contructor declaration +contract SimpleContractB is SimpleContractA { + uint static m_x2; + constructor(string name) { + } +} +``` + +File with new contracts: +```TVMSolidity +pragma ever-solidity >= 0.72.0; +contract ContractCreator { + function deploy(uint pubkey, TvmCell code) public pure returns (address) { + TvmCell data = abi.encodeOldDataInit({ + pubkey: pubkey, + varInit: { + m_x0: 100, + m_x1: 200, + m_x2: 300 + }, + contr: SimpleContractB + }); + TvmCell stateInit = abi.encodeStateInit({ + code: code, + data: data + }); + SimpleContractB addr = new SimpleContractB{ + wid: 0, + value: 1 ever, + stateInit: stateInit, + flag: 1 + }("Hello, world!"); + return addr; + } +} +``` + +##### abi.decodeData() + +```TVMSolidity +abi.decodeData(ContractName, TvmSlice) returns (uint256 /*pubkey*/, uint64 /*timestamp*/, bool /*constructorFlag*/, Type1 /*var1*/, Type2 /*var2*/, ...); +``` + +Loads state variables from `TvmSlice` that is obtained from the field `data` of `stateInit`. + +Example: + +``` +contract A { + uint a = 111; + uint b = 22; + uint c = 3; + uint d = 44; + address e = address(12); + address f; +} + +contract B { + function f(TvmCell data) public pure { + TvmSlice s = data.toSlice(); + (uint256 pubkey, uint64 timestamp, bool flag, + uint a, uint b, uint c, uint d, address e, address f) = abi.decodeData(A, s); + + // pubkey - pubkey of the contract A + // timestamp - timestamp that used for replay protection + // flag - always is equal to true + // a == 111 + // b == 22 + // c == 3 + // d == 44 + // e == address(12) + // f == address.addrNone + // s.empty() + } +} +``` + +##### abi.encodeStateInit() + +```TVMSolidity +// 1) +abi.encodeStateInit(TvmCell code, TvmCell data) returns (TvmCell stateInit); +// 2) +abi.encodeStateInit(TvmCell code, TvmCell data, uint8 splitDepth) returns (TvmCell stateInit); +// 3) +abi.encodeStateInit({TvmCell code, TvmCell data, uint8 splitDepth, + uint256 pubkey, Contract contr, varInit: {VarName0: varValue0, ...}}); +``` + +Generates a `StateInit` ([TBLKCH][2] - 3.1.7.) from `code` and `data` `TvmCell`s. +Member `splitDepth` of the tree of cell `StateInit`: + +1) is not set. Has no value. +2) is set. `0 <= splitDepth <= 31` +3) Arguments can also be set with names. + +List of possible names: +* `code` (`TvmCell`) - defines the code field of the `StateInit`. Must be specified. +* `data` (`TvmCell`) - defines the data field of the `StateInit`. Conflicts with `pubkey` and + `varInit`. Can be omitted, in this case data field would be built from `pubkey` and `varInit`. +* `splitDepth` (`uint8`) - splitting depth. `0 <= splitDepth <= 31`. Can be omitted. By default, + it has no value. +* `pubkey` (`uint256`) - defines the public key of the new contract. Conflicts with `data`. + Can be omitted, default value is 0. +* `varInit` (`initializer list`) - used to set [static](#keyword-static) variables of the contract. + Conflicts with `data` and requires `contr` to be set. Can be omitted. +* `contr` (`contract`) - defines the contract whose `StateInit` is being built. Mandatory to be set if the + option `varInit` is specified. + +Examples of this function usage: + +```TvmSolidity +contract A { + uint static var0; + address static var1; +} + +contract C { + + function f() public pure { + TvmCell code; + TvmCell data; + uint8 depth; + TvmCell stateInit = abi.encodeStateInit(code, data); + stateInit = abi.encodeStateInit(code, data, depth); + } + + function f1() public pure { + TvmCell code; + TvmCell data; + uint8 depth; + uint pubkey; + uint var0; + address var1; + + TvmCell stateInit1 = abi.encodeStateInit({code: code, data: data, splitDepth: depth}); + stateInit1 = abi.encodeStateInit({code: code, splitDepth: depth, varInit: {var0: var0, var1: var1}, pubkey: pubkey, contr: A}); + stateInit1 = abi.encodeStateInit({varInit: {var0: var0, var1: var1}, pubkey: pubkey, contr: A, code: code, splitDepth: depth}); + stateInit1 = abi.encodeStateInit({contr: A, varInit: {var0: var0, var1: var1}, pubkey: pubkey, code: code, splitDepth: depth}); + stateInit1 = abi.encodeStateInit({contr: A, varInit: {var0: var0, var1: var1}, pubkey: pubkey, code: code}); + stateInit1 = abi.encodeStateInit({contr: A, varInit: {var0: var0, var1: var1}, code: code, splitDepth: depth}); + } +} +``` + +##### abi.stateInitHash() + +```TVMSolidity +abi.stateInitHash(uint256 codeHash, uint256 dataHash, uint16 codeDepth, uint16 dataDepth) returns (uint256); +``` + +Calculates hash of the stateInit for given code and data specifications. + +Example: + +```TVMSolidity +TvmCell code = ...; +TvmCell data = ...; +uint codeHash = tvm.hash(code); +uint dataHash = tvm.hash(data); +uint16 codeDepth = code.depth(); +uint16 dataDepth = data.depth(); +uint256 hash = abi.stateInitHash(codeHash, dataHash, codeDepth, dataDepth); +``` + +See also [internal doc](https://github.com/tonlabs/TON-Solidity-Compiler/blob/master/docs/internal/stateInit_hash.md) to read more about this +function mechanics. + +##### abi.encodeBody() + +```TVMSolidity +abi.encodeBody(function, arg0, arg1, arg2, ...) returns (TvmCell); +abi.encodeBody(function, callbackFunction, arg0, arg1, arg2, ...) returns (TvmCell); +abi.encodeBody(contract, arg0, arg1, arg2, ...) returns (TvmCell); +``` + +Constructs a message body for the function call. Body can be used as a payload for [\.transfer()](#addresstransfer). +If the **function** is `responsible`, **callbackFunction** must be set. + +Example: + +```TVMSolidity +contract Remote { + constructor(uint x, uint y, uint z) public { /* */ } + function func(uint256 num, int64 num2) public pure { /* */ } + function getCost(uint256 num) public responsible pure returns (uint128) { /* */ } +} + +// deploy the contract +TvmCell body = abi.encodeBody(Remote, 100, 200, 300); +addr.transfer({value: 10 ever, body: body, stateInit: stateInit }); + +// call the function +TvmCell body = abi.encodeBody(Remote.func, 123, -654); +addr.transfer({value: 10 ever, body: body}); + +// call the responsible function +TvmCell body = abi.encodeBody(Remote.getCost, onGetCost, 105); +addr.transfer({value: 10 ever, body: body}); +``` + +See also: + +* [External function calls](#external-function-calls) +* [abi.loadFunctionParams()](#abidecodefunctionparams) +* [abi.encodeIntMsg()](#abiencodeintmsg) + +##### abi.decodeFunctionParams() + +```TVMSolidity +// Loads parameters of the public/external function without "responsible" attribute +abi.decodeFunctionParams(functionName) returns (TypeA /*a*/, TypeB /*b*/, ...); + +// Loads parameters of the public/external function with "responsible" attribute +abi.decodeFunctionParams(functionName) returns (uint32 callbackFunctionId, TypeA /*a*/, TypeB /*b*/, ...); + +// Loads constructor parameters +abi.decodeFunctionParams(ContractName) returns (TypeA /*a*/, TypeB /*b*/, ...); +``` + +Loads parameters of the function or constructor (if contract type is provided). This function is usually used in +**[onBounce](#onbounce)** function. + +See example of how to use **onBounce** function: + +* [onBounceHandler](https://github.com/tonlabs/samples/blob/master/solidity/16_onBounceHandler.sol) + +##### abi.codeSalt() + +```TVMSolidity +abi.codeSalt(TvmCell code) returns (optional(TvmCell) optSalt); +``` + +If **code** contains salt, then **optSalt** contains one. Otherwise, **optSalt** doesn't contain any value. + +##### abi.setCodeSalt() + +```TVMSolidity +abi.setCodeSalt(TvmCell code, TvmCell salt) returns (TvmCell newCode); +``` + +Inserts **salt** into **code** and returns new code **newCode**. + +##### abi.functionId() + +```TVMSolidity +// id of public function +abi.functionId(functionName) returns (uint32); + +// id of public constructor +abi.functionId(ContractName) returns (uint32); +``` + +Returns the function id (uint32) of a public/external function or constructor. + +Example: + +```TVMSolidity +contract MyContract { + constructor(uint a) public { + } + /*...*/ + } + + function f() public pure returns (uint) { + /*...*/ + } + + function getConstructorID() public pure returns (uint32) { + uint32 functionId = abi.functionId(MyContract); + return functionId; + } + + function getFuncID() public pure returns (uint32) { + uint32 functionId = abi.functionId(f); + return functionId; + } +} +``` + +See example of how to use this function: + +* [onBounceHandler](https://github.com/tonlabs/samples/blob/master/solidity/16_onBounceHandler.sol) + +##### abi.encodeExtMsg() + +```TVMSolidity +abi.encodeExtMsg({ + dest: address, + time: uint64, + expire: uint32, + call: {functionIdentifier [, list of function arguments]}, + sign: bool, + pubkey: optional(uint256), + callbackId: (uint32 | functionIdentifier), + onErrorId: (uint32 | functionIdentifier), + stateInit: TvmCell, + signBoxHandle: optional(uint32), + abiVer: uint8, + flags: uint8 +}) +returns (TvmCell); +``` + +Function should be used only offchain and intended to be used only in debot contracts. +Allows creating an external inbound message, that calls the **func** function of the +contract on address **destination** with specified function arguments. + +Mandatory parameters that are used to form a src field that is used for debots: + +* `callbackId` - identifier of the callback function. +* `onErrorId` - identifier of the function that is called in case of an error. +* `signBoxHandle` - handle of the sign box entity, that engine will use to sign the message. + +These parameters are stored in addr_extern and placed to the src field of the message. +Message is of type [ext_in_msg_info](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L127) +and src address is of type [addr_extern](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/block/block.tlb#L101) +but stores special data: + +* callback id - 32 bits; +* on error id - 32 bits; +* abi version - 8 bits; Can be specified manually and contain full abi version in little endian half bytes (e.g. version = "2.3" -> abiVer: 0x32) +* header mask - 3 bits in such order: time, expire, pubkey; +* optional value signBoxHandle - 1 bit (whether value exists) + \[32 bits\]; +* control flags byte - 8 bits. + Currently used bits: + 1 - override time (dengine will replace time value with current time) + 2 - override exp (dengine will replace time value with actual expire value) + 4 - async call (dengine must send message and don't wait for the result) + +Other function parameters define fields of the message: + +* `time` - message creation timestamp. Used for replay attack protection, encoded as 64 bit Unix +time in milliseconds. +* `expire` - Unix time (in seconds, 32 bit) after that message should not be processed by contract. +* `pubkey` - public key from key pair used for signing the message body. This parameter is optional +and can be omitted. +* `sign` - constant bool flag that shows whether message should contain signature. If set to +**true**, message is generated with signature field filled with zeroes. This parameter is optional +and can be omitted (in this case is equal to **false**). + +User can also attach stateInit to the message using `stateInit` parameter. + +Function throws an exception with code 64 if function is called with wrong parameters (pubkey is set +and has value, but sign is false or omitted). + +Example: + +```TVMSolidity + +interface Foo { + function bar(uint a, uint b) external pure; +} + +contract Test { + + TvmCell public m_cell; + + function generate0() public { + tvm.accept(); + address addr = address.makeAddrStd(0, 0x0123456789012345678901234567890123456789012345678901234567890123); + m_cell = abi.encodeExtMsg({callbackId: 0, onErrorId: 0, dest: addr, time: 0x123, expire: 0x12345, call: {Foo.bar, 111, 88}}); + } + + function generate1() public { + tvm.accept(); + optional(uint) pubkey; + address addr = address.makeAddrStd(0, 0x0123456789012345678901234567890123456789012345678901234567890123); + m_cell = abi.encodeExtMsg({callbackId: 0, onErrorId: 0, dest: addr, time: 0x123, expire: 0x12345, call: {Foo.bar, 111, 88}, pubkey: pubkey}); + } + + function generate2() public { + tvm.accept(); + optional(uint) pubkey; + pubkey.set(0x95c06aa743d1f9000dd64b75498f106af4b7e7444234d7de67ea26988f6181df); + address addr = address.makeAddrStd(0, 0x0123456789012345678901234567890123456789012345678901234567890123); + optional(uint32) signBox; + signBox.set(0x12333112); + m_cell = abi.encodeExtMsg({callbackId: 0, onErrorId: 0, dest: addr, time: 0x1771c58ef9a, expire: 0x600741e4, call: {Foo.bar, 111, 88}, pubkey: pubkey, sign: true, signBoxHandle: signBox}); + } + +} +``` + +External inbound message can also be built and sent with construction similar to remote contract +call. It requires suffix ".extMsg" and call options similar to `buildExtMsg` function call. +**Note**: this type of call should be used only offchain in debot contracts. + +```TVMSolidity +interface Foo { + function bar(uint a, uint b) external pure; +} + +contract Test { + + function test7() public { + address addr = address.makeAddrStd(0, 0x0123456789012345678901234567890123456789012345678901234567890123); + Foo(addr).bar{expire: 0x12345, time: 0x123}(123, 45).extMsg; + optional(uint) pubkey; + optional(uint32) signBox; + Foo(addr).bar{expire: 0x12345, time: 0x123, pubkey: pubkey}(123, 45).extMsg; + Foo(addr).bar{expire: 0x12345, time: 0x123, pubkey: pubkey, sign: true}(123, 45).extMsg; + pubkey.set(0x95c06aa743d1f9000dd64b75498f106af4b7e7444234d7de67ea26988f6181df); + Foo(addr).bar{expire: 0x12345, time: 0x123, pubkey: pubkey, sign: true}(123, 45).extMsg; + Foo(addr).bar{expire: 0x12345, time: 0x123, sign: true, signBoxHandle: signBox}(123, 45).extMsg; + Foo(addr).bar{expire: 0x12345, time: 0x123, sign: true, signBoxHandle: signBox, abiVer: 0x32, flags: 0x07}(123, 45).extMsg; + } +} +``` + +##### abi.encodeIntMsg() + +```TVMSolidity +abi.encodeIntMsg({ + dest: address, + value: uint128, + call: {function, [callbackFunction,] arg0, arg1, arg2, ...}, + bounce: bool, + currencies: mapping(uint32 => varUint32), + stateInit: TvmCell +}) +returns (TvmCell); +``` + +Generates an internal outbound message that contains a function call. The result `TvmCell` can be used to send a +message using [tvm.sendrawmsg()](#tvmsendrawmsg). If the `function` is `responsible`, then +`callbackFunction` parameter must be set. + +`dest`, `value` and `call` parameters are mandatory. Another parameters can be omitted. See +[\.transfer()](#addresstransfer) where these options and their default values are +described. + +See also: + +* sample [22_sender.sol](https://github.com/tonlabs/samples/blob/master/solidity/22_sender.sol) +* [abi.encodeBody()](#abiencodebody) + ### **gosh** namespace All `gosh.*` functions are experimental features and are available only in certain blockchain @@ -4950,15 +5044,13 @@ Solidity runtime error codes: * **51** - Contract's constructor has already been called. * **52** - Replay protection exception. See `timestamp` in [pragma AbiHeader](#pragma-abiheader). * **54** - `.pop` call for an empty array. - * **55** - See [tvm.insertPubkey()](#tvminsertpubkey). * **57** - External inbound message is expired. See `expire` in [pragma AbiHeader](#pragma-abiheader). * **58** - External inbound message has no signature but has public key. See `pubkey` in [pragma AbiHeader](#pragma-abiheader). * **60** - Inbound message has wrong function id. In the contract there are no functions with such function id and there is no fallback function that could handle the message. See [fallback](#fallback). * **61** - Deploying `StateInit` has no public key in `data` field. * **62** - Reserved for internal usage. * **63** - See [\.get()](#optionaltget). - * **64** - `tvm.buildExtMSg()` call with wrong parameters. See [tvm.buildExtMsg()](#tvmbuildextmsg). - * **66** - Convert an integer to a string with width less than number length. See [format()](#format). + * **64** - `abi.encodeExtMsg()` call with wrong parameters. See [abi.encodeExtMsg()](#abiencodeextmsg). * **67** - See [gasToValue](#gastovalue) and [valueToGas](#valuetogas). * **68** - There is no config parameter 20 or 21. * **69** - Zero to the power of zero calculation (`0**0` in TVM Solidity style or `0^0`). diff --git a/Cargo.lock b/Cargo.lock index 6af72adf..3eabf46f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,9 +51,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -75,9 +75,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" dependencies = [ "anstyle", "anstyle-parse", @@ -89,33 +89,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", "windows-sys", @@ -123,9 +123,9 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "2.0.12" +version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6" +checksum = "00ad3f3a942eee60335ab4342358c161ee296829e0d16ff42fc1d6cb07815467" dependencies = [ "anstyle", "bstr", @@ -142,7 +142,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -207,11 +207,23 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blst" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94087b935a822949d3291a9989ad2b2051ea141eda0fd4e478a75f6aa3e604b" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + [[package]] name = "bstr" -version = "1.6.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" dependencies = [ "memchr", "regex-automata", @@ -220,15 +232,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "d32a994c2b3ca201d9b263612a374263f05e7adde37c4707f693dcd375076d1f" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" @@ -247,9 +259,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "android-tzdata", "iana-time-zone", @@ -270,9 +282,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.6" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" dependencies = [ "clap_builder", "clap_derive", @@ -280,9 +292,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.6" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" dependencies = [ "anstream", "anstyle", @@ -292,21 +304,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.49", ] [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "cmake" @@ -325,21 +337,21 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -355,9 +367,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crypto-common" @@ -393,9 +405,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.1" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ "cfg-if", "cpufeatures", @@ -410,13 +422,13 @@ dependencies = [ [[package]] name = "curve25519-dalek-derive" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.49", ] [[package]] @@ -477,12 +489,12 @@ dependencies = [ [[package]] name = "ed25519" -version = "2.2.2" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", - "signature 2.1.0", + "signature 2.2.0", ] [[package]] @@ -501,24 +513,19 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ - "curve25519-dalek 4.1.1", - "ed25519 2.2.2", + "curve25519-dalek 4.1.2", + "ed25519 2.2.3", "rand_core 0.6.4", "serde", "sha2 0.10.8", + "subtle", "zeroize", ] -[[package]] -name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" - [[package]] name = "failure" version = "0.1.8" @@ -543,9 +550,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.1" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" +checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" [[package]] name = "float-cmp" @@ -579,9 +586,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -590,9 +597,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "heck" @@ -609,6 +622,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" + [[package]] name = "hex" version = "0.3.2" @@ -623,16 +642,16 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -644,26 +663,17 @@ dependencies = [ "cc", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] @@ -676,9 +686,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "lockfree" @@ -696,15 +706,15 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] @@ -742,9 +752,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" dependencies = [ "num-traits", ] @@ -762,19 +772,18 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" dependencies = [ "autocfg", "num-integer", @@ -795,27 +804,37 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.6", + "libc", +] + [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" @@ -841,9 +860,9 @@ dependencies = [ [[package]] name = "platforms" -version = "3.1.2" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" [[package]] name = "ppv-lite86" @@ -853,14 +872,13 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "predicates" -version = "3.0.4" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" dependencies = [ "anstyle", "difflib", "float-cmp", - "itertools", "normalize-line-endings", "predicates-core", "regex", @@ -884,18 +902,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -959,7 +977,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.12", ] [[package]] @@ -973,9 +991,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.6" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -985,9 +1003,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.9" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -996,9 +1014,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustc-demangle" @@ -1017,41 +1035,41 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "semver" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.49", ] [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" dependencies = [ "itoa", "ryu", @@ -1090,19 +1108,22 @@ checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] [[package]] name = "smallvec" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "sold" -version = "0.72.0" +version = "0.73.0" dependencies = [ "assert_cmd", "atty", @@ -1123,9 +1144,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -1142,9 +1163,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "subtle" @@ -1165,9 +1186,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" dependencies = [ "proc-macro2", "quote", @@ -1192,10 +1213,19 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "ton_abi" -version = "2.3.147" -source = "git+https://github.com/tonlabs/ever-abi.git?tag=2.3.147#a3194277b02417e4f01221f5cf6b943fc4bd6653" +version = "2.4.14" +source = "git+https://github.com/tonlabs/ever-abi.git?tag=2.4.14#df0aacbd2059f5bf16c7b29000b32a957a8440e2" dependencies = [ "base64 0.10.1", "byteorder", @@ -1214,13 +1244,9 @@ dependencies = [ [[package]] name = "ton_block" -version = "1.9.104" -source = "git+https://github.com/tonlabs/ever-block.git?tag=1.9.104#2c6cf392260d047cff5962572a628c207ea28309" +version = "1.9.122" +source = "git+https://github.com/tonlabs/ever-block.git?tag=1.9.122#4330c36880e3bef543e18f4f6af1bb323da97b2a" dependencies = [ - "base64 0.13.1", - "crc", - "ed25519 1.5.3", - "ed25519-dalek 1.0.1", "failure", "hex 0.4.3", "log", @@ -1232,8 +1258,8 @@ dependencies = [ [[package]] name = "ton_labs_assembler" -version = "1.4.15" -source = "git+https://github.com/tonlabs/ever-assembler.git?tag=1.4.15#0d0db5b69a5eec932dd53b83fb6c02c89897c1fe" +version = "1.4.39" +source = "git+https://github.com/tonlabs/ever-assembler.git?tag=1.4.39#fc2ac6d6f8ed1ceda60373de2188e844a88f0ea3" dependencies = [ "clap", "failure", @@ -1249,15 +1275,16 @@ dependencies = [ [[package]] name = "ton_types" -version = "2.0.28" -source = "git+https://github.com/tonlabs/ever-types.git?tag=2.0.28#e6ce975e741634638e4d42f4e8b273ea4d046370" +version = "2.0.32" +source = "git+https://github.com/tonlabs/ever-types.git?tag=2.0.32#adc33ce8001ba52de4de9c2168e1f8e6a83ef5e0" dependencies = [ "aes-ctr", "base64 0.13.1", + "blst", "crc", - "curve25519-dalek 4.1.1", - "ed25519 2.2.2", - "ed25519-dalek 2.0.0", + "curve25519-dalek 4.1.2", + "ed25519 2.2.3", + "ed25519-dalek 2.1.1", "failure", "hex 0.4.3", "lazy_static", @@ -1276,8 +1303,8 @@ dependencies = [ [[package]] name = "ton_vm" -version = "1.8.211" -source = "git+https://github.com/tonlabs/ever-vm.git?tag=1.8.211#ca3e6e70793a86ec20d27d1463030ca864bf1f59" +version = "1.9.6" +source = "git+https://github.com/tonlabs/ever-vm.git?tag=1.9.6#fd7864b934e89c8c3578efcd5575e6cf116a9b03" dependencies = [ "ed25519 1.5.3", "ed25519-dalek 1.0.1", @@ -1365,9 +1392,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1375,24 +1402,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.49", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1400,22 +1427,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.49", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "winapi" @@ -1440,28 +1467,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1474,53 +1501,53 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "x25519-dalek" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "rand_core 0.6.4", "serde", "zeroize", @@ -1528,9 +1555,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] @@ -1543,5 +1570,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.49", ] diff --git a/Changelog.md b/Changelog.md index 7b61ecf4..97c557a1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,4 +1,57 @@ -### 0.72.0 (2023-??-??) +### 0.73.0 (2024-02-12) + +Update compiler frontend (from original version 0.8.17 to 0.8.24). Full changelog can be found [here](./compiler/Changelog.md). + +Breaking changes: + * Supported [ABI 2.4](https://github.com/tonlabs/ever-abi/blob/master/CHANGELOG.md#version-240). + * Deleted `tvm.insertPubkey()` function. + * Deleted `address.makeAddrNone()` function. Use [address.addrNone](./API.md#addressaddrnone). + * Default value for `address` type was `address(0)` but now it is [address.addrNone](./API.md#addressaddrnone). + * `byteN` in `*abi.json` file marked as `fixedbytesN`. See [ABI.md](https://github.com/tonlabs/ever-abi/blob/master/docs/ABI.md#fixedbytesn). + * [abi.encodeStateInit()](./API.md#abiencodestateinit), [abi.encodeData()](./API.md#abiencodedata) functions and [Deploy via new](./API.md#deploy-via-new) generate contract's data in the new format. To deploy old contracts (with version < 0.72.0) from new ones (with version >= 0.72.0) use [abi.encodeOldDataInit()](./API.md#abiencodeolddatainit). + * [format()](API.md#format) no longer throws an exception if formatted value exceeds the specified width. E.g. `format("{:1d}", 10) == "10"` now is correct, no exception. + * Functions and constructions previously working with `uint128` value, now using `varUint16`: + * [\.transfer()](API.md#addresstransfer) + * [deploy via **new**](API.md#deploy-via-new) + * [external function calls](API.md#external-function-calls) + * [\.balance](API.md#addressbalance) + * [msg.value](API.md#msgvalue) + * [abi.encodeIntMsg()](API.md#abiencodeintmsg) + * [return](API.md#return) + * Control construction [repeat](API.md#repeat) takes `uint31` instead of `uint256`. + * Supported [\.cast()](API.md#integercast) and changed conversion. + +Bugfixes: + * Fixed compiler assert fault that occurred in the case of using [user-defined value types](https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types) as return parameter of public/external function. + * Fixed segmentation fault of the compiler that could occur in some cases of using invalid rational number (too large or division by zero), e.g. `math.muldiv(2**500, 1, 1);`. + * Now [\.substr()](API.md#stringsubstr) does not throw an exception if `pos + count > string.byteLength()`. + * Fixed minor bugs in TypeChecker. + +Compiler features: + * Supported defining operators for a [user-defined type](API.md#user-defined-type). + * Supported [keyword nostorage](API.md#keyword-nostorage). + * Now you can use the keyword [coins](API.md#varint-and-varuint) as an alias for `varUint16` + +Gas optimizations: + * Optimized functions that work with strings: concatenation, substring, format etc. + * Assorted stack optimizations. + +Other changes: + * Renamed some functions. Old functions are available and marked as deprecated. Renaming: + * `tvm.buildDataInit()` -> `abi.encodeData()` + * `.loadStateVars()` -> `abi.decodeData()` + * `tvm.buildStateInit()` -> `abi.encodeStateInit()` + * `tvm.stateInitHash()` -> `abi.stateInitHash()` + * `tvm.codeSalt()` -> `abi.codeSalt()` + * `tvm.setCodeSalt()` -> `abi.setCodeSalt()` + * `tvm.functionId()` -> `abi.functionId()` + * `tvm.buildExtMsg()` -> `abi.encodeExtMsg()` + * `tvm.buildIntMsg()` -> `abi.encodeIntMsg()` + * `tvm.encodeBody()` -> `abi.encodeBody()` + * `.loadFunctionParams()` -> `abi.decodeFunctionParams()` + * Marked functions `.loadTons()` and `.storeTons()` as deprecated. Use `.load()` and `.store()` that take `varUint16` type. + +### 0.72.0 (2023-10-31) Use [sold](https://github.com/tonlabs/TON-Solidity-Compiler/tree/master/sold) to compile contracts. If you used `solc`+`tvm_linker`, then use `solc`+[asm](https://github.com/tonlabs/ever-assembler). Generated `*.code` files have some another format. diff --git a/README.md b/README.md index b5fda561..017496ac 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ cd TON-Solidity-Compiler compiler\scripts\install_deps.ps1 mkdir build cd build -cmake -DBoost_DIR="..\compiler\deps\boost\lib\cmake\Boost-1.77.0" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded ..\compiler +cmake -DBOOST_ROOT="..\compiler\deps\boost\" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded ..\compiler cmake --build . --config Release -- /m ``` diff --git a/compiler/.clang-format b/compiler/.clang-format index edd5de4d..95b0feba 100644 --- a/compiler/.clang-format +++ b/compiler/.clang-format @@ -12,7 +12,7 @@ Language: Cpp BasedOnStyle: LLVM AccessModifierOffset: -4 AlignAfterOpenBracket: AlwaysBreak -AlignEscapedNewlinesLeft: true +AlignEscapedNewlines: Left AlwaysBreakAfterReturnType: None AlwaysBreakTemplateDeclarations: Yes BinPackArguments: false diff --git a/compiler/.gitignore b/compiler/.gitignore index 4f171718..8ca5f86e 100644 --- a/compiler/.gitignore +++ b/compiler/.gitignore @@ -27,6 +27,9 @@ __pycache__ *.a *.lib +# ignore git mergetool backup files +*.orig + # Executables *.exe *.out @@ -38,6 +41,7 @@ emscripten_build/ /docs/_build /docs/_static/robots.txt /deps +/reports # vim stuff [._]*.sw[a-p] diff --git a/compiler/.readthedocs.yml b/compiler/.readthedocs.yml new file mode 100644 index 00000000..c0c139b7 --- /dev/null +++ b/compiler/.readthedocs.yml @@ -0,0 +1,18 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +sphinx: + builder: html + configuration: docs/conf.py + +formats: + - pdf + - epub + +python: + install: + - requirements: docs/requirements.txt diff --git a/compiler/CMakeLists.txt b/compiler/CMakeLists.txt index 75a8c5fe..d042cb0e 100644 --- a/compiler/CMakeLists.txt +++ b/compiler/CMakeLists.txt @@ -1,11 +1,11 @@ cmake_minimum_required(VERSION 3.13.0) -set(ETH_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}/cmake" CACHE PATH "The the path to the cmake directory") +set(ETH_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}/cmake" CACHE PATH "The path to the cmake directory") list(APPEND CMAKE_MODULE_PATH ${ETH_CMAKE_DIR}) # Set the build type, if none was specified. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - if(EXISTS "${CMAKE_SOURCE_DIR}/.git") + if(EXISTS "${PROJECT_SOURCE_DIR}/.git") set(DEFAULT_BUILD_TYPE "RelWithDebInfo") else() set(DEFAULT_BUILD_TYPE "Release") @@ -21,7 +21,7 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.72.0") +set(PROJECT_VERSION "0.73.0") # OSX target needed in order to support std::visit set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14") project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX) @@ -37,6 +37,7 @@ option(WITH_TESTS "Run solc tests" OFF) option(SOLC_STATIC_STDLIBS "Link solc against static versions of libgcc and libstdc++ on supported platforms" OFF) option(STRICT_Z3_VERSION "Use the latest version of Z3" ON) option(PEDANTIC "Enable extra warnings and pedantic build flags. Treat all warnings as errors." ON) +option(PROFILE_OPTIMIZER_STEPS "Output performance metrics for the optimiser steps." OFF) # Setup cccache. include(EthCcache) @@ -53,6 +54,11 @@ find_package(Threads) if(NOT PEDANTIC) message(WARNING "-- Pedantic build flags turned off. Warnings will not make compilation fail. This is NOT recommended in development builds.") endif() + +if (PROFILE_OPTIMIZER_STEPS) + add_definitions(-DPROFILE_OPTIMIZER_STEPS) +endif() + # Figure out what compiler and system are we using include(EthCompilerSettings) @@ -61,22 +67,21 @@ include(EthUtils) # Create license.h from LICENSE.txt and template # Converting to char array is required due to MSVC's string size limit. -file(READ ${CMAKE_SOURCE_DIR}/LICENSE.txt LICENSE_TEXT HEX) +file(READ ${PROJECT_SOURCE_DIR}/LICENSE.txt LICENSE_TEXT HEX) string(REGEX MATCHALL ".." LICENSE_TEXT "${LICENSE_TEXT}") string(REGEX REPLACE ";" ",\n\t0x" LICENSE_TEXT "${LICENSE_TEXT}") set(LICENSE_TEXT "0x${LICENSE_TEXT}") -configure_file("${CMAKE_SOURCE_DIR}/cmake/templates/license.h.in" include/license.h) +configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/license.h.in" include/license.h) include(EthOptions) configure_project(TESTS) -set(LATEST_Z3_VERSION "4.11.0") -set(MINIMUM_Z3_VERSION "4.8.0") add_subdirectory(libsolutil) add_subdirectory(liblangutil) add_subdirectory(libsolidity) add_subdirectory(libsolc) +add_subdirectory(libstdlib) if (WITH_TESTS) add_subdirectory(test2) endif() diff --git a/compiler/CODE_OF_CONDUCT.md b/compiler/CODE_OF_CONDUCT.md index 36813f36..4c75649a 100644 --- a/compiler/CODE_OF_CONDUCT.md +++ b/compiler/CODE_OF_CONDUCT.md @@ -55,10 +55,8 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at chris@ethereum.org which only goes to -Christian Reitwiessner or axic@ethereum.org which only goes to Alex Beregszaszi. -To report an issue involving either of them please email Hudson Jameson at -hudson@ethereum.org. +reported by contacting the project team at solidity@ethereum.org. +To report an issue involving the Solidity team please email José Pedro Cabrita at zepedro@ethereum.org. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. diff --git a/compiler/CODING_STYLE.md b/compiler/CODING_STYLE.md index 674c2add..5b9129fb 100644 --- a/compiler/CODING_STYLE.md +++ b/compiler/CODING_STYLE.md @@ -51,10 +51,11 @@ To set indentation and tab width settings uniformly, the repository contains an ## 1. Namespaces 1. No `using namespace` declarations in header files. -2. Use `using namespace std;` in cpp files, but avoid importing namespaces from boost and others. -3. All symbols should be declared in a namespace except for final applications. -4. Use anonymous namespaces for helpers whose scope is a cpp file only. -5. Preprocessor symbols should be prefixed with the namespace in all-caps and an underscore. +2. `using namespace solidity;` and other project local namespaces is fine in cpp files, and generally encouraged. +3. Avoid `using namespace` at file level for third party libraries, such as boost, ranges, etc. +4. All symbols should be declared in a namespace except for final applications. +5. Use anonymous namespaces for helpers whose scope is a cpp file only. +6. Preprocessor symbols should be prefixed with the namespace in all-caps and an underscore. Only in the header: ```cpp @@ -65,16 +66,6 @@ std::tuple meanAndSigma(std::vector const& _v); } ``` -Only in the cpp file: -```cpp -#include -using namespace std; -tuple myNamespace::meanAndSigma(vector const& _v) -{ - // ... -} -``` - ## 2. Preprocessor 1. File comment is always at top, and includes: @@ -116,7 +107,7 @@ Use `solAssert` and `solUnimplementedAssert` generously to check assumptions tha 1. {Typename} + {qualifiers} + {name}. 2. Only one per line. 3. Associate */& with type, not variable (at ends with parser, but more readable, and safe if in conjunction with (b)). -4. Favour declarations close to use; don't habitually declare at top of scope ala C. +4. Favour declarations close to use; do not habitually declare at top of scope ala C. 5. Pass non-trivial parameters as const reference, unless the data is to be copied into the function, then either pass by const reference or by value and use std::move. 6. If a function returns multiple values, use std::tuple (std::pair acceptable) or better introduce a struct type. Do not use */& arguments. 7. Use parameters of pointer type only if ``nullptr`` is a valid argument, use references otherwise. Often, ``std::optional`` is better suited than a raw pointer. @@ -179,7 +170,7 @@ for (map::iterator i = l.begin(); i != l.end(); ## 9. Naming -1. Avoid unpronouncable names. +1. Avoid unpronounceable names. 2. Names should be shortened only if they are extremely common, but shortening should be generally avoided 3. Avoid prefixes of initials (e.g. do not use `IMyInterface`, `CMyImplementation`) 4. Find short, memorable & (at least semi-) descriptive names for commonly used classes or name-fragments: diff --git a/compiler/Changelog.md b/compiler/Changelog.md index 2167d5b7..d36470b0 100644 --- a/compiler/Changelog.md +++ b/compiler/Changelog.md @@ -1,3 +1,203 @@ +### 0.8.24 (2024-01-25) + +Language Features: + * Introduce global ``block.blobbasefee`` for retrieving the blob base fee of the current block. + * Introduce global function ``blobhash(uint)`` for retrieving versioned hashes of blobs, akin to the homonymous Yul builtin. + * Yul: Introduce builtin ``blobbasefee()`` for retrieving the blob base fee of the current block. + * Yul: Introduce builtin ``blobhash()`` for retrieving versioned hashes of blobs associated with the transaction. + * Yul: Introduce builtin ``mcopy()`` for cheaply copying data between memory areas. + * Yul: Introduce builtins ``tload()`` and ``tstore()`` for transient storage access. + + +Compiler Features: +* EVM: Support for the EVM Version "Cancun". +* SMTChecker: Support `bytes.concat` except when string literals are passed as arguments. +* Standard JSON Interface: Add experimental support to import EVM assembly in the format used by ``--asm-json``. +* TypeChecker: Comparison of internal function pointers now yields a warning, as it can produce unexpected results with the legacy pipeline enabled. + + +Bugfixes: + * AST import: Fix bug when importing inline assembly with empty ``let`` variable declaration. + + +### 0.8.23 (2023-11-08) + +Important Bugfixes: + * Optimizer: Fix block deduplicator bug which led to blocks which are identical apart from the contents of ``verbatim`` instructions to be treated as equivalent and thus collapsed into a single one. + + +Compiler Features: + * Commandline Interface: An empty ``--yul-optimizations`` sequence can now be always provided. + * Standard JSON Interface: An empty ``optimizerSteps`` sequence can now always be provided. + + +### 0.8.22 (2023-10-25) + +Language Features: + * Allow defining events at file level. + + +Compiler Features: + * Code Generator: Remove redundant overflow checks of certain ``for`` loops when the counter variable cannot overflow. + * Commandline Interface: Add ``--no-import-callback`` option that prevents the compiler from loading source files not given explicitly on the CLI or in Standard JSON input. + * Commandline Interface: Add an experimental ``--import-asm-json`` option that can import EVM assembly in the format used by ``--asm-json``. + * Commandline Interface: Use proper severity and coloring also for error messages produced outside of the compilation pipeline. + * EVM: Deprecate support for "homestead", "tangerineWhistle", "spuriousDragon" and "byzantium" EVM versions. + * Parser: Remove the experimental error recovery mode (``--error-recovery`` / ``settings.parserErrorRecovery``). + * SMTChecker: Support user-defined operators. + * Yul Optimizer: If ``PUSH0`` is supported, favor zero literals over storing zero values in variables. + * Yul Optimizer: Run the ``Rematerializer`` and ``UnusedPruner`` steps at the end of the default clean-up sequence. + + +Bugfixes: + * AST: Fix wrong initial ID for Yul nodes in the AST. + * Code Generator: Fix output from via-IR code generator being dependent on which files were discovered by import callback. In some cases, a different AST ID assignment would alter the order of functions in internal dispatch, resulting in superficially different but semantically equivalent bytecode. + * NatSpec: Fix internal error when requesting userdoc or devdoc for a contract that emits an event defined in a foreign contract or interface. + * SMTChecker: Fix encoding error that causes loops to unroll after completion. + * SMTChecker: Fix inconsistency on constant condition checks when ``while`` or ``for`` loops are unrolled before the condition check. + * Yul Optimizer: Fix replacement decisions during CSE being affected by Yul variable names generated by the compiler, resulting in different (but equivalent) bytecode in some situations. + + +### 0.8.21 (2023-07-19) + +Important Bugfixes: + * Code Generator: Always generate code for the expression in ``.selector`` in the legacy code generation pipeline. + * Yul Optimizer: Fix ``FullInliner`` step (``i``) not preserving the evaluation order of arguments passed into inlined functions in code that is not in expression-split form (i.e. when using a custom optimizer sequence in which the step not preceded by ``ExpressionSplitter`` (``x``)). + + +Language Features: + * Allow qualified access to events from other contracts. + * Relax restrictions on initialization of immutable variables. Reads and writes may now happen at any point at construction time outside of functions and modifiers. Explicit initialization is no longer mandatory. + + +Compiler Features: + * Commandline Interface: Add ``--ast-compact-json`` output in assembler mode. + * Commandline Interface: Add ``--ir-ast-json`` and ``--ir-optimized-ast-json`` outputs for Solidity input, providing AST in compact JSON format for IR and optimized IR. + * Commandline Interface: Respect ``--optimize-yul`` and ``--no-optimize-yul`` in compiler mode and accept them in assembler mode as well. ``--optimize --no-optimize-yul`` combination now allows enabling EVM assembly optimizer without enabling Yul optimizer. + * EWasm: Remove EWasm backend. + * Parser: Introduce ``pragma experimental solidity``, which will enable an experimental language mode that in particular has no stability guarantees between non-breaking releases and is not suited for production use. + * SMTChecker: Add ``--model-checker-print-query`` CLI option and ``settings.modelChecker.printQuery`` JSON option to output the SMTChecker queries in the SMTLIB2 format. This requires using ``smtlib2`` solver only. + * Standard JSON Interface: Add ``ast`` file-level output for Yul input. + * Standard JSON Interface: Add ``irAst`` and ``irOptimizedAst`` contract-level outputs for Solidity input, providing AST in compact JSON format for IR and optimized IR. + * Yul Optimizer: Remove experimental ``ReasoningBasedSimplifier`` optimization step. + * Yul Optimizer: Stack-to-memory mover is now enabled by default whenever possible for via IR code generation and pure Yul compilation. + + +Bugfixes: + * Code Generator: Disallow complex expressions whose results are types, built-ins, modules or some unassignable functions. The legacy code generation pipeline would not actually evaluate them, discarding any side-effects they might have. + * Code Generator: Fix not entirely deterministic order of functions in unoptimized Yul output. The choice of C++ compiler in some cases would result in different (but equivalent) bytecode (especially from native binaries vs emscripten binaries). + * Commandline Interface: Fix internal error when using ``--stop-after parsing`` and requesting some of the outputs that require full analysis or compilation. + * Commandline Interface: It is no longer possible to specify both ``--optimize-yul`` and ``--no-optimize-yul`` at the same time. + * SMTChecker: Fix encoding of side-effects inside ``if`` and ``ternary conditional``statements in the BMC engine. + * SMTChecker: Fix false negative when a verification target can be violated only by trusted external call from another public function. + * SMTChecker: Fix generation of invalid SMT-LIB2 scripts in BMC engine with trusted mode for external calls when CHC engine times out. + * SMTChecker: Fix internal error caused by incorrectly classifying external function call using function pointer as a public getter. + * SMTChecker: Fix internal error caused by using external identifier to encode member access to functions that take an internal function as a parameter. + * Standard JSON Interface: Fix an incomplete AST being returned when analysis is interrupted by certain kinds of fatal errors. + * Type Checker: Disallow using certain unassignable function types in complex expressions. + * Type Checker: Function declaration types referring to different declarations are no longer convertible to each other. + * Yul Optimizer: Ensure that the assignment of memory slots for variables moved to memory does not depend on AST IDs that may depend on whether additional files are included during compilation. + * Yul Optimizer: Fix ``FullInliner`` step not ignoring code that is not in expression-split form. + * Yul Optimizer: Fix optimized IR being unnecessarily passed through the Yul optimizer again before bytecode generation. + + +AST Changes: + * AST: Add the ``experimentalSolidity`` field to the ``SourceUnit`` nodes, which indicate whether the experimental parsing mode has been enabled via ``pragma experimental solidity``. + + +### 0.8.20 (2023-05-10) + +Compiler Features: + * Assembler: Use ``push0`` for placing ``0`` on the stack for EVM versions starting from "Shanghai". This decreases the deployment and runtime costs. + * EVM: Set default EVM version to "Shanghai". + * EVM: Support for the EVM Version "Shanghai". + * NatSpec: Add support for NatSpec documentation in ``enum`` definitions. + * NatSpec: Add support for NatSpec documentation in ``struct`` definitions. + * NatSpec: Include NatSpec from events that are emitted by a contract but defined outside of it in userdoc and devdoc output. + * Optimizer: Re-implement simplified version of ``UnusedAssignEliminator`` and ``UnusedStoreEliminator``. It can correctly remove some unused assignments in deeply nested loops that were ignored by the old version. + * Parser: Unary plus is no longer recognized as a unary operator in the AST and triggers an error at the parsing stage (rather than later during the analysis). + * SMTChecker: Add CLI option ``--model-checker-bmc-loop-iterations`` and a JSON option ``settings.modelChecker.bmcLoopIterations`` that specify how many loop iterations the BMC engine should unroll. Note that false negatives are possible when unrolling loops. This is due to the possibility that bmc loop iteration setting is less than actual number of iterations needed to complete a loop. + * SMTChecker: Group all messages about unsupported language features in a single warning. The CLI option ``--model-checker-show-unsupported`` and the JSON option ``settings.modelChecker.showUnsupported`` can be enabled to show the full list. + * SMTChecker: Properties that are proved safe are now reported explicitly at the end of analysis. By default, only the number of safe properties is shown. The CLI option ``--model-checker-show-proved-safe`` and the JSON option ``settings.modelChecker.showProvedSafe`` can be enabled to show the full list of safe properties. + * Standard JSON Interface: Add experimental support for importing ASTs via Standard JSON. + * Yul EVM Code Transform: If available, use ``push0`` instead of ``codesize`` to produce an arbitrary value on stack in order to create equal stack heights between branches. + + +Bugfixes: + * ABI: Include events in the ABI that are emitted by a contract but defined outside of it. + * Immutables: Disallow initialization of immutables in try/catch statements. + * SMTChecker: Fix false positives in ternary operators that contain verification targets in its branches, directly or indirectly. + + +AST Changes: + * AST: Add the ``internalFunctionIDs`` field to the AST nodes of contracts containing IDs of functions that may be called via the internal dispatch. The field is a map from function AST IDs to internal dispatch function IDs. These IDs are always generated, but they are only used in via-IR code generation. + * AST: Add the ``usedEvents`` field to ``ContractDefinition`` which contains the AST IDs of all events emitted by the contract as well as all events defined and inherited by the contract. + + +### 0.8.19 (2023-02-22) + +Language Features: + * Allow defining custom operators for user-defined value types via ``using {f as +} for T global`` syntax. + + +Compiler Features: + * SMTChecker: New trusted mode that assumes that any compile-time available code is the actual used code even in external calls. This can be used via the CLI option ``--model-checker-ext-calls trusted`` or the JSON field ``settings.modelChecker.extCalls: "trusted"``. + + +Bugfixes: + * Assembler: Avoid duplicating subassembly bytecode where possible. + * Code Generator: Avoid including references to the deployed label of referenced functions if they are called right away. + * ContractLevelChecker: Properly distinguish the case of missing base constructor arguments from having an unimplemented base function. + * SMTChecker: Fix internal error caused by unhandled ``z3`` expressions that come from the solver when bitwise operators are used. + * SMTChecker: Fix internal error when using the custom NatSpec annotation to abstract free functions. + * TypeChecker: Also allow external library functions in ``using for``. + + +AST Changes: + * AST: Add ``function`` field to ``UnaryOperation`` and ``BinaryOperation`` AST nodes. ``functionList`` in ``UsingForDirective`` AST nodes will now contain ``operator`` and ``definition`` members instead of ``function`` when the list entry defines an operator. + + +### 0.8.18 (2023-02-01) + +Language Features: + * Allow named parameters in mapping types. + + +Compiler Features: + * Commandline Interface: Add ``--no-cbor-metadata`` that skips CBOR metadata from getting appended at the end of the bytecode. + * Commandline Interface: Return exit code ``2`` on uncaught exceptions. + * EVM: Deprecate ``block.difficulty`` and disallow ``difficulty()`` in inline assembly for EVM versions >= paris. The change is due to the renaming introduced by [EIP-4399](https://eips.ethereum.org/EIPS/eip-4399). + * EVM: Introduce ``block.prevrandao`` in Solidity and ``prevrandao()`` in inline assembly for EVM versions >= paris. + * EVM: Set the default EVM version to "Paris". + * EVM: Support for the EVM version "Paris". + * Language Server: Add basic document hover support. + * Natspec: Add event Natspec inheritance for devdoc. + * Optimizer: Added optimization rule ``and(shl(X, Y), shl(X, Z)) => shl(X, and(Y, Z))``. + * Parser: More detailed error messages about invalid version pragmas. + * SMTChecker: Make ``z3`` the default solver for the BMC and CHC engines instead of all solvers. + * SMTChecker: Support Eldarica as a Horn solver for the CHC engine when using the CLI option ``--model-checker-solvers eld``. The binary ``eld`` must be available in the system. + * Solidity Upgrade Tool: Remove ``solidity-upgrade`` tool. + * Standard JSON: Add a boolean field ``settings.metadata.appendCBOR`` that skips CBOR metadata from getting appended at the end of the bytecode. + * TypeChecker: Warn when using deprecated builtin ``selfdestruct``. + * Yul EVM Code Transform: Generate more optimal code for user-defined functions that always terminate a transaction. No return labels will be pushed for calls to functions that always terminate. + * Yul Optimizer: Allow replacing the previously hard-coded cleanup sequence by specifying custom steps after a colon delimiter (``:``) in the sequence string. + * Yul Optimizer: Eliminate ``keccak256`` calls if the value was already calculated by a previous call and can be reused. + + +Bugfixes: + * Parser: Disallow several ``indexed`` attributes for the same event parameter. + * Parser: Disallow usage of the ``indexed`` attribute for modifier parameters. + * SMTChecker: Fix display error for negative integers that are one more than powers of two. + * SMTChecker: Fix internal error on chain assignments using static fully specified state variables. + * SMTChecker: Fix internal error on multiple wrong SMTChecker natspec entries. + * SMTChecker: Fix internal error when a public library function is called internally. + * SMTChecker: Fix internal error when deleting struct member of function type. + * SMTChecker: Fix internal error when using user-defined types as mapping indices or struct members. + * SMTChecker: Improved readability for large integers that are powers of two or almost powers of two in error messages. + * TypeChecker: Fix bug where private library functions could be attached with ``using for`` outside of their declaration scope. + * Yul Optimizer: Hash hex and decimal literals according to their value instead of their representation, improving the detection of equivalent functions. + + ### 0.8.17 (2022-09-08) Important Bugfixes: diff --git a/compiler/README.md b/compiler/README.md index 9637503b..e64f8cbf 100644 --- a/compiler/README.md +++ b/compiler/README.md @@ -3,10 +3,10 @@ [![Matrix Chat](https://img.shields.io/badge/Matrix%20-chat-brightgreen?style=plastic&logo=matrix)](https://matrix.to/#/#ethereum_solidity:gitter.im) [![Gitter Chat](https://img.shields.io/badge/Gitter%20-chat-brightgreen?style=plastic&logo=gitter)](https://gitter.im/ethereum/solidity) [![Solidity Forum](https://img.shields.io/badge/Solidity_Forum%20-discuss-brightgreen?style=plastic&logo=discourse)](https://forum.soliditylang.org/) -[![Twitter Follow](https://img.shields.io/twitter/follow/solidity_lang?style=plastic&logo=twitter)](https://twitter.com/solidity_lang) +[![X Follow](https://img.shields.io/twitter/follow/solidity_lang?style=plastic&logo=x)](https://X.com/solidity_lang) [![Mastodon Follow](https://img.shields.io/mastodon/follow/000335908?domain=https%3A%2F%2Ffosstodon.org%2F&logo=mastodon&style=plastic)](https://fosstodon.org/@solidity) -You can talk to us on Gitter and Matrix, tweet at us on Twitter or create a new topic in the Solidity forum. Questions, feedback, and suggestions are welcome! +You can talk to us on Gitter and Matrix, tweet at us on X (previously Twitter) or create a new topic in the Solidity forum. Questions, feedback, and suggestions are welcome! Solidity is a statically typed, contract-oriented, high-level language for implementing smart contracts on the Ethereum platform. @@ -66,7 +66,7 @@ browser-based IDE. Here are some example contracts: ## Documentation -The Solidity documentation is hosted at [Read the docs](https://docs.soliditylang.org). +The Solidity documentation is hosted using [Read the Docs](https://docs.soliditylang.org). ## Development @@ -79,8 +79,8 @@ You can find our current feature and bug priorities for forthcoming releases in the [projects section](https://github.com/ethereum/solidity/projects). ## Maintainers -* [@axic](https://github.com/axic) -* [@chriseth](https://github.com/chriseth) +The Solidity programming language and compiler are open-source community projects governed by a core team. +The core team is sponsored by the [Ethereum Foundation](https://ethereum.foundation/). ## License Solidity is licensed under [GNU General Public License v3.0](LICENSE.txt). diff --git a/compiler/ReleaseChecklist.md b/compiler/ReleaseChecklist.md index e1a6a4c1..167417a9 100644 --- a/compiler/ReleaseChecklist.md +++ b/compiler/ReleaseChecklist.md @@ -1,22 +1,43 @@ ## Checklist for making a release: ### Requirements - - [ ] Github account with access to [solidity](https://github.com/ethereum/solidity), [solc-js](https://github.com/ethereum/solc-js), - [solc-bin](https://github.com/ethereum/solc-bin), [homebrew-ethereum](https://github.com/ethereum/homebrew-ethereum), - [solidity-blog](https://github.com/ethereum/solidity-blog) and [solidity-portal](https://github.com/ethereum/solidity-portal) repositories. + - [ ] GitHub account with access to [solidity](https://github.com/ethereum/solidity), [solc-js](https://github.com/ethereum/solc-js), + [solc-bin](https://github.com/ethereum/solc-bin), [solidity-website](https://github.com/ethereum/solidity-website). - [ ] DockerHub account with push rights to the [``solc`` image](https://hub.docker.com/r/ethereum/solc). - - [ ] Lauchpad (Ubuntu One) account with a membership in the ["Ethereum" team](https://launchpad.net/~ethereum) and + - [ ] Launchpad (Ubuntu One) account with a membership in the ["Ethereum" team](https://launchpad.net/~ethereum) and a gnupg key for your email in the ``ethereum.org`` domain (has to be version 1, gpg2 won't work). + - [ ] Ubuntu/Debian dependencies of the PPA scripts: ``devscripts``, ``debhelper``, ``dput``, ``git``, ``wget``, ``ca-certificates``. - [ ] [npm Registry](https://www.npmjs.com) account added as a collaborator for the [``solc`` package](https://www.npmjs.com/package/solc). - [ ] Access to the [solidity_lang Twitter account](https://twitter.com/solidity_lang). - [ ] [Reddit](https://www.reddit.com) account that is at least 10 days old with a minimum of 20 comment karma (``/r/ethereum`` requirements). -### Blog Post - - [ ] Create a post on [solidity-blog](https://github.com/ethereum/solidity-blog) in the ``Releases`` category and explain some of the new features or concepts. - - [ ] Create a post on [solidity-blog](https://github.com/ethereum/solidity-blog) in the ``Security Alerts`` category in case of important bug(s). +### Pre-flight checks +At least a day before the release: + - [ ] Run ``make linkcheck`` from within ``docs/`` and fix any broken links it finds. + Ignore false positives caused by ``href`` anchors and dummy links not meant to work. + - [ ] Double-check that [the most recent docs builds at readthedocs](https://readthedocs.org/projects/solidity/builds/) succeeded. + - [ ] Make sure that all merged PRs that should have changelog entries do have them. + - [ ] Rerun CI on the top commits of main branches in all repositories that do not have daily activity by creating a test branch or PR: + - [ ] ``solc-js`` + - [ ] ``solc-bin`` (make sure the bytecode comparison check did run) + - [ ] (Optional) Create a prerelease in our Ubuntu PPA by following the steps in the PPA section below on ``develop`` rather than on a tag. + This is recommended especially when dealing with PPA for the first time, when we add a new Ubuntu version or when the PPA scripts were modified in this release cycle. + - [ ] Verify that the release tarball of ``solc-js`` works. + Bump version locally, add ``soljson.js`` from CI, build it, compare the file structure with the previous version, install it locally and try to use it. + +### Drafts +At least a day before the release: + - [ ] Create a draft PR to sort the changelog. + - [ ] Create draft PRs to bump version in ``solidity`` and ``solc-js``. + **Note**: The ``solc-js`` PR won't pass CI checks yet because it depends on the soljson binary from ``solc-bin``. + - [ ] Create a draft of the release on github. + - [ ] Create a draft PR to update soliditylang.org. + - [ ] Create drafts of blog posts. + - [ ] Prepare drafts of Twitter, Reddit and Solidity Forum announcements. -### Documentation check - - [ ] Run ``make linkcheck`` from within ``docs/`` and fix any broken links it finds. Ignore false positives caused by ``href`` anchors and dummy links not meant to work. +### Blog Post + - [ ] Create a post on [solidity-website](https://github.com/ethereum/solidity-website/tree/main/src/posts) in the ``Releases`` category and explain some of the new features or concepts. + - [ ] Create a post on [solidity-website](https://github.com/ethereum/solidity-website/tree/main/src/posts) in the ``Security Alerts`` category in case of important bug(s). ### Changelog - [ ] Sort the changelog entries alphabetically and correct any errors you notice. Commit it. @@ -27,13 +48,16 @@ - [ ] Copy the changelog into the release blog post. ### Create the Release - - [ ] Create a [release on github](https://github.com/ethereum/solidity/releases/new). + - [ ] Create a [release on GitHub](https://github.com/ethereum/solidity/releases/new). Set the target to the ``develop`` branch and the tag to the new version, e.g. ``v0.8.5``. Include the following warning: ``**The release is still in progress and the binaries may not yet be available from all sources.**``. - Don't publish it yet - click the ``Save draft`` button instead. - - [ ] Thank voluntary contributors in the Github release notes (use ``git shortlog --summary --email v0.5.3..origin/develop``). + Do not publish it yet - click the ``Save draft`` button instead. + - [ ] Thank voluntary contributors in the GitHub release notes. + Use ``scripts/list_contributors.sh v`` to get initial list of names. + Remove different variants of the same name manually before using the output. - [ ] Check that all tests on the latest commit in ``develop`` are green. - [ ] Click the ``Publish release`` button on the release page, creating the tag. + **Important: Must not be done before all the PRs, including changelog cleanup and date, are merged.** - [ ] Wait for the CI runs on the tag itself. ### Upload Release Artifacts and Publish Binaries @@ -44,43 +68,51 @@ - [ ] Take the ``github-binaries.tar`` tarball from ``c_release_binaries`` run of the tagged commit in circle-ci and add all binaries from it to the release page. Make sure it contains four binaries: ``solc-windows.exe``, ``solc-macos``, ``solc-static-linux`` and ``soljson.js``. - [ ] Take the ``solc-bin-binaries.tar`` tarball from ``c_release_binaries`` run of the tagged commit in circle-ci and add all binaries from it to solc-bin. - - [ ] Run ``./update --reuse-hashes`` in ``solc-bin`` and verify that the script has updated ``list.js``, ``list.txt`` and ``list.json`` files correctly and that symlinks to the new release have been added in ``solc-bin/wasm/`` and ``solc-bin/emscripten-wasm32/``. + - [ ] Run ``npm run update -- --reuse-hashes`` in ``solc-bin`` and verify that the script has updated ``list.js``, ``list.txt`` and ``list.json`` files correctly and that symlinks to the new release have been added in ``solc-bin/wasm/`` and ``solc-bin/emscripten-wasm32/``. - [ ] Create a pull request in solc-bin and merge. ### Homebrew and MacOS - [ ] Update the version and the hash (``sha256sum solidity_$VERSION.tar.gz``) in the [``solidity`` formula in Homebrew core repository](https://github.com/Homebrew/homebrew-core/blob/master/Formula/solidity.rb). - - [ ] Update the version and the hash (``sha256sum solidity_$VERSION.tar.gz``) in [our custom ``solidity`` Homebrew formula](https://github.com/ethereum/homebrew-ethereum/blob/master/solidity.rb). ### Docker - [ ] Run ``./scripts/docker_deploy_manual.sh v$VERSION``. ### PPA - [ ] Create ``.release_ppa_auth`` at the root of your local Solidity checkout and set ``LAUNCHPAD_EMAIL`` and ``LAUNCHPAD_KEYID`` to your key's email and key id. - - [ ] Double-check that the ``DISTRIBUTIONS`` list in ``scripts/release_ppa.sh`` and ``scripts/deps-ppa/static-z3.sh`` contains the most recent versions of Ubuntu. + - [ ] Double-check that the ``DISTRIBUTIONS`` list in ``scripts/release_ppa.sh`` and ``scripts/deps-ppa/static_z3.sh`` contains the most recent versions of Ubuntu. - [ ] Make sure the [``~ethereum/cpp-build-deps`` PPA repository](https://launchpad.net/~ethereum/+archive/ubuntu/cpp-build-deps) contains ``libz3-static-dev builds`` for all current versions of Ubuntu. - If not, run ``scripts/deps-ppa/static-z3.sh`` (after changing email address and key id) and wait for the builds to succeed before continuing. + Note that it may be included in the ``z3-static`` multipackage (follow the ``View package details`` link to check). + If not present, run ``scripts/deps-ppa/static_z3.sh`` and wait for the builds to succeed before continuing. - [ ] Run ``scripts/release_ppa.sh v$VERSION`` to create the PPA release. - - [ ] Wait for the [``~ethereum/ethereum-static`` PPA](https://launchpad.net/~ethereum/+archive/ubuntu/ethereum-static) build to be finished and published for *all platforms*. + This will create a single package containing static binary for older Ubuntu versions in the [``~ethereum/ethereum-static`` PPA](https://launchpad.net/~ethereum/+archive/ubuntu/ethereum-static) + and separate packages with dynamically-linked binaries for recent versions (those listed in ``DISTRIBUTIONS``) in the [``~ethereum/ethereum`` PPA](https://launchpad.net/~ethereum/+archive/ubuntu/ethereum). + - [ ] Wait for the build to be finished and published for *all architectures* (currently we only build for ``amd64``, but we may add ``arm`` in the future). **SERIOUSLY: DO NOT PROCEED EARLIER!!!** - *After* the static builds are *published*, copy the static package to the [``~ethereum/ethereum`` PPA](https://launchpad.net/~ethereum/+archive/ubuntu/ethereum) + - [ ] *After* the package with the static build is *published*, use it to create packages for older Ubuntu versions. + Copy the static package to the [``~ethereum/ethereum`` PPA](https://launchpad.net/~ethereum/+archive/ubuntu/ethereum) for the destination series ``Trusty``, ``Xenial`` and ``Bionic`` while selecting ``Copy existing binaries``. ### Release solc-js - [ ] Wait until solc-bin was properly deployed. You can test this via remix - a test run through remix is advisable anyway. - [ ] Increment the version number, create a pull request for that, merge it after tests succeeded. - - [ ] Run ``npm run build:tarball`` in the updated ``solc-js`` repository to create ``solc-.tgz``. Inspect the tarball to ensure that it contains an up to date compiler binary. - - [ ] Run ``npm run publish:tarball`` to publish the newly created tarball. - [ ] Create a tag using ``git tag --annotate v$VERSION`` and push it with ``git push --tags``. + - [ ] Wait for the CI runs on the tag itself. + - [ ] Take the ``solc-x.y.z.tgz`` artifact from ``build-package`` run on the tagged commit in circle-ci. + Inspect the tarball to ensure that it contains an up-to-date compiler binary (``soljson.js``). + - [ ] Run ``npm publish solc-x.y.z.tgz`` to publish the newly created tarball. ### Post-release - [ ] Make sure the documentation for the new release has been published successfully. Go to the [documentation status page at ReadTheDocs](https://readthedocs.org/projects/solidity/) and verify that the new version is listed, works and is marked as default. - - [ ] Remove "still in progress" warning from the release notes. - - [ ] Publish the blog posts. + - [ ] Remove "still in progress" warning from the [release notes](https://github.com/ethereum/solidity/releases). + - [ ] Merge the [blog posts](https://github.com/ethereum/solidity-website/pulls) related to the release. - [ ] Create a commit to increase the version number on ``develop`` in ``CMakeLists.txt`` and add a new skeleton changelog entry. - - [ ] Announce on Twitter, including links to the release and the blog post. - Use ``#xp`` at the end of the tweet to automatically cross post the announcement to Fosstodon. + - [ ] Update the release information section [in the source of soliditylang.org](https://github.com/ethereum/solidity-website/blob/main/src/pages/index.tsx). + - [ ] Announce on [Twitter](https://twitter.com/solidity_lang), including links to the release and the blog post. + - [ ] Announce on [Fosstodon](https://fosstodon.org/@solidity/), including links to the release and the blog post. - [ ] Share the announcement on Reddit in [``/r/ethdev``](https://reddit.com/r/ethdev/), cross-posted to [``/r/ethereum``](https://reddit.com/r/ethereum/). - - [ ] Share the announcement the [Solidity forum](https://forum.soliditylang.org) in the ``Announcements`` category. - - [ ] Update the release information section on [soliditylang.org](https://github.com/ethereum/solidity-portal). - - [ ] Lean back, wait for bug reports and repeat from step 1 :) + - [ ] Share the announcement on the [Solidity forum](https://forum.soliditylang.org) in the ``Announcements`` category. + - [ ] Share the announcement on [Project Updates](https://discord.com/channels/420394352083337236/798974456704925696) + - [ ] Share the announcement on [`#solidity` channel on Matrix](https://matrix.to/#/#ethereum_solidity:gitter.im) + - [ ] Share the announcement on [`#solc-tooling`](https://matrix.to/#/#solc-tooling:matrix.org) + - [ ] Lean back, wait for bug reports and repeat from step 1 :). diff --git a/compiler/ReviewChecklist.md b/compiler/ReviewChecklist.md new file mode 100644 index 00000000..3f0af6f9 --- /dev/null +++ b/compiler/ReviewChecklist.md @@ -0,0 +1,200 @@ +# PR Review Checklist +The Solidity compiler is a critical piece of infrastructure in the Ethereum ecosystem. +For this reason, our review process is quite strict and all PRs have to fulfill certain quality +expectations and guidelines. +The list below is meant to reduce the workload on the core team by helping contributors self-identify +and solve common issues before they are pointed out in the review. +It is also meant to serve as a final checklist for reviewers to go through before approving a PR. + +## Before You Submit a PR +- [ ] **Do you have any other open PRs?** + Work on a PR is not done until it is merged or closed. + Our reviewing capacity is limited, so we require that external contributors work on **no more than one PR at a time**. + - If your PR is not getting reviewed, feel free to bring it to our attention on the [#solidity-dev](https://gitter.im/ethereum/solidity-dev) channel. + - Unless they were requested, we are going to close any excess PRs, leaving only the earliest one open. + You may reopen them, one at a time, when your current PR is done. +- [ ] **Is the issue ready to be worked on?** + - If the issue does not have a desirability label (`nice to have`, `should have`, + `must have eventually`, `must have`, `roadmap`) we have not yet decided whether to implement it. + - If the issue has the `needs design` label, we have not yet decided how it should be implemented. + - `good first issue candidate` means that the issue will potentially be a `good first issue` + eventually but at the moment it is not yet ready to be worked on. +- [ ] **Is this a breaking change?** Breaking changes should be based on the `breaking` branch rather than on the `develop` branch. +- [ ] **Does the PR actually address the issue?** + - [ ] Mention the issue number in the PR description. + If the PR solves it completely, use the `Fixes #` form so that Github will close the issue automatically. + - [ ] Do not include the issue number in the PR title, branch name or commit description. +- [ ] When submitting a PR from a fork **create a branch and give it a descriptive name.** + E.g. `fix-array-abi-encoding-bug`. + Do not submit PRs directly from the `develop` branch of your fork since it makes rebasing and fetching new changes harder. +- [ ] **Does the PR depend on other PRs?** + - [ ] If the PR has dependencies, mention them in bold in the description. + - [ ] Avoid basing PRs from forks on branches other than `develop` or `breaking` because + GitHub closes them when the base branch gets merged. + Do this only for PRs created directly in the main repo. +- [ ] **Does the PR update test expectations to match the modified code?** If not, your PR will not pass some of the `_soltest_`, jobs in CI. + In many cases the expectations can be updated automatically: + - `cmdlineTests.sh --update` for command-line tests. + - `isoltest --enforce-gas-cost --accept-updates` for soltest-based tests. + - If your PR affects gas costs, an extra run of `isoltest --enforce-gas-cost --optimize --accept-updates` is needed to update gas expectations with optimizer enabled. + - Review updated files before committing them. + **Are expectations correct and do updated tests still serve their purpose?** + +## Abandoned PRs +- [ ] **Is the submitter still responsive?** + - If the PR had no activity from the submitter in the last 2 weeks (despite receiving reviews and our prompts) we consider it abandoned. +- [ ] **Is the abandoned PR easy to finish or relevant?** + - Apply the `takeover` label if the PR can be finished without significant effort or is something that actually needs to be done right now. + Otherwise close it. + It can still be taken over later or reopened by the submitter but until then we should not be getting sidetracked by it. + +## Light Review +Before an in-depth review, it is recommended to give new PRs a quick, superficial review, which +is not meant to provide complete and detailed feedback, but instead give the submitter a rough idea +if the PR is even on the right track and let them solve the obvious problems on their own. + +Light review should focus on the following three areas: +- [ ] **Are there any obvious mistakes?** Style issues, bad practices, easy to identify bugs, etc. +- [ ] **Is there anything missing?** Tests (of the right kind), documentation, etc. Does it address the whole issue? +- [ ] **Is it the right solution?** Are there better ways to do this? Is the change even necessary? + +If the answers above are "Yes, Yes, No", thank the contributor for their effort and **close the PR**. + +## Coding Style and Good Practices +- [ ] Does the PR follow our [coding style](CODING_STYLE.md)? + +### Reliability +- [ ] **Use assertions liberally.** If you are certain your assumption will not be broken, prove it with `solAssert()`. +- [ ] **Validate inputs and handle errors**. Note that assertions are **not** validation. + +### Readability +- [ ] **Choose good names.** + - [ ] Is the name straightforward to understand? + Do you feel the need to jump back to the definition and remind yourself what it was whenever you see it? + - [ ] Is the name unambiguous in the context where it is used? + - [ ] Avoid abbreviations. +- [ ] **Source files, classes and public functions should have docstrings.** +- [ ] **Avoid code duplication.** But not fanatically. Minimal amounts of duplication are acceptable if it aids readability. +- [ ] **Do not leave dead or commented-out code behind.** You can still see old code in history. + If you really have a good reason to do it, always leave a comment explaining what it is and why it is there. +- [ ] **Mark hacks as such.** If you have to leave behind a temporary workaround, make + sure to include a comment that explains why and in what circumstances it can be removed. + Preferably link to an issue you reported upstream. +- [ ] **Avoid obvious comments.** +- [ ] **Do include comments when the reader may need extra context to understand the code.** + +### Commits and PRs +- [ ] **Avoid hiding functional changes inside refactors.** + E.g. when fixing a small bug, or changing user-visible behavior, put the change in a separate commit. + Do not mix it with another change that renames things or reformats the code around, making the fix itself hard to identify. +- [ ] **Whenever possible, split off refactors or unrelated changes into separate PRs.** + Smaller PRs are easier and quicker to review. + Splitting off refactors helps focus on the main point of the PR. + +### Common Pitfalls +The following points are all covered by the coding style but come up so often that it is worth singling them out here: +- [ ] **Always initialize value types in the definition,** even if you are sure you will assign them later. +- [ ] **Use "east const" style.** I.e. `T const*`, not `const T *`. +- [ ] **Keep indentation consistent.** See our [`.editorconfig`](.editorconfig). + - [ ] Tabs for C++. But use them for indentation only. Any whitespace later on the line must consist of spaces. + - [ ] 4 spaces for most other file types. +- [ ] **Use `auto` sparingly.** Only use it when the actual type is very long and complicated or when it is + already used elsewhere in the same expression. +- [ ] **Indent braces and parentheses in a way that makes nesting clear.** +- [ ] **Use `using namespace` only in `.cpp` files.** Use it for `std` and our own modules. + Avoid unnecessary `std::` prefix in `.cpp` files (except for `std::move` and `std::forward`). +- [ ] **Use range-based loops and destructuring.** +- [ ] **Include any headers you use directly,** even if they are implicitly included through other headers. + +## Documentation +- [ ] **Does the PR update relevant documentation?** + +### Documentation Style and Good Practices +- [ ] **Use double backticks in RST (``` ``x`` ```). Prefer single backticks in Markdown (`` `x` ``),** + but note that double backticks are valid too and we use them in some cases for legacy reasons. +- [ ] **Always start a new sentence on a new line.** + This way you do not have to rewrap the surrounding text when you rewrite the sentence. + This also makes changes actually easier to spot in the diff. + +## Testing + +### What to Test +- [ ] **Is newly added code adequately covered by tests?** Have you considered all the important corner cases? +- If it is a bugfix: + - [ ] **The PR must include tests that reproduce the bug.** + - [ ] **Are there gaps in test coverage of the buggy feature?** Fill them by adding more tests. + - [ ] **Try to break it.** Can you of any similar features that could also be buggy? + Play with the repro and include prominent variants as separate test cases, even if they don't trigger a bug. +- [ ] **Positive cases (code that compiles) should have a semantic test.** +- [ ] **Negative cases (code with compilation errors) should have a syntax test.** +- [ ] **Avoid mixing positive and negative cases in the same syntax test.** + If the test produces an error, we stop at the analysis stage and we will not detect + problems that only occur in code generation, optimizer or assembler. + - [ ] If you have to do it, at least mark positive cases inside the file with a short comment. + - This way, when the test is updated, it is easier to verify that these cases still do not trigger an error. +- [ ] New syntax: **does it have an [`ASTJSON`](test/libsolidity/ASTJSON/) test?** +- [ ] New CLI or StandardJSON option: + - [ ] **Does it have a [command-line test](test/cmdlineTests/)?** + - [ ] **Is the option listed for every input mode in [`CommandLineParser` tests](test/solc/CommandLineParser.cpp)?** +- [ ] **Did you consider interactions with other language features?** + - [ ] Are all types covered? Structs? Enums? Contracts/libraries/interfaces? User-defined value types? + Value types: integers, fixed bytes, `address`, `address payable`, `bool`? Function pointers? + Static and dynamic arrays? `string` and `bytes`? Mappings? + Values of types that cannot be named: literals, tuples, array slices, storage references? + - [ ] If it accepts a function, does it also accept an event or an error? These have function types but are not functions. + - [ ] If it affects free functions, what about internal library functions? + - [ ] Attached library functions? Functions attached with `using for`? + - [ ] Possible combinations of `storage`, `memory`, `calldata`, `immutable`, `constant`? + Remember that internal functions can take `storage` arguments. + - [ ] Does it work at construction time as well? What if you store it at construction time and read after deployment? + - [ ] What about importing it from a different module or inheriting it? + - [ ] Have you tested it with the ternary operator? + +### Test Style and Good Practices +- [ ] **Make test case file names long and specific enough** so that it is easy to guess what is inside. + When checking if we have the case already covered the name is usually the only clue we see. + - [ ] Place them in the right subdirectory. + - [ ] **Avoid simply appending numbers to the name to distinguish similar cases.** + Coming up with good names is hard but figuring out if any of hundreds of tests with names that + match your search actually fits your case is even harder. +- [ ] **Do not include version pragma and the SPDX comment in semantic and syntax test cases**. + In other test types include them if necessary to suppress warnings. +- [ ] **If you have to use a version pragma, avoid hard-coding version.** Use `pragma solidity *`. +- [ ] **When writing StandardJSON command-line tests, use `urls` instead of `content`** and put + the Solidity or Yul code in a separate file. + +## Compiler-specific +- [ ] **Are error messages sensible and understandable to users?** +- [ ] **Are error codes consistent?** + - [ ] Avoid randomly changing or reassigning error codes. + - [ ] Avoid defining separate codes for trivial variants of the same issue. + Make it easy for tools to consistently refer to the same error with the same code. +- [ ] **Error messages should end with a full stop.** +- [ ] **Prefer Ranges v3 to Boost where possible.** + +## Take a Step Back +- [ ] **Do you fully understand what the PR does and why?** +- [ ] **Are you confident that the code works and does not break unrelated functionality?** +- [ ] **Is this a reasonable way to achieve the goal stated in the issue?** +- [ ] **Is the code simple?** Does the PR achieve its objective at the cost of significant + complexity that may be a source of future bugs? +- [ ] **Is the code efficient?** Does the PR introduce any major performance bottlenecks? +- [ ] **Does the PR introduce any breaking changes beyond what was agreed in the issue?** + +## Final Checks Before Merging +- [ ] **Is the PR rebased on top of the `develop` branch** (or `breaking` if it is a breaking change)? +- [ ] **Did all CI checks pass?** + - Note that we have a few jobs that tend to randomly fail due to outside factors, especially external tests (with `_ext_` in the name). + If these fail, rebase on latest `develop` (or `breaking`) and try rerunning them. + Note also that not all of these checks are required for the PR to be merged. +- [ ] If the change is visible to users, **does the PR have a [changelog](Changelog.md) entry?** + - [ ] Is the changelog entry in the right section? + Make sure to move it up if there was a release recently. +- [ ] **Is commit history simple and understandable?** + - [ ] Each commit should be a self-contained, logical step leading the goal of the PR, without going back and forth. + In particular, review fixups should be squashed into the commits they fix. + - [ ] Do not include any merge commits in your branch. Please use rebase to keep up to date with the base branch. +- [ ] **Is the PR properly labeled?** + - Use `external contribution` label to mark PRs not coming from the core team. + - If the PR depends on other PRs, use `has dependencies` and set the base branch accordingly. + - Labels like `documentation` or `optimizer` are helpful for filtering PRs. diff --git a/compiler/cmake/EthCompilerSettings.cmake b/compiler/cmake/EthCompilerSettings.cmake index 9803818d..d43e18f7 100644 --- a/compiler/cmake/EthCompilerSettings.cmake +++ b/compiler/cmake/EthCompilerSettings.cmake @@ -28,7 +28,7 @@ if(PEDANTIC) endif() # Prevent the path of the source directory from ending up in the binary via __FILE__ macros. -eth_add_cxx_compiler_flag_if_supported("-fmacro-prefix-map=${CMAKE_SOURCE_DIR}=/solidity") +eth_add_cxx_compiler_flag_if_supported("-fmacro-prefix-map=${PROJECT_SOURCE_DIR}=/solidity") # -Wpessimizing-move warns when a call to std::move would prevent copy elision # if the argument was not wrapped in a call. This happens when moving a local @@ -58,13 +58,20 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA add_compile_options(-Wmissing-declarations) add_compile_options(-Wno-unknown-pragmas) add_compile_options(-Wimplicit-fallthrough) -# add_compile_options(-Wsign-conversion) # TODO return this after -# add_compile_options(-Wconversion) # TODO return this after +# add_compile_options(-Wsign-conversion) # TODO DELETE return this after +# add_compile_options(-Wconversion) # TODO DELETE return this after check_cxx_compiler_flag(-Wextra-semi WEXTRA_SEMI) if(WEXTRA_SEMI) add_compile_options($<$:-Wextra-semi>) endif() + # See https://gcc.gnu.org/git/gitweb.cgi?p=gcc.git;h=6b927b1297e66e26e62e722bf15c921dcbbd25b9 + check_cxx_compiler_flag(-Wno-dangling-reference WNO_DANGLING_REFERENCE) + if (WNO_DANGLING_REFERENCE) + add_compile_options($<$:-Wno-dangling-reference>) + endif() + + eth_add_cxx_compiler_flag_if_supported(-Wfinal-dtor-non-final-class) eth_add_cxx_compiler_flag_if_supported(-Wnewline-eof) eth_add_cxx_compiler_flag_if_supported(-Wsuggest-destructor-override) diff --git a/compiler/cmake/fmtlib.cmake b/compiler/cmake/fmtlib.cmake index 5ed196ce..032a68cd 100644 --- a/compiler/cmake/fmtlib.cmake +++ b/compiler/cmake/fmtlib.cmake @@ -2,11 +2,11 @@ include(FetchContent) FetchContent_Declare( fmtlib - PREFIX "${CMAKE_BINARY_DIR}/deps" - DOWNLOAD_DIR "${CMAKE_SOURCE_DIR}/deps/downloads" - DOWNLOAD_NAME fmt-8.0.1.tar.gz - URL https://github.com/fmtlib/fmt/archive/8.0.1.tar.gz - URL_HASH SHA256=b06ca3130158c625848f3fb7418f235155a4d389b2abc3a6245fb01cb0eb1e01 + PREFIX "${PROJECT_BINARY_DIR}/deps" + DOWNLOAD_DIR "${PROJECT_SOURCE_DIR}/deps/downloads" + DOWNLOAD_NAME fmt-9.1.0.tar.gz + URL https://github.com/fmtlib/fmt/archive/9.1.0.tar.gz + URL_HASH SHA256=5dea48d1fcddc3ec571ce2058e13910a0d4a6bab4cc09a809d8b1dd1c88ae6f2 ) if (CMAKE_VERSION VERSION_LESS "3.14.0") diff --git a/compiler/cmake/jsoncpp.cmake b/compiler/cmake/jsoncpp.cmake index 29b8f5f0..c47c2697 100644 --- a/compiler/cmake/jsoncpp.cmake +++ b/compiler/cmake/jsoncpp.cmake @@ -6,7 +6,7 @@ else() set(JSONCPP_CMAKE_COMMAND ${CMAKE_COMMAND}) endif() -set(prefix "${CMAKE_BINARY_DIR}/deps") +set(prefix "${PROJECT_BINARY_DIR}/deps") set(JSONCPP_LIBRARY "${prefix}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}jsoncpp${CMAKE_STATIC_LIBRARY_SUFFIX}") set(JSONCPP_INCLUDE_DIR "${prefix}/include") @@ -40,9 +40,10 @@ if (WIN32 AND POLICY CMP0091 AND CMAKE_MSVC_RUNTIME_LIBRARY) list(APPEND JSONCPP_CMAKE_ARGS "-DCMAKE_MSVC_RUNTIME_LIBRARY=${CMAKE_MSVC_RUNTIME_LIBRARY}") endif() +string(REPLACE ";" "$" CMAKE_OSX_ARCHITECTURES_ "${CMAKE_OSX_ARCHITECTURES}") ExternalProject_Add(jsoncpp-project PREFIX "${prefix}" - DOWNLOAD_DIR "${CMAKE_SOURCE_DIR}/deps/downloads" + DOWNLOAD_DIR "${PROJECT_SOURCE_DIR}/deps/downloads" DOWNLOAD_NAME jsoncpp-1.9.3.tar.gz URL https://github.com/open-source-parsers/jsoncpp/archive/1.9.3.tar.gz URL_HASH SHA256=8593c1d69e703563d94d8c12244e2e18893eeb9a8a9f8aa3d09a327aa45c8f7d @@ -57,6 +58,7 @@ ExternalProject_Add(jsoncpp-project -DJSONCPP_WITH_PKGCONFIG_SUPPORT=OFF -DCMAKE_CXX_FLAGS=${JSONCPP_CXX_FLAGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES_} ${JSONCPP_CMAKE_ARGS} ${byproducts} ) diff --git a/compiler/cmake/range-v3.cmake b/compiler/cmake/range-v3.cmake index 75ed5a92..d2f54452 100644 --- a/compiler/cmake/range-v3.cmake +++ b/compiler/cmake/range-v3.cmake @@ -6,12 +6,12 @@ else() set(RANGE_V3_CMAKE_COMMAND ${CMAKE_COMMAND}) endif() -set(prefix "${CMAKE_BINARY_DIR}/deps") +set(prefix "${PROJECT_BINARY_DIR}/deps") set(RANGE_V3_INCLUDE_DIR "${prefix}/include") ExternalProject_Add(range-v3-project PREFIX "${prefix}" - DOWNLOAD_DIR "${CMAKE_SOURCE_DIR}/deps/downloads" + DOWNLOAD_DIR "${PROJECT_SOURCE_DIR}/deps/downloads" DOWNLOAD_NAME range-v3-0.12.0.tar.gz URL https://github.com/ericniebler/range-v3/archive/0.12.0.tar.gz URL_HASH SHA256=015adb2300a98edfceaf0725beec3337f542af4915cec4d0b89fa0886f4ba9cb diff --git a/compiler/cmake/templates/ewasm_polyfill.in b/compiler/cmake/templates/ewasm_polyfill.in deleted file mode 100644 index 8ac36428..00000000 --- a/compiler/cmake/templates/ewasm_polyfill.in +++ /dev/null @@ -1,13 +0,0 @@ -// The generation of this file is defined in libyul/CMakeLists.txt. -// This file was generated by using the content of libyul/backends/wasm/polyfill/@EWASM_POLYFILL_NAME@.yul. - -#pragma once - -namespace solidity::yul::wasm::polyfill -{ - -static char const @EWASM_POLYFILL_NAME@[] = { - @EWASM_POLYFILL_CONTENT@, 0 -}; - -} // namespace solidity::yul::wasm::polyfill diff --git a/compiler/docs/050-breaking-changes.rst b/compiler/docs/050-breaking-changes.rst index 3f41ac86..a40bbcf6 100644 --- a/compiler/docs/050-breaking-changes.rst +++ b/compiler/docs/050-breaking-changes.rst @@ -137,7 +137,7 @@ For most of the topics the compiler will provide suggestions. ``payable`` or create a new internal function for the program logic that uses ``msg.value``. -* For clarity reasons, the command line interface now requires ``-`` if the +* For clarity reasons, the command-line interface now requires ``-`` if the standard input is used as source. Deprecated Elements @@ -147,18 +147,18 @@ This section lists changes that deprecate prior features or syntax. Note that many of these changes were already enabled in the experimental mode ``v0.5.0``. -Command Line and JSON Interfaces +Command-line and JSON Interfaces -------------------------------- -* The command line option ``--formal`` (used to generate Why3 output for +* The command-line option ``--formal`` (used to generate Why3 output for further formal verification) was deprecated and is now removed. A new formal verification module, the SMTChecker, is enabled via ``pragma experimental SMTChecker;``. -* The command line option ``--julia`` was renamed to ``--yul`` due to the +* The command-line option ``--julia`` was renamed to ``--yul`` due to the renaming of the intermediate language ``Julia`` to ``Yul``. -* The ``--clone-bin`` and ``--combined-json clone-bin`` command line options +* The ``--clone-bin`` and ``--combined-json clone-bin`` command-line options were removed. * Remappings with empty prefix are disallowed. diff --git a/compiler/docs/060-breaking-changes.rst b/compiler/docs/060-breaking-changes.rst index 915a9f33..e7c93057 100644 --- a/compiler/docs/060-breaking-changes.rst +++ b/compiler/docs/060-breaking-changes.rst @@ -12,7 +12,7 @@ For the full list check Changes the Compiler Might not Warn About ========================================= -This section lists changes where the behaviour of your code might +This section lists changes where the behavior of your code might change without the compiler telling you about it. * The resulting type of an exponentiation is the type of the base. It used to be the smallest type @@ -53,6 +53,8 @@ For most of the topics the compiler will provide suggestions. If the name contains a dot, its prefix up to the dot may not conflict with any declaration outside the inline assembly block. +* In inline assembly, opcodes that do not take arguments are now represented as "built-in functions" instead of standalone identifiers. So ``gas`` is now ``gas()``. + * State variable shadowing is now disallowed. A derived contract can only declare a state variable ``x``, if there is no visible state variable with the same name in any of its bases. @@ -103,23 +105,23 @@ Interface Changes ================= This section lists changes that are unrelated to the language itself, but that have an effect on the interfaces of -the compiler. These may change the way how you use the compiler on the command line, how you use its programmable +the compiler. These may change the way how you use the compiler on the command-line, how you use its programmable interface, or how you analyze the output produced by it. New Error Reporter ~~~~~~~~~~~~~~~~~~ -A new error reporter was introduced, which aims at producing more accessible error messages on the command line. -It is enabled by default, but passing ``--old-reporter`` falls back to the the deprecated old error reporter. +A new error reporter was introduced, which aims at producing more accessible error messages on the command-line. +It is enabled by default, but passing ``--old-reporter`` falls back to the deprecated old error reporter. Metadata Hash Options ~~~~~~~~~~~~~~~~~~~~~ The compiler now appends the `IPFS `_ hash of the metadata file to the end of the bytecode by default (for details, see documentation on :doc:`contract metadata `). Before 0.6.0, the compiler appended the -`Swarm `_ hash by default, and in order to still support this behaviour, -the new command line option ``--metadata-hash`` was introduced. It allows you to select the hash to be produced and -appended, by passing either ``ipfs`` or ``swarm`` as value to the ``--metadata-hash`` command line option. +`Swarm `_ hash by default, and in order to still support this behavior, +the new command-line option ``--metadata-hash`` was introduced. It allows you to select the hash to be produced and +appended, by passing either ``ipfs`` or ``swarm`` as value to the ``--metadata-hash`` command-line option. Passing the value ``none`` completely removes the hash. These changes can also be used via the :ref:`Standard JSON Interface` and effect the metadata JSON generated by the compiler. @@ -174,3 +176,6 @@ This section gives detailed instructions on how to update prior code for every b ``override`` to every overriding function. For multiple inheritance, add ``override(A, B, ..)``, where you list all contracts that define the overridden function in the parentheses. When multiple bases define the same function, the inheriting contract must override all conflicting functions. + +* In inline assembly, add ``()`` to all opcodes that do not otherwise accept an argument. + For example, change ``pc`` to ``pc()``, and ``gas`` to ``gas()``. diff --git a/compiler/docs/080-breaking-changes.rst b/compiler/docs/080-breaking-changes.rst index b322d2a4..524d12ac 100644 --- a/compiler/docs/080-breaking-changes.rst +++ b/compiler/docs/080-breaking-changes.rst @@ -10,18 +10,18 @@ For the full list check Silent Changes of the Semantics =============================== -This section lists changes where existing code changes its behaviour without +This section lists changes where existing code changes its behavior without the compiler notifying you about it. * Arithmetic operations revert on underflow and overflow. You can use ``unchecked { ... }`` to use - the previous wrapping behaviour. + the previous wrapping behavior. Checks for overflow are very common, so we made them the default to increase readability of code, even if it comes at a slight increase of gas costs. * ABI coder v2 is activated by default. - You can choose to use the old behaviour using ``pragma abicoder v1;``. + You can choose to use the old behavior using ``pragma abicoder v1;``. The pragma ``pragma experimental ABIEncoderV2;`` is still valid, but it is deprecated and has no effect. If you want to be explicit, please use ``pragma abicoder v2;`` instead. @@ -57,7 +57,7 @@ New Restrictions This section lists changes that might cause existing contracts to not compile anymore. -* There are new restrictions related to explicit conversions of literals. The previous behaviour in +* There are new restrictions related to explicit conversions of literals. The previous behavior in the following cases was likely ambiguous: 1. Explicit conversions from negative literals and literals larger than ``type(uint160).max`` to @@ -106,7 +106,7 @@ This section lists changes that might cause existing contracts to not compile an * The global functions ``log0``, ``log1``, ``log2``, ``log3`` and ``log4`` have been removed. - These are low-level functions that were largely unused. Their behaviour can be accessed from inline assembly. + These are low-level functions that were largely unused. Their behavior can be accessed from inline assembly. * ``enum`` definitions cannot contain more than 256 members. @@ -173,4 +173,4 @@ How to update your code - Change ``msg.sender.transfer(x)`` to ``payable(msg.sender).transfer(x)`` or use a stored variable of ``address payable`` type. - Change ``x**y**z`` to ``(x**y)**z``. - Use inline assembly as a replacement for ``log0``, ..., ``log4``. -- Negate unsigned integers by subtracting them from the maximum value of the type and adding 1 (e.g. ``type(uint256).max - x + 1``, while ensuring that `x` is not zero) +- Negate unsigned integers by subtracting them from the maximum value of the type and adding 1 (e.g. ``type(uint256).max - x + 1``, while ensuring that ``x`` is not zero) diff --git a/compiler/docs/Makefile b/compiler/docs/Makefile index 3cc98f69..01660bd3 100644 --- a/compiler/docs/Makefile +++ b/compiler/docs/Makefile @@ -34,7 +34,6 @@ help: @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @@ -116,12 +115,6 @@ latexpdf: $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo diff --git a/compiler/docs/README.md b/compiler/docs/README.md new file mode 100644 index 00000000..a3b5f25a --- /dev/null +++ b/compiler/docs/README.md @@ -0,0 +1,23 @@ +# Solidity Language Docs + +## Local environment setup + +1. Install python https://www.python.org/downloads/ +1. Install sphinx (the tool used to generate the docs) https://www.sphinx-doc.org/en/master/usage/installation.html + +Go to `/docs` and run `./docs.sh` to install dependencies and build the project: + +```sh +cd docs +./docs.sh +``` + +That will output the generated htmls under _build/ + +## Serve environment + +```py +python3 -m http.server -d _build/html --cgi 8080 +``` + +Visit dev server at http://localhost:8080 diff --git a/compiler/docs/_static/css/custom-dark.css b/compiler/docs/_static/css/custom-dark.css new file mode 100644 index 00000000..044a8f80 --- /dev/null +++ b/compiler/docs/_static/css/custom-dark.css @@ -0,0 +1,595 @@ + + +/* DARK MODE STYLING */ + +/* code directives */ + +:root[style*=dark] .method dt, +:root[style*=dark] .class dt, +:root[style*=dark] .data dt, +:root[style*=dark] .attribute dt, +:root[style*=dark] .function dt, +:root[style*=dark] .classmethod dt, +:root[style*=dark] .exception dt, +:root[style*=dark] .descclassname, +:root[style*=dark] .descname { + background-color: #2d2d2d !important; +} + +:root[style*=dark] .rst-content dl:not(.docutils) dt { + background-color: #0008; + border-top: solid 3px #fff2; + border-left: solid 3px #fff2; +} + +:root[style*=dark] em.property { + color: #888888; +} + + +/* tables */ + +:root[style*=dark] .rst-content table.docutils td { + border: 0px; +} + +:root[style*=dark] .rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { + background-color: #0002; +} + +:root[style*=dark] .rst-content pre { + background: none; +} + +/* inlined code highlights */ + +:root[style*=dark] .xref, +:root[style*=dark] .py-meth { + color: #aaddff !important; + font-weight: normal !important; +} + +/* highlight color search text */ + +:root[style*=dark] .rst-content .highlighted { + background: #ff5722; + box-shadow: 0 0 0 2px #f0978b; +} + +/* notes, warnings, hints */ + +:root[style*=dark] .hint .admonition-title { + background: #2aa87c !important; +} + +:root[style*=dark] .warning .admonition-title { + background: #cc4444 !important; +} + +:root[style*=dark] .admonition-title { + background: #3a7ca8 !important; +} + +:root[style*=dark] .admonition, +:root[style*=dark] .note { + background-color: #0008 !important; +} + + +/* table of contents */ + +:root[style*=dark] .sidebar { + background-color: #191919 !important; +} + +:root[style*=dark] .sidebar-title { + background-color: #2b2b2b !important; +} + +:root[style*=dark] .wy-menu-vertical code.docutils.literal.notranslate { + background: none !important; + border: none !important; +} + + +:root[style*=dark] .toc-backref { + color: grey !important; +} + +:root[style*=dark] .highlight { + background: #0008; + color: #f8f8f2 +} + +:root[style*=dark] .highlight .c { + color: #888 +} + + +/* Comment */ + +:root[style*=dark] .highlight .err { + color: #960050; + background-color: #1e0010 +} + + +/* Error */ + +:root[style*=dark] .highlight .k { + color: #66d9ef +} + + +/* Keyword */ + +:root[style*=dark] .highlight .l { + color: #ae81ff +} + + +/* Literal */ + +:root[style*=dark] .highlight .n { + color: #f8f8f2 +} + + +/* Name */ + +:root[style*=dark] .highlight .o { + color: #f92672 +} + + +/* Operator */ + +:root[style*=dark] .highlight .p { + color: #f8f8f2 +} + + +/* Punctuation */ + +:root[style*=dark] .highlight .ch { + color: #888 +} + + +/* Comment.Hashbang */ + +:root[style*=dark] .highlight .cm { + color: #888 +} + + +/* Comment.Multiline */ + +:root[style*=dark] .highlight .cp { + color: #888 +} + + +/* Comment.Preproc */ + +:root[style*=dark] .highlight .cpf { + color: #888 +} + + +/* Comment.PreprocFile */ + +:root[style*=dark] .highlight .c1 { + color: #888 +} + + +/* Comment.Single */ + +:root[style*=dark] .highlight .cs { + color: #888 +} + + +/* Comment.Special */ + +:root[style*=dark] .highlight .gd { + color: #f92672 +} + + +/* Generic.Deleted */ + +:root[style*=dark] .highlight .ge { + font-style: italic +} + + +/* Generic.Emph */ + +:root[style*=dark] .highlight .gi { + color: #a6e22e +} + + +/* Generic.Inserted */ + +:root[style*=dark] .highlight .gs { + font-weight: bold +} + + +/* Generic.Strong */ + +:root[style*=dark] .highlight .gu { + color: #888 +} + + +/* Generic.Subheading */ + +:root[style*=dark] .highlight .kc { + color: #66d9ef +} + + +/* Keyword.Constant */ + +:root[style*=dark] .highlight .kd { + color: #66d9ef +} + + +/* Keyword.Declaration */ + +:root[style*=dark] .highlight .kn { + color: #f92672 +} + + +/* Keyword.Namespace */ + +:root[style*=dark] .highlight .kp { + color: #66d9ef +} + + +/* Keyword.Pseudo */ + +:root[style*=dark] .highlight .kr { + color: #66d9ef +} + + +/* Keyword.Reserved */ + +:root[style*=dark] .highlight .kt { + color: #66d9ef +} + + +/* Keyword.Type */ + +:root[style*=dark] .highlight .ld { + color: #e6db74 +} + + +/* Literal.Date */ + +:root[style*=dark] .highlight .m { + color: #ae81ff +} + + +/* Literal.Number */ + +:root[style*=dark] .highlight .s { + color: #e6db74 +} + + +/* Literal.String */ + +:root[style*=dark] .highlight .na { + color: #a6e22e +} + + +/* Name.Attribute */ + +:root[style*=dark] .highlight .nb { + color: #f8f8f2 +} + + +/* Name.Builtin */ + +:root[style*=dark] .highlight .nc { + color: #a6e22e +} + + +/* Name.Class */ + +:root[style*=dark] .highlight .no { + color: #66d9ef +} + + +/* Name.Constant */ + +:root[style*=dark] .highlight .nd { + color: #a6e22e +} + + +/* Name.Decorator */ + +:root[style*=dark] .highlight .ni { + color: #f8f8f2 +} + + +/* Name.Entity */ + +:root[style*=dark] .highlight .ne { + color: #a6e22e +} + + +/* Name.Exception */ + +:root[style*=dark] .highlight .nf { + color: #a6e22e +} + + +/* Name.Function */ + +:root[style*=dark] .highlight .nl { + color: #f8f8f2 +} + + +/* Name.Label */ + +:root[style*=dark] .highlight .nn { + color: #f8f8f2 +} + + +/* Name.Namespace */ + +:root[style*=dark] .highlight .nx { + color: #a6e22e +} + + +/* Name.Other */ + +:root[style*=dark] .highlight .py { + color: #f8f8f2 +} + + +/* Name.Property */ + +:root[style*=dark] .highlight .nt { + color: #f92672 +} + + +/* Name.Tag */ + +:root[style*=dark] .highlight .nv { + color: #f8f8f2 +} + + +/* Name.Variable */ + +:root[style*=dark] .highlight .ow { + color: #f92672 +} + + +/* Operator.Word */ + +:root[style*=dark] .highlight .w { + color: #f8f8f2 +} + + +/* Text.Whitespace */ + +:root[style*=dark] .highlight .mb { + color: #ae81ff +} + + +/* Literal.Number.Bin */ + +:root[style*=dark] .highlight .mf { + color: #ae81ff +} + + +/* Literal.Number.Float */ + +:root[style*=dark] .highlight .mh { + color: #ae81ff +} + + +/* Literal.Number.Hex */ + +:root[style*=dark] .highlight .mi { + color: #ae81ff +} + + +/* Literal.Number.Integer */ + +:root[style*=dark] .highlight .mo { + color: #ae81ff +} + + +/* Literal.Number.Oct */ + +:root[style*=dark] .highlight .sa { + color: #e6db74 +} + + +/* Literal.String.Affix */ + +:root[style*=dark] .highlight .sb { + color: #e6db74 +} + + +/* Literal.String.Backtick */ + +:root[style*=dark] .highlight .sc { + color: #e6db74 +} + + +/* Literal.String.Char */ + +:root[style*=dark] .highlight .dl { + color: #e6db74 +} + + +/* Literal.String.Delimiter */ + +:root[style*=dark] .highlight .sd { + color: #e6db74 +} + + +/* Literal.String.Doc */ + +:root[style*=dark] .highlight .s2 { + color: #e6db74 +} + + +/* Literal.String.Double */ + +:root[style*=dark] .highlight .se { + color: #ae81ff +} + + +/* Literal.String.Escape */ + +:root[style*=dark] .highlight .sh { + color: #e6db74 +} + + +/* Literal.String.Heredoc */ + +:root[style*=dark] .highlight .si { + color: #e6db74 +} + + +/* Literal.String.Interpol */ + +:root[style*=dark] .highlight .sx { + color: #e6db74 +} + + +/* Literal.String.Other */ + +:root[style*=dark] .highlight .sr { + color: #e6db74 +} + + +/* Literal.String.Regex */ + +:root[style*=dark] .highlight .s1 { + color: #e6db74 +} + + +/* Literal.String.Single */ + +:root[style*=dark] .highlight .ss { + color: #e6db74 +} + + +/* Literal.String.Symbol */ + +:root[style*=dark] .highlight .bp { + color: #f8f8f2 +} + + +/* Name.Builtin.Pseudo */ + +:root[style*=dark] .highlight .fm { + color: #a6e22e +} + + +/* Name.Function.Magic */ + +:root[style*=dark] .highlight .vc { + color: #f8f8f2 +} + + +/* Name.Variable.Class */ + +:root[style*=dark] .highlight .vg { + color: #f8f8f2 +} + + +/* Name.Variable.Global */ + +:root[style*=dark] .highlight .vi { + color: #f8f8f2 +} + + +/* Name.Variable.Instance */ + +:root[style*=dark] .highlight .vm { + color: #f8f8f2 +} + + +/* Name.Variable.Magic */ + +:root[style*=dark] .highlight .il { + color: #ae81ff +} + + +/* Grammar */ + +:root[style*=dark] .railroad-diagram { + fill: white; +} + +:root[style*=dark] .railroad-diagram path { + stroke: white; +} + +:root[style*=dark] .railroad-diagram rect { + stroke: white; +} + +:root[style*=dark] .a4 .sig-name { + background-color: transparent !important; +} diff --git a/compiler/docs/_static/css/custom.css b/compiler/docs/_static/css/custom.css index 4ff53f3a..25ab2654 100644 --- a/compiler/docs/_static/css/custom.css +++ b/compiler/docs/_static/css/custom.css @@ -1,23 +1,185 @@ +/* ROOT DECLARATIONS */ +:root { + /* Text */ + --color-a: #2B247C; + --color-b: #672AC8; + --color-c: #5554D9; + --color-d: #9F94E8; + --color-e: #AEC0F1; + --color-f: #E6E3EC; + /* Background */ + + --white: #FAF8FF; + --black: #110C4E; + --menu-bg: #2B247C06; + --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + + --navHeight: 4.5rem; + --sideWidth: 300px; + --maxWidth: 80rem; + --desktopInlinePadding: 2rem; + --mobileInlinePadding: 1rem; + --currentVersionHeight: 45px; + + text-rendering: geometricPrecision; + -webkit-font-smoothing: antialiased; +} + +a, +button { + border-radius: 0; +} + +:root[style*=dark] { + --color-a: #E6E3EC !important; + --color-b: #AEC0F1 !important; + --color-c: #9F94E8 !important; + --color-d: #5554D9 !important; + --color-e: #672AC8 !important; + --color-f: #2B247C !important; + + --white: #110C4E !important; + --black: #FAF8FF !important; + --menu-bg: #E6E3EC06 !important; +} + +html, +body, +.unified-header::before, +.wy-nav-side, +.rst-versions, +code, +div, +input[type=text], +a, +.wy-grid-for-nav { + transition: background 150ms ease-in-out; +} + +html, +body, +.wy-grid-for-nav { + background-color: var(--color-f) !important; +} + +body { + font-family: "Overpass", sans-serif; +} + +a { + color: var(--color-c); +} + +a, section { + scroll-margin-top: calc(var(--navHeight) + 2rem); +} + +hr { + margin-block: 2rem; + border-color: var(--color-d) !important; +} + + +/* HEADER STYLES */ +h1 { + font-family: 'Overpass', sans-serif; + font-weight: 700; + font-size: 44px; + color: var(--color-a) !important; + line-height: 1.1; + text-wrap: balance; + margin-top: 4rem; + margin-bottom: 1.5rem; +} + +section:first-of-type h1:first-of-type { + font-family: 'Overpass mono', monospace; + font-size: 48px; + margin-top: 3rem; + margin-bottom: 5rem; +} + +h2 { + font-family: 'Overpass', sans-serif; + font-weight: 700; + font-size: 38px; + color: var(--color-a) !important; + line-height: 46px; + text-wrap: balance; + margin-top: 4rem; + margin-bottom: 1.5rem; +} + +*:not([role=navigation])>p[role=heading]>span, +h3 { + font-family: 'Overpass', sans-serif; + font-weight: 700; + font-size: 32px; + color: var(--color-a) !important; + line-height: 46px; + text-wrap: balance; + margin-top: 4rem; + margin-bottom: 1.5rem; +} + +h4 { + font-family: 'Overpass', sans-serif; + font-weight: 700; + font-size: 32px; + color: var(--color-a) !important; + line-height: 46px; + text-wrap: balance; + margin-top: 3rem; + margin-bottom: 1.5rem; +} + +h5 { + font-family: 'Overpass', sans-serif; + font-weight: 700; + font-size: 18px; + color: var(--color-a) !important; + line-height: 1.4; + text-wrap: balance; +} + +h6 { + font-family: 'Overpass', sans-serif; + font-weight: 700; + font-size: 16px; + color: var(--color-a) !important; + line-height: 1.4; + text-wrap: balance; +} + +span.pre, pre { - white-space: pre-wrap; /* css-3 */ - white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ - white-space: -pre-wrap; /* Opera 4-6 */ - white-space: -o-pre-wrap; /* Opera 7 */ + /* css-3 */ + white-space: pre-wrap; + /* Mozilla, since 1999 */ + white-space: -moz-pre-wrap; + /* Opera 4-6 */ + white-space: -pre-wrap; + /* Opera 7 */ + white-space: -o-pre-wrap; word-wrap: break-word; + font-family: 'Overpass Mono', monospace; } -.wy-table-responsive table td, .wy-table-responsive table th { +small, +small * { + font-size: 12px; +} + +.wy-table-responsive table td, +.wy-table-responsive table th { white-space: normal; } + .rst-content table.docutils td { vertical-align: top; } /* links */ -.rst-content a:not(:visited) { - color: #002fa7; -} - .rst-content .highlighted { background: #eac545; } @@ -27,60 +189,638 @@ pre { background: #fafafa; } -.wy-side-nav-search img.logo { - width: 100px !important; - padding: 0; +/* project version (displayed under project logo) */ +.wy-side-nav-search>div.version { + color: var(--color-b); + margin-top: 0; + margin-bottom: 0.5rem; + text-align: start; } -.wy-side-nav-search > a { - padding: 0; +/* Link to Remix IDE shown next to code snippets */ +.rst-content p.remix-link-container { + display: block; + text-align: right; margin: 0; + line-height: 1em; } -/* project version (displayed under project logo) */ -.wy-side-nav-search .version { - color: #272525 !important; +.rst-content .remix-link-container a.remix-link { + font-size: 0.7em; + padding: 0.1em 0.5em; + background: transparent; + color: var(--color-a) !important; + border: 1px solid var(--color-a); + text-decoration: none; } -/* menu section headers */ -.wy-menu .caption { - color: #65afff !important; +.rst-content div.highlight-solidity, +.rst-content div.highlight-yul { + margin-top: 0; } -/* Link to Remix IDE shown next to code snippets */ -p.remix-link-container { +/* CUSTOMIZATION UPDATES */ + +.wy-nav-content-wrap, +.wy-nav-content { + background: transparent !important; +} + +.wy-side-nav-search { + background-color: transparent !important; + color: var(--color-a) !important; + box-shadow: 0 4 4 0 var(--color-a); + border-bottom: 1px solid var(--color-d) !important; +} + +.wy-side-nav-search svg { + color: var(--color-a) !important; +} + +.wy-nav-top { + background-color: transparent !important; + color: var(--color-a) !important; +} + +.wy-nav-top a { + color: var(--color-a) !important; +} + +.wy-breadcrumbs a.icon-home:before { + content: "Documentation"; + font-family: "Overpass", sans-serif; +} + +.rst-content table.docutils thead { + color: var(--color-a); +} + +code.docutils.literal.notranslate { + padding: 2px 4px; + font-size: 0.875em; + font-family: "Overpass Mono", monospace; + background: var(--white); + color: var(--color-c); + border: 0px; +} + +dt code.docutils.literal.notranslate { + background: none; +} + +.wy-nav-content { + color: var(--color-a); +} + +/* .rst-content a:not(:visited) { */ +/* color: var(--color-b) !important; */ +/* } */ + +.rst-content a:visited { + color: var(--color-c) !important; +} + +.rst-content a { + text-decoration: underline; +} + +.rst-content a:where(:focus, :focus-visible, :hover) { + color: var(--color-d) !important; +} + +.wy-side-scroll a { + color: var(--color-a); + background: transparent; + font-size: 1rem; + line-height: 125%; +} + +.wy-menu-vertical li.current a, +.wy-menu-vertical li.current li a, +.wy-menu-vertical li.current li a code { + border: none; + color: var(--color-a); +} + +ul.current ul, +.wy-menu-vertical li.current a:hover, +.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a, +.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a, +.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a, +.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a, +.wy-menu-vertical li.current { + background: var(--menu-bg) !important; +} + +.wy-menu.wy-menu-vertical>ul { + margin-bottom: 3rem; +} + +.wy-menu.wy-menu-vertical>p { + color: var(--color-c); +} + +.wy-menu-vertical li.on a, +.wy-menu-vertical li.current>a { + background: var(--menu-bg) !important; + border-bottom: 0px !important; + border-top: 0px !important; +} + +.btn { + border-radius: 0; + text-decoration: none !important; +} + +.wy-breadcrumbs-aside a, +.wy-breadcrumbs-aside a:visited, +a.fa.fa-github, +a.fa.fa-github:visited, +a.fa.fa-github:not(:visited), +a.btn.btn-neutral:visited, +a.btn.btn-neutral:not(:visited), +a.btn.btn-neutral { + background: transparent !important; + color: var(--color-a) !important; + border: 2px solid var(--color-a) !important; + text-decoration: none; +} + +.rst-content .remix-link-container a.remix-link:hover, +.wy-breadcrumbs-aside a:hover, +a.fa.fa-github:hover, +a.btn.btn-neutral:hover { + background: var(--white) !important; + color: var(--color-b) !important; + border-color: var(--color-b) !important; +} + +footer .rst-footer-buttons { + display: flex; + justify-content: center; + gap: 2rem; +} + +/** + * Customization for the unified layout + */ + +/* Site wrapper, and two children: header and rest */ +.unified-wrapper { position: relative; - right: -100%; /* Positioned next to the the top-right corner of the code block following it. */ + display: flex; + flex-direction: column; + inset: 0; + max-width: var(--maxWidth); + margin-inline: auto; +} + +/* Site header */ +.unified-header { + position: fixed; + top: 0; + inset-inline: 0; + z-index: 99999; + display: flex; + align-items: center; + box-shadow: var(--shadow); } -a.remix-link { - position: absolute; /* Remove it from normal flow not to affect the original position of the snippet. */ - top: 0.5em; - width: 3.236em; /* Size of the margin (= right-side padding in .wy-nav-content in the current theme). */ +.unified-header .inner-header { + display: flex; + margin-inline: auto; + width: 100%; + max-width: var(--maxWidth); + align-items: center; + justify-content: space-between; + padding-inline: var(--desktopInlinePadding); + padding-block: 1rem; } -a.remix-link .link-icon { - background: url("../img/solid-share-arrow.svg") no-repeat; +.unified-header::before { + content: ""; + position: absolute; + inset: 0; + opacity: 95%; + background: var(--color-f); + z-index: -1; + backdrop-filter: blur(3px); +} + +.unified-header .home-link { display: block; - width: 1.5em; - height: 1.5em; - margin: auto; + text-decoration: none; + width: 25px; + height: 40px; } -a.remix-link .link-text { - display: none; /* Visible only on hover. */ - width: 3.3em; /* Narrow enough to get two lines of text. */ - margin: auto; - text-align: center; - font-size: 0.8em; - line-height: normal; - color: black; +.unified-header .home-link:hover .solidity-logo { + transform: scale(1.1); + transition: transform 100ms ease-in-out; } -a.remix-link:hover { +.unified-header img.solidity-logo { + transform: scale(1); + transition: transform 100ms ease-in-out; + width: 100%; + height: 100%; +} + +.unified-header .nav-bar { + display: flex; + align-items: center; + justify-content: flex-end; +} + +.unified-header .nav-bar .nav-button-container { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.unified-header .nav-link { + display: inline-block; + padding-inline: 8px; + padding-block: 4px; + font-size: 14px; + font-family: 'Overpass Mono', monospace; + text-decoration: none; + color: var(--color-a); + letter-spacing: -0.02em; + font-weight: 400; + box-sizing: content-box; + border-bottom: 1px solid transparent; + white-space: nowrap; +} + +.unified-header .nav-link.active { + background: var(--white); +} + +.unified-header .nav-link:hover { + color: var(--color-c); + border-bottom: 1px solid var(--color-c); +} + +/* Rest: Flex-row, with two children: side bar, and content */ +.unified-wrapper .wy-grid-for-nav { + position: relative !important; + display: flex; + margin-inline: auto; +} + +/* First child: Side bar */ +.unified-wrapper .wy-grid-for-nav nav.wy-nav-side { + position: fixed; + display: flex; + flex-direction: column; + background: var(--color-f); + color: var(--color-a); + padding-bottom: unset !important; + z-index: 10 !important; + min-height: unset !important; + width: var(--sideWidth) !important; + top: var(--navHeight); + bottom: 0; + left: auto; + overflow: auto; +} + +.unified-wrapper .wy-grid-for-nav nav.wy-nav-side .wy-side-scroll { + position: static !important; + width: unset !important; + overflow: unset !important; + height: unset !important; + padding-bottom: 2rem; +} + +.unified-wrapper .wy-grid-for-nav nav.wy-nav-side .wy-side-scroll .wy-side-nav-search { + margin: 0 !important; + width: var(--sideWidth) !important; +} + +.wy-nav-side, +.wy-side-scroll, +.wy-side-nav-search, +.my-menu { + width: 100% !important; +} + +.wy-nav-side input[type=text] { + font-family: "Overpass", sans-serif; + border-radius: 0; + border-color: var(--color-d); + background: var(--white); + box-shadow: none; + color: var(--color-a); +} + +.wy-nav-side input[type=text]::placeholder { + font-family: "Overpass", sans-serif; + color: var(--color-e); + font-size: 16px; + position: relative; + top: 4px; +} + +/* Second child: Content */ +.unified-wrapper .wy-grid-for-nav .wy-nav-content { + width: 100%; + max-width: unset !important; /* override */ + padding-inline: var(--desktopInlinePadding); + margin-inline-start: var(--sideWidth); + margin-top: var(--navHeight); +} + +.unified-wrapper .wy-grid-for-nav .wy-nav-content .rst-content { + max-width: min(70ch, calc(100vw - 2 * var(--desktopInlinePadding) - var(--sideWidth))); + margin-inline: auto; +} + +.unified-wrapper.menu-open .backdrop { opacity: 0.5; } -a.remix-link:hover .link-text { +.unified-wrapper .wy-nav-side, +.unified-wrapper .rst-versions { + left: auto; + +} + +.unified-wrapper .backdrop { + opacity: 0; + transition: opacity 200ms ease-in-out; +} + +@media (max-width: 768px) { + h2 { + margin-top: 3rem; + margin-bottom: 1rem; + } + + h3 { + margin-top: 3rem; + margin-bottom: 1rem; + } + + h4 { + margin-top: 2rem; + margin-bottom: 1rem; + } + + /* Menu closed styles */ + .unified-header .nav-link { + display: none; + } + + .unified-header .inner-header { + padding-inline: var(--mobileInlinePadding); + } + + .unified-wrapper .wy-grid-for-nav nav.wy-nav-side { + transform: translateX(-100%); + transition: transform 200ms ease-in-out; + } + + /* Menu open styles */ + .unified-wrapper.menu-open nav.wy-nav-side { + transform: translateX(0); + transition: transform 200ms ease-in-out; + } + + .unified-wrapper.menu-open .rst-versions { + position: sticky; + bottom: 0; + width: 100%; + } + + .unified-wrapper.menu-open .backdrop { + display: block; + position: fixed; + inset: 0; + opacity: 1; + transition: opacity 200ms ease-in-out; + z-index: 5; + background: #0006; + } + + a.skip-to-content { + display: none; + } + + .wy-nav-content { + margin-inline-start: 0 !important; + } + + .rst-content { + max-width: 100% !important; + } + + .wy-side-scroll { + padding-bottom: 0 !important; + } +} + +ul.search .context { + color: var(--color-a) !important; +} + +.rst-versions { + background: var(--color-f); +} + +.rst-versions.shift-up { + height: unset !important; + max-height: unset !important; + overflow-y: unset !important; +} + +.rst-content dl:not(.docutils) dt { + color: var(--color-a); + background-color: #fff8; + border-top: solid 3px #0002; + border-inline-start: solid 3px #0002; + padding: 2px 6px; +} + +.rst-versions .rst-current-version { + border-color: var(--color-d) !important; +} + +.rst-current-version *, +.rst-current-version .fa:before, +.rst-current-version .fa-element { + color: var(--color-b) !important; +} + +.rst-current-version dt, +.rst-current-version dd, +.rst-current-version dd a, +.rst-other-versions dl:last-of-type dt, +.rst-other-versions dl:last-of-type dd, +.rst-other-versions dl:last-of-type dd a { + font-size: 14px !important; +} + +.rst-other-versions { + background: var(--white) !important; + color: var(--color-a) !important; + max-height: calc(100vh - var(--navHeight) - var(--currentVersionHeight)); + overflow-y: scroll; +} + +.rst-other-versions a { + text-decoration: underline; + color: var(--color-c) !important; +} + +.rst-other-versions dt { + color: var(--color-a) !important; +} + +.rst-other-versions dl { + margin-bottom: 1.5rem !important; +} + +.rst-other-versions dl:last-of-type { + margin-top: 2rem !important; +} + +/* Bottom Search */ +.wy-nav-side input[type=text], +.rst-other-versions dl:last-of-type dd { + width: 100%; +} + +.rst-other-versions dl:last-of-type dt { + color: var(--color-b) !important; +} + +.rst-other-versions dl:last-of-type div[style*=padding], +.rst-other-versions dl dd:first-of-type a { + padding-inline-start: 0 !important; +} + +button.toctree-expand { + color: var(--black) !important; +} + +/* Light/dark color mode toggle 🌓 */ +button.color-toggle { + display: inline-flex; + appearance: none; + -webkit-box-align: center; + align-items: center; + -webkit-box-pack: center; + justify-content: center; + user-select: none; + outline: none; + height: 28px; + width: 28px; + background: none; + border: none; + padding: 6px; + margin: 6px; + transition-duration: 200ms; + transition-property: background-color, + color, + fill, + stroke, + opacity; +} + +button.color-toggle:focus-visible { + outline: 2px solid var(--color-c); + color: var(--color-c); +} + +button.color-toggle:hover { + color: var(--color-c); + background: #0002; +} + +button.color-toggle .color-toggle-icon { + width: 100%; + height: 100%; + margin: 0; + display: inline-block; + line-height: 1em; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + vertical-align: middle; + /* color: var(--color-a); */ +} + + +button.mobile-menu-button { + display: none; +} + +@media (max-width: 768px) { + nav.wy-nav-top { + display: none; + } + + button.mobile-menu-button { + display: flex; + } +} + + +.hidden { + display: none; +} + +#search-results .search li:first-child, +#search-results .search li { + border-color: var(--color-d); +} + +#search-results .search li:last-child { + border: 0px; +} + +.forum-link::after { + content: ' ↗'; + font-size: 14px; + font-family: 'Overpass Mono', monospace; +} + +.wy-breadcrumbs>li { + padding-top: 8px; +} + +.wy-breadcrumbs-aside a { + padding: 0.5rem 0.75rem; + font-size: 12px; + font-family: "'Overpass'", sans-serif; + font-weight: 700; +} + +a.skip-to-content:visited, +a.skip-to-content:not(:visited), +a.skip-to-content { display: block; + pointer-events: none; + width: fit-content; + opacity: 0; + transition: opacity 200ms ease-in-out; + padding: 2px 4px; + font-size: 14px; + margin-inline-end: auto; + margin-inline-start: 1.5rem; + color: var(--color-a); + white-space: nowrap; } + +a.skip-to-content:focus { + opacity: 1; + transition: opacity 200ms ease-in-out; +} + +#content { + scroll-margin-top: 6rem; + scroll-behavior: smooth; +} \ No newline at end of file diff --git a/compiler/docs/_static/css/dark.css b/compiler/docs/_static/css/dark.css deleted file mode 100644 index a87ff09e..00000000 --- a/compiler/docs/_static/css/dark.css +++ /dev/null @@ -1,635 +0,0 @@ -/* links */ - -.rst-content a:not(:visited) { - color: #aaddff !important; -} - -/* code directives */ - -.method dt, -.class dt, -.data dt, -.attribute dt, -.function dt, -.classmethod dt, -.exception dt, -.descclassname, -.descname { - background-color: #2d2d2d !important; -} - -.rst-content dl:not(.docutils) dt { - color: #aaddff; - background-color: #2d2d2d; - border-top: solid 3px #525252; - border-left: solid 3px #525252; -} - -em.property { - color: #888888; -} - - -/* tables */ - -.rst-content table.docutils thead { - color: #ddd; -} - -.rst-content table.docutils td { - border: 0px; -} - -.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { - background-color: #5a5a5a; -} - -.rst-content pre { - background: none; -} - -/* inlined code highlights */ - -.xref, -.py-meth, -.rst-content a code { - color: #aaddff !important; - font-weight: normal !important; -} - -.rst-content code { - color: #eee !important; - font-weight: normal !important; -} - -code.literal { - background-color: #2d2d2d !important; - border: 1px solid #6d6d6d !important; -} - -code.docutils.literal.notranslate { - color: #ddd; -} - -/* highlight color search text */ - -.rst-content .highlighted { - background: #ff5722; - box-shadow: 0 0 0 2px #f0978b; -} - -/* notes, warnings, hints */ - -.hint .admonition-title { - background: #2aa87c !important; -} - -.warning .admonition-title { - background: #cc4444 !important; -} - -.admonition-title { - background: #3a7ca8 !important; -} - -.admonition, -.note { - background-color: #2d2d2d !important; -} - - -/* table of contents */ - -.wy-nav-content-wrap { - background-color: rgba(0, 0, 0, 0.6) !important; -} - -.sidebar { - background-color: #191919 !important; -} - -.sidebar-title { - background-color: #2b2b2b !important; -} - -.wy-menu-vertical a { - color: #ddd; -} - -.wy-menu-vertical code.docutils.literal.notranslate { - color: #404040; - background: none !important; - border: none !important; -} - -.wy-nav-content { - background: #3c3c3c; - color: #dddddd; -} - -.wy-menu-vertical li.on a, -.wy-menu-vertical li.current>a { - background: #a3a3a3; - border-bottom: 0px !important; - border-top: 0px !important; -} - -.wy-menu-vertical li.current { - background: #b3b3b3; -} - -.toc-backref { - color: grey !important; -} - -.highlight .hll { - background-color: #49483e -} - -.highlight { - background: #222; - color: #f8f8f2 -} - -.highlight .c { - color: #888 -} - - -/* Comment */ - -.highlight .err { - color: #960050; - background-color: #1e0010 -} - - -/* Error */ - -.highlight .k { - color: #66d9ef -} - - -/* Keyword */ - -.highlight .l { - color: #ae81ff -} - - -/* Literal */ - -.highlight .n { - color: #f8f8f2 -} - - -/* Name */ - -.highlight .o { - color: #f92672 -} - - -/* Operator */ - -.highlight .p { - color: #f8f8f2 -} - - -/* Punctuation */ - -.highlight .ch { - color: #888 -} - - -/* Comment.Hashbang */ - -.highlight .cm { - color: #888 -} - - -/* Comment.Multiline */ - -.highlight .cp { - color: #888 -} - - -/* Comment.Preproc */ - -.highlight .cpf { - color: #888 -} - - -/* Comment.PreprocFile */ - -.highlight .c1 { - color: #888 -} - - -/* Comment.Single */ - -.highlight .cs { - color: #888 -} - - -/* Comment.Special */ - -.highlight .gd { - color: #f92672 -} - - -/* Generic.Deleted */ - -.highlight .ge { - font-style: italic -} - - -/* Generic.Emph */ - -.highlight .gi { - color: #a6e22e -} - - -/* Generic.Inserted */ - -.highlight .gs { - font-weight: bold -} - - -/* Generic.Strong */ - -.highlight .gu { - color: #888 -} - - -/* Generic.Subheading */ - -.highlight .kc { - color: #66d9ef -} - - -/* Keyword.Constant */ - -.highlight .kd { - color: #66d9ef -} - - -/* Keyword.Declaration */ - -.highlight .kn { - color: #f92672 -} - - -/* Keyword.Namespace */ - -.highlight .kp { - color: #66d9ef -} - - -/* Keyword.Pseudo */ - -.highlight .kr { - color: #66d9ef -} - - -/* Keyword.Reserved */ - -.highlight .kt { - color: #66d9ef -} - - -/* Keyword.Type */ - -.highlight .ld { - color: #e6db74 -} - - -/* Literal.Date */ - -.highlight .m { - color: #ae81ff -} - - -/* Literal.Number */ - -.highlight .s { - color: #e6db74 -} - - -/* Literal.String */ - -.highlight .na { - color: #a6e22e -} - - -/* Name.Attribute */ - -.highlight .nb { - color: #f8f8f2 -} - - -/* Name.Builtin */ - -.highlight .nc { - color: #a6e22e -} - - -/* Name.Class */ - -.highlight .no { - color: #66d9ef -} - - -/* Name.Constant */ - -.highlight .nd { - color: #a6e22e -} - - -/* Name.Decorator */ - -.highlight .ni { - color: #f8f8f2 -} - - -/* Name.Entity */ - -.highlight .ne { - color: #a6e22e -} - - -/* Name.Exception */ - -.highlight .nf { - color: #a6e22e -} - - -/* Name.Function */ - -.highlight .nl { - color: #f8f8f2 -} - - -/* Name.Label */ - -.highlight .nn { - color: #f8f8f2 -} - - -/* Name.Namespace */ - -.highlight .nx { - color: #a6e22e -} - - -/* Name.Other */ - -.highlight .py { - color: #f8f8f2 -} - - -/* Name.Property */ - -.highlight .nt { - color: #f92672 -} - - -/* Name.Tag */ - -.highlight .nv { - color: #f8f8f2 -} - - -/* Name.Variable */ - -.highlight .ow { - color: #f92672 -} - - -/* Operator.Word */ - -.highlight .w { - color: #f8f8f2 -} - - -/* Text.Whitespace */ - -.highlight .mb { - color: #ae81ff -} - - -/* Literal.Number.Bin */ - -.highlight .mf { - color: #ae81ff -} - - -/* Literal.Number.Float */ - -.highlight .mh { - color: #ae81ff -} - - -/* Literal.Number.Hex */ - -.highlight .mi { - color: #ae81ff -} - - -/* Literal.Number.Integer */ - -.highlight .mo { - color: #ae81ff -} - - -/* Literal.Number.Oct */ - -.highlight .sa { - color: #e6db74 -} - - -/* Literal.String.Affix */ - -.highlight .sb { - color: #e6db74 -} - - -/* Literal.String.Backtick */ - -.highlight .sc { - color: #e6db74 -} - - -/* Literal.String.Char */ - -.highlight .dl { - color: #e6db74 -} - - -/* Literal.String.Delimiter */ - -.highlight .sd { - color: #e6db74 -} - - -/* Literal.String.Doc */ - -.highlight .s2 { - color: #e6db74 -} - - -/* Literal.String.Double */ - -.highlight .se { - color: #ae81ff -} - - -/* Literal.String.Escape */ - -.highlight .sh { - color: #e6db74 -} - - -/* Literal.String.Heredoc */ - -.highlight .si { - color: #e6db74 -} - - -/* Literal.String.Interpol */ - -.highlight .sx { - color: #e6db74 -} - - -/* Literal.String.Other */ - -.highlight .sr { - color: #e6db74 -} - - -/* Literal.String.Regex */ - -.highlight .s1 { - color: #e6db74 -} - - -/* Literal.String.Single */ - -.highlight .ss { - color: #e6db74 -} - - -/* Literal.String.Symbol */ - -.highlight .bp { - color: #f8f8f2 -} - - -/* Name.Builtin.Pseudo */ - -.highlight .fm { - color: #a6e22e -} - - -/* Name.Function.Magic */ - -.highlight .vc { - color: #f8f8f2 -} - - -/* Name.Variable.Class */ - -.highlight .vg { - color: #f8f8f2 -} - - -/* Name.Variable.Global */ - -.highlight .vi { - color: #f8f8f2 -} - - -/* Name.Variable.Instance */ - -.highlight .vm { - color: #f8f8f2 -} - - -/* Name.Variable.Magic */ - -.highlight .il { - color: #ae81ff -} - - -/* Literal.Number.Integer.Long */ - - -/* Link to Remix IDE shown over code snippets */ -a.remix-link { - filter: invert(1); /* The icon is black. In dark mode we want it white. */ -} diff --git a/compiler/docs/_static/css/fonts.css b/compiler/docs/_static/css/fonts.css new file mode 100644 index 00000000..1a987a6d --- /dev/null +++ b/compiler/docs/_static/css/fonts.css @@ -0,0 +1,2 @@ +@import url("https://fonts.cdnfonts.com/css/overpass"); +@import url("https://fonts.cdnfonts.com/css/overpass-mono"); \ No newline at end of file diff --git a/compiler/docs/_static/css/pygments.css b/compiler/docs/_static/css/pygments.css new file mode 100644 index 00000000..0e640681 --- /dev/null +++ b/compiler/docs/_static/css/pygments.css @@ -0,0 +1,399 @@ +pre { + line-height: 125%; +} + +td.linenos .normal { + color: inherit; + background-color: transparent; + padding-left: 5px; + padding-right: 5px; +} + +span.linenos { + color: inherit; + background-color: transparent; + padding-left: 5px; + padding-right: 5px; +} + +td.linenos .special { + color: #000000; + background-color: #ffffc0; + padding-left: 5px; + padding-right: 5px; +} + +span.linenos.special { + color: #000000; + background-color: #ffffc0; + padding-left: 5px; + padding-right: 5px; +} + +.highlight .hll { + background-color: #ffffcc +} + +.highlight { + background: #eeffcc; +} + +.highlight .c { + color: #408090; + font-style: italic +} + +/* Comment */ +.highlight .err { + border: 1px solid #FF0000 +} + +/* Error */ +.highlight .k { + color: #007020; + font-weight: bold +} + +/* Keyword */ +.highlight .o { + color: #666666 +} + +/* Operator */ +.highlight .ch { + color: #408090; + font-style: italic +} + +/* Comment.Hashbang */ +.highlight .cm { + color: #408090; + font-style: italic +} + +/* Comment.Multiline */ +.highlight .cp { + color: #007020 +} + +/* Comment.Preproc */ +.highlight .cpf { + color: #408090; + font-style: italic +} + +/* Comment.PreprocFile */ +.highlight .c1 { + color: #408090; + font-style: italic +} + +/* Comment.Single */ +.highlight .cs { + color: #408090; + background-color: #fff0f0 +} + +/* Comment.Special */ +.highlight .gd { + color: #A00000 +} + +/* Generic.Deleted */ +.highlight .ge { + font-style: italic +} + +/* Generic.Emph */ +.highlight .gr { + color: #FF0000 +} + +/* Generic.Error */ +.highlight .gh { + color: #000080; + font-weight: bold +} + +/* Generic.Heading */ +.highlight .gi { + color: #00A000 +} + +/* Generic.Inserted */ +.highlight .go { + color: #333333 +} + +/* Generic.Output */ +.highlight .gp { + color: #c65d09; + font-weight: bold +} + +/* Generic.Prompt */ +.highlight .gs { + font-weight: bold +} + +/* Generic.Strong */ +.highlight .gu { + color: #800080; + font-weight: bold +} + +/* Generic.Subheading */ +.highlight .gt { + color: #0044DD +} + +/* Generic.Traceback */ +.highlight .kc { + color: #007020; + font-weight: bold +} + +/* Keyword.Constant */ +.highlight .kd { + color: #007020; + font-weight: bold +} + +/* Keyword.Declaration */ +.highlight .kn { + color: #007020; + font-weight: bold +} + +/* Keyword.Namespace */ +.highlight .kp { + color: #007020 +} + +/* Keyword.Pseudo */ +.highlight .kr { + color: #007020; + font-weight: bold +} + +/* Keyword.Reserved */ +.highlight .kt { + color: #902000 +} + +/* Keyword.Type */ +.highlight .m { + color: #208050 +} + +/* Literal.Number */ +.highlight .s { + color: #4070a0 +} + +/* Literal.String */ +.highlight .na { + color: #4070a0 +} + +/* Name.Attribute */ +.highlight .nb { + color: #007020 +} + +/* Name.Builtin */ +.highlight .nc { + color: #0e84b5; + font-weight: bold +} + +/* Name.Class */ +.highlight .no { + color: #60add5 +} + +/* Name.Constant */ +.highlight .nd { + color: #555555; + font-weight: bold +} + +/* Name.Decorator */ +.highlight .ni { + color: #d55537; + font-weight: bold +} + +/* Name.Entity */ +.highlight .ne { + color: #007020 +} + +/* Name.Exception */ +.highlight .nf { + color: #06287e +} + +/* Name.Function */ +.highlight .nl { + color: #002070; + font-weight: bold +} + +/* Name.Label */ +.highlight .nn { + color: #0e84b5; + font-weight: bold +} + +/* Name.Namespace */ +.highlight .nt { + color: #062873; + font-weight: bold +} + +/* Name.Tag */ +.highlight .nv { + color: #bb60d5 +} + +/* Name.Variable */ +.highlight .ow { + color: #007020; + font-weight: bold +} + +/* Operator.Word */ +.highlight .w { + color: #bbbbbb +} + +/* Text.Whitespace */ +.highlight .mb { + color: #208050 +} + +/* Literal.Number.Bin */ +.highlight .mf { + color: #208050 +} + +/* Literal.Number.Float */ +.highlight .mh { + color: #208050 +} + +/* Literal.Number.Hex */ +.highlight .mi { + color: #208050 +} + +/* Literal.Number.Integer */ +.highlight .mo { + color: #208050 +} + +/* Literal.Number.Oct */ +.highlight .sa { + color: #4070a0 +} + +/* Literal.String.Affix */ +.highlight .sb { + color: #4070a0 +} + +/* Literal.String.Backtick */ +.highlight .sc { + color: #4070a0 +} + +/* Literal.String.Char */ +.highlight .dl { + color: #4070a0 +} + +/* Literal.String.Delimiter */ +.highlight .sd { + color: #4070a0; + font-style: italic +} + +/* Literal.String.Doc */ +.highlight .s2 { + color: #4070a0 +} + +/* Literal.String.Double */ +.highlight .se { + color: #4070a0; + font-weight: bold +} + +/* Literal.String.Escape */ +.highlight .sh { + color: #4070a0 +} + +/* Literal.String.Heredoc */ +.highlight .si { + color: #70a0d0; + font-style: italic +} + +/* Literal.String.Interpol */ +.highlight .sx { + color: #c65d09 +} + +/* Literal.String.Other */ +.highlight .sr { + color: #235388 +} + +/* Literal.String.Regex */ +.highlight .s1 { + color: #4070a0 +} + +/* Literal.String.Single */ +.highlight .ss { + color: #517918 +} + +/* Literal.String.Symbol */ +.highlight .bp { + color: #007020 +} + +/* Name.Builtin.Pseudo */ +.highlight .fm { + color: #06287e +} + +/* Name.Function.Magic */ +.highlight .vc { + color: #bb60d5 +} + +/* Name.Variable.Class */ +.highlight .vg { + color: #bb60d5 +} + +/* Name.Variable.Global */ +.highlight .vi { + color: #bb60d5 +} + +/* Name.Variable.Instance */ +.highlight .vm { + color: #bb60d5 +} + +/* Name.Variable.Magic */ +.highlight .il { + color: #208050 +} + +/* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/compiler/docs/_static/css/toggle.css b/compiler/docs/_static/css/toggle.css index add134f6..6f03e6fb 100644 --- a/compiler/docs/_static/css/toggle.css +++ b/compiler/docs/_static/css/toggle.css @@ -9,6 +9,13 @@ input[type=checkbox] { padding: 10px; display: flex; justify-content: space-between; + background-color: var(--color-f); + border-top: 1px solid var(--color-c); +} + +.fa-caret-down, +.fa-book { + color: var(--color-a) !important; } .rst-versions .rst-current-version .fa-book, @@ -76,8 +83,6 @@ html.transition *:after { transition-delay: 0 !important; } -nav.wy-nav-side { - /* The default padding of 2em is too small and the "Keyword Index" link gets obscured - * by the version toggle. */ - padding-bottom: 3em; -} +.wy-menu-vertical a:hover { + background-color: #0002; +} \ No newline at end of file diff --git a/compiler/docs/_static/img/favicon.ico b/compiler/docs/_static/img/favicon.ico new file mode 100644 index 00000000..a2b8f877 Binary files /dev/null and b/compiler/docs/_static/img/favicon.ico differ diff --git a/compiler/docs/_static/img/favicon.png b/compiler/docs/_static/img/favicon.png new file mode 100644 index 00000000..3991d87e Binary files /dev/null and b/compiler/docs/_static/img/favicon.png differ diff --git a/compiler/docs/_static/img/hamburger-dark.svg b/compiler/docs/_static/img/hamburger-dark.svg new file mode 100644 index 00000000..26d9fed9 --- /dev/null +++ b/compiler/docs/_static/img/hamburger-dark.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/compiler/docs/_static/img/hamburger-light.svg b/compiler/docs/_static/img/hamburger-light.svg new file mode 100644 index 00000000..d5d0d0ae --- /dev/null +++ b/compiler/docs/_static/img/hamburger-light.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/compiler/docs/_static/img/logo-dark.svg b/compiler/docs/_static/img/logo-dark.svg new file mode 100644 index 00000000..92a12a9f --- /dev/null +++ b/compiler/docs/_static/img/logo-dark.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/compiler/docs/_static/img/logo.svg b/compiler/docs/_static/img/logo.svg new file mode 100644 index 00000000..19391843 --- /dev/null +++ b/compiler/docs/_static/img/logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/compiler/docs/_static/img/moon.svg b/compiler/docs/_static/img/moon.svg new file mode 100644 index 00000000..607dc1b4 --- /dev/null +++ b/compiler/docs/_static/img/moon.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/compiler/docs/_static/img/sun.svg b/compiler/docs/_static/img/sun.svg new file mode 100644 index 00000000..f86fd22b --- /dev/null +++ b/compiler/docs/_static/img/sun.svg @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/compiler/docs/_static/js/constants.js b/compiler/docs/_static/js/constants.js new file mode 100644 index 00000000..67fa16cd --- /dev/null +++ b/compiler/docs/_static/js/constants.js @@ -0,0 +1,38 @@ +// Site URL +const SITE_URL = "https://docs.soliditylang.org" +const { origin, pathname } = location; +const pathSplit = pathname.split("/"); +const rootPath = origin.includes(SITE_URL) && pathSplit.length > 3 ? pathSplit.splice(1, 2).join("/") : '' +const ROOT_URL = `${origin}/${rootPath}`; + +// Color mode constants +const [DARK, LIGHT] = ["dark", "light"]; +const LIGHT_LOGO_PATH = `${ROOT_URL}/_static/img/logo.svg`; +const DARK_LOGO_PATH = `${ROOT_URL}/_static/img/logo-dark.svg`; +const SUN_ICON_PATH = `${ROOT_URL}/_static/img/sun.svg`; +const MOON_ICON_PATH = `${ROOT_URL}/_static/img/moon.svg`; +const LIGHT_HAMBURGER_PATH = `${ROOT_URL}/_static/img/hamburger-light.svg`; +const DARK_HAMBURGER_PATH = `${ROOT_URL}/_static/img/hamburger-dark.svg`; +const COLOR_TOGGLE_ICON_CLASS = "color-toggle-icon"; +const SOLIDITY_LOGO_CLASS = "solidity-logo"; +const LS_COLOR_SCHEME = "color-scheme"; + +// Solidity navigation constants +const SOLIDITY_HOME_URL = "https://soliditylang.org"; +const BLOG_URL = `${SOLIDITY_HOME_URL}/blog`; +const DOCS_URL = "/"; +const USE_CASES_PATH = `${SOLIDITY_HOME_URL}/use-cases`; +const CONTRIBUTE_PATH = `/en/latest/contributing.html`; +const ABOUT_PATH = `${SOLIDITY_HOME_URL}/about`; +const FORUM_URL = "https://forum.soliditylang.org/"; +const NAV_LINKS = [ + { name: "Blog", href: BLOG_URL }, + { name: "Documentation", href: DOCS_URL }, + { name: "Use cases", href: USE_CASES_PATH }, + { name: "Contribute", href: CONTRIBUTE_PATH }, + { name: "About", href: ABOUT_PATH }, + { name: "Forum", href: FORUM_URL }, +]; + +const MOBILE_MENU_TOGGLE_CLASS = "shift"; +const WRAPPER_CLASS = "unified-wrapper"; diff --git a/compiler/docs/_static/js/initialize.js b/compiler/docs/_static/js/initialize.js new file mode 100644 index 00000000..a20d4fce --- /dev/null +++ b/compiler/docs/_static/js/initialize.js @@ -0,0 +1,250 @@ +const getLogoSrc = (isDark) => (isDark ? DARK_LOGO_PATH : LIGHT_LOGO_PATH); + +const getModeIconSrc = (isDark) => (isDark ? SUN_ICON_PATH : MOON_ICON_PATH); + +const getMenuIconSrc = (isDark) => + isDark ? DARK_HAMBURGER_PATH : LIGHT_HAMBURGER_PATH; + +function addFooterNote() { + const contentInfo = document.querySelector("div[role=contentinfo]"); + const footerNote = document.createElement("p"); + footerNote.classList.add("footer-note"); + footerNote.innerHTML = + 'Customized with ❤️ by the ethereum.org team.'; + contentInfo.parentNode.insertBefore(footerNote, contentInfo.nextSibling); +} + +function rearrangeDom() { + const bodyDivs = document.querySelectorAll("body>div"); + bodyDivs.forEach((div) => div.remove()); + const wrapperDiv = document.createElement("div"); + wrapperDiv.classList.add(WRAPPER_CLASS); + bodyDivs.forEach((div) => wrapperDiv.appendChild(div)); + document.body.prepend(wrapperDiv); + + const rstVersions = document.querySelector(".rst-versions"); + rstVersions.remove(); + const wyNavSide = document.querySelector("nav.wy-nav-side"); + wyNavSide.appendChild(rstVersions); + const backdrop = document.createElement("div"); + backdrop.classList.add("backdrop"); + wrapperDiv.appendChild(backdrop); + + const content = document.querySelector(".wy-nav-content"); + content.id = "content"; + const oldWrap = document.querySelector("section.wy-nav-content-wrap"); + oldWrap.remove(); + document.querySelector(".wy-grid-for-nav").appendChild(content); +} + +function buildHeader() { + const isDarkMode = localStorage.getItem(LS_COLOR_SCHEME) == DARK; + + const header = document.createElement("div"); + header.classList.add("unified-header"); + document.querySelector(`.${WRAPPER_CLASS}`).prepend(header); + + const innerHeader = document.createElement("div"); + innerHeader.classList.add("inner-header"); + header.appendChild(innerHeader); + + const homeLink = document.createElement("a"); + homeLink.classList.add("home-link"); + homeLink.href = SOLIDITY_HOME_URL; + homeLink.ariaLabel = "Solidity home"; + innerHeader.appendChild(homeLink); + + const logo = document.createElement("img"); + logo.classList.add(SOLIDITY_LOGO_CLASS); + logo.src = getLogoSrc(isDarkMode); + logo.alt = "Solidity logo"; + homeLink.appendChild(logo); + + const skipToContent = document.createElement("a"); + skipToContent.classList.add("skip-to-content"); + skipToContent.href = "#content"; + skipToContent.innerText = "{skip to content}"; + innerHeader.appendChild(skipToContent); + + const navBar = document.createElement("nav"); + navBar.classList.add("nav-bar"); + innerHeader.appendChild(navBar); + + const linkElements = NAV_LINKS.map(({ name, href }) => { + const link = document.createElement("a"); + link.classList.add("nav-link"); + link.setAttribute("key", name); + link.setAttribute("href", href); + link.setAttribute("aria-label", name); + if (href === FORUM_URL) { + link.classList.add("forum-link"); + link.setAttribute("target", "_blank"); + link.setAttribute("rel", "noopener noreferrer"); + } + link.innerText = name; + return link; + }); + linkElements.forEach((link) => navBar.appendChild(link)); + + // Flex wrapper for color mode and mobile menu buttons + const navButtonContainer = document.createElement("div"); + navButtonContainer.classList.add("nav-button-container"); + navBar.appendChild(navButtonContainer); + + // Build color toggle + const toggleIcon = document.createElement("img"); + toggleIcon.classList.add(COLOR_TOGGLE_ICON_CLASS); + toggleIcon.src = getModeIconSrc(isDarkMode); + toggleIcon.alt = "Color mode toggle icon"; + toggleIcon.setAttribute("aria-hidden", "true"); + toggleIcon.setAttribute("key", "toggle icon"); + const colorModeButton = document.createElement("button"); + colorModeButton.classList.add("color-toggle"); + colorModeButton.setAttribute("type", "button"); + colorModeButton.setAttribute("aria-label", "Toggle light dark mode"); + colorModeButton.setAttribute("key", "color mode button"); + colorModeButton.addEventListener("click", toggleColorMode); + colorModeButton.appendChild(toggleIcon); + navButtonContainer.appendChild(colorModeButton); + + // Build mobile hamburger menu + const menuIcon = document.createElement("img"); + menuIcon.classList.add(COLOR_TOGGLE_ICON_CLASS); + menuIcon.src = getMenuIconSrc(isDarkMode); + menuIcon.alt = "Toggle menu"; + menuIcon.setAttribute("aria-hidden", "true"); + menuIcon.setAttribute("key", "menu icon"); + const menuButton = document.createElement("button"); + menuButton.classList.add("color-toggle"); + menuButton.classList.add("mobile-menu-button"); + menuButton.setAttribute("type", "button"); + menuButton.setAttribute("aria-label", "Toggle menu"); + menuButton.setAttribute("key", "menu button"); + menuButton.addEventListener("click", toggleMenu); + menuButton.appendChild(menuIcon); + navButtonContainer.appendChild(menuButton); +} + +const updateActiveNavLink = () => { + const navLinks = document.querySelectorAll(".unified-header .nav-link"); + navLinks.forEach((link) => { + const href = link.getAttribute("href"); + if (document.documentURI.includes("contributing.html")) { + link.classList[href.includes("contributing.html") ? "add" : "remove"]( + "active" + ); + } else { + link.classList[document.documentURI.includes(href) ? "add" : "remove"]( + "active" + ); + } + }); +}; + +document.addEventListener("locationchange", updateActiveNavLink); + +function updateGitHubEditPath() { + // Replaces the version number in the GitHub edit path with "develop" + const gitHubEditAnchor = document.querySelector(".wy-breadcrumbs-aside > a"); + const url = new URL(gitHubEditAnchor.href); + const split = url.pathname.split("/"); + const versionIndex = split.indexOf("blob") + 1; + split[versionIndex] = "develop"; + url.pathname = split.join("/"); + gitHubEditAnchor.setAttribute("href", url.toString()); + gitHubEditAnchor.setAttribute("target", "_blank"); + gitHubEditAnchor.setAttribute("rel", "noopener noreferrer"); +} + +function initialize() { + // Rearrange DOM elements for styling + rearrangeDom(); + + // Check localStorage for existing color scheme preference + var prefersDark = localStorage.getItem(LS_COLOR_SCHEME) == DARK; + // Check link for search param "color"... it may be "light" or "dark" + var urlParams = new URLSearchParams(window.location.search); + if (urlParams.size > 0) { + // This is used for color mode continuity between the main Solidity Lang site and the docs + var colorSchemeParam = urlParams.get("color"); + // If present, overwrite prefersDark accordingly + if (colorSchemeParam) { + prefersDark = colorSchemeParam == DARK; + } + + // Remove "color" search param from URL + const { location, title } = document; + const { pathname, origin, search, hash } = location; + const newSearchParams = new URLSearchParams(search); + newSearchParams.delete("color"); + const sanitizedSearch = + newSearchParams.size < 1 ? "" : "?" + newSearchParams.toString(); + window.history.replaceState( + origin, + title, + pathname + sanitizedSearch + hash + ); + } + + // In case none existed, establish localStorage color scheme preference + var mode = prefersDark ? DARK : LIGHT; + localStorage.setItem(LS_COLOR_SCHEME, mode); + + // Select the root element and set the style attribute to denote color-scheme attribute + document + .querySelector(":root") + .setAttribute("style", `--color-scheme: ${mode}`); + + // Remove old input and RTD logo anchor element + document.querySelector("input[name=mode]").remove(); + document.querySelector("label[for=switch]").remove(); + document.querySelector(".wy-side-nav-search > a").remove(); + + // Add footer note + addFooterNote(); + + // Build header + buildHeader(); + + // Close menu + toggleMenu({ force: false }); + + // Update active nav link + updateActiveNavLink(); + + // Update GitHub edit path to direct to `develop` branch + updateGitHubEditPath(); +} + +document.addEventListener("DOMContentLoaded", initialize); + +const handleClick = (e) => { + if (e.target.closest(".backdrop")) { + toggleMenu({ force: false }); + } + + if (e.target.closest("a")) { + const target = e.target.closest("a"); + const href = target.getAttribute("href"); + if (href.includes(SOLIDITY_HOME_URL)) { + const url = new URL(href); + const params = new URLSearchParams(url.search); + params.set("color", localStorage.getItem(LS_COLOR_SCHEME)); + url.search = params.toString(); + target.setAttribute("href", url.toString()); + } + } +}; +document.addEventListener("click", handleClick); + +const handleKeyDown = (e) => { + if (e.metaKey && e.key === "k") { + document.querySelector("#rtd-search-form input").focus(); + } else if (e.key === "Escape") { + toggleMenu({ force: false }); + } + if (e.metaKey && e.code === "Backslash") { + toggleColorMode(); + } +}; +document.addEventListener("keydown", handleKeyDown); diff --git a/compiler/docs/_static/js/toggle.js b/compiler/docs/_static/js/toggle.js index 780ea9ee..6ea2dd1f 100644 --- a/compiler/docs/_static/js/toggle.js +++ b/compiler/docs/_static/js/toggle.js @@ -1,39 +1,47 @@ -document.addEventListener('DOMContentLoaded', function() { +function toggleColorMode() { + // Check localStorage for previous color scheme preference, assign the opposite + var newMode = localStorage.getItem(LS_COLOR_SCHEME) == DARK ? LIGHT : DARK; - function toggleCssMode(isDay) { - var mode = (isDay ? "Day" : "Night"); - localStorage.setItem("css-mode", mode); + // Update localStorage with new color scheme preference + localStorage.setItem(LS_COLOR_SCHEME, newMode); - var url_root = DOCUMENTATION_OPTIONS.URL_ROOT == "./" ? "" : DOCUMENTATION_OPTIONS.URL_ROOT; - var daysheet = $(`link[href="${url_root}_static/pygments.css"]`)[0].sheet; - daysheet.disabled = !isDay; + // Update the root element with the new color scheme preference + document + .querySelector(":root") + .setAttribute("style", `--color-scheme: ${newMode}`); - var nightsheet = $(`link[href="${url_root}_static/css/dark.css"]`)[0]; - if (!isDay && nightsheet === undefined) { - var element = document.createElement("link"); - element.setAttribute("rel", "stylesheet"); - element.setAttribute("type", "text/css"); - element.setAttribute("href", `${url_root}_static/css/dark.css`); - document.getElementsByTagName("head")[0].appendChild(element); - return; - } - if (nightsheet !== undefined) { - nightsheet.sheet.disabled = isDay; - } - } - - var initial = localStorage.getItem("css-mode") != "Night"; - var checkbox = document.querySelector('input[name=mode]'); + // Update logo + document + .querySelector(`img.${SOLIDITY_LOGO_CLASS}`) + .setAttribute("src", newMode === LIGHT ? LIGHT_LOGO_PATH : DARK_LOGO_PATH); - toggleCssMode(initial); - checkbox.checked = initial; + // Update color mode toggle icon + document + .querySelector(`img.${COLOR_TOGGLE_ICON_CLASS}`) + .setAttribute("src", newMode === LIGHT ? MOON_ICON_PATH : SUN_ICON_PATH); - checkbox.addEventListener('change', function() { - document.documentElement.classList.add('transition'); - window.setTimeout(() => { - document.documentElement.classList.remove('transition'); - }, 1000) - toggleCssMode(this.checked); - }) + // Update hamburger menu icon color + document + .querySelector("button.mobile-menu-button img") + .setAttribute( + "src", + newMode === LIGHT ? LIGHT_HAMBURGER_PATH : DARK_HAMBURGER_PATH + ); +} -}); \ No newline at end of file +function toggleMenu(options = {}) { + const handleClassToggle = ({ classList }, className) => { + if (typeof options.force !== "undefined") { + classList.toggle(className, options.force); + } else { + classList.toggle(className); + } + }; + document + .querySelectorAll('[data-toggle="rst-versions"]') + .forEach((e) => handleClassToggle(e, MOBILE_MENU_TOGGLE_CLASS)); + document + .querySelectorAll('[data-toggle="wy-nav-shift"]') + .forEach((e) => handleClassToggle(e, MOBILE_MENU_TOGGLE_CLASS)); + handleClassToggle(document.querySelector(`.${WRAPPER_CLASS}`), "menu-open"); +} diff --git a/compiler/docs/abi-spec.rst b/compiler/docs/abi-spec.rst index 9164ae6a..0d48b51d 100644 --- a/compiler/docs/abi-spec.rst +++ b/compiler/docs/abi-spec.rst @@ -191,9 +191,9 @@ on the type of ``X`` being - ``T[]`` where ``X`` has ``k`` elements (``k`` is assumed to be of type ``uint256``): - ``enc(X) = enc(k) enc([X[0], ..., X[k-1]])`` + ``enc(X) = enc(k) enc((X[0], ..., X[k-1]))`` - i.e. it is encoded as if it were an array of static size ``k``, prefixed with + i.e. it is encoded as if it were a tuple with ``k`` elements of the same type (resp. an array of static size ``k``), prefixed with the number of elements. - ``bytes``, of length ``k`` (which is assumed to be of type ``uint256``): @@ -252,7 +252,21 @@ Given the contract: } -Thus, for our ``Foo`` example if we wanted to call ``baz`` with the parameters ``69`` and +Thus, for our ``Foo`` example, if we wanted to call ``bar`` with the argument ``["abc", "def"]``, we would pass 68 bytes total, broken down into: + +- ``0xfce353f6``: the Method ID. This is derived from the signature ``bar(bytes3[2])``. +- ``0x6162630000000000000000000000000000000000000000000000000000000000``: the first part of the first + parameter, a ``bytes3`` value ``"abc"`` (left-aligned). +- ``0x6465660000000000000000000000000000000000000000000000000000000000``: the second part of the first + parameter, a ``bytes3`` value ``"def"`` (left-aligned). + +In total: + +.. code-block:: none + + 0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000 + +If we wanted to call ``baz`` with the parameters ``69`` and ``true``, we would pass 68 bytes total, which can be broken down into: - ``0xcdcd77c0``: the Method ID. This is derived as the first 4 bytes of the Keccak hash of @@ -271,20 +285,6 @@ In total: It returns a single ``bool``. If, for example, it were to return ``false``, its output would be the single byte array ``0x0000000000000000000000000000000000000000000000000000000000000000``, a single bool. -If we wanted to call ``bar`` with the argument ``["abc", "def"]``, we would pass 68 bytes total, broken down into: - -- ``0xfce353f6``: the Method ID. This is derived from the signature ``bar(bytes3[2])``. -- ``0x6162630000000000000000000000000000000000000000000000000000000000``: the first part of the first - parameter, a ``bytes3`` value ``"abc"`` (left-aligned). -- ``0x6465660000000000000000000000000000000000000000000000000000000000``: the second part of the first - parameter, a ``bytes3`` value ``"def"`` (left-aligned). - -In total: - -.. code-block:: none - - 0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000 - If we wanted to call ``sam`` with the arguments ``"dave"``, ``true`` and ``[1,2,3]``, we would pass 292 bytes total, broken down into: @@ -311,7 +311,7 @@ Use of Dynamic Types A call to a function with the signature ``f(uint256,uint32[],bytes10,bytes)`` with values ``(0x123, [0x456, 0x789], "1234567890", "Hello, world!")`` is encoded in the following way: -We take the first four bytes of ``sha3("f(uint256,uint32[],bytes10,bytes)")``, i.e. ``0x8be65246``. +We take the first four bytes of ``keccak("f(uint256,uint32[],bytes10,bytes)")``, i.e. ``0x8be65246``. Then we encode the head parts of all four arguments. For the static types ``uint256`` and ``bytes10``, these are directly the values we want to pass, whereas for the dynamic types ``uint32[]`` and ``bytes``, we use the offset in bytes to the start of their data area, measured from the start of the value @@ -563,7 +563,7 @@ A function description is a JSON object with the fields: blockchain state `), ``view`` (:ref:`specified to not modify the blockchain state `), ``nonpayable`` (function does not accept Ether - the default) and ``payable`` (function accepts Ether). -Constructor and fallback function never have ``name`` or ``outputs``. Fallback function doesn't have ``inputs`` either. +Constructor, receive, and fallback never have ``name`` or ``outputs``. Receive and fallback do not have ``inputs`` either. .. note:: Sending non-zero Ether to non-payable function will revert the transaction. @@ -581,7 +581,7 @@ An event description is a JSON object with fairly similar fields: * ``name``: the name of the parameter. * ``type``: the canonical type of the parameter (more below). * ``components``: used for tuple types (more below). - * ``indexed``: ``true`` if the field is part of the log's topics, ``false`` if it one of the log's data segment. + * ``indexed``: ``true`` if the field is part of the log's topics, ``false`` if it is one of the log's data segments. - ``anonymous``: ``true`` if the event was declared as ``anonymous``. diff --git a/compiler/docs/analysing-compilation-output.rst b/compiler/docs/analysing-compilation-output.rst index 892b18b9..183f7732 100644 --- a/compiler/docs/analysing-compilation-output.rst +++ b/compiler/docs/analysing-compilation-output.rst @@ -11,7 +11,7 @@ visual diff of the assembly before and after a change is often very enlightening Consider the following contract (named, say ``contract.sol``): -.. code-block:: Solidity +.. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.5.0 <0.9.0; diff --git a/compiler/docs/assembly.rst b/compiler/docs/assembly.rst index b527ffba..e08ff9f6 100644 --- a/compiler/docs/assembly.rst +++ b/compiler/docs/assembly.rst @@ -362,7 +362,7 @@ in memory is automatically considered memory-safe and does not need to be annota .. warning:: It is your responsibility to make sure that the assembly actually satisfies the memory model. If you annotate an assembly block as memory-safe, but violate one of the memory assumptions, this **will** lead to incorrect and - undefined behaviour that cannot easily be discovered by testing. + undefined behavior that cannot easily be discovered by testing. In case you are developing a library that is meant to be compatible across multiple versions of Solidity, you can use a special comment to annotate an assembly block as memory-safe: @@ -375,4 +375,4 @@ of Solidity, you can use a special comment to annotate an assembly block as memo } Note that we will disallow the annotation via comment in a future breaking release; so, if you are not concerned with -backwards-compatibility with older compiler versions, prefer using the dialect string. +backward-compatibility with older compiler versions, prefer using the dialect string. diff --git a/compiler/docs/brand-guide.rst b/compiler/docs/brand-guide.rst index 5601b16a..cf471c5e 100644 --- a/compiler/docs/brand-guide.rst +++ b/compiler/docs/brand-guide.rst @@ -67,7 +67,7 @@ When using the Solidity logo, please respect the Solidity logo guidelines. Solidity Logo Guidelines ======================== -.. image:: logo.svg +.. image:: solidity_logo.svg :width: 256 *(Right click on the logo to download it.)* diff --git a/compiler/docs/bugs.json b/compiler/docs/bugs.json index 03dafbe4..c853f95e 100644 --- a/compiler/docs/bugs.json +++ b/compiler/docs/bugs.json @@ -1,4 +1,40 @@ [ + { + "uid": "SOL-2023-3", + "name": "VerbatimInvalidDeduplication", + "summary": "All ``verbatim`` blocks are considered identical by deduplicator and can incorrectly be unified when surrounded by identical opcodes.", + "description": "The block deduplicator is a step of the opcode-based optimizer which identifies equivalent assembly blocks and merges them into a single one. However, when blocks contained ``verbatim``, their comparison was performed incorrectly, leading to the collapse of assembly blocks which are identical except for the contents of the ``verbatim`` items. Since ``verbatim`` is only available in Yul, compilation of Solidity sources is not affected.", + "link": "https://blog.soliditylang.org/2023/11/08/verbatim-invalid-deduplication-bug/", + "introduced": "0.8.5", + "fixed": "0.8.23", + "severity": "low" + }, + { + "uid": "SOL-2023-2", + "name": "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "summary": "Optimizer sequences containing FullInliner do not preserve the evaluation order of arguments of inlined function calls in code that is not in expression-split form.", + "description": "Function call arguments in Yul are evaluated right to left. This order matters when the argument expressions have side-effects, and changing it may change contract behavior. FullInliner is an optimizer step that can replace a function call with the body of that function. The transformation involves assigning argument expressions to temporary variables, which imposes an explicit evaluation order. FullInliner was written with the assumption that this order does not necessarily have to match usual argument evaluation order because the argument expressions have no side-effects. In most circumstances this assumption is true because the default optimization step sequence contains the ExpressionSplitter step. ExpressionSplitter ensures that the code is in *expression-split form*, which means that function calls cannot appear nested inside expressions, and all function call arguments have to be variables. The assumption is, however, not guaranteed to be true in general. Version 0.6.7 introduced a setting allowing users to specify an arbitrary optimization step sequence, making it possible for the FullInliner to actually encounter argument expressions with side-effects, which can result in behavior differences between optimized and unoptimized bytecode. Contracts compiled without optimization or with the default optimization sequence are not affected. To trigger the bug the user has to explicitly choose compiler settings that contain a sequence with FullInliner step not preceded by ExpressionSplitter.", + "link": "https://blog.soliditylang.org/2023/07/19/full-inliner-non-expression-split-argument-evaluation-order-bug/", + "introduced": "0.6.7", + "fixed": "0.8.21", + "severity": "low", + "conditions": { + "yulOptimizer": true + } + }, + { + "uid": "SOL-2023-1", + "name": "MissingSideEffectsOnSelectorAccess", + "summary": "Accessing the ``.selector`` member on complex expressions leaves the expression unevaluated in the legacy code generation.", + "description": "When accessing the ``.selector`` member on an expression with side-effects, like an assignment, a function call or a conditional, the expression would not be evaluated in the legacy code generation. This would happen in expressions where the functions used in the expression were all known at compilation time, regardless of whether the whole expression could be evaluated at compilation time or not. Note that the code generated by the IR pipeline was unaffected and would behave as expected.", + "link": "https://blog.soliditylang.org/2023/07/19/missing-side-effects-on-selector-access-bug/", + "introduced": "0.6.2", + "fixed": "0.8.21", + "severity": "low", + "conditions": { + "viaIR": false + } + }, { "uid": "SOL-2022-7", "name": "StorageWriteRemovalBeforeConditionalTermination", @@ -60,7 +96,7 @@ }, { "uid": "SOL-2022-2", - "name": "NestedCallataArrayAbiReencodingSizeValidation", + "name": "NestedCalldataArrayAbiReencodingSizeValidation", "summary": "ABI-reencoding of nested dynamic calldata arrays did not always perform proper size checks against the size of calldata and could read beyond ``calldatasize()``.", "description": "Calldata validation for nested dynamic types is deferred until the first access to the nested values. Such an access may for example be a copy to memory or an index or member access to the outer type. While in most such accesses calldata validation correctly checks that the data area of the nested array is completely contained in the passed calldata (i.e. in the range [0, calldatasize()]), this check may not be performed, when ABI encoding such nested types again directly from calldata. For instance, this can happen, if a value in calldata with a nested dynamic array is passed to an external call, used in ``abi.encode`` or emitted as event. In such cases, if the data area of the nested array extends beyond ``calldatasize()``, ABI encoding it did not revert, but continued reading values from beyond ``calldatasize()`` (i.e. zero values).", "link": "https://blog.soliditylang.org/2022/05/17/calldata-reencode-size-check-bug/", diff --git a/compiler/docs/bugs.rst b/compiler/docs/bugs.rst index 75a23e49..350b1e7a 100644 --- a/compiler/docs/bugs.rst +++ b/compiler/docs/bugs.rst @@ -68,7 +68,7 @@ conditions If no conditions are given, assume that the bug is present. check This field contains different checks that report whether the smart contract - contains the bug or not. The first type of check are Javascript regular + contains the bug or not. The first type of check are JavaScript regular expressions that are to be matched against the source code ("source-regex") if the bug is present. If there is no match, then the bug is very likely not present. If there is a match, the bug might be present. For improved diff --git a/compiler/docs/bugs_by_version.json b/compiler/docs/bugs_by_version.json index 11ddd40b..156b846d 100644 --- a/compiler/docs/bugs_by_version.json +++ b/compiler/docs/bugs_by_version.json @@ -1085,7 +1085,7 @@ "bugs": [ "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1103,7 +1103,7 @@ "bugs": [ "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1120,7 +1120,7 @@ "bugs": [ "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1137,7 +1137,7 @@ "bugs": [ "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1154,7 +1154,7 @@ "bugs": [ "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1173,7 +1173,7 @@ "bugs": [ "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1191,7 +1191,7 @@ "bugs": [ "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1208,7 +1208,7 @@ "bugs": [ "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1348,7 +1348,7 @@ "bugs": [ "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1369,7 +1369,7 @@ "bugs": [ "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1389,7 +1389,7 @@ "bugs": [ "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1407,7 +1407,7 @@ "bugs": [ "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1422,10 +1422,12 @@ }, "0.6.10": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1436,10 +1438,12 @@ }, "0.6.11": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1450,10 +1454,12 @@ }, "0.6.12": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1464,9 +1470,10 @@ }, "0.6.2": { "bugs": [ + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1481,9 +1488,10 @@ }, "0.6.3": { "bugs": [ + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1498,9 +1506,10 @@ }, "0.6.4": { "bugs": [ + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", "EmptyByteArrayCopy", @@ -1515,9 +1524,10 @@ }, "0.6.5": { "bugs": [ + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1532,9 +1542,10 @@ }, "0.6.6": { "bugs": [ + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1548,9 +1559,11 @@ }, "0.6.7": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1564,9 +1577,11 @@ }, "0.6.8": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1577,10 +1592,12 @@ }, "0.6.9": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1592,10 +1609,12 @@ }, "0.7.0": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1606,10 +1625,12 @@ }, "0.7.1": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1621,10 +1642,12 @@ }, "0.7.2": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1635,10 +1658,12 @@ }, "0.7.3": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching", @@ -1648,10 +1673,12 @@ }, "0.7.4": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching" @@ -1660,10 +1687,12 @@ }, "0.7.5": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching" @@ -1672,10 +1701,12 @@ }, "0.7.6": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching" @@ -1684,10 +1715,12 @@ }, "0.8.0": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching" @@ -1696,10 +1729,12 @@ }, "0.8.1": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching" @@ -1708,46 +1743,61 @@ }, "0.8.10": { "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation" + "NestedCalldataArrayAbiReencodingSizeValidation" ], "released": "2021-11-09" }, "0.8.11": { "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "AbiEncodeCallLiteralAsFixedBytesBug" ], "released": "2021-12-20" }, "0.8.12": { "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "AbiEncodeCallLiteralAsFixedBytesBug" ], "released": "2022-02-16" }, "0.8.13": { "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "StorageWriteRemovalBeforeConditionalTermination", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "InlineAssemblyMemorySideEffects", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation" + "NestedCalldataArrayAbiReencodingSizeValidation" ], "released": "2022-03-16" }, "0.8.14": { "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "StorageWriteRemovalBeforeConditionalTermination", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", @@ -1757,6 +1807,9 @@ }, "0.8.15": { "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "StorageWriteRemovalBeforeConditionalTermination", "AbiReencodingHeadOverflowWithStaticArrayCleanup" ], @@ -1764,32 +1817,87 @@ }, "0.8.16": { "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "StorageWriteRemovalBeforeConditionalTermination" ], "released": "2022-08-08" }, "0.8.17": { - "bugs": [], + "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess" + ], "released": "2022-09-08" }, + "0.8.18": { + "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess" + ], + "released": "2023-02-01" + }, + "0.8.19": { + "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess" + ], + "released": "2023-02-22" + }, "0.8.2": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory", "KeccakCaching" ], "released": "2021-03-02" }, + "0.8.20": { + "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess" + ], + "released": "2023-05-10" + }, + "0.8.21": { + "bugs": [ + "VerbatimInvalidDeduplication" + ], + "released": "2023-07-19" + }, + "0.8.22": { + "bugs": [ + "VerbatimInvalidDeduplication" + ], + "released": "2023-10-25" + }, + "0.8.23": { + "bugs": [], + "released": "2023-11-08" + }, + "0.8.24": { + "bugs": [], + "released": "2024-01-25" + }, "0.8.3": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables", "ABIDecodeTwoDimensionalArrayMemory" ], @@ -1797,50 +1905,64 @@ }, "0.8.4": { "bugs": [ + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables" ], "released": "2021-04-21" }, "0.8.5": { "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables" ], "released": "2021-06-10" }, "0.8.6": { "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables" ], "released": "2021-06-22" }, "0.8.7": { "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "SignedImmutables" ], "released": "2021-08-11" }, "0.8.8": { "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation", + "NestedCalldataArrayAbiReencodingSizeValidation", "UserDefinedValueTypesBug", "SignedImmutables" ], @@ -1848,10 +1970,13 @@ }, "0.8.9": { "bugs": [ + "VerbatimInvalidDeduplication", + "FullInlinerNonExpressionSplitArgumentEvaluationOrder", + "MissingSideEffectsOnSelectorAccess", "AbiReencodingHeadOverflowWithStaticArrayCleanup", "DirtyBytesArrayToStorage", "DataLocationChangeInInternalOverride", - "NestedCallataArrayAbiReencodingSizeValidation" + "NestedCalldataArrayAbiReencodingSizeValidation" ], "released": "2021-09-29" } diff --git a/compiler/docs/cheatsheet.rst b/compiler/docs/cheatsheet.rst index 3a7d478b..2b68d731 100644 --- a/compiler/docs/cheatsheet.rst +++ b/compiler/docs/cheatsheet.rst @@ -2,16 +2,17 @@ Cheatsheet ********** -.. index:: operator; precedence +.. index:: operator;precedence Order of Precedence of Operators ================================ + .. include:: types/operator-precedence-table.rst -.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, codehash, send +.. index:: abi;decode, abi;encode, abi;encodePacked, abi;encodeWithSelector, abi;encodeCall, abi;encodeWithSignature -Global Variables -================ +ABI Encoding and Decoding Functions +=================================== - ``abi.decode(bytes memory encodedData, (...)) returns (...)``: :ref:`ABI `-decodes the provided data. The types are given in parentheses as second argument. @@ -24,17 +25,55 @@ Global Variables - ``abi.encodeCall(function functionPointer, (...)) returns (bytes memory)``: ABI-encodes a call to ``functionPointer`` with the arguments found in the tuple. Performs a full type-check, ensuring the types match the function signature. Result equals ``abi.encodeWithSelector(functionPointer.selector, (...))`` - ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent - to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)`` + to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)`` + +.. index:: bytes;concat, string;concat + +Members of ``bytes`` and ``string`` +==================================== + - ``bytes.concat(...) returns (bytes memory)``: :ref:`Concatenates variable number of arguments to one byte array` + - ``string.concat(...) returns (string memory)``: :ref:`Concatenates variable number of arguments to one string array` + +.. index:: address;balance, address;codehash, address;send, address;code, address;transfer + +Members of ``address`` +====================== + +- ``
.balance`` (``uint256``): balance of the :ref:`address` in Wei +- ``
.code`` (``bytes memory``): code at the :ref:`address` (can be empty) +- ``
.codehash`` (``bytes32``): the codehash of the :ref:`address` +- ``
.call(bytes memory) returns (bool, bytes memory)``: issue low-level ``CALL`` with the given payload, + returns success condition and return data +- ``
.delegatecall(bytes memory) returns (bool, bytes memory)``: issue low-level ``DELEGATECALL`` with the given payload, + returns success condition and return data +- ``
.staticcall(bytes memory) returns (bool, bytes memory)``: issue low-level ``STATICCALL`` with the given payload, + returns success condition and return data +- ``
.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, + returns ``false`` on failure +- ``
.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure + +.. index:: blockhash, blobhash, block, block;basefee, block;blobbasefee, block;chainid, block;coinbase, block;difficulty, block;gaslimit, block;number, block;prevrandao, block;timestamp +.. index:: gasleft, msg;data, msg;sender, msg;sig, msg;value, tx;gasprice, tx;origin + +Block and Transaction Properties +================================ + +- ``blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks +- ``blobhash(uint index) returns (bytes32)``: versioned hash of the ``index``-th blob associated with the current transaction. + A versioned hash consists of a single byte representing the version (currently ``0x01``), followed by the last 31 bytes + of the SHA256 hash of the KZG commitment (`EIP-4844 `_). - ``block.basefee`` (``uint``): current block's base fee (`EIP-3198 `_ and `EIP-1559 `_) +- ``block.blobbasefee`` (``uint``): current block's blob base fee (`EIP-7516 `_ and `EIP-4844 `_) - ``block.chainid`` (``uint``): current chain id - ``block.coinbase`` (``address payable``): current block miner's address -- ``block.difficulty`` (``uint``): current block difficulty +- ``block.difficulty`` (``uint``): current block difficulty (``EVM < Paris``). For other EVM versions it behaves as a deprecated alias for ``block.prevrandao`` that will be removed in the next breaking release - ``block.gaslimit`` (``uint``): current block gaslimit - ``block.number`` (``uint``): current block number +- ``block.prevrandao`` (``uint``): random number provided by the beacon chain (``EVM >= Paris``) (see `EIP-4399 `_ ) - ``block.timestamp`` (``uint``): current block timestamp in seconds since Unix epoch - ``gasleft() returns (uint256)``: remaining gas - ``msg.data`` (``bytes``): complete calldata @@ -43,6 +82,12 @@ Global Variables - ``msg.value`` (``uint``): number of wei sent with the message - ``tx.gasprice`` (``uint``): gas price of the transaction - ``tx.origin`` (``address``): sender of the transaction (full call chain) + +.. index:: assert, require, revert + +Validations and Assertions +========================== + - ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error) - ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component) @@ -50,7 +95,12 @@ Global Variables condition is ``false`` (use for malformed input or error in external component). Also provide error message. - ``revert()``: abort execution and revert state changes - ``revert(string memory message)``: abort execution and revert state changes providing an explanatory string -- ``blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks + +.. index:: cryptography, keccak256, sha256, ripemd160, ecrecover, addmod, mulmod + +Mathematical and Cryptographic Functions +======================================== + - ``keccak256(bytes memory) returns (bytes32)``: compute the Keccak-256 hash of the input - ``sha256(bytes memory) returns (bytes32)``: compute the SHA-256 hash of the input - ``ripemd160(bytes memory) returns (bytes20)``: compute the RIPEMD-160 hash of the input @@ -60,15 +110,21 @@ Global Variables arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0. - ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0. + +.. index:: this, super, selfdestruct + +Contract-related +================ + - ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` or ``address payable`` -- ``super``: the contract one level higher in the inheritance hierarchy +- ``super``: a contract one level higher in the inheritance hierarchy - ``selfdestruct(address payable recipient)``: destroy the current contract, sending its funds to the given address -- ``
.balance`` (``uint256``): balance of the :ref:`address` in Wei -- ``
.code`` (``bytes memory``): code at the :ref:`address` (can be empty) -- ``
.codehash`` (``bytes32``): the codehash of the :ref:`address` -- ``
.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, - returns ``false`` on failure -- ``
.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure + +.. index:: type;name, type;creationCode, type;runtimeCode, type;interfaceId, type;min, type;max + +Type Information +================ + - ``type(C).name`` (``string``): the name of the contract - ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information`. - ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information`. @@ -104,11 +160,11 @@ Modifiers - ``view`` for functions: Disallows modification of state. - ``payable`` for functions: Allows them to receive Ether together with a call. - ``constant`` for state variables: Disallows assignment (except initialisation), does not occupy storage slot. -- ``immutable`` for state variables: Allows exactly one assignment at construction time and is constant afterwards. Is stored in code. +- ``immutable`` for state variables: Allows assignment at construction time and is constant when deployed. Is stored in code. - ``anonymous`` for events: Does not store event signature as topic. - ``indexed`` for event parameters: Stores the parameter as topic. - ``virtual`` for functions and modifiers: Allows the function's or modifier's - behaviour to be changed in derived contracts. + behavior to be changed in derived contracts. - ``override``: States that this function, modifier or public state variable changes - the behaviour of a function or modifier in a base contract. + the behavior of a function or modifier in a base contract. diff --git a/compiler/docs/common-patterns.rst b/compiler/docs/common-patterns.rst index 1793ebd2..cbc219b5 100644 --- a/compiler/docs/common-patterns.rst +++ b/compiler/docs/common-patterns.rst @@ -18,7 +18,7 @@ introduces a potential security risk. You may read more about this on the :ref:`security_considerations` page. The following is an example of the withdrawal pattern in practice in -a contract where the goal is to send the most money to the +a contract where the goal is to send the most of some compensation, e.g. Ether, to the contract in order to become the "richest", inspired by `King of the Ether `_. @@ -34,7 +34,7 @@ you receive the funds of the person who is now the richest. address public richest; uint public mostSent; - mapping (address => uint) pendingWithdrawals; + mapping(address => uint) pendingWithdrawals; /// The amount of Ether sent was not higher than /// the currently highest amount. @@ -55,7 +55,7 @@ you receive the funds of the person who is now the richest. function withdraw() public { uint amount = pendingWithdrawals[msg.sender]; // Remember to zero the pending refund before - // sending to prevent re-entrancy attacks + // sending to prevent reentrancy attacks pendingWithdrawals[msg.sender] = 0; payable(msg.sender).transfer(amount); } diff --git a/compiler/docs/conf.py b/compiler/docs/conf.py index 10aa406f..ddc3ee81 100644 --- a/compiler/docs/conf.py +++ b/compiler/docs/conf.py @@ -31,7 +31,10 @@ def setup(sphinx): sphinx.add_lexer('Solidity', SolidityLexer) sphinx.add_lexer('Yul', YulLexer) + sphinx.add_css_file('css/fonts.css') sphinx.add_css_file('css/custom.css') + sphinx.add_css_file('css/custom-dark.css') + sphinx.add_css_file('css/pygments.css') # -- General configuration ------------------------------------------------ @@ -45,6 +48,7 @@ def setup(sphinx): 'sphinx_a4doc', 'html_extra_template_renderer', 'remix_code_links', + 'sphinx.ext.imgconverter', ] a4_base_path = os.path.dirname(__file__) + '/grammar' @@ -63,7 +67,7 @@ def setup(sphinx): # General information about the project. project = 'Solidity' -project_copyright = '2016-2021, Ethereum' +project_copyright = '2016-2023, The Solidity Authors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -131,7 +135,6 @@ def setup(sphinx): # documentation. html_theme_options = { 'logo_only': True, - 'style_nav_header_background': '#65afff', 'display_version': True, } @@ -147,12 +150,12 @@ def setup(sphinx): # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = "logo.svg" +# html_logo = "logo.svg" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +html_favicon = "_static/img/favicon.ico" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -161,7 +164,7 @@ def setup(sphinx): html_css_files = ["css/toggle.css"] -html_js_files = ["js/toggle.js"] +html_js_files = ["js/constants.js", "js/initialize.js", "js/toggle.js"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -209,7 +212,7 @@ def setup(sphinx): #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +html_show_sphinx = False # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True diff --git a/compiler/docs/contracts/constant-state-variables.rst b/compiler/docs/contracts/constant-state-variables.rst index 2e94397b..a1bc24f5 100644 --- a/compiler/docs/contracts/constant-state-variables.rst +++ b/compiler/docs/contracts/constant-state-variables.rst @@ -30,19 +30,23 @@ Not all types for constants and immutables are implemented at this time. The onl .. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.7.4; + pragma solidity ^0.8.21; uint constant X = 32**22 + 8; contract C { string constant TEXT = "abc"; bytes32 constant MY_HASH = keccak256("abc"); - uint immutable decimals; + uint immutable decimals = 18; uint immutable maxBalance; address immutable owner = msg.sender; constructor(uint decimals_, address ref) { - decimals = decimals_; + if (decimals_ != 0) + // Immutables are only immutable when deployed. + // At construction time they can be assigned to any number of times. + decimals = decimals_; + // Assignments to immutables can even access the environment. maxBalance = ref.balance; } @@ -74,25 +78,34 @@ Immutable ========= Variables declared as ``immutable`` are a bit less restricted than those -declared as ``constant``: Immutable variables can be assigned an arbitrary -value in the constructor of the contract or at the point of their declaration. -They can be assigned only once and can, from that point on, be read even during -construction time. +declared as ``constant``: Immutable variables can be assigned a +value at construction time. +The value can be changed at any time before deployment and then it becomes permanent. + +One additional restriction is that immutables can only be assigned to inside expressions for which +there is no possibility of being executed after creation. +This excludes all modifier definitions and functions other than constructors. + +There are no restrictions on reading immutable variables. +The read is even allowed to happen before the variable is written to for the first time because variables in +Solidity always have a well-defined initial value. +For this reason it is also allowed to never explicitly assign a value to an immutable. + +.. warning:: + When accessing immutables at construction time, please keep the :ref:`initialization order + ` in mind. + Even if you provide an explicit initializer, some expressions may end up being evaluated before + that initializer, especially when they are at a different level in inheritance hierarchy. + +.. note:: + Before Solidity 0.8.21 initialization of immutable variables was more restrictive. + Such variables had to be initialized exactly once at construction time and could not be read + before then. The contract creation code generated by the compiler will modify the contract's runtime code before it is returned by replacing all references to immutables with the values assigned to them. This is important if you are comparing the runtime code generated by the compiler with the one actually stored in the -blockchain. - -.. note:: - Immutables that are assigned at their declaration are only considered - initialized once the constructor of the contract is executing. - This means you cannot initialize immutables inline with a value - that depends on another immutable. You can do this, however, - inside the constructor of the contract. - - This is a safeguard against different interpretations about the order - of state variable initialization and constructor execution, especially - with regards to inheritance. +blockchain. The compiler outputs where these immutables are located in the deployed bytecode +in the ``immutableReferences`` field of the :ref:`compiler JSON standard output `. diff --git a/compiler/docs/contracts/creating-contracts.rst b/compiler/docs/contracts/creating-contracts.rst index f574a5a6..8eb4e5f5 100644 --- a/compiler/docs/contracts/creating-contracts.rst +++ b/compiler/docs/contracts/creating-contracts.rst @@ -8,7 +8,7 @@ Contracts can be created "from outside" via Ethereum transactions or from within IDEs, such as `Remix `_, make the creation process seamless using UI elements. -One way to create contracts programmatically on Ethereum is via the JavaScript API `web3.js `_. +One way to create contracts programmatically on Ethereum is via the JavaScript API `web3.js `_. It has a function called `web3.eth.Contract `_ to facilitate contract creation. diff --git a/compiler/docs/contracts/events.rst b/compiler/docs/contracts/events.rst index 8618e752..8c73c316 100644 --- a/compiler/docs/contracts/events.rst +++ b/compiler/docs/contracts/events.rst @@ -9,12 +9,13 @@ Events Solidity events give an abstraction on top of the EVM's logging functionality. Applications can subscribe and listen to these events through the RPC interface of an Ethereum client. -Events are inheritable members of contracts. When you call them, they cause the +Events can be defined at file level or as inheritable members of contracts (including interfaces and libraries). +When you call them, they cause the arguments to be stored in the transaction's log - a special data structure -in the blockchain. These logs are associated with the address of the contract, +in the blockchain. These logs are associated with the address of the contract that emitted them, are incorporated into the blockchain, and stay there as long as a block is accessible (forever as of now, but this might -change with Serenity). The Log and its event data is not accessible from within +change in the future). The Log and its event data is not accessible from within contracts (not even from the contract that created them). It is possible to request a Merkle proof for logs, so if @@ -151,6 +152,6 @@ The output of the above looks like the following (trimmed): Additional Resources for Understanding Events ============================================= -- `Javascript documentation `_ +- `JavaScript documentation `_ - `Example usage of events `_ - `How to access them in js `_ diff --git a/compiler/docs/contracts/function-modifiers.rst b/compiler/docs/contracts/function-modifiers.rst index f2311954..454e12ab 100644 --- a/compiler/docs/contracts/function-modifiers.rst +++ b/compiler/docs/contracts/function-modifiers.rst @@ -6,7 +6,7 @@ Function Modifiers ****************** -Modifiers can be used to change the behaviour of functions in a declarative way. +Modifiers can be used to change the behavior of functions in a declarative way. For example, you can use a modifier to automatically check a condition prior to executing the function. @@ -19,6 +19,7 @@ if they are marked ``virtual``. For details, please see // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.1 <0.9.0; + // This will report a warning due to deprecated selfdestruct contract owned { constructor() { owner = payable(msg.sender); } @@ -60,7 +61,7 @@ if they are marked ``virtual``. For details, please see } contract Register is priced, destructible { - mapping (address => bool) registeredAddresses; + mapping(address => bool) registeredAddresses; uint price; constructor(uint initialPrice) { price = initialPrice; } @@ -131,7 +132,7 @@ variables are set to their :ref:`default values` just as if the f body. The ``_`` symbol can appear in the modifier multiple times. Each occurrence is replaced with -the function body. +the function body, and the function returns the return value of the final occurrence. Arbitrary expressions are allowed for modifier arguments and in this context, all symbols visible from the function are visible in the modifier. Symbols diff --git a/compiler/docs/contracts/functions.rst b/compiler/docs/contracts/functions.rst index f14e4b8a..5b82a93b 100644 --- a/compiler/docs/contracts/functions.rst +++ b/compiler/docs/contracts/functions.rst @@ -1,4 +1,4 @@ -.. index:: ! functions +.. index:: ! functions, ! function;free .. _functions: @@ -250,7 +250,7 @@ Reverting a state change is not considered a "state modification", as only chang state made previously in code that did not have the ``view`` or ``pure`` restriction are reverted and that code has the option to catch the ``revert`` and not pass it on. -This behaviour is also in line with the ``STATICCALL`` opcode. +This behavior is also in line with the ``STATICCALL`` opcode. .. warning:: It is not possible to prevent functions from reading the state at the level @@ -277,7 +277,7 @@ This behaviour is also in line with the ``STATICCALL`` opcode. Special Functions ================= -.. index:: ! receive ether function, function;receive ! receive +.. index:: ! receive ether function, function;receive, ! receive .. _receive-ether-function: @@ -297,7 +297,7 @@ on plain Ether transfers (e.g. via ``.send()`` or ``.transfer()``). If no such function exists, but a payable :ref:`fallback function ` exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the -contract cannot receive Ether through regular transactions and throws an +contract cannot receive Ether through a transaction that does not represent a payable function call and throws an exception. In the worst case, the ``receive`` function can only rely on 2300 gas being diff --git a/compiler/docs/contracts/inheritance.rst b/compiler/docs/contracts/inheritance.rst index a33a36d2..c787c5af 100644 --- a/compiler/docs/contracts/inheritance.rst +++ b/compiler/docs/contracts/inheritance.rst @@ -40,7 +40,7 @@ Details are given in the following example. // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; - + // This will report a warning due to deprecated selfdestruct contract Owned { constructor() { owner = payable(msg.sender); } @@ -54,7 +54,7 @@ Details are given in the following example. // accessed externally via `this`, though. contract Destructible is Owned { // The keyword `virtual` means that the function can change - // its behaviour in derived classes ("overriding"). + // its behavior in derived classes ("overriding"). function destroy() virtual public { if (msg.sender == owner) selfdestruct(owner); } @@ -115,7 +115,7 @@ Details are given in the following example. // Here, we only specify `override` and not `virtual`. // This means that contracts deriving from `PriceFeed` - // cannot change the behaviour of `destroy` anymore. + // cannot change the behavior of `destroy` anymore. function destroy() public override(Destructible, Named) { Named.destroy(); } function get() public view returns(uint r) { return info; } @@ -130,6 +130,7 @@ seen in the following example: // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; + // This will report a warning due to deprecated selfdestruct contract owned { constructor() { owner = payable(msg.sender); } @@ -162,6 +163,7 @@ explicitly in the final override, but this function will bypass // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; + // This will report a warning due to deprecated selfdestruct contract owned { constructor() { owner = payable(msg.sender); } @@ -291,7 +293,7 @@ and ends at a contract mentioning a function with that signature that does not override. If you do not mark a function that overrides as ``virtual``, derived -contracts can no longer change the behaviour of that function. +contracts can no longer change the behavior of that function. .. note:: @@ -434,11 +436,11 @@ You can use internal parameters in a constructor (for example storage pointers). the contract has to be marked :ref:`abstract `, because these parameters cannot be assigned valid values from outside but only through the constructors of derived contracts. -.. warning :: +.. warning:: Prior to version 0.4.22, constructors were defined as functions with the same name as the contract. This syntax was deprecated and is not allowed anymore in version 0.5.0. -.. warning :: +.. warning:: Prior to version 0.7.0, you had to specify the visibility of constructors as either ``internal`` or ``public``. @@ -485,7 +487,7 @@ One way is directly in the inheritance list (``is Base(7)``). The other is in the way a modifier is invoked as part of the derived constructor (``Base(y * y)``). The first way to do it is more convenient if the constructor argument is a -constant and defines the behaviour of the contract or +constant and defines the behavior of the contract or describes it. The second way has to be used if the constructor arguments of the base depend on those of the derived contract. Arguments have to be given either in the @@ -588,9 +590,11 @@ One area where inheritance linearization is especially important and perhaps not Inheriting Different Kinds of Members of the Same Name ====================================================== -It is an error when any of the following pairs in a contract have the same name due to inheritance: - - a function and a modifier - - a function and an event - - an event and a modifier +The only situations where, due to inheritance, a contract may contain multiple definitions sharing +the same name are: -As an exception, a state variable getter can override an external function. +- Overloading of functions. +- Overriding of virtual functions. +- Overriding of external virtual functions by state variable getters. +- Overriding of virtual modifiers. +- Overloading of events. diff --git a/compiler/docs/contracts/interfaces.rst b/compiler/docs/contracts/interfaces.rst index cc71cf64..a3085667 100644 --- a/compiler/docs/contracts/interfaces.rst +++ b/compiler/docs/contracts/interfaces.rst @@ -65,7 +65,7 @@ inheritance. Types defined inside interfaces and other contract-like structures can be accessed from other contracts: ``Token.TokenType`` or ``Token.Coin``. -.. warning: +.. warning:: Interfaces have supported ``enum`` types since :doc:`Solidity version 0.5.0 <050-breaking-changes>`, make sure the pragma version specifies this version as a minimum. diff --git a/compiler/docs/contracts/using-for.rst b/compiler/docs/contracts/using-for.rst index b4a60585..a05cb277 100644 --- a/compiler/docs/contracts/using-for.rst +++ b/compiler/docs/contracts/using-for.rst @@ -1,4 +1,4 @@ -.. index:: ! using for, library +.. index:: ! using for, library, ! operator;user-defined, function;free .. _using-for: @@ -6,36 +6,87 @@ Using For ********* -The directive ``using A for B;`` can be used to attach -functions (``A``) as member functions to any type (``B``). -These functions will receive the object they are called on +The directive ``using A for B`` can be used to attach +functions (``A``) as operators to user-defined value types +or as member functions to any type (``B``). +The member functions receive the object they are called on as their first parameter (like the ``self`` variable in Python). +The operator functions receive operands as parameters. It is valid either at file level or inside a contract, at contract level. The first part, ``A``, can be one of: -- a list of file-level or library functions (``using {f, g, h, L.t} for uint;``) - - only those functions will be attached to the type. -- the name of a library (``using L for uint;``) - - all functions (both public and internal ones) of the library are attached to the type +- A list of functions, optionally with an operator name assigned (e.g. + ``using {f, g as +, h, L.t} for uint``). + If no operator is specified, the function can be either a library function or a free function and + is attached to the type as a member function. + Otherwise it must be a free function and it becomes the definition of that operator on the type. +- The name of a library (e.g. ``using L for uint``) - + all non-private functions of the library are attached to the type + as member functions At file level, the second part, ``B``, has to be an explicit type (without data location specifier). -Inside contracts, you can also use ``using L for *;``, +Inside contracts, you can also use ``*`` in place of the type (e.g. ``using L for *;``), which has the effect that all functions of the library ``L`` are attached to *all* types. -If you specify a library, *all* functions in the library are attached, +If you specify a library, *all* non-private functions in the library get attached, even those where the type of the first parameter does not match the type of the object. The type is checked at the point the function is called and function overload resolution is performed. -If you use a list of functions (``using {f, g, h, L.t} for uint;``), +If you use a list of functions (e.g. ``using {f, g, h, L.t} for uint``), then the type (``uint``) has to be implicitly convertible to the first parameter of each of these functions. This check is performed even if none of these functions are called. +Note that private library functions can only be specified when ``using for`` is inside a library. + +If you define an operator (e.g. ``using {f as +} for T``), then the type (``T``) must be a +:ref:`user-defined value type ` and the definition must be a ``pure`` function. +Operator definitions must be global. +The following operators can be defined this way: + ++------------+----------+---------------------------------------------+ +| Category | Operator | Possible signatures | ++============+==========+=============================================+ +| Bitwise | ``&`` | ``function (T, T) pure returns (T)`` | +| +----------+---------------------------------------------+ +| | ``|`` | ``function (T, T) pure returns (T)`` | +| +----------+---------------------------------------------+ +| | ``^`` | ``function (T, T) pure returns (T)`` | +| +----------+---------------------------------------------+ +| | ``~`` | ``function (T) pure returns (T)`` | ++------------+----------+---------------------------------------------+ +| Arithmetic | ``+`` | ``function (T, T) pure returns (T)`` | +| +----------+---------------------------------------------+ +| | ``-`` | ``function (T, T) pure returns (T)`` | +| + +---------------------------------------------+ +| | | ``function (T) pure returns (T)`` | +| +----------+---------------------------------------------+ +| | ``*`` | ``function (T, T) pure returns (T)`` | +| +----------+---------------------------------------------+ +| | ``/`` | ``function (T, T) pure returns (T)`` | +| +----------+---------------------------------------------+ +| | ``%`` | ``function (T, T) pure returns (T)`` | ++------------+----------+---------------------------------------------+ +| Comparison | ``==`` | ``function (T, T) pure returns (bool)`` | +| +----------+---------------------------------------------+ +| | ``!=`` | ``function (T, T) pure returns (bool)`` | +| +----------+---------------------------------------------+ +| | ``<`` | ``function (T, T) pure returns (bool)`` | +| +----------+---------------------------------------------+ +| | ``<=`` | ``function (T, T) pure returns (bool)`` | +| +----------+---------------------------------------------+ +| | ``>`` | ``function (T, T) pure returns (bool)`` | +| +----------+---------------------------------------------+ +| | ``>=`` | ``function (T, T) pure returns (bool)`` | ++------------+----------+---------------------------------------------+ + +Note that unary and binary ``-`` need separate definitions. +The compiler will choose the right definition based on how the operator is invoked. The ``using A for B;`` directive is active only within the current scope (either the contract or the current module/source unit), @@ -45,7 +96,7 @@ outside of the contract or module in which it is used. When the directive is used at file level and applied to a user-defined type which was defined at file level in the same file, the word ``global`` can be added at the end. This will have the -effect that the functions are attached to the type everywhere +effect that the functions and operators are attached to the type everywhere the type is available (including other files), not only in the scope of the using statement. @@ -149,3 +200,37 @@ if you pass memory or value types, a copy will be performed, even in case of the ``self`` variable. The only situation where no copy will be performed is when storage reference variables are used or when internal library functions are called. + +Another example shows how to define a custom operator for a user-defined type: + +.. code-block:: solidity + + // SPDX-License-Identifier: GPL-3.0 + pragma solidity ^0.8.19; + + type UFixed16x2 is uint16; + + using { + add as +, + div as / + } for UFixed16x2 global; + + uint32 constant SCALE = 100; + + function add(UFixed16x2 a, UFixed16x2 b) pure returns (UFixed16x2) { + return UFixed16x2.wrap(UFixed16x2.unwrap(a) + UFixed16x2.unwrap(b)); + } + + function div(UFixed16x2 a, UFixed16x2 b) pure returns (UFixed16x2) { + uint32 a32 = UFixed16x2.unwrap(a); + uint32 b32 = UFixed16x2.unwrap(b); + uint32 result32 = a32 * SCALE / b32; + require(result32 <= type(uint16).max, "Divide overflow"); + return UFixed16x2.wrap(uint16(a32 * SCALE / b32)); + } + + contract Math { + function avg(UFixed16x2 a, UFixed16x2 b) public pure returns (UFixed16x2) { + return (a + b) / UFixed16x2.wrap(200); + } + } diff --git a/compiler/docs/contracts/visibility-and-getters.rst b/compiler/docs/contracts/visibility-and-getters.rst index 8932c507..5bf46dea 100644 --- a/compiler/docs/contracts/visibility-and-getters.rst +++ b/compiler/docs/contracts/visibility-and-getters.rst @@ -200,12 +200,12 @@ The next example is more complex: struct Data { uint a; bytes3 b; - mapping (uint => uint) map; + mapping(uint => uint) map; uint[3] c; uint[] d; bytes e; } - mapping (uint => mapping(bool => Data[])) public data; + mapping(uint => mapping(bool => Data[])) public data; } It generates a function of the following form. The mapping and arrays (with the diff --git a/compiler/docs/contributing.rst b/compiler/docs/contributing.rst index 9da7f02d..ac4e2375 100644 --- a/compiler/docs/contributing.rst +++ b/compiler/docs/contributing.rst @@ -12,7 +12,7 @@ In particular, we appreciate support in the following areas: `"good first issue" `_ which are meant as introductory issues for external contributors. * Improving the documentation. -* Translating the documentation into more languages. +* `Translating `_ the documentation into more languages. * Responding to questions from other users on `StackExchange `_ and the `Solidity Gitter Chat `_. @@ -28,11 +28,11 @@ Team Calls ========== If you have issues or pull requests to discuss, or are interested in hearing what -the team and contributors are working on, you can join our public team calls: +the team and contributors are working on, you can join our public team call: -- Mondays and Wednesdays at 3pm CET/CEST. +- Wednesdays at 3PM CET/CEST. -Both calls take place on `Jitsi `_. +The call takes place on `Jitsi `_. How to Report Issues ==================== @@ -45,11 +45,14 @@ reporting issues, please mention the following details: * Source code (if applicable). * Operating system. * Steps to reproduce the issue. -* Actual vs. expected behaviour. +* Actual vs. expected behavior. Reducing the source code that caused the issue to a bare minimum is always very helpful, and sometimes even clarifies a misunderstanding. +For technical discussions about language design, a post in the +`Solidity forum `_ is the correct place (see :ref:`solidity_language_design`). + Workflow for Pull Requests ========================== @@ -77,6 +80,9 @@ Finally, please make sure you respect the `coding style for this project. Also, even though we do CI testing, please test your code and ensure that it builds locally before submitting a pull request. +We highly recommend going through our `review checklist `_ before submitting the pull request. +We thoroughly review every PR and will help you get it right, but there are many common problems that can be easily avoided, making the review much smoother. + Thank you for your help! Running the Compiler Tests @@ -87,8 +93,7 @@ Prerequisites For running all compiler tests you may want to optionally install a few dependencies (`evmone `_, -`libz3 `_, and -`libhera `_). +`libz3 `_). On macOS systems, some of the testing scripts expect GNU coreutils to be installed. This can be easiest accomplished using Homebrew: ``brew install coreutils``. @@ -96,9 +101,9 @@ This can be easiest accomplished using Homebrew: ``brew install coreutils``. On Windows systems, make sure that you have a privilege to create symlinks, otherwise several tests may fail. Administrators should have that privilege, but you may also -`grant it to other users `_ +`grant it to other users `_ or -`enable Developer Mode `_. +`enable Developer Mode `_. Running the Tests ----------------- @@ -109,7 +114,7 @@ Running ``build/test/soltest`` or its wrapper ``scripts/soltest.sh`` is sufficie The ``./scripts/tests.sh`` script executes most Solidity tests automatically, including those bundled into the `Boost C++ Test Framework `_ -application ``soltest`` (or its wrapper ``scripts/soltest.sh``), as well as command line tests and +application ``soltest`` (or its wrapper ``scripts/soltest.sh``), as well as command-line tests and compilation tests. The test system automatically tries to discover the location of @@ -123,13 +128,7 @@ for the ``evmone`` shared object can be specified via the ``ETH_EVMONE`` environ If you do not have it installed, you can skip these tests by passing the ``--no-semantic-tests`` flag to ``scripts/soltest.sh``. -Running Ewasm tests is disabled by default and can be explicitly enabled -via ``./scripts/soltest.sh --ewasm`` and requires `hera `_ -to be found by ``soltest``. -The mechanism for locating the ``hera`` library is the same as for ``evmone``, except that the -variable for specifying an explicit location is called ``ETH_HERA``. - -The ``evmone`` and ``hera`` libraries should both end with the file name +The ``evmone`` library should both end with the file name extension ``.so`` on Linux, ``.dll`` on Windows systems and ``.dylib`` on macOS. For running SMT tests, the ``libz3`` library must be installed and locatable @@ -140,7 +139,7 @@ SMT tests by exporting ``SMT_FLAGS=--no-smt`` before running ``./scripts/tests.s running ``./scripts/soltest.sh --no-smt``. These tests are ``libsolidity/smtCheckerTests`` and ``libsolidity/smtCheckerTestsJSON``. -.. note :: +.. note:: To get a list of all unit tests run by Soltest, run ``./build/test/soltest --list_content=HRF``. @@ -161,7 +160,7 @@ See especially: - `run_test (-t) `_ to run specific tests cases, and - `report-level (-r) `_ give a more detailed report. -.. note :: +.. note:: Those working in a Windows environment wanting to run the above basic sets without libz3. Using Git Bash, you use: ``./build/test/Release/soltest.exe -- --no-smt``. @@ -169,6 +168,7 @@ See especially: If you want to debug using GDB, make sure you build differently than the "usual". For example, you could run the following command in your ``build`` folder: + .. code-block:: bash cmake -DCMAKE_BUILD_TYPE=Debug .. @@ -239,7 +239,7 @@ provides a way to edit, update or skip the current contract file, or quit the ap It offers several options for failing tests: -- ``edit``: ``isoltest`` tries to open the contract in an editor so you can adjust it. It either uses the editor given on the command line (as ``isoltest --editor /path/to/editor``), in the environment variable ``EDITOR`` or just ``/usr/bin/editor`` (in that order). +- ``edit``: ``isoltest`` tries to open the contract in an editor so you can adjust it. It either uses the editor given on the command-line (as ``isoltest --editor /path/to/editor``), in the environment variable ``EDITOR`` or just ``/usr/bin/editor`` (in that order). - ``update``: Updates the expectations for contract under test. This updates the annotations by removing unmet expectations and adding missing expectations. The test is then run again. - ``skip``: Skips the execution of this particular test. - ``quit``: Quits ``isoltest``. @@ -269,6 +269,62 @@ and re-run the test. It now passes again: Do not put more than one contract into a single file, unless you are testing inheritance or cross-contract calls. Each file should test one aspect of your new feature. +Command-line Tests +------------------ + +Our suite of end-to-end command-line tests checks the behaviour of the compiler binary as a whole +in various scenarios. +These tests are located in `test/cmdlineTests/ `_, +one per subdirectory, and can be executed using the ``cmdlineTests.sh`` script. + +By default the script runs all available tests. +You can also provide one or more `file name patterns `_, +in which case only the tests matching at least one pattern will be executed. +It is also possible to exclude files matching a specific pattern by prefixing it with ``--exclude``. + +By default the script assumes that a ``solc`` binary is available inside the ``build/`` subdirectory +inside the working copy. +If you build the compiler outside of the source tree, you can use the ``SOLIDITY_BUILD_DIR`` environment +variable to specify a different location for the build directory. + +Example: + +.. code-block:: bash + + export SOLIDITY_BUILD_DIR=~/solidity/build/ + test/cmdlineTests.sh "standard_*" "*_yul_*" --exclude "standard_yul_*" + +The commands above will run tests from directories starting with ``test/cmdlineTests/standard_`` and +subdirectories of ``test/cmdlineTests/`` that have ``_yul_`` somewhere in the name, +but no test whose name starts with ``standard_yul_`` will be executed. +It will also assume that the file ``solidity/build/solc/solc`` inside your home directory is the +compiler binary (unless you are on Windows -- then ``solidity/build/solc/Release/solc.exe``). + +There are several kinds of command-line tests: + +- *Standard JSON test*: contains at least an ``input.json`` file. + In general may contain: + + - ``input.json``: input file to be passed to the ``--standard-json`` option on the command line. + - ``output.json``: expected Standard JSON output. + - ``args``: extra command-line arguments passed to ``solc``. + +- *CLI test*: contains at least an ``input.*`` file (other than ``input.json``). + In general may contain: + + - ``input.*``: a single input file, whose name will be supplied to ``solc`` on the command line. + Usually ``input.sol`` or ``input.yul``. + - ``args``: extra command-line arguments passed to ``solc``. + - ``stdin``: content to be passed to ``solc`` via standard input. + - ``output``: expected content of the standard output. + - ``err``: expected content of the standard error output. + - ``exit``: expected exit code. If not provided, zero is expected. + +- *Script test*: contains a ``test.*`` file. + In general may contain: + + - ``test.*``: a single script to run, usually ``test.sh`` or ``test.py``. + The script must be executable. Running the Fuzzer via AFL ========================== @@ -350,7 +406,7 @@ The AFL documentation states that the corpus (the initial input files) should no too large. The files themselves should not be larger than 1 kB and there should be at most one input file per functionality, so better start with a small number of. There is also a tool called ``afl-cmin`` that can trim input files -that result in similar behaviour of the binary. +that result in similar behavior of the binary. Now run the fuzzer (the ``-m`` extends the size of memory to 60 MB): @@ -397,18 +453,17 @@ contributions to Solidity. English Language ---------------- -Use English, with British English spelling preferred, unless using project or brand names. Try to reduce the usage of -local slang and references, making your language as clear to all readers as possible. Below are some references to help: +Use International English, unless using project or brand names. Try to reduce the usage of +local slang and references, making your language as clear to all readers as possible. +Below are some references to help: * `Simplified technical English `_ * `International English `_ -* `British English spelling `_ - .. note:: While the official Solidity documentation is written in English, there are community contributed :ref:`translations` - in other languages available. Please refer to the `translation guide `_ + in other languages available. Please refer to the `translation guide `_ for information on how to contribute to the community translations. Title Case for Headings @@ -477,6 +532,8 @@ Running Documentation Tests Make sure your contributions pass our documentation tests by running ``./docs/docs.sh`` that installs dependencies needed for documentation and checks for any problems such as broken links or syntax issues. +.. _solidity_language_design: + Solidity Language Design ======================== diff --git a/compiler/docs/control-structures.rst b/compiler/docs/control-structures.rst index 18a52221..ea8a9c56 100644 --- a/compiler/docs/control-structures.rst +++ b/compiler/docs/control-structures.rst @@ -173,7 +173,6 @@ parameters from the function declaration, but can be in arbitrary order. function set(uint key, uint value) public { data[key] = value; } - } Omitted Names in Function Definitions @@ -366,13 +365,13 @@ i.e. the following is not valid: ``(x, uint y) = (1, 2);`` .. warning:: Be careful when assigning to multiple variables at the same time when reference types are involved, because it could lead to unexpected - copying behaviour. + copying behavior. Complications for Arrays and Structs ------------------------------------ The semantics of assignments are more complicated for non-value types like arrays and structs, -including ``bytes`` and ``string``, see :ref:`Data location and assignment behaviour ` for details. +including ``bytes`` and ``string``, see :ref:`Data location and assignment behavior ` for details. In the example below the call to ``g(x)`` has no effect on ``x`` because it creates an independent copy of the storage value in memory. However, ``h(x)`` successfully modifies ``x`` @@ -511,7 +510,7 @@ additional checks. Since Solidity 0.8.0, all arithmetic operations revert on over- and underflow by default, thus making the use of these libraries unnecessary. -To obtain the previous behaviour, an ``unchecked`` block can be used: +To obtain the previous behavior, an ``unchecked`` block can be used: .. code-block:: solidity @@ -651,7 +650,7 @@ in the following situations: For the following cases, the error data from the external call (if provided) is forwarded. This means that it can either cause -an `Error` or a `Panic` (or whatever else was given): +an ``Error`` or a ``Panic`` (or whatever else was given): #. If a ``.transfer()`` fails. #. If you call a function via a message call but it does not finish @@ -686,7 +685,7 @@ and ``assert`` for internal error checking. addr.transfer(msg.value / 2); // Since transfer throws an exception on failure and // cannot call back here, there should be no way for us to - // still have half of the money. + // still have half of the Ether. assert(address(this).balance == balanceBeforeTransfer - msg.value / 2); return address(this).balance; } @@ -720,7 +719,7 @@ The ``revert`` statement takes a custom error as direct argument without parenth revert CustomError(arg1, arg2); -For backwards-compatibility reasons, there is also the ``revert()`` function, which uses parentheses +For backward-compatibility reasons, there is also the ``revert()`` function, which uses parentheses and accepts a string: revert(); diff --git a/compiler/docs/examples/blind-auction.rst b/compiler/docs/examples/blind-auction.rst index b0a86f24..47e19033 100644 --- a/compiler/docs/examples/blind-auction.rst +++ b/compiler/docs/examples/blind-auction.rst @@ -16,11 +16,11 @@ Simple Open Auction =================== The general idea of the following simple auction contract is that everyone can -send their bids during a bidding period. The bids already include sending money -/ Ether in order to bind the bidders to their bid. If the highest bid is -raised, the previous highest bidder gets their money back. After the end of +send their bids during a bidding period. The bids already include sending some compensation, +e.g. Ether, in order to bind the bidders to their bid. If the highest bid is +raised, the previous highest bidder gets their Ether back. After the end of the bidding period, the contract has to be called manually for the beneficiary -to receive their money - contracts cannot activate themselves. +to receive their Ether - contracts cannot activate themselves. .. code-block:: solidity @@ -92,19 +92,19 @@ to receive their money - contracts cannot activate themselves. revert AuctionAlreadyEnded(); // If the bid is not higher, send the - // money back (the revert statement + // Ether back (the revert statement // will revert all changes in this // function execution including - // it having received the money). + // it having received the Ether). if (msg.value <= highestBid) revert BidNotHighEnough(highestBid); if (highestBid != 0) { - // Sending back the money by simply using + // Sending back the Ether by simply using // highestBidder.send(highestBid) is a security risk // because it could execute an untrusted contract. // It is always safer to let the recipients - // withdraw their money themselves. + // withdraw their Ether themselves. pendingReturns[highestBidder] += highestBid; } highestBidder = msg.sender; @@ -177,19 +177,19 @@ During the **bidding period**, a bidder does not actually send their bid, but only a hashed version of it. Since it is currently considered practically impossible to find two (sufficiently long) values whose hash values are equal, the bidder commits to the bid by that. After the end of the bidding period, -the bidders have to reveal their bids: They send their values unencrypted and +the bidders have to reveal their bids: They send their values unencrypted, and the contract checks that the hash value is the same as the one provided during the bidding period. Another challenge is how to make the auction **binding and blind** at the same -time: The only way to prevent the bidder from just not sending the money after +time: The only way to prevent the bidder from just not sending the Ether after they won the auction is to make them send it together with the bid. Since value transfers cannot be blinded in Ethereum, anyone can see the value. The following contract solves this problem by accepting any value that is larger than the highest bid. Since this can of course only be checked during the reveal phase, some bids might be **invalid**, and this is on purpose (it -even provides an explicit flag to place invalid bids with high value +even provides an explicit flag to place invalid bids with high-value transfers): Bidders can confuse competition by placing several high or low invalid bids. diff --git a/compiler/docs/examples/micropayment.rst b/compiler/docs/examples/micropayment.rst index c95d7c5e..50f28005 100644 --- a/compiler/docs/examples/micropayment.rst +++ b/compiler/docs/examples/micropayment.rst @@ -2,7 +2,7 @@ Micropayment Channel ******************** -In this section we will learn how to build an example implementation +In this section, we will learn how to build an example implementation of a payment channel. It uses cryptographic signatures to make repeated transfers of Ether between the same parties secure, instantaneous, and without transaction fees. For the example, we need to understand how to @@ -17,14 +17,14 @@ Alice is the sender and Bob is the recipient. Alice only needs to send cryptographically signed messages off-chain (e.g. via email) to Bob and it is similar to writing checks. -Alice and Bob use signatures to authorise transactions, which is possible with smart contracts on Ethereum. +Alice and Bob use signatures to authorize transactions, which is possible with smart contracts on Ethereum. Alice will build a simple smart contract that lets her transmit Ether, but instead of calling a function herself to initiate a payment, she will let Bob do that, and therefore pay the transaction fee. The contract will work as follows: 1. Alice deploys the ``ReceiverPays`` contract, attaching enough Ether to cover the payments that will be made. - 2. Alice authorises a payment by signing a message with her private key. + 2. Alice authorizes a payment by signing a message with her private key. 3. Alice sends the cryptographically signed message to Bob. The message does not need to be kept secret (explained later), and the mechanism for sending it does not matter. 4. Bob claims his payment by presenting the signed message to the smart contract, it verifies the @@ -36,7 +36,7 @@ Creating the signature Alice does not need to interact with the Ethereum network to sign the transaction, the process is completely offline. In this tutorial, we will sign messages in the browser -using `web3.js `_ and +using `web3.js `_ and `MetaMask `_, using the method described in `EIP-712 `_, as it provides a number of other security benefits. @@ -86,7 +86,7 @@ Packing arguments Now that we have identified what information to include in the signed message, we are ready to put the message together, hash it, and sign it. For simplicity, we concatenate the data. The `ethereumjs-abi `_ -library provides a function called ``soliditySHA3`` that mimics the behaviour of +library provides a function called ``soliditySHA3`` that mimics the behavior of Solidity's ``keccak256`` function applied to arguments encoded using ``abi.encodePacked``. Here is a JavaScript function that creates the proper signature for the ``ReceiverPays`` example: @@ -144,6 +144,7 @@ The full contract // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; + // This will report a warning due to deprecated selfdestruct contract ReceiverPays { address owner = msg.sender; @@ -259,7 +260,7 @@ Messages are cryptographically signed by the sender and then transmitted directl Each message includes the following information: * The smart contract's address, used to prevent cross-contract replay attacks. - * The total amount of Ether that is owed the recipient so far. + * The total amount of Ether that is owed to the recipient so far. A payment channel is closed just once, at the end of a series of transfers. Because of this, only one of the messages sent is redeemed. This is why @@ -341,6 +342,7 @@ The full contract // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; + // This will report a warning due to deprecated selfdestruct contract SimplePaymentChannel { address payable public sender; // The account sending payments. address payable public recipient; // The account receiving the payments. diff --git a/compiler/docs/examples/modular.rst b/compiler/docs/examples/modular.rst index 697699ae..9ae2849c 100644 --- a/compiler/docs/examples/modular.rst +++ b/compiler/docs/examples/modular.rst @@ -7,7 +7,7 @@ Modular Contracts A modular approach to building your contracts helps you reduce the complexity and improve the readability which will help to identify bugs and vulnerabilities during development and code review. -If you specify and control the behaviour of each module in isolation, the +If you specify and control the behavior of each module in isolation, the interactions you have to consider are only those between the module specifications and not every other moving part of the contract. In the example below, the contract uses the ``move`` method @@ -34,7 +34,7 @@ and the sum of all balances is an invariant across the lifetime of the contract. contract Token { mapping(address => uint256) balances; using Balances for *; - mapping(address => mapping (address => uint256)) allowed; + mapping(address => mapping(address => uint256)) allowed; event Transfer(address from, address to, uint amount); event Approval(address owner, address spender, uint amount); diff --git a/compiler/docs/examples/safe-remote.rst b/compiler/docs/examples/safe-remote.rst index e42b017f..a2651af2 100644 --- a/compiler/docs/examples/safe-remote.rst +++ b/compiler/docs/examples/safe-remote.rst @@ -6,18 +6,18 @@ Safe Remote Purchase Purchasing goods remotely currently requires multiple parties that need to trust each other. The simplest configuration involves a seller and a buyer. The buyer would like to receive -an item from the seller and the seller would like to get money (or an equivalent) +an item from the seller and the seller would like to get some compensation, e.g. Ether, in return. The problematic part is the shipment here: There is no way to determine for sure that the item arrived at the buyer. There are multiple ways to solve this problem, but all fall short in one or the other way. In the following example, both parties have to put twice the value of the item into the -contract as escrow. As soon as this happened, the money will stay locked inside +contract as escrow. As soon as this happened, the Ether will stay locked inside the contract until the buyer confirms that they received the item. After that, the buyer is returned the value (half of their deposit) and the seller gets three times the value (their deposit plus the value). The idea behind this is that both parties have an incentive to resolve the situation or otherwise -their money is locked forever. +their Ether is locked forever. This contract of course does not solve the problem, but gives an overview of how you can use state machine-like constructs inside a contract. diff --git a/compiler/docs/ext/remix_code_links.py b/compiler/docs/ext/remix_code_links.py index 2fc15ddd..55fc0ef5 100644 --- a/compiler/docs/ext/remix_code_links.py +++ b/compiler/docs/ext/remix_code_links.py @@ -22,23 +22,16 @@ def remix_code_url(source_code, language, solidity_version): # NOTE: base64 encoded data may contain +, = and / characters. Remix seems to handle them just # fine without any escaping. base64_encoded_source = base64.b64encode(source_code.encode('utf-8')).decode('ascii') - return f"https://remix.ethereum.org/?language={language}&version={solidity_version}&code={base64_encoded_source}" + return f"https://remix.ethereum.org/?#language={language}&version={solidity_version}&code={base64_encoded_source}" def build_remix_link_node(url): - link_icon_node = docutils.nodes.inline() - link_icon_node.set_class('link-icon') - - link_text_node = docutils.nodes.inline(text="open in Remix") - link_text_node.set_class('link-text') - - reference_node = docutils.nodes.reference('', '', internal=False, refuri=url) - reference_node.set_class('remix-link') - reference_node += [link_icon_node, link_text_node] + reference_node = docutils.nodes.reference('', 'open in Remix', internal=False, refuri=url, target='_blank') + reference_node['classes'].append('remix-link') paragraph_node = docutils.nodes.paragraph() - paragraph_node.set_class('remix-link-container') - paragraph_node += reference_node + paragraph_node['classes'].append('remix-link-container') + paragraph_node.append(reference_node) return paragraph_node @@ -49,22 +42,24 @@ def insert_remix_link(app, doctree, solidity_version): for literal_block_node in doctree.traverse(docutils.nodes.literal_block): assert 'language' in literal_block_node.attributes language = literal_block_node.attributes['language'].lower() - if language in ['solidity', 'yul']: - text_nodes = list(literal_block_node.traverse(docutils.nodes.Text)) - assert len(text_nodes) == 1 - - remix_url = remix_code_url(text_nodes[0], language, solidity_version) - url_length = len(remix_url.encode('utf-8')) - if url_length > MAX_SAFE_URL_LENGTH: - logger.warning( - "Remix URL generated from the code snippet exceeds the maximum safe URL length " - " (%d > %d bytes).", - url_length, - MAX_SAFE_URL_LENGTH, - location=(literal_block_node.source, literal_block_node.line), - ) - - insert_node_before(literal_block_node, build_remix_link_node(remix_url)) + if language not in ['solidity', 'yul']: + continue + + text_nodes = list(literal_block_node.traverse(docutils.nodes.Text)) + assert len(text_nodes) == 1 + + remix_url = remix_code_url(text_nodes[0], language, solidity_version) + url_length = len(remix_url.encode('utf-8')) + if url_length > MAX_SAFE_URL_LENGTH: + logger.warning( + "Remix URL generated from the code snippet exceeds the maximum safe URL length " + " (%d > %d bytes).", + url_length, + MAX_SAFE_URL_LENGTH, + location=(literal_block_node.source, literal_block_node.line), + ) + + insert_node_before(literal_block_node, build_remix_link_node(remix_url)) def setup(app): diff --git a/compiler/docs/grammar/SolidityLexer.g4 b/compiler/docs/grammar/SolidityLexer.g4 index c47dc411..43648ceb 100644 --- a/compiler/docs/grammar/SolidityLexer.g4 +++ b/compiler/docs/grammar/SolidityLexer.g4 @@ -62,7 +62,7 @@ New: 'new'; /** * Unit denomination for numbers. */ -NumberUnit: 'wei' | 'gwei' | 'ether' | 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years'; +SubDenomination: 'wei' | 'gwei' | 'ether' | 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years'; Override: 'override'; Payable: 'payable'; Pragma: 'pragma' -> pushMode(PragmaMode); @@ -90,6 +90,7 @@ Try: 'try'; Type: 'type'; Ufixed: 'ufixed' | ('ufixed' [1-9][0-9]+ 'x' [1-9][0-9]+); Unchecked: 'unchecked'; +Unicode: 'unicode'; /** * Sized unsigned integer types. * uint is an alias of uint256. @@ -198,9 +199,7 @@ fragment EscapeSequence: /** * A single quoted string literal allowing arbitrary unicode characters. */ -UnicodeStringLiteral: - 'unicode"' DoubleQuotedUnicodeStringCharacter* '"' - | 'unicode\'' SingleQuotedUnicodeStringCharacter* '\''; +UnicodeStringLiteral: 'unicode' (('"' DoubleQuotedUnicodeStringCharacter* '"') | ('\'' SingleQuotedUnicodeStringCharacter* '\'')); //@doc:inline fragment DoubleQuotedUnicodeStringCharacter: ~["\r\n\\] | EscapeSequence; //@doc:inline @@ -222,6 +221,14 @@ fragment EvenHexDigits: HexCharacter HexCharacter ('_'? HexCharacter HexCharacte //@doc:inline fragment HexCharacter: [0-9A-Fa-f]; +/** + * Scanned but not used by any rule, i.e, disallowed. + * solc parser considers number starting with '0', not immediately followed by '.' or 'x' as + * octal, even if non octal digits '8' and '9' are present. + */ +OctalNumber: '0' DecimalDigits ('.' DecimalDigits)?; + + /** * A decimal number literal consists of decimal digits that may be delimited by underscores and * an optional positive or negative exponent. @@ -232,6 +239,12 @@ DecimalNumber: (DecimalDigits | (DecimalDigits? '.' DecimalDigits)) ([eE] '-'? D fragment DecimalDigits: [0-9] ('_'? [0-9])* ; +/** + * This is needed to avoid successfully parsing a number followed by a string with no whitespace between. + */ +DecimalNumberFollowedByIdentifier: DecimalNumber Identifier; + + /** * An identifier in solidity has to start with a letter, a dollar-sign or an underscore and * may additionally contain numbers after the first symbol. @@ -285,14 +298,14 @@ YulEVMBuiltin: 'stop' | 'add' | 'sub' | 'mul' | 'div' | 'sdiv' | 'mod' | 'smod' | 'exp' | 'not' | 'lt' | 'gt' | 'slt' | 'sgt' | 'eq' | 'iszero' | 'and' | 'or' | 'xor' | 'byte' | 'shl' | 'shr' | 'sar' | 'addmod' | 'mulmod' | 'signextend' | 'keccak256' - | 'pop' | 'mload' | 'mstore' | 'mstore8' | 'sload' | 'sstore' | 'msize' | 'gas' + | 'pop' | 'mload' | 'mstore' | 'mstore8' | 'sload' | 'sstore' | 'tload' | 'tstore'| 'msize' | 'gas' | 'address' | 'balance' | 'selfbalance' | 'caller' | 'callvalue' | 'calldataload' | 'calldatasize' | 'calldatacopy' | 'extcodesize' | 'extcodecopy' | 'returndatasize' - | 'returndatacopy' | 'extcodehash' | 'create' | 'create2' | 'call' | 'callcode' + | 'returndatacopy' | 'mcopy' | 'extcodehash' | 'create' | 'create2' | 'call' | 'callcode' | 'delegatecall' | 'staticcall' | 'return' | 'revert' | 'selfdestruct' | 'invalid' | 'log0' | 'log1' | 'log2' | 'log3' | 'log4' | 'chainid' | 'origin' | 'gasprice' - | 'blockhash' | 'coinbase' | 'timestamp' | 'number' | 'difficulty' | 'gaslimit' - | 'basefee'; + | 'blockhash' | 'blobhash' | 'coinbase' | 'timestamp' | 'number' | 'difficulty' + | 'prevrandao' | 'gaslimit' | 'basefee' | 'blobbasefee'; YulLBrace: '{' -> pushMode(YulMode); YulRBrace: '}' -> popMode; diff --git a/compiler/docs/grammar/SolidityParser.g4 b/compiler/docs/grammar/SolidityParser.g4 index 92718b97..4a756029 100644 --- a/compiler/docs/grammar/SolidityParser.g4 +++ b/compiler/docs/grammar/SolidityParser.g4 @@ -22,6 +22,7 @@ sourceUnit: ( | enumDefinition | userDefinedValueTypeDefinition | errorDefinition + | eventDefinition )* EOF; //@doc: inline @@ -152,7 +153,7 @@ stateMutability: Pure | View | Payable; */ overrideSpecifier: Override (LParen overrides+=identifierPath (Comma overrides+=identifierPath)* RParen)?; /** - * The definition of contract, library and interface functions. + * The definition of contract, library, interface or free functions. * Depending on the context in which the function is defined, further restrictions may apply, * e.g. functions in interfaces have to be unimplemented, i.e. may not contain a body block. */ @@ -161,7 +162,7 @@ locals[ boolean visibilitySet = false, boolean mutabilitySet = false, boolean virtualSet = false, - boolean overrideSpecifierSet = false + boolean overrideSpecifierSet = false, ] : Function (identifier | Fallback | Receive) @@ -175,6 +176,7 @@ locals[ )* (Returns LParen returnParameters=parameterList RParen)? (Semicolon | body=block); + /** * The definition of a modifier. * Note that within the body block of a modifier, the underscore cannot be used as identifier, @@ -312,10 +314,30 @@ errorDefinition: Semicolon; /** - * Using directive to bind library functions and free functions to types. + * Operators that users are allowed to implement for some types with `using for`. + */ +userDefinableOperator: + BitAnd + | BitNot + | BitOr + | BitXor + | Add + | Div + | Mod + | Mul + | Sub + | Equal + | GreaterThan + | GreaterThanOrEqual + | LessThan + | LessThanOrEqual + | NotEqual; + +/** + * Using directive to attach library functions and free functions to types. * Can occur within contracts and libraries and at the file level. */ -usingDirective: Using (identifierPath | (LBrace identifierPath (Comma identifierPath)* RBrace)) For (Mul | typeName) Global? Semicolon; +usingDirective: Using (identifierPath | (LBrace identifierPath (As userDefinableOperator)? (Comma identifierPath (As userDefinableOperator)?)* RBrace)) For (Mul | typeName) Global? Semicolon; /** * A type name can be an elementary type, a function type, a mapping type, a user-defined type * (e.g. a contract or struct) or an array type. @@ -347,7 +369,7 @@ dataLocation: Memory | Storage | Calldata; */ expression: expression LBrack index=expression? RBrack # IndexAccess - | expression LBrack start=expression? Colon end=expression? RBrack # IndexRangeAccess + | expression LBrack startIndex=expression? Colon endIndex=expression? RBrack # IndexRangeAccess | expression Period (identifier | Address) # MemberAccess | expression LBrace (namedArgument (Comma namedArgument)*)? RBrace # FunctionCallOptions | expression callArgumentList # FunctionCall @@ -368,12 +390,13 @@ expression: | expression Or expression # OrOperation | expression Conditional expression Colon expression # Conditional | expression assignOp expression # Assignment - | New typeName # NewExpression + | New typeName # NewExpr | tupleExpression # Tuple | inlineArrayExpression # InlineArray | ( identifier | literal + | literalWithSubDenomination | elementaryTypeName[false] ) # PrimaryExpression ; @@ -392,6 +415,9 @@ inlineArrayExpression: LBrack (expression ( Comma expression)* ) RBrack; identifier: Identifier | From | Error | Revert | Global; literal: stringLiteral | numberLiteral | booleanLiteral | hexStringLiteral | unicodeStringLiteral; + +literalWithSubDenomination: numberLiteral SubDenomination; + booleanLiteral: True | False; /** * A full string literal consists of either one or several consecutive quoted strings. @@ -409,7 +435,8 @@ unicodeStringLiteral: UnicodeStringLiteral+; /** * Number literals can be decimal or hexadecimal numbers with an optional unit. */ -numberLiteral: (DecimalNumber | HexNumber) NumberUnit?; +numberLiteral: DecimalNumber | HexNumber; + /** * A curly-braced block of statements. Opens its own scope. */ @@ -504,7 +531,7 @@ variableDeclarationTuple: variableDeclarationStatement: ((variableDeclaration (Assign expression)?) | (variableDeclarationTuple Assign expression)) Semicolon; expressionStatement: expression Semicolon; -mappingType: Mapping LParen key=mappingKeyType DoubleArrow value=typeName RParen; +mappingType: Mapping LParen key=mappingKeyType name=identifier? DoubleArrow value=typeName name=identifier? RParen; /** * Only elementary types or user defined types are viable as mapping keys. */ diff --git a/compiler/docs/index.rst b/compiler/docs/index.rst index 7576b363..600173d6 100644 --- a/compiler/docs/index.rst +++ b/compiler/docs/index.rst @@ -1,38 +1,35 @@ Solidity ======== -Solidity is an object-oriented, high-level language for implementing smart -contracts. Smart contracts are programs which govern the behaviour of accounts -within the Ethereum state. +Solidity is an object-oriented, high-level language for implementing smart contracts. +Smart contracts are programs that govern the behavior of accounts within the Ethereum state. Solidity is a `curly-bracket language `_ designed to target the Ethereum Virtual Machine (EVM). -It is influenced by C++, Python and JavaScript. You can find more details about which languages Solidity has been inspired by in the :doc:`language influences ` section. +It is influenced by C++, Python, and JavaScript. +You can find more details about which languages Solidity has been inspired by in the :doc:`language influences ` section. -Solidity is statically typed, supports inheritance, libraries and complex -user-defined types among other features. +Solidity is statically typed, supports inheritance, libraries, and complex user-defined types, among other features. -With Solidity you can create contracts for uses such as voting, crowdfunding, blind auctions, -and multi-signature wallets. +With Solidity, you can create contracts for uses such as voting, crowdfunding, blind auctions, and multi-signature wallets. -When deploying contracts, you should use the latest released -version of Solidity. Apart from exceptional cases, only the latest version receives +When deploying contracts, you should use the latest released version of Solidity. +Apart from exceptional cases, only the latest version receives `security fixes `_. -Furthermore, breaking changes as well as -new features are introduced regularly. We currently use -a 0.y.z version number `to indicate this fast pace of change `_. +Furthermore, breaking changes, as well as new features, are introduced regularly. +We currently use a 0.y.z version number `to indicate this fast pace of change `_. .. warning:: - Solidity recently released the 0.8.x version that introduced a lot of breaking - changes. Make sure you read :doc:`the full list <080-breaking-changes>`. + Solidity recently released the 0.8.x version that introduced a lot of breaking changes. + Make sure you read :doc:`the full list <080-breaking-changes>`. Ideas for improving Solidity or this documentation are always welcome, read our :doc:`contributors guide ` for more details. .. Hint:: - You can download this documentation as PDF, HTML or Epub by clicking on the versions - flyout menu in the bottom-left corner and selecting the preferred download format. + You can download this documentation as PDF, HTML or Epub + by clicking on the versions flyout menu in the bottom-left corner and selecting the preferred download format. Getting Started @@ -40,8 +37,7 @@ Getting Started **1. Understand the Smart Contract Basics** -If you are new to the concept of smart contracts we recommend you to get started by digging -into the "Introduction to Smart Contracts" section, which covers: +If you are new to the concept of smart contracts, we recommend you to get started by digging into the "Introduction to Smart Contracts" section, which covers the following: * :ref:`A simple example smart contract ` written in Solidity. * :ref:`Blockchain Basics `. @@ -59,29 +55,27 @@ simply choose your preferred option and follow the steps outlined on the :ref:`i .. hint:: You can try out code examples directly in your browser with the - `Remix IDE `_. Remix is a web browser based IDE - that allows you to write, deploy and administer Solidity smart contracts, without - the need to install Solidity locally. + `Remix IDE `_. + Remix is a web browser-based IDE that allows you to write, deploy and administer Solidity smart contracts, + without the need to install Solidity locally. .. warning:: - As humans write software, it can have bugs. You should follow established - software development best-practices when writing your smart contracts. This - includes code review, testing, audits, and correctness proofs. Smart contract - users are sometimes more confident with code than their authors, and - blockchains and smart contracts have their own unique issues to - watch out for, so before working on production code, make sure you read the - :ref:`security_considerations` section. + As humans write software, it can have bugs. + Therefore, you should follow established software development best practices when writing your smart contracts. + This includes code review, testing, audits, and correctness proofs. + Smart contract users are sometimes more confident with code than their authors, + and blockchains and smart contracts have their own unique issues to watch out for, + so before working on production code, make sure you read the :ref:`security_considerations` section. **4. Learn More** -If you want to learn more about building decentralized applications on Ethereum, the -`Ethereum Developer Resources `_ -can help you with further general documentation around Ethereum, and a wide selection of tutorials, -tools and development frameworks. +If you want to learn more about building decentralized applications on Ethereum, +the `Ethereum Developer Resources `_ can help you with further general documentation around Ethereum, +and a wide selection of tutorials, tools, and development frameworks. If you have any questions, you can try searching for answers or asking on the -`Ethereum StackExchange `_, or -our `Gitter channel `_. +`Ethereum StackExchange `_, +or our `Gitter channel `_. .. _translations: @@ -89,23 +83,26 @@ Translations ------------ Community contributors help translate this documentation into several languages. -Note that they have varying degrees of completeness and up-to-dateness. The English -version stands as a reference. +Note that they have varying degrees of completeness and up-to-dateness. +The English version stands as a reference. You can switch between languages by clicking on the flyout menu in the bottom-left corner and selecting the preferred language. +* `Chinese `_ * `French `_ * `Indonesian `_ -* `Persian `_ * `Japanese `_ * `Korean `_ -* `Chinese `_ +* `Persian `_ +* `Russian `_ +* `Spanish `_ +* `Turkish `_ .. note:: - We recently set up a new GitHub organization and translation workflow to help streamline the - community efforts. Please refer to the `translation guide `_ + We set up a GitHub organization and translation workflow to help streamline the community efforts. + Please refer to the translation guide in the `solidity-docs org `_ for information on how to start a new language or contribute to the community translations. Contents @@ -118,8 +115,8 @@ Contents :caption: Basics introduction-to-smart-contracts.rst - installing-solidity.rst solidity-by-example.rst + installing-solidity.rst .. toctree:: :maxdepth: 2 @@ -158,21 +155,31 @@ Contents .. toctree:: :maxdepth: 2 - :caption: Additional Material + :caption: Advisory content + security-considerations.rst + bugs.rst 050-breaking-changes.rst 060-breaking-changes.rst 070-breaking-changes.rst 080-breaking-changes.rst + +.. toctree:: + :maxdepth: 2 + :caption: Additional Material + natspec-format.rst - security-considerations.rst smtchecker.rst - resources.rst - path-resolution.rst yul.rst + path-resolution.rst + +.. toctree:: + :maxdepth: 2 + :caption: Resources + style-guide.rst common-patterns.rst - bugs.rst + resources.rst contributing.rst - brand-guide.rst language-influences.rst + brand-guide.rst diff --git a/compiler/docs/installing-solidity.rst b/compiler/docs/installing-solidity.rst index 9a613d75..2f048fe3 100644 --- a/compiler/docs/installing-solidity.rst +++ b/compiler/docs/installing-solidity.rst @@ -10,12 +10,12 @@ Versioning ========== Solidity versions follow `Semantic Versioning `_. In -addition, patch level releases with major release 0 (i.e. 0.x.y) will not +addition, patch-level releases with major release 0 (i.e. 0.x.y) will not contain breaking changes. That means code that compiles with version 0.x.y can be expected to compile with 0.x.z where z > y. -In addition to releases, we provide **nightly development builds** with the -intention of making it easy for developers to try out upcoming features and +In addition to releases, we provide **nightly development builds** to make +it easy for developers to try out upcoming features and provide early feedback. Note, however, that while the nightly builds are usually very stable, they contain bleeding-edge code from the development branch and are not guaranteed to be always working. Despite our best efforts, they might @@ -33,12 +33,12 @@ Remix `Access Remix online `_, you do not need to install anything. If you want to use it without connection to the Internet, go to -https://github.com/ethereum/remix-live/tree/gh-pages and download the ``.zip`` file as -explained on that page. Remix is also a convenient option for testing nightly builds +https://github.com/ethereum/remix-live/tree/gh-pages#readme and follow the instructions on that page. +Remix is also a convenient option for testing nightly builds without installing multiple Solidity versions. -Further options on this page detail installing commandline Solidity compiler software -on your computer. Choose a commandline compiler if you are working on a larger contract +Further options on this page detail installing command-line Solidity compiler software +on your computer. Choose a command-line compiler if you are working on a larger contract or if you require more compilation options. .. _solcjs: @@ -54,7 +54,7 @@ the full-featured compiler, ``solc``. The usage of ``solcjs`` is documented insi `repository `_. Note: The solc-js project is derived from the C++ -`solc` by using Emscripten which means that both use the same compiler source code. +`solc` by using Emscripten, which means that both use the same compiler source code. `solc-js` can be used in JavaScript projects directly (such as Remix). Please refer to the solc-js repository for instructions. @@ -64,18 +64,18 @@ Please refer to the solc-js repository for instructions. .. note:: - The commandline executable is named ``solcjs``. + The command-line executable is named ``solcjs``. - The commandline options of ``solcjs`` are not compatible with ``solc`` and tools (such as ``geth``) - expecting the behaviour of ``solc`` will not work with ``solcjs``. + The command-line options of ``solcjs`` are not compatible with ``solc`` and tools (such as ``geth``) + expecting the behavior of ``solc`` will not work with ``solcjs``. Docker ====== -Docker images of Solidity builds are available using the ``solc`` image from the ``ethereum`` organisation. -Use the ``stable`` tag for the latest released version, and ``nightly`` for potentially unstable changes in the develop branch. +Docker images of Solidity builds are available using the ``solc`` image from the ``ethereum`` organization. +Use the ``stable`` tag for the latest released version, and ``nightly`` for potentially unstable changes in the ``develop`` branch. -The Docker image runs the compiler executable, so you can pass all compiler arguments to it. +The Docker image runs the compiler executable so that you can pass all compiler arguments to it. For example, the command below pulls the stable version of the ``solc`` image (if you do not have it already), and runs it in a new container, passing the ``--help`` argument. @@ -83,13 +83,13 @@ and runs it in a new container, passing the ``--help`` argument. docker run ethereum/solc:stable --help -You can also specify release build versions in the tag, for example, for the 0.5.4 release. +For example, You can specify release build versions in the tag for the 0.5.4 release. .. code-block:: bash docker run ethereum/solc:0.5.4 --help -To use the Docker image to compile Solidity files on the host machine mount a +To use the Docker image to compile Solidity files on the host machine, mount a local folder for input and output, and specify the contract to compile. For example. .. code-block:: bash @@ -97,7 +97,7 @@ local folder for input and output, and specify the contract to compile. For exam docker run -v /local/path:/sources ethereum/solc:stable -o /sources/output --abi --bin /sources/Contract.sol You can also use the standard JSON interface (which is recommended when using the compiler with tooling). -When using this interface it is not necessary to mount any directories as long as the JSON input is +When using this interface, it is not necessary to mount any directories as long as the JSON input is self-contained (i.e. it does not refer to any external files that would have to be :ref:`loaded by the import callback `). @@ -130,13 +130,15 @@ The nightly version can be installed using these commands: sudo apt-get install solc Furthermore, some Linux distributions provide their own packages. These packages are not directly -maintained by us, but usually kept up-to-date by the respective package maintainers. +maintained by us but usually kept up-to-date by the respective package maintainers. -For example, Arch Linux has packages for the latest development version: +For example, Arch Linux has packages for the latest development version as AUR packages: `solidity `_ +and `solidity-bin `_. -.. code-block:: bash +.. note:: - pacman -S solidity + Please be aware that `AUR `_ packages + are user-produced content and unofficial packages. Exercise caution when using them. There is also a `snap package `_, however, it is **currently unmaintained**. It is installable in all the `supported Linux distros `_. To @@ -212,12 +214,12 @@ out-of-the-box but it is also meant to be friendly to third-party tools: HTTPS without any authentication, rate limiting or the need to use git. - Content is served with correct `Content-Type` headers and lenient CORS configuration so that it can be directly loaded by tools running in the browser. -- Binaries do not require installation or unpacking (with the exception of older Windows builds +- Binaries do not require installation or unpacking (exception for older Windows builds bundled with necessary DLLs). -- We strive for a high level of backwards-compatibility. Files, once added, are not removed or moved +- We strive for a high level of backward-compatibility. Files, once added, are not removed or moved without providing a symlink/redirect at the old location. They are also never modified in place and should always match the original checksum. The only exception would be broken or - unusable files with a potential to cause more harm than good if left as is. + unusable files with the potential to cause more harm than good if left as is. - Files are served over both HTTP and HTTPS. As long as you obtain the file list in a secure way (via git, HTTPS, IPFS or just have it cached locally) and verify hashes of the binaries after downloading them, you do not have to use HTTPS for the binaries themselves. @@ -228,7 +230,7 @@ that we do not rename them if the naming convention changes and we do not add bu that were not supported at the time of release. This only happens in ``solc-bin``. The ``solc-bin`` repository contains several top-level directories, each representing a single platform. -Each one contains a ``list.json`` file listing the available binaries. For example in +Each one includes a ``list.json`` file listing the available binaries. For example in ``emscripten-wasm32/list.json`` you will find the following information about version 0.7.4: .. code-block:: json @@ -259,7 +261,7 @@ This means that: - The file might in future be available on Swarm at `16c5f09109c793db99fe35f037c6092b061bd39260ee7a677c8a97f18c955ab1`_. - You can verify the integrity of the binary by comparing its keccak256 hash to ``0x300330ecd127756b824aa13e843cb1f43c473cb22eaf3750d5fb9c99279af8c3``. The hash can be computed - on the command line using ``keccak256sum`` utility provided by `sha3sum`_ or `keccak256() function + on the command-line using ``keccak256sum`` utility provided by `sha3sum`_ or `keccak256() function from ethereumjs-util`_ in JavaScript. - You can also verify the integrity of the binary by comparing its sha256 hash to ``0x2b55ed5fec4d9625b6c7b3ab1abd2b7fb7dd2a9c68543bf0323db2c7e2d55af2``. @@ -310,7 +312,6 @@ This means that: Building from Source ==================== - Prerequisites - All Operating Systems ------------------------------------- @@ -319,14 +320,15 @@ The following are dependencies for all builds of Solidity: +-----------------------------------+-------------------------------------------------------+ | Software | Notes | +===================================+=======================================================+ -| `CMake`_ (version 3.13+) | Cross-platform build file generator. | +| `CMake`_ (version 3.21.3+ on | Cross-platform build file generator. | +| Windows, 3.13+ otherwise) | | +-----------------------------------+-------------------------------------------------------+ | `Boost`_ (version 1.77+ on | C++ libraries. | | Windows, 1.65+ otherwise) | | +-----------------------------------+-------------------------------------------------------+ | `Git`_ | Command-line tool for retrieving source code. | +-----------------------------------+-------------------------------------------------------+ -| `z3`_ (version 4.8+, Optional) | For use with SMT checker. | +| `z3`_ (version 4.8.16+, Optional) | For use with SMT checker. | +-----------------------------------+-------------------------------------------------------+ | `cvc4`_ (Optional) | For use with SMT checker. | +-----------------------------------+-------------------------------------------------------+ @@ -340,7 +342,7 @@ The following are dependencies for all builds of Solidity: .. note:: Solidity versions prior to 0.5.10 can fail to correctly link against Boost versions 1.70+. A possible workaround is to temporarily rename ``/lib/cmake/Boost-1.70.0`` - prior to running the cmake command to configure solidity. + prior to running the cmake command to configure Solidity. Starting from 0.5.10 linking against Boost 1.70+ should work without manual intervention. @@ -378,7 +380,7 @@ Prerequisites - macOS --------------------- For macOS builds, ensure that you have the latest version of -`Xcode installed `_. +`Xcode installed `_. This contains the `Clang C++ compiler `_, the `Xcode IDE `_ and other Apple development tools that are required for building C++ applications on OS X. @@ -428,7 +430,7 @@ in Visual Studio 2019 Build Tools or Visual Studio 2019: * C++/CLI support .. _Visual Studio 2019: https://www.visualstudio.com/vs/ -.. _Visual Studio 2019 Build Tools: https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2019 +.. _Visual Studio 2019 Build Tools: https://visualstudio.microsoft.com/vs/older-downloads/#visual-studio-2019-and-other-products We have a helper script which you can use to install all required external dependencies: @@ -448,7 +450,7 @@ To clone the source code, execute the following command: git clone --recursive https://github.com/ethereum/solidity.git cd solidity -If you want to help developing Solidity, +If you want to help develop Solidity, you should fork Solidity and add your personal fork as a second remote: .. code-block:: bash @@ -456,7 +458,7 @@ you should fork Solidity and add your personal fork as a second remote: git remote add personal git@github.com:[username]/solidity.git .. note:: - This method will result in a prerelease build leading to e.g. a flag + This method will result in a pre-release build leading to e.g. a flag being set in each bytecode produced by such a compiler. If you want to re-build a released Solidity compiler, then please use the source tarball on the github release page: @@ -526,7 +528,7 @@ If you are interested what CMake options are available run ``cmake .. -LH``. SMT Solvers ----------- Solidity can be built against SMT solvers and will do so by default if -they are found in the system. Each solver can be disabled by a `cmake` option. +they are found in the system. Each solver can be disabled by a ``cmake`` option. *Note: In some cases, this can also be a potential workaround for build failures.* @@ -579,4 +581,4 @@ Example: 4. A breaking change is introduced --> version is bumped to 0.5.0. 5. The 0.5.0 release is made. -This behaviour works well with the :ref:`version pragma `. +This behavior works well with the :ref:`version pragma `. diff --git a/compiler/docs/internals/layout_in_storage.rst b/compiler/docs/internals/layout_in_storage.rst index 53670eeb..7afcfc2a 100644 --- a/compiler/docs/internals/layout_in_storage.rst +++ b/compiler/docs/internals/layout_in_storage.rst @@ -208,7 +208,7 @@ of types), arrays have its ``base`` type, and structs list their ``members`` in the same format as the top-level ``storage`` (see :ref:`above `). -.. note :: +.. note:: The JSON output format of a contract's storage layout is still considered experimental and is subject to change in non-breaking releases of Solidity. @@ -232,7 +232,7 @@ value and reference types, types that are encoded packed, and nested types. uint y; S s; address addr; - mapping (uint => mapping (address => bool)) map; + mapping(uint => mapping(address => bool)) map; uint[] array; string s1; bytes b1; diff --git a/compiler/docs/internals/optimizer.rst b/compiler/docs/internals/optimizer.rst index 3c12f0ad..70896333 100644 --- a/compiler/docs/internals/optimizer.rst +++ b/compiler/docs/internals/optimizer.rst @@ -5,8 +5,11 @@ The Optimizer ************* -The Solidity compiler uses two different optimizer modules: The "old" optimizer -that operates at the opcode level and the "new" optimizer that operates on Yul IR code. +The Solidity compiler involves optimizations at three different levels (in order of execution): + +- Optimizations during code generation based on a direct analysis of Solidity code. +- Optimizing transformations on the Yul IR code. +- Optimizations at the opcode level. The opcode-based optimizer applies a set of `simplification rules `_ to opcodes. It also combines equal code sets and removes unused code. @@ -20,6 +23,17 @@ the function calls. Similarly, if a function is side-effect free and its result is multiplied by zero, you can remove the function call completely. +The codegen-based optimizer affects the initial low-level code produced from the Solidity input. +In the legacy pipeline, the bytecode is generated immediately and most of the optimizations of this +kind are implicit and not configurable, the only exception being an optimization which changes the +order of literals in binary operations. +The IR-based pipeline takes a different approach and produces Yul IR closely matching the structure +of the Solidity code, with nearly all optimizations deferred to the Yul optimizer module. +In that case codegen-level optimization is done only in very limited cases which are difficult to +handle in Yul IR, but are straightforward with the high-level information from analysis phase at hand. +An example of such an optimization is the bypass of checked arithmetic when incrementing the counter +in certain idiomatic ``for`` loops. + Currently, the parameter ``--optimize`` activates the opcode-based optimizer for the generated bytecode and the Yul optimizer for the Yul code generated internally, for example for ABI coder v2. One can use ``solc --ir-optimized --optimize`` to produce an @@ -27,9 +41,15 @@ optimized Yul IR for a Solidity source. Similarly, one can use ``solc --strict-a for a stand-alone Yul mode. .. note:: - The `peephole optimizer `_ and the inliner are always + Some optimizer steps, such as, for example, the `peephole optimizer `_ + and the :ref:`unchecked loop increment optimizer ` are always enabled by default and can only be turned off via the :ref:`Standard JSON `. +.. note:: + An empty optimizer sequence is accepted even without ``--optimize`` in order to fully disable + the user-supplied portion of the Yul :ref:`optimizer sequence `, as by default, + even when the optimizer is not turned on, the :ref:`unused pruner ` step will be run. + You can find more details on both optimizer modules and their optimization steps below. Benefits of Optimizing Solidity Code @@ -281,60 +301,82 @@ The following transformation steps are the main components: - Redundant Assign Eliminator - Full Inliner +.. _optimizer-steps: + Optimizer Steps --------------- This is a list of all steps the Yul-based optimizer sorted alphabetically. You can find more information on the individual steps and their sequence below. -- :ref:`block-flattener`. -- :ref:`circular-reference-pruner`. -- :ref:`common-subexpression-eliminator`. -- :ref:`conditional-simplifier`. -- :ref:`conditional-unsimplifier`. -- :ref:`control-flow-simplifier`. -- :ref:`dead-code-eliminator`. -- :ref:`equal-store-eliminator`. -- :ref:`equivalent-function-combiner`. -- :ref:`expression-joiner`. -- :ref:`expression-simplifier`. -- :ref:`expression-splitter`. -- :ref:`for-loop-condition-into-body`. -- :ref:`for-loop-condition-out-of-body`. -- :ref:`for-loop-init-rewriter`. -- :ref:`expression-inliner`. -- :ref:`full-inliner`. -- :ref:`function-grouper`. -- :ref:`function-hoister`. -- :ref:`function-specializer`. -- :ref:`literal-rematerialiser`. -- :ref:`load-resolver`. -- :ref:`loop-invariant-code-motion`. -- :ref:`redundant-assign-eliminator`. -- :ref:`reasoning-based-simplifier`. -- :ref:`rematerialiser`. -- :ref:`SSA-reverser`. -- :ref:`SSA-transform`. -- :ref:`structural-simplifier`. -- :ref:`unused-function-parameter-pruner`. -- :ref:`unused-pruner`. -- :ref:`var-decl-initializer`. +============ =============================== +Abbreviation Full name +============ =============================== +``f`` :ref:`block-flattener` +``l`` :ref:`circular-reference-pruner` +``c`` :ref:`common-subexpression-eliminator` +``C`` :ref:`conditional-simplifier` +``U`` :ref:`conditional-unsimplifier` +``n`` :ref:`control-flow-simplifier` +``D`` :ref:`dead-code-eliminator` +``E`` :ref:`equal-store-eliminator` +``v`` :ref:`equivalent-function-combiner` +``e`` :ref:`expression-inliner` +``j`` :ref:`expression-joiner` +``s`` :ref:`expression-simplifier` +``x`` :ref:`expression-splitter` +``I`` :ref:`for-loop-condition-into-body` +``O`` :ref:`for-loop-condition-out-of-body` +``o`` :ref:`for-loop-init-rewriter` +``i`` :ref:`full-inliner` +``g`` :ref:`function-grouper` +``h`` :ref:`function-hoister` +``F`` :ref:`function-specializer` +``T`` :ref:`literal-rematerialiser` +``L`` :ref:`load-resolver` +``M`` :ref:`loop-invariant-code-motion` +``r`` :ref:`redundant-assign-eliminator` +``m`` :ref:`rematerialiser` +``V`` :ref:`SSA-reverser` +``a`` :ref:`SSA-transform` +``t`` :ref:`structural-simplifier` +``p`` :ref:`unused-function-parameter-pruner` +``S`` :ref:`unused-store-eliminator` +``u`` :ref:`unused-pruner` +``d`` :ref:`var-decl-initializer` +============ =============================== + +Some steps depend on properties ensured by ``BlockFlattener``, ``FunctionGrouper``, ``ForLoopInitRewriter``. +For this reason the Yul optimizer always applies them before applying any steps supplied by the user. + +.. _selecting-optimizations: Selecting Optimizations ----------------------- -By default the optimizer applies its predefined sequence of optimization steps to -the generated assembly. You can override this sequence and supply your own using -the ``--yul-optimizations`` option: +By default the optimizer applies its predefined sequence of optimization steps to the generated assembly. +You can override this sequence and supply your own using the ``--yul-optimizations`` option: .. code-block:: bash - solc --optimize --ir-optimized --yul-optimizations 'dhfoD[xarrscLMcCTU]uljmul' + solc --optimize --ir-optimized --yul-optimizations 'dhfoD[xarrscLMcCTU]uljmul:fDnTOcmu' + +The order of steps is significant and affects the quality of the output. +Moreover, applying a step may uncover new optimization opportunities for others that were already applied, +so repeating steps is often beneficial. The sequence inside ``[...]`` will be applied multiple times in a loop until the Yul code remains unchanged or until the maximum number of rounds (currently 12) has been reached. +Brackets (``[]``) may be used multiple times in a sequence, but can not be nested. + +An important thing to note, is that there are some hardcoded steps that are always run before and after the +user-supplied sequence, or the default sequence if one was not supplied by the user. -Available abbreviations are listed in the :ref:`Yul optimizer docs `. +The cleanup sequence delimiter ``:`` is optional, and is used to supply a custom cleanup sequence +in order to replace the default one. If omitted, the optimizer will simply apply the default cleanup +sequence. In addition, the delimiter may be placed at the beginning of the user-supplied sequence, +which will result in the optimization sequence being empty, whereas conversely, if placed at the end of +the sequence, will be treated as an empty cleanup sequence. Preprocessing ------------- @@ -549,7 +591,7 @@ It is not applied to loop iteration-condition, because the loop control flow doe this "outlining" of the inner expressions in all cases. We can sidestep this limitation by applying :ref:`for-loop-condition-into-body` to move the iteration condition into loop body. -The final program should be in a form such that (with the exception of loop conditions) +The final program should be in an *expression-split form*, where (with the exception of loop conditions) function calls cannot appear nested inside expressions and all function call arguments have to be variables. @@ -800,10 +842,10 @@ if the common subexpression eliminator was run right before it. .. _expression-simplifier: -Expression Simplifier -^^^^^^^^^^^^^^^^^^^^^ +ExpressionSimplifier +^^^^^^^^^^^^^^^^^^^^ -The Expression Simplifier uses the Dataflow Analyzer and makes use +The ExpressionSimplifier uses the Dataflow Analyzer and makes use of a list of equivalence transforms on expressions like ``X + 0 -> X`` to simplify the code. @@ -837,22 +879,6 @@ Works best if the code is in SSA form. Prerequisite: Disambiguator, ForLoopInitRewriter. -.. _reasoning-based-simplifier: - -ReasoningBasedSimplifier -^^^^^^^^^^^^^^^^^^^^^^^^ - -This optimizer uses SMT solvers to check whether ``if`` conditions are constant. - -- If ``constraints AND condition`` is UNSAT, the condition is never true and the whole body can be removed. -- If ``constraints AND NOT condition`` is UNSAT, the condition is always true and can be replaced by ``1``. - -The simplifications above can only be applied if the condition is movable. - -It is only effective on the EVM dialect, but safe to use on other dialects. - -Prerequisite: Disambiguator, SSATransform. - Statement-Scale Simplifications ------------------------------- @@ -933,7 +959,7 @@ DeadCodeEliminator This optimization stage removes unreachable code. Unreachable code is any code within a block which is preceded by a -leave, return, invalid, break, continue, selfdestruct or revert. +leave, return, invalid, break, continue, selfdestruct, revert or by a call to a user-defined function that recurses infinitely. Function definitions are retained as they might be called by earlier code and thus are considered reachable. @@ -1096,6 +1122,52 @@ The step LiteralRematerialiser is not required for correctness. It helps deal wi ``function f(x) -> y { revert(y, y} }`` where the literal ``y`` will be replaced by its value ``0``, allowing us to rewrite the function. +.. index:: ! unused store eliminator +.. _unused-store-eliminator: + +UnusedStoreEliminator +^^^^^^^^^^^^^^^^^^^^^ + +Optimizer component that removes redundant ``sstore`` and memory store statements. +In case of an ``sstore``, if all outgoing code paths revert (due to an explicit ``revert()``, ``invalid()``, or infinite recursion) or +lead to another ``sstore`` for which the optimizer can tell that it will overwrite the first store, the statement will be removed. +However, if there is a read operation between the initial ``sstore`` and the revert, or the overwriting ``sstore``, the statement +will not be removed. +Such read operations include: external calls, user-defined functions with any storage access, and ``sload`` of a slot that cannot be +proven to differ from the slot written by the initial ``sstore``. + +For example, the following code + +.. code-block:: yul + + { + let c := calldataload(0) + sstore(c, 1) + if c { + sstore(c, 2) + } + sstore(c, 3) + } + +will be transformed into the code below after the Unused Store Eliminator step is run + +.. code-block:: yul + + { + let c := calldataload(0) + if c { } + sstore(c, 3) + } + +For memory store operations, things are generally simpler, at least in the outermost yul block as all such +statements will be removed if they are never read from in any code path. +At function analysis level, however, the approach is similar to ``sstore``, as we do not know whether the memory location will +be read once we leave the function's scope, so the statement will be removed only if all code paths lead to a memory overwrite. + +Best run in SSA form. + +Prerequisites: Disambiguator, ForLoopInitRewriter. + .. _equivalent-function-combiner: EquivalentFunctionCombiner @@ -1141,7 +1213,7 @@ This component can only be used on sources with unique names. FullInliner ^^^^^^^^^^^ -The Full Inliner replaces certain calls of certain functions +The FullInliner replaces certain calls of certain functions by the function's body. This is not very helpful in most cases, because it just increases the code size but does not have a benefit. Furthermore, code is usually very expensive and we would often rather have shorter @@ -1165,6 +1237,11 @@ we can run the optimizer on this specialized function. If it results in heavy gains, the specialized function is kept, otherwise the original function is used instead. +FunctionHoister and ExpressionSplitter are recommended as prerequisites since they make the step +more efficient, but are not required for correctness. +In particular, function calls with other function calls as arguments are not inlined, but running +ExpressionSplitter beforehand ensures that there are no such calls in the input. + Cleanup ------- @@ -1207,8 +1284,8 @@ This is a tiny step that helps in reversing the effects of the SSA transform if it is combined with the Common Subexpression Eliminator and the Unused Pruner. -The SSA form we generate is detrimental to code generation on the EVM and -WebAssembly alike because it generates many local variables. It would +The SSA form we generate is detrimental to code generation +because it produces many local variables. It would be better to just re-use existing variables with assignments instead of fresh variable declarations. @@ -1327,14 +1404,62 @@ into The LiteralRematerialiser should be run before this step. +Codegen-Based Optimizer Module +============================== -WebAssembly specific --------------------- +Currently, the codegen-based optimizer module provides two optimizations. -MainFunction -^^^^^^^^^^^^ +The first one, available in the legacy code generator, moves literals to the right side of +commutative binary operators, which helps exploit their associativity. + +The other one, available in the IR-based code generator, enables the use of unchecked arithmetic +when generating code for incrementing the counter variable of certain idiomatic ``for`` loops. +This avoids wasting gas by identifying some conditions that guarantee that the counter variable +cannot overflow. +This eliminates the need to use a verbose unchecked arithmetic block inside the loop body to +increment the counter variable. + +.. _unchecked-loop-optimizer: -Changes the topmost block to be a function with a specific name ("main") which has no -inputs nor outputs. +Unchecked Loop Increment +------------------------ + +Introduced in Solidity ``0.8.22``, the overflow check optimization step is concerned with identifying +the conditions under which the ``for`` loop counter can be safely incremented +without overflow checks. + +This optimization is **only** applied to ``for`` loops of the general form: + +.. code-block:: solidity + + for (uint i = X; i < Y; ++i) { + // variable i is not modified in the loop body + } + +The condition and the fact that the counter variable is only ever incremented +guarantee that it never overflows. +The precise requirements for the loop to be eligible for the optimization are as follows: + +- The loop condition is a comparison of the form ``i < Y``, for a local counter variable ``i`` + (called the "loop counter" hereon) and an expression ``Y``. +- The built-in operator ``<`` is necessarily used in the loop condition and is the only operator + that triggers the optimization. ``<=`` and the like are intentionally excluded. Additionally, + user-defined operators are **not** eligible. +- The loop expression is a prefix or postfix increment of the counter variable, i.e, ``i++`` or ``++i``. +- The loop counter is a local variable of a built-in integer type. +- The loop counter is **not** modified by the loop body or by the expression used as the loop condition. +- The comparison is performed on the same type as the loop counter, meaning that the type of the + right-hand-side expression is implicitly convertible to the type of the counter, such that the latter + is not implicitly widened before the comparison. + +To clarify the last condition, consider the following example: + +.. code-block:: solidity + + for (uint8 i = 0; i < uint16(1000); i++) { + // ... + } -Depends on the Function Grouper. +In this case, the counter ``i`` has its type implicitly converted from ``uint8`` +to ``uint16`` before the comparison and the condition is in fact never false, so +the overflow check for the increment cannot be removed. diff --git a/compiler/docs/internals/source_mappings.rst b/compiler/docs/internals/source_mappings.rst index 11027b8a..68c29d95 100644 --- a/compiler/docs/internals/source_mappings.rst +++ b/compiler/docs/internals/source_mappings.rst @@ -26,7 +26,7 @@ that are not part of the original input but are referenced from the source mappings. These source files together with their identifiers can be obtained via ``output['contracts'][sourceName][contractName]['evm']['bytecode']['generatedSources']``. -.. note :: +.. note:: In the case of instructions that are not associated with any particular source file, the source mapping assigns an integer identifier of ``-1``. This may happen for bytecode sections stemming from compiler-generated inline assembly statements. diff --git a/compiler/docs/internals/variable_cleanup.rst b/compiler/docs/internals/variable_cleanup.rst index 9fec6929..bab3c8be 100644 --- a/compiler/docs/internals/variable_cleanup.rst +++ b/compiler/docs/internals/variable_cleanup.rst @@ -4,9 +4,10 @@ Cleaning Up Variables ********************* -When a value is shorter than 256 bit, in some cases the remaining bits -must be cleaned. -The Solidity compiler is designed to clean such remaining bits before any operations +Ultimately, all values in the EVM are stored in 256 bit words. +Thus, in some cases, when the type of a value has less than 256 bits, +it is necessary to clean the remaining bits. +The Solidity compiler is designed to do such cleaning before any operations that might be adversely affected by the potential garbage in the remaining bits. For example, before writing a value to memory, the remaining bits need to be cleared because the memory contents can be used for computing @@ -28,25 +29,83 @@ the boolean values before they are used as the condition for In addition to the design principle above, the Solidity compiler cleans input data when it is loaded onto the stack. -Different types have different rules for cleaning up invalid values: - -+---------------+---------------+-------------------+ -|Type |Valid Values |Invalid Values Mean| -+===============+===============+===================+ -|enum of n |0 until n - 1 |exception | -|members | | | -+---------------+---------------+-------------------+ -|bool |0 or 1 |1 | -+---------------+---------------+-------------------+ -|signed integers|sign-extended |currently silently | -| |word |wraps; in the | -| | |future exceptions | -| | |will be thrown | -| | | | -| | | | -+---------------+---------------+-------------------+ -|unsigned |higher bits |currently silently | -|integers |zeroed |wraps; in the | -| | |future exceptions | -| | |will be thrown | -+---------------+---------------+-------------------+ +The following table describes the cleaning rules applied to different types, +where ``higher bits`` refers to the remaining bits in case the type has less than 256 bits. + ++---------------+---------------+-------------------------+ +|Type |Valid Values |Cleanup of Invalid Values| ++===============+===============+=========================+ +|enum of n |0 until n - 1 |throws exception | +|members | | | ++---------------+---------------+-------------------------+ +|bool |0 or 1 |results in 1 | ++---------------+---------------+-------------------------+ +|signed integers|higher bits |currently silently | +| |set to the |signextends to a valid | +| |sign bit |value, i.e. all higher | +| | |bits are set to the sign | +| | |bit; may throw an | +| | |exception in the future | ++---------------+---------------+-------------------------+ +|unsigned |higher bits |currently silently masks | +|integers |zeroed |to a valid value, i.e. | +| | |all higher bits are set | +| | |to zero; may throw an | +| | |exception in the future | ++---------------+---------------+-------------------------+ + +Note that valid and invalid values are dependent on their type size. +Consider ``uint8``, the unsigned 8-bit type, which has the following valid values: + +.. code-block:: none + + 0000...0000 0000 0000 + 0000...0000 0000 0001 + 0000...0000 0000 0010 + .... + 0000...0000 1111 1111 + +Any invalid value will have the higher bits set to zero: + +.. code-block:: none + + 0101...1101 0010 1010 invalid value + 0000...0000 0010 1010 cleaned value + +For ``int8``, the signed 8-bit type, the valid values are: + +Negative + +.. code-block:: none + + 1111...1111 1111 1111 + 1111...1111 1111 1110 + .... + 1111...1111 1000 0000 + +Positive + +.. code-block:: none + + 0000...0000 0000 0000 + 0000...0000 0000 0001 + 0000...0000 0000 0010 + .... + 0000...0000 1111 1111 + +The compiler will ``signextend`` the sign bit, which is 1 for negative and 0 for +positive values, overwriting the higher bits: + +Negative + +.. code-block:: none + + 0010...1010 1111 1111 invalid value + 1111...1111 1111 1111 cleaned value + +Positive + +.. code-block:: none + + 1101...0101 0000 0100 invalid value + 0000...0000 0000 0100 cleaned value diff --git a/compiler/docs/introduction-to-smart-contracts.rst b/compiler/docs/introduction-to-smart-contracts.rst index 875e5ebc..c4cc1201 100644 --- a/compiler/docs/introduction-to-smart-contracts.rst +++ b/compiler/docs/introduction-to-smart-contracts.rst @@ -91,7 +91,7 @@ registering with a username and password, all you need is an Ethereum keypair. // The keyword "public" makes variables // accessible from other contracts address public minter; - mapping (address => uint) public balances; + mapping(address => uint) public balances; // Events allow clients to react to specific // contract changes you declare @@ -151,7 +151,7 @@ You do not need to do this, the compiler figures it out for you. .. index:: mapping -The next line, ``mapping (address => uint) public balances;`` also +The next line, ``mapping(address => uint) public balances;`` also creates a public state variable, but it is a more complex datatype. The :ref:`mapping ` type maps addresses to :ref:`unsigned integers `. @@ -185,7 +185,7 @@ arguments ``from``, ``to`` and ``amount``, which makes it possible to track transactions. To listen for this event, you could use the following -JavaScript code, which uses `web3.js `_ to create the ``Coin`` contract object, +JavaScript code, which uses `web3.js `_ to create the ``Coin`` contract object, and any user interface calls the automatically generated ``balances`` function from above: .. code-block:: javascript @@ -282,7 +282,7 @@ the source account is also not modified. Furthermore, a transaction is always cryptographically signed by the sender (creator). This makes it straightforward to guard access to specific modifications of the database. In the example of the electronic currency, a simple check ensures that -only the person holding the keys to the account can transfer money from it. +only the person holding the keys to the account can transfer some compensation, e.g. Ether, from it. .. index:: ! block @@ -300,9 +300,9 @@ and then they will be executed and distributed among all participating nodes. If two transactions contradict each other, the one that ends up being second will be rejected and not become part of the block. -These blocks form a linear sequence in time and that is where the word "blockchain" -derives from. Blocks are added to the chain in rather regular intervals - for -Ethereum this is roughly every 17 seconds. +These blocks form a linear sequence in time, and that is where the word "blockchain" derives from. +Blocks are added to the chain at regular intervals, although these intervals may be subject to change in the future. +For the most up-to-date information, it is recommended to monitor the network, for example, on `Etherscan `_. As part of the "order selection mechanism" (which is called "mining") it may happen that blocks are reverted from time to time, but only at the "tip" of the chain. The more @@ -562,6 +562,11 @@ is removed from the state. Removing the contract in theory sounds like a good idea, but it is potentially dangerous, as if someone sends Ether to removed contracts, the Ether is forever lost. +.. warning:: + From version 0.8.18 and up, the use of ``selfdestruct`` in both Solidity and Yul will trigger a + deprecation warning, since the ``SELFDESTRUCT`` opcode will eventually undergo breaking changes in behavior + as stated in `EIP-6049 `_. + .. warning:: Even if a contract is removed by ``selfdestruct``, it is still part of the history of the blockchain and probably retained by most Ethereum nodes. @@ -586,7 +591,7 @@ Precompiled Contracts There is a small set of contract addresses that are special: The address range between ``1`` and (including) ``8`` contains "precompiled contracts" that can be called as any other contract -but their behaviour (and their gas consumption) is not defined +but their behavior (and their gas consumption) is not defined by EVM code stored at that address (they do not contain code) but instead is implemented in the EVM execution environment itself. diff --git a/compiler/docs/ir-breaking-changes.rst b/compiler/docs/ir-breaking-changes.rst index 7e4efce6..4917cfc7 100644 --- a/compiler/docs/ir-breaking-changes.rst +++ b/compiler/docs/ir-breaking-changes.rst @@ -15,13 +15,13 @@ The IR-based code generator was introduced with an aim to not only allow code generation to be more transparent and auditable but also to enable more powerful optimization passes that span across functions. -You can enable it on the command line using ``--via-ir`` +You can enable it on the command-line using ``--via-ir`` or with the option ``{"viaIR": true}`` in standard-json and we encourage everyone to try it out! For several reasons, there are tiny semantic differences between the old and the IR-based code generator, mostly in areas where we would not -expect people to rely on this behaviour anyway. +expect people to rely on this behavior anyway. This section highlights the main differences between the old and the IR-based codegen. Semantic Only Changes @@ -30,6 +30,8 @@ Semantic Only Changes This section lists the changes that are semantic-only, thus potentially hiding new and different behavior in existing code. +.. _state-variable-initialization-order: + - The order of state variable initialization has changed in case of inheritance. The order used to be: @@ -122,8 +124,8 @@ hiding new and different behavior in existing code. modifier mod() { _; _; } } - If you execute ``f(0)`` in the old code generator, it will return ``2``, while - it will return ``1`` when using the new code generator. + If you execute ``f(0)`` in the old code generator, it will return ``1``, while + it will return ``0`` when using the new code generator. .. code-block:: solidity @@ -174,8 +176,8 @@ hiding new and different behavior in existing code. The function ``preincr_u8(1)`` returns the following values: - - Old code generator: 3 (``1 + 2``) but the return value is unspecified in general - - New code generator: 4 (``2 + 2``) but the return value is not guaranteed + - Old code generator: ``3`` (``1 + 2``) but the return value is unspecified in general + - New code generator: ``4`` (``2 + 2``) but the return value is not guaranteed .. index:: ! evaluation order; function arguments @@ -247,7 +249,7 @@ hiding new and different behavior in existing code. } } - The function `f()` behaves as follows: + The function ``f()`` behaves as follows: - Old code generator: runs out of gas while zeroing the array contents after the large memory allocation - New code generator: reverts due to free memory pointer overflow (does not run out of gas) diff --git a/compiler/docs/layout-of-source-files.rst b/compiler/docs/layout-of-source-files.rst index 9a584785..600f2ed0 100644 --- a/compiler/docs/layout-of-source-files.rst +++ b/compiler/docs/layout-of-source-files.rst @@ -40,7 +40,7 @@ The comment is recognized by the compiler anywhere in the file at the file level, but it is recommended to put it at the top of the file. More information about how to use SPDX license identifiers -can be found at the `SPDX website `_. +can be found at the `SPDX website `_. .. index:: ! pragma @@ -182,7 +182,7 @@ Syntax and Semantics Solidity supports import statements to help modularise your code that are similar to those available in JavaScript (from ES6 on). However, Solidity does not support the concept of -a `default export `_. +a `default export `_. At a global level, you can use import statements of the following form: diff --git a/compiler/docs/logo.svg b/compiler/docs/logo.svg index 86b9f499..19391843 100644 --- a/compiler/docs/logo.svg +++ b/compiler/docs/logo.svg @@ -1,27 +1,8 @@ - - - - -Vector 1 -Created with Sketch. - - - - - - - - - - - - + + + + + + + diff --git a/compiler/docs/make.bat b/compiler/docs/make.bat index bc06e706..d11deb3f 100644 --- a/compiler/docs/make.bat +++ b/compiler/docs/make.bat @@ -28,6 +28,7 @@ if "%1" == "help" ( echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. latexpdf to make LaTeX files and run them through pdflatex echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files @@ -155,16 +156,6 @@ if "%1" == "latexpdf" ( goto end ) -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %BUILDDIR%/.. - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 diff --git a/compiler/docs/metadata.rst b/compiler/docs/metadata.rst index 317ad220..ede16a73 100644 --- a/compiler/docs/metadata.rst +++ b/compiler/docs/metadata.rst @@ -6,18 +6,19 @@ Contract Metadata .. index:: metadata, contract verification -The Solidity compiler automatically generates a JSON file, the contract -metadata, that contains information about the compiled contract. You can use -this file to query the compiler version, the sources used, the ABI and NatSpec -documentation to more safely interact with the contract and verify its source -code. +The Solidity compiler automatically generates a JSON file. +The file contains two kinds of information about the compiled contract: + +- How to interact with the contract: ABI, and NatSpec documentation. +- How to reproduce the compilation and verify a deployed contract: + compiler version, compiler settings, and source files used. The compiler appends by default the IPFS hash of the metadata file to the end -of the bytecode (for details, see below) of each contract, so that you can -retrieve the file in an authenticated way without having to resort to a -centralized data provider. The other available options are the Swarm hash and -not appending the metadata hash to the bytecode. These can be configured via -the :ref:`Standard JSON Interface`. +of the runtime bytecode (not necessarily the creation bytecode) of each contract, +so that, if published, you can retrieve the file in an authenticated way without +having to resort to a centralized data provider. The other available options are +the Swarm hash and not appending the metadata hash to the bytecode. These can be +configured via the :ref:`Standard JSON Interface`. You have to publish the metadata file to IPFS, Swarm, or another service so that others can access it. You create the file by using the ``solc --metadata`` @@ -30,106 +31,50 @@ shall match with the one contained in the bytecode. The metadata file has the following format. The example below is presented in a human-readable way. Properly formatted metadata should use quotes correctly, -reduce whitespace to a minimum and sort the keys of all objects to arrive at a -unique formatting. Comments are not permitted and used here only for +reduce whitespace to a minimum, and sort the keys of all objects in alphabetical order +to arrive at a canonical formatting. Comments are not permitted and are used here only for explanatory purposes. .. code-block:: javascript { - // Required: The version of the metadata format - "version": "1", - // Required: Source code language, basically selects a "sub-version" - // of the specification - "language": "Solidity", // Required: Details about the compiler, contents are specific // to the language. "compiler": { - // Required for Solidity: Version of the compiler - "version": "0.8.2+commit.661d1103", // Optional: Hash of the compiler binary which produced this output - "keccak256": "0x123..." - }, - // Required: Compilation source files/source units, keys are file paths - "sources": - { - "myDirectory/myFile.sol": { - // Required: keccak256 hash of the source file - "keccak256": "0x123...", - // Required (unless "content" is used, see below): Sorted URL(s) - // to the source file, protocol is more or less arbitrary, but an - // IPFS URL is recommended - "urls": [ "bzz-raw://7d7a...", "dweb:/ipfs/QmN..." ], - // Optional: SPDX license identifier as given in the source file - "license": "MIT" - }, - "destructible": { - // Required: keccak256 hash of the source file - "keccak256": "0x234...", - // Required (unless "url" is used): literal contents of the source file - "content": "contract destructible is owned { function destroy() { if (msg.sender == owner) selfdestruct(owner); } }" - } - }, - // Required: Compiler settings - "settings": - { - // Required for Solidity: Sorted list of import remappings - "remappings": [ ":g=/dir" ], - // Optional: Optimizer settings. The fields "enabled" and "runs" are deprecated - // and are only given for backwards-compatibility. - "optimizer": { - "enabled": true, - "runs": 500, - "details": { - // peephole defaults to "true" - "peephole": true, - // inliner defaults to "true" - "inliner": true, - // jumpdestRemover defaults to "true" - "jumpdestRemover": true, - "orderLiterals": false, - "deduplicate": false, - "cse": false, - "constantOptimizer": false, - "yul": true, - // Optional: Only present if "yul" is "true" - "yulDetails": { - "stackAllocation": false, - "optimizerSteps": "dhfoDgvulfnTUtnIf..." - } - } - }, - "metadata": { - // Reflects the setting used in the input json, defaults to "false" - "useLiteralContent": true, - // Reflects the setting used in the input json, defaults to "ipfs" - "bytecodeHash": "ipfs" - }, - // Required for Solidity: File path and the name of the contract or library this - // metadata is created for. - "compilationTarget": { - "myDirectory/myFile.sol": "MyContract" - }, - // Required for Solidity: Addresses for libraries used - "libraries": { - "MyLib": "0x123123..." - } + "keccak256": "0x123...", + // Required for Solidity: Version of the compiler + "version": "0.8.2+commit.661d1103" }, + // Required: Source code language, basically selects a "sub-version" + // of the specification + "language": "Solidity", // Required: Generated information about the contract. - "output": - { + "output": { // Required: ABI definition of the contract. See "Contract ABI Specification" "abi": [/* ... */], - // Required: NatSpec developer documentation of the contract. + // Required: NatSpec developer documentation of the contract. See https://docs.soliditylang.org/en/latest/natspec-format.html for details. "devdoc": { - "version": 1 // NatSpec version - "kind": "dev", // Contents of the @author NatSpec field of the contract "author": "John Doe", - // Contents of the @title NatSpec field of the contract - "title": "MyERC20: an example ERC20" // Contents of the @dev NatSpec field of the contract "details": "Interface of the ERC20 standard as defined in the EIP. See https://eips.ethereum.org/EIPS/eip-20 for details", + "errors": { + "MintToZeroAddress()" : { + "details": "Cannot mint to zero address" + } + }, + "events": { + "Transfer(address,address,uint256)": { + "details": "Emitted when `value` tokens are moved from one account (`from`) toanother (`to`).", + "params": { + "from": "The sender address", + "to": "The receiver address", + "value": "The token amount" + } + } + }, + "kind": "dev", "methods": { "transfer(address,uint256)": { // Contents of the @dev NatSpec field of the method @@ -138,7 +83,7 @@ explanatory purposes. "params": { "_value": "The amount tokens to be transferred", "_to": "The receiver address" - } + }, // Contents of the @return NatSpec field. "returns": { // Return var name (here "success") if exists. "_0" as key if return var is unnamed @@ -151,34 +96,104 @@ explanatory purposes. // Contents of the @dev NatSpec field of the state variable "details": "Must be set during contract creation. Can then only be changed by the owner" } - } - "events": { - "Transfer(address,address,uint256)": { - "details": "Emitted when `value` tokens are moved from one account (`from`) toanother (`to`)." - "params": { - "from": "The sender address" - "to": "The receiver address" - "value": "The token amount" - } - } - } + }, + // Contents of the @title NatSpec field of the contract + "title": "MyERC20: an example ERC20", + "version": 1 // NatSpec version }, - // Required: NatSpec user documentation of the contract + // Required: NatSpec user documentation of the contract. See "NatSpec Format" "userdoc": { - "version": 1 // NatSpec version + "errors": { + "ApprovalCallerNotOwnerNorApproved()": [ + { + "notice": "The caller must own the token or be an approved operator." + } + ] + }, + "events": { + "Transfer(address,address,uint256)": { + "notice": "`_value` tokens have been moved from `from` to `to`" + } + }, "kind": "user", "methods": { "transfer(address,uint256)": { "notice": "Transfers `_value` tokens to address `_to`" } }, - "events": { - "Transfer(address,address,uint256)": { - "notice": "`_value` tokens have been moved from `from` to `to`" + "version": 1 // NatSpec version + } + }, + // Required: Compiler settings. Reflects the settings in the JSON input during compilation. + // Check the documentation of standard JSON input's "settings" field + "settings": { + // Required for Solidity: File path and the name of the contract or library this + // metadata is created for. + "compilationTarget": { + "myDirectory/myFile.sol": "MyContract" + }, + // Required for Solidity. + "evmVersion": "london", + // Required for Solidity: Addresses for libraries used. + "libraries": { + "MyLib": "0x123123..." + }, + "metadata": { + // Reflects the setting used in the input json, defaults to "true" + "appendCBOR": true, + // Reflects the setting used in the input json, defaults to "ipfs" + "bytecodeHash": "ipfs", + // Reflects the setting used in the input json, defaults to "false" + "useLiteralContent": true + }, + // Optional: Optimizer settings. The fields "enabled" and "runs" are deprecated + // and are only given for backward-compatibility. + "optimizer": { + "details": { + "constantOptimizer": false, + "cse": false, + "deduplicate": false, + // inliner defaults to "false" + "inliner": false, + // jumpdestRemover defaults to "true" + "jumpdestRemover": true, + "orderLiterals": false, + // peephole defaults to "true" + "peephole": true, + "yul": true, + // Optional: Only present if "yul" is "true" + "yulDetails": { + "optimizerSteps": "dhfoDgvulfnTUtnIf...", + "stackAllocation": false } - } + }, + "enabled": true, + "runs": 500 + }, + // Required for Solidity: Sorted list of import remappings. + "remappings": [ ":g=/dir" ] + }, + // Required: Compilation source files/source units, keys are file paths + "sources": { + "destructible": { + // Required (unless "url" is used): literal contents of the source file + "content": "contract destructible is owned { function destroy() { if (msg.sender == owner) selfdestruct(owner); } }", + // Required: keccak256 hash of the source file + "keccak256": "0x234..." + }, + "myDirectory/myFile.sol": { + // Required: keccak256 hash of the source file + "keccak256": "0x123...", + // Optional: SPDX license identifier as given in the source file + "license": "MIT", + // Required (unless "content" is used, see above): Sorted URL(s) + // to the source file, protocol is more or less arbitrary, but an + // IPFS URL is recommended + "urls": [ "bzz-raw://7d7a...", "dweb:/ipfs/QmN..." ] } - } + }, + // Required: The version of the metadata format + "version": 1 } .. warning:: @@ -198,41 +213,46 @@ explanatory purposes. Encoding of the Metadata Hash in the Bytecode ============================================= -Because we might support other ways to retrieve the metadata file in the future, -the mapping ``{"ipfs": , "solc": }`` is stored -`CBOR `_-encoded. Since the mapping might -contain more keys (see below) and the beginning of that -encoding is not easy to find, its length is added in a two-byte big-endian -encoding. The current version of the Solidity compiler usually adds the following -to the end of the deployed bytecode +The compiler currently by default appends the +`IPFS hash (in CID v0) `_ +of the canonical metadata file and the compiler version to the end of the bytecode. +Optionally, a Swarm hash instead of the IPFS, or an experimental flag is used. +Below are all the possible fields: -.. code-block:: text +.. code-block:: javascript - 0xa2 - 0x64 'i' 'p' 'f' 's' 0x58 0x22 <34 bytes IPFS hash> - 0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding> - 0x00 0x33 + { + "ipfs": "", + // If "bytecodeHash" was "bzzr1" in compiler settings not "ipfs" but "bzzr1" + "bzzr1": "", + // Previous versions were using "bzzr0" instead of "bzzr1" + "bzzr0": "", + // If any experimental features that affect code generation are used + "experimental": true, + "solc": "" + } + +Because we might support other ways to retrieve the +metadata file in the future, this information is stored +`CBOR `_-encoded. The last two bytes in the bytecode +indicate the length of the CBOR encoded information. By looking at this length, the +relevant part of the bytecode can be decoded with a CBOR decoder. -So in order to retrieve the data, the end of the deployed bytecode can be checked -to match that pattern and the IPFS hash can be used to retrieve the file (if pinned/published). +Check the `Metadata Playground `_ to see it in action. Whereas release builds of solc use a 3 byte encoding of the version as shown -above (one byte each for major, minor and patch version number), prerelease builds +above (one byte each for major, minor and patch version number), pre-release builds will instead use a complete version string including commit hash and build date. -.. note:: - The CBOR mapping can also contain other keys, so it is better to fully - decode the data instead of relying on it starting with ``0xa264``. - For example, if any experimental features that affect code generation - are used, the mapping will also contain ``"experimental": true``. +The commandline flag ``--no-cbor-metadata`` can be used to skip metadata +from getting appended at the end of the deployed bytecode. Equivalently, the +boolean field ``settings.metadata.appendCBOR`` in Standard JSON input can be set to false. .. note:: - The compiler currently uses the IPFS hash of the metadata by default, but - it may also use the bzzr1 hash or some other hash in the future, so do - not rely on this sequence to start with ``0xa2 0x64 'i' 'p' 'f' 's'``. We - might also add additional data to this CBOR structure, so the best option - is to use a proper CBOR parser. - + The CBOR mapping can also contain other keys, so it is better to fully + decode the data by looking at the end of the bytecode for the CBOR length, + and to use a proper CBOR parser. Do not rely on it starting with ``0xa264`` + or ``0xa2 0x64 'i' 'p' 'f' 's'``. Usage for Automatic Interface Generation and NatSpec ==================================================== @@ -246,24 +266,27 @@ 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, the wallet can use the NatSpec user documentation to display a human-readable confirmation message to the user -whenever they interact with the contract, together with requesting -authorization for the transaction signature. +Furthermore, the wallet can use the NatSpec user documentation to display a +human-readable confirmation message to the user whenever they interact with +the contract, together with requesting authorization for the transaction signature. For additional information, read :doc:`Ethereum Natural Language Specification (NatSpec) format `. Usage for Source Code Verification ================================== -In order to verify the compilation, sources can be retrieved from IPFS/Swarm -via the link in the metadata file. -The compiler of the correct version (which is checked to be part of the "official" compilers) -is invoked on that input with the specified settings. The resulting -bytecode is compared to the data of the creation transaction or ``CREATE`` opcode data. -This automatically verifies the metadata since its hash is part of the bytecode. -Excess data corresponds to the constructor input data, which should be decoded -according to the interface and presented to the user. +If pinned/published, it is possible to retrieve the metadata of the contract from IPFS/Swarm. +The metadata file also contains the URLs or the IPFS hashes of the source files, as well as +the compilation settings, i.e. everything needed to reproduce a compilation. + +With this information it is then possible to verify the source code of a contract by +reproducing the compilation, and comparing the bytecode from the compilation with +the bytecode of the deployed contract. + +This automatically verifies the metadata since its hash is part of the bytecode, as well +as the source codes, because their hashes are part of the metadata. Any change in the files +or settings would result in a different metadata hash. The metadata here serves +as a fingerprint of the whole compilation. -In the repository `sourcify `_ -(`npm package `_) you can see -example code that shows how to use this feature. +`Sourcify `_ makes use of this feature for "full/perfect verification", +as well as pinning the files publicly on IPFS to be accessed with the metadata hash. diff --git a/compiler/docs/natspec-format.rst b/compiler/docs/natspec-format.rst index 28848dae..0265fef3 100644 --- a/compiler/docs/natspec-format.rst +++ b/compiler/docs/natspec-format.rst @@ -46,7 +46,7 @@ for the purposes of NatSpec. - For Vyper, use ``"""`` indented to the inner contents with bare comments. See the `Vyper - documentation `__. + documentation `__. The following example shows a contract and a function using all available tags. @@ -58,7 +58,7 @@ The following example shows a contract and a function using all available tags. This may change in the future. -.. code-block:: Solidity +.. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.2 < 0.9.0; @@ -154,10 +154,6 @@ to the end-user as: if a function is being called and the input ``a`` is assigned a value of 10. -Specifying these dynamic expressions is outside the scope of the Solidity -documentation and you may read more at -`the radspec project `__. - .. _header-inheritance: Inheritance Notes diff --git a/compiler/docs/path-resolution.rst b/compiler/docs/path-resolution.rst index 40a72706..ff4930bc 100644 --- a/compiler/docs/path-resolution.rst +++ b/compiler/docs/path-resolution.rst @@ -21,7 +21,7 @@ source unit is assigned a unique *source unit name* which is an opaque and unstr When you use the :ref:`import statement `, you specify an *import path* that references a source unit name. -.. index:: ! import callback, ! Host Filesystem Loader +.. index:: ! import callback, ! Host Filesystem Loader, ! --no-import-callback .. _import-callback: Import Callback @@ -36,11 +36,12 @@ An import callback is free to interpret source unit names in an arbitrary way, n If there is no callback available when one is needed or if it fails to locate the source code, compilation fails. -The command-line compiler provides the *Host Filesystem Loader* - a rudimentary callback +By default, the command-line compiler provides the *Host Filesystem Loader* - a rudimentary callback that interprets a source unit name as a path in the local filesystem. +This callback can be disabled using the ``--no-import-callback`` command-line option. The `JavaScript interface `_ does not provide any by default, but one can be provided by the user. -This mechanism can be used to obtain source code from locations other then the local filesystem +This mechanism can be used to obtain source code from locations other than the local filesystem (which may not even be accessible, e.g. when the compiler is running in a browser). For example the `Remix IDE `_ provides a versatile callback that lets you `import files from HTTP, IPFS and Swarm URLs or refer directly to packages in NPM registry @@ -139,7 +140,7 @@ The initial content of the VFS depends on how you invoke the compiler: #. **Standard input** - On the command line it is also possible to provide the source by sending it to compiler's + On the command-line it is also possible to provide the source by sending it to compiler's standard input: .. code-block:: bash @@ -197,7 +198,7 @@ source unit name. A source unit name is just an identifier and even if its value happens to look like a path, it is not subject to the normalization rules you would typically expect in a shell. - Any ``/./`` or ``/../`` seguments or sequences of multiple slashes remain a part of it. + Any ``/./`` or ``/../`` segments or sequences of multiple slashes remain a part of it. When the source is provided via Standard JSON interface it is entirely possible to associate different content with source unit names that would refer to the same file on disk. @@ -248,19 +249,15 @@ and is bounded by two path separators. A separator is a forward slash or the beginning/end of the string. For example in ``./abc/..//`` there are three path segments: ``.``, ``abc`` and ``..``. -The compiler computes a source unit name from the import path in the following way: +The compiler resolves the import into a source unit name based on the import path, in the following way: -1. First a prefix is computed +#. We start with the source unit name of the importing source unit. +#. The last path segment with preceding slashes is removed from the resolved name. +#. Then, for every segment in the import path, starting from the leftmost one: - - Prefix is initialized with the source unit name of the importing source unit. - - The last path segment with preceding slashes is removed from the prefix. - - Then, the leading part of the normalized import path, consisting only of ``/`` and ``.`` - characters is considered. - For every ``..`` segment found in this part the last path segment with preceding slashes is - removed from the prefix. - -2. Then the prefix is prepended to the normalized import path. - If the prefix is non-empty, a single slash is inserted between it and the import path. + - If the segment is ``.``, it is skipped. + - If the segment is ``..``, the last path segment with preceding slashes is removed from the resolved name. + - Otherwise, the segment (preceded by a single slash if the resolved name is not empty), is appended to the resolved name. The removal of the last path segment with preceding slashes is understood to work as follows: @@ -268,14 +265,10 @@ work as follows: 1. Everything past the last slash is removed (i.e. ``a/b//c.sol`` becomes ``a/b//``). 2. All trailing slashes are removed (i.e. ``a/b//`` becomes ``a/b``). -The normalization rules are the same as for UNIX paths, namely: - -- All the internal ``.`` segments are removed. -- Every internal ``..`` segment backtracks one level up in the hierarchy. -- Multiple slashes are squashed into a single one. - -Note that normalization is performed only on the import path. -The source unit name of the importing module that is used for the prefix remains unnormalized. +Note that the process normalizes the part of the resolved source unit name that comes from the import path according +to the usual rules for UNIX paths, i.e. all ``.`` and ``..`` are removed and multiple slashes are +squashed into a single one. +On the other hand, the part that comes from the source unit name of the importing module remains unnormalized. This ensures that the ``protocol://`` part does not turn into ``protocol:/`` if the importing file is identified with a URL. @@ -353,13 +346,13 @@ of the compiler. CLI Path Normalization and Stripping ------------------------------------ -On the command line the compiler behaves just as you would expect from any other program: +On the command-line the compiler behaves just as you would expect from any other program: it accepts paths in a format native to the platform and relative paths are relative to the current working directory. -The source unit names assigned to files whose paths are specified on the command line, however, +The source unit names assigned to files whose paths are specified on the command-line, however, should not change just because the project is being compiled on a different platform or because the compiler happens to have been invoked from a different directory. -To achieve this, paths to source files coming from the command line must be converted to a canonical +To achieve this, paths to source files coming from the command-line must be converted to a canonical form, and, if possible, made relative to the base path or one of the include paths. The normalization rules are as follows: @@ -416,7 +409,7 @@ The resulting file path becomes the source unit name. Prior to version 0.8.8, CLI path stripping was not performed and the only normalization applied was the conversion of path separators. When working with older versions of the compiler it is recommended to invoke the compiler from - the base path and to only use relative paths on the command line. + the base path and to only use relative paths on the command-line. .. index:: ! allowed paths, ! --allow-paths, remapping; target .. _allowed-paths: @@ -429,7 +422,7 @@ locations that are considered safe by default: - Outside of Standard JSON mode: - - The directories containing input files listed on the command line. + - The directories containing input files listed on the command-line. - The directories used as :ref:`remapping ` targets. If the target is not a directory (i.e does not end with ``/``, ``/.`` or ``/..``) the directory containing the target is used instead. @@ -523,7 +516,7 @@ you can use the following in your source file: The compiler will look for the file in the VFS under ``dapp-bin/library/math.sol``. If the file is not available there, the source unit name will be passed to the Host Filesystem -Loader, which will then look in ``/project/dapp-bin/library/iterable_mapping.sol``. +Loader, which will then look in ``/project/dapp-bin/library/math.sol``. .. warning:: @@ -559,7 +552,7 @@ you checked out to ``/project/dapp-bin_old``, then you can run: This means that all imports in ``module2`` point to the old version but imports in ``module1`` point to the new version. -Here are the detailed rules governing the behaviour of remappings: +Here are the detailed rules governing the behavior of remappings: #. **Remappings only affect the translation between import paths and source unit names.** diff --git a/compiler/docs/requirements.txt b/compiler/docs/requirements.txt index 23c8f904..b1b21a86 100644 --- a/compiler/docs/requirements.txt +++ b/compiler/docs/requirements.txt @@ -4,7 +4,7 @@ sphinx_rtd_theme>=0.5.2 pygments-lexer-solidity>=0.7.0 -sphinx-a4doc>=1.2.1 +sphinx-a4doc>=1.6.0 # Sphinx 2.1.0 is the oldest version that accepts a lexer class in add_lexer() -sphinx>=2.1.0 +sphinx>=2.1.0, <6.0 diff --git a/compiler/docs/resources.rst b/compiler/docs/resources.rst index c70b35fb..85b3cf9a 100644 --- a/compiler/docs/resources.rst +++ b/compiler/docs/resources.rst @@ -23,12 +23,9 @@ Integrated (Ethereum) Development Environments Python-based development and testing framework for smart contracts targeting the Ethereum Virtual Machine. * `Dapp `_ - Tool for building, testing and deploying smart contracts from the command line. + Tool for building, testing and deploying smart contracts from the command-line. - * `Embark `_ - Developer platform for building and deploying decentralized applications. - - * `Foundry `_ + * `Foundry `_ Fast, portable and modular toolkit for Ethereum application development written in Rust. * `Hardhat `_ @@ -37,23 +34,12 @@ Integrated (Ethereum) Development Environments * `Remix `_ Browser-based IDE with integrated compiler and Solidity runtime environment without server-side components. - * `Truffle `_ + * `Truffle `_ Ethereum development framework. Editor Integrations =================== -* Atom - - * `Etheratom `_ - Plugin for the Atom editor that features syntax highlighting, compilation and a runtime environment (Backend node & VM compatible). - - * `Atom Solidity Linter `_ - Plugin for the Atom editor that provides Solidity linting. - - * `Atom Solium Linter `_ - Configurable Solidity linter for Atom using Solium (now Ethlint) as a base. - * Emacs * `Emacs Solidity `_ @@ -61,30 +47,42 @@ Editor Integrations * IntelliJ - * `IntelliJ IDEA plugin `_ - Solidity plugin for IntelliJ IDEA (and all other JetBrains IDEs) + * `IntelliJ IDEA plugin `_ + Solidity plugin for IntelliJ IDEA (and all other JetBrains IDEs). -* Sublime +* Sublime Text * `Package for SublimeText - Solidity language syntax `_ Solidity syntax highlighting for SublimeText editor. * Vim - * `Vim Solidity `_ - Plugin for the Vim editor providing syntax highlighting. + * `Vim Solidity by Thesis `_ + Syntax highlighting for Solidity in Vim. + + * `Vim Solidity by TovarishFin `_ + Vim syntax file for Solidity. * `Vim Syntastic `_ Plugin for the Vim editor providing compile checking. -* Visual Studio Code +* Visual Studio Code (VS Code) + + * `Ethereum Remix Visual Studio Code extension `_ + Ethereum Remix extension pack for VS Code - * `Visual Studio Code extension `_ + * `Solidity Visual Studio Code extension, by Juan Blanco `_ Solidity plugin for Microsoft Visual Studio Code that includes syntax highlighting and the Solidity compiler. + * `Solidity Visual Studio Code extension, by Nomic Foundation `_ + Solidity and Hardhat support by the Hardhat team, including: syntax highlighting, jump to definition, renames, quick fixes and inline solc warnings and errors. + * `Solidity Visual Auditor extension `_ Adds security centric syntax and semantic highlighting to Visual Studio Code. + * `Truffle for VS Code `_ + Build, debug and deploy smart contracts on Ethereum and EVM-compatible blockchains. + Solidity Tools ============== @@ -112,9 +110,6 @@ Solidity Tools * `leafleth `_ A documentation generator for Solidity smart-contracts. -* `PIET `_ - A tool to develop, audit and use Solidity smart contracts through a simple graphical interface. - * `Scaffold-ETH `_ Forkable Ethereum development stack focused on fast product iterations. diff --git a/compiler/docs/security-considerations.rst b/compiler/docs/security-considerations.rst index 6507f7c1..92d60104 100644 --- a/compiler/docs/security-considerations.rst +++ b/compiler/docs/security-considerations.rst @@ -7,28 +7,28 @@ Security Considerations While it is usually quite easy to build software that works as expected, it is much harder to check that nobody can use it in a way that was **not** anticipated. -In Solidity, this is even more important because you can use smart contracts -to handle tokens or, possibly, even more valuable things. Furthermore, every -execution of a smart contract happens in public and, in addition to that, -the source code is often available. - -Of course you always have to consider how much is at stake: -You can compare a smart contract with a web service that is open to the -public (and thus, also to malicious actors) and perhaps even open source. -If you only store your grocery list on that web service, you might not have -to take too much care, but if you manage your bank account using that web service, -you should be more careful. - -This section will list some pitfalls and general security recommendations but -can, of course, never be complete. Also, keep in mind that even if your smart -contract code is bug-free, the compiler or the platform itself might have a -bug. A list of some publicly known security-relevant bugs of the compiler can -be found in the :ref:`list of known bugs`, which is also -machine-readable. Note that there is a bug bounty program that covers the code -generator of the Solidity compiler. - -As always, with open source documentation, please help us extend this section -(especially, some examples would not hurt)! +In Solidity, this is even more important because you can use smart contracts to handle tokens or, +possibly, even more valuable things. +Furthermore, every execution of a smart contract happens in public and, +in addition to that, the source code is often available. + +Of course, you always have to consider how much is at stake: +You can compare a smart contract with a web service that is open to the public +(and thus, also to malicious actors) and perhaps even open-source. +If you only store your grocery list on that web service, you might not have to take too much care, +but if you manage your bank account using that web service, you should be more careful. + +This section will list some pitfalls and general security recommendations +but can, of course, never be complete. +Also, keep in mind that even if your smart contract code is bug-free, +the compiler or the platform itself might have a bug. +A list of some publicly known security-relevant bugs of the compiler can be found +in the :ref:`list of known bugs`, which is also machine-readable. +Note that there is a `Bug Bounty Program `_ +that covers the code generator of the Solidity compiler. + +As always, with open-source documentation, +please help us extend this section (especially, some examples would not hurt)! NOTE: In addition to the list below, you can find more security recommendations and best practices `in Guy Lando's knowledge list `_ and @@ -41,20 +41,18 @@ Pitfalls Private Information and Randomness ================================== -Everything you use in a smart contract is publicly visible, even -local variables and state variables marked ``private``. +Everything you use in a smart contract is publicly visible, +even local variables and state variables marked ``private``. -Using random numbers in smart contracts is quite tricky if you do not want -miners to be able to cheat. +Using random numbers in smart contracts is quite tricky if you do not want block builders to be able to cheat. -Re-Entrancy -=========== +Reentrancy +========== -Any interaction from a contract (A) with another contract (B) and any transfer -of Ether hands over control to that contract (B). This makes it possible for B -to call back into A before this interaction is completed. To give an example, -the following code contains a bug (it is just a snippet and not a -complete contract): +Any interaction from a contract (A) with another contract (B) +and any transfer of Ether hands over control to that contract (B). +This makes it possible for B to call back into A before this interaction is completed. +To give an example, the following code contains a bug (it is just a snippet and not a complete contract): .. code-block:: solidity @@ -72,12 +70,12 @@ complete contract): } } -The problem is not too serious here because of the limited gas as part -of ``send``, but it still exposes a weakness: Ether transfer can always -include code execution, so the recipient could be a contract that calls -back into ``withdraw``. This would let it get multiple refunds and -basically retrieve all the Ether in the contract. In particular, the -following contract will allow an attacker to refund multiple times +The problem is not too serious here because of the limited gas as part of ``send``, +but it still exposes a weakness: +Ether transfer can always include code execution, +so the recipient could be a contract that calls back into ``withdraw``. +This would let it get multiple refunds and, basically, retrieve all the Ether in the contract. +In particular, the following contract will allow an attacker to refund multiple times as it uses ``call`` which forwards all remaining gas by default: .. code-block:: solidity @@ -97,8 +95,7 @@ as it uses ``call`` which forwards all remaining gas by default: } } -To avoid re-entrancy, you can use the Checks-Effects-Interactions pattern as -demonstrated below: +To avoid reentrancy, you can use the Checks-Effects-Interactions pattern as demonstrated below: .. code-block:: solidity @@ -116,58 +113,58 @@ demonstrated below: } } -The Checks-Effects-Interactions pattern ensures that all code paths through a contract complete all required checks -of the supplied parameters before modifying the contract's state (Checks); only then it makes any changes to the state (Effects); -it may make calls to functions in other contracts *after* all planned state changes have been written to -storage (Interactions). This is a common foolproof way to prevent *re-entrancy attacks*, where an externally called -malicious contract is able to double-spend an allowance, double-withdraw a balance, among other things, by using logic that calls back into the -original contract before it has finalized its transaction. - -Note that re-entrancy is not only an effect of Ether transfer but of any -function call on another contract. Furthermore, you also have to take -multi-contract situations into account. A called contract could modify the -state of another contract you depend on. +The Checks-Effects-Interactions pattern ensures that all code paths through a contract +complete all required checks of the supplied parameters before modifying the contract's state (Checks); +only then it makes any changes to the state (Effects); +it may make calls to functions in other contracts +*after* all planned state changes have been written to storage (Interactions). +This is a common foolproof way to prevent *reentrancy attacks*, +where an externally called malicious contract can double-spend an allowance, +double-withdraw a balance, among other things, +by using logic that calls back into the original contract before it has finalized its transaction. + +Note that reentrancy is not only an effect of Ether transfer +but of any function call on another contract. +Furthermore, you also have to take multi-contract situations into account. +A called contract could modify the state of another contract you depend on. Gas Limit and Loops =================== -Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully: -Due to the block gas limit, transactions can only consume a certain amount of gas. Either explicitly or just due to -normal operation, the number of iterations in a loop can grow beyond the block gas limit which can cause the complete -contract to be stalled at a certain point. This may not apply to ``view`` functions that are only executed -to read data from the blockchain. Still, such functions may be called by other contracts as part of on-chain operations -and stall those. Please be explicit about such cases in the documentation of your contracts. +Loops that do not have a fixed number of iterations, for example, +loops that depend on storage values, have to be used carefully: +Due to the block gas limit, transactions can only consume a certain amount of gas. +Either explicitly or just due to normal operation, +the number of iterations in a loop can grow beyond the block gas limit +which can cause the complete contract to be stalled at a certain point. +This may not apply to ``view`` functions that are only executed to read data from the blockchain. +Still, such functions may be called by other contracts as part of on-chain operations and stall those. +Please be explicit about such cases in the documentation of your contracts. Sending and Receiving Ether =========================== -- Neither contracts nor "external accounts" are currently able to prevent that someone sends them Ether. - Contracts can react on and reject a regular transfer, but there are ways - to move Ether without creating a message call. One way is to simply "mine to" - the contract address and the second way is using ``selfdestruct(x)``. +- Neither contracts nor "external accounts" are currently able to prevent someone from sending them Ether. + Contracts can react on and reject a regular transfer, but there are ways to move Ether without creating a message call. + One way is to simply "mine to" the contract address and the second way is using ``selfdestruct(x)``. -- If a contract receives Ether (without a function being called), - either the :ref:`receive Ether ` +- If a contract receives Ether (without a function being called), either the :ref:`receive Ether ` or the :ref:`fallback ` function is executed. - If it does not have a receive nor a fallback function, the Ether will be - rejected (by throwing an exception). During the execution of one of these - functions, 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 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 receive and fallback functions + If it does not have a ``receive`` nor a ``fallback`` function, the Ether will be rejected (by throwing an exception). + During the execution of one of these functions, 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 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 receive and fallback functions (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 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. +- 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 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. -- Use the most precise units to represent the wei amount as possible, as you lose - any that is rounded due to a lack of precision. +- Use the most precise units to represent the Wei amount as possible, as you lose any that is rounded due to a lack of precision. - If you want to send Ether using ``address.transfer``, there are certain details to be aware of: @@ -191,24 +188,28 @@ Sending and Receiving Ether Call Stack Depth ================ -External function calls can fail any time because they exceed the maximum -call stack size limit of 1024. In such situations, Solidity throws an exception. +External function calls can fail at any time +because they exceed the maximum call stack size limit of 1024. +In such situations, Solidity throws an exception. Malicious actors might be able to force the call stack to a high value -before they interact with your contract. Note that, since `Tangerine Whistle `_ hardfork, the `63/64 rule `_ makes call stack depth attack impractical. Also note that the call stack and the expression stack are unrelated, even though both have a size limit of 1024 stack slots. +before they interact with your contract. +Note that, since `Tangerine Whistle `_ hardfork, +the `63/64 rule `_ makes call stack depth attack impractical. +Also note that the call stack and the expression stack are unrelated, +even though both have a size limit of 1024 stack slots. -Note that ``.send()`` does **not** throw an exception if the call stack is -depleted but rather returns ``false`` in that case. The low-level functions -``.call()``, ``.delegatecall()`` and ``.staticcall()`` behave in the same way. +Note that ``.send()`` does **not** throw an exception if the call stack is depleted +but rather returns ``false`` in that case. +The low-level functions ``.call()``, ``.delegatecall()`` and ``.staticcall()`` behave in the same way. Authorized Proxies ================== -If your contract can act as a proxy, i.e. if it can call arbitrary contracts -with user-supplied data, then the user can essentially assume the identity -of the proxy contract. Even if you have other protective measures in place, -it is best to build your contract system such that the proxy does not have -any permissions (not even for itself). If needed, you can accomplish that -using a second proxy: +If your contract can act as a proxy, i.e. if it can call arbitrary contracts with user-supplied data, +then the user can essentially assume the identity of the proxy contract. +Even if you have other protective measures in place, it is best to build your contract system such +that the proxy does not have any permissions (not even for itself). +If needed, you can accomplish that using a second proxy: .. code-block:: solidity @@ -236,7 +237,8 @@ using a second proxy: tx.origin ========= -Never use tx.origin for authorization. Let's say you have a wallet contract like this: +Never use ``tx.origin`` for authorization. +Let's say you have a wallet contract like this: .. code-block:: solidity @@ -279,7 +281,11 @@ 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. +If your wallet had checked ``msg.sender`` for authorization, it would get the address of the attack wallet, +instead of the owner's address. +But by checking ``tx.origin``, it gets the original address that kicked off the transaction, +which is still the owner's address. +The attack wallet instantly drains all your funds. .. _underflow-overflow: @@ -319,16 +325,14 @@ Try to use ``require`` to limit the size of inputs to a reasonable range and use Clearing Mappings ================= -The Solidity type ``mapping`` (see :ref:`mapping-types`) is a storage-only -key-value data structure that does not keep track of the keys that were -assigned a non-zero value. Because of that, cleaning a mapping without extra -information about the written keys is not possible. -If a ``mapping`` is used as the base type of a dynamic storage array, deleting -or popping the array will have no effect over the ``mapping`` elements. The -same happens, for example, if a ``mapping`` is used as the type of a member -field of a ``struct`` that is the base type of a dynamic storage array. The -``mapping`` is also ignored in assignments of structs or arrays containing a -``mapping``. +The Solidity type ``mapping`` (see :ref:`mapping-types`) is a storage-only key-value data structure +that does not keep track of the keys that were assigned a non-zero value. +Because of that, cleaning a mapping without extra information about the written keys is not possible. +If a ``mapping`` is used as the base type of a dynamic storage array, +deleting or popping the array will have no effect over the ``mapping`` elements. +The same happens, for example, if a ``mapping`` is used as the type of a member field of a ``struct`` +that is the base type of a dynamic storage array. +The ``mapping`` is also ignored in assignments of structs or arrays containing a ``mapping``. .. code-block:: solidity @@ -336,7 +340,7 @@ field of a ``struct`` that is the base type of a dynamic storage array. The pragma solidity >=0.6.0 <0.9.0; contract Map { - mapping (uint => uint)[] array; + mapping(uint => uint)[] array; function allocate(uint newMaps) public { for (uint i = 0; i < newMaps; i++) @@ -356,15 +360,12 @@ field of a ``struct`` that is the base type of a dynamic storage array. The } } -Consider the example above and the following sequence of calls: ``allocate(10)``, -``writeMap(4, 128, 256)``. +Consider the example above and the following sequence of calls: ``allocate(10)``, ``writeMap(4, 128, 256)``. At this point, calling ``readMap(4, 128)`` returns 256. -If we call ``eraseMaps``, the length of state variable ``array`` is zeroed, but -since its ``mapping`` elements cannot be zeroed, their information stays alive -in the contract's storage. -After deleting ``array``, calling ``allocate(5)`` allows us to access -``array[4]`` again, and calling ``readMap(4, 128)`` returns 256 even without -another call to ``writeMap``. +If we call ``eraseMaps``, the length of the state variable ``array`` is zeroed, +but since its ``mapping`` elements cannot be zeroed, their information stays alive in the contract's storage. +After deleting ``array``, calling ``allocate(5)`` allows us to access ``array[4]`` again, +and calling ``readMap(4, 128)`` returns 256 even without another call to ``writeMap``. If your ``mapping`` information must be deleted, consider using a library similar to `iterable mapping `_, @@ -375,10 +376,11 @@ Minor Details - Types that do not occupy the full 32 bytes might contain "dirty higher order bits". This is especially important if you access ``msg.data`` - it poses a malleability risk: - You can craft transactions that call a function ``f(uint8 x)`` with a raw byte argument - of ``0xff000001`` and with ``0x00000001``. Both are fed to the contract and both will - look like the number ``1`` as far as ``x`` is concerned, but ``msg.data`` will - be different, so if you use ``keccak256(msg.data)`` for anything, you will get different results. + You can craft transactions that call a function ``f(uint8 x)`` + with a raw byte argument of ``0xff000001`` and with ``0x00000001``. + Both are fed to the contract and both will look like the number ``1`` as far as ``x`` is concerned, + but ``msg.data`` will be different, so if you use ``keccak256(msg.data)`` for anything, + you will get different results. *************** Recommendations @@ -388,48 +390,45 @@ Take Warnings Seriously ======================= If the compiler warns you about something, you should change it. -Even if you do not think that this particular warning has security -implications, there might be another issue buried beneath it. -Any compiler warning we issue can be silenced by slight changes to the -code. +Even if you do not think that this particular warning has security implications, +there might be another issue buried beneath it. +Any compiler warning we issue can be silenced by slight changes to the code. -Always use the latest version of the compiler to be notified about all recently -introduced warnings. +Always use the latest version of the compiler to be notified about all recently introduced warnings. -Messages of type ``info`` issued by the compiler are not dangerous, and simply -represent extra suggestions and optional information that the compiler thinks -might be useful to the user. +Messages of type ``info``, issued by the compiler, are not dangerous +and simply represent extra suggestions and optional information +that the compiler thinks might be useful to the user. Restrict the Amount of Ether ============================ -Restrict the amount of Ether (or other tokens) that can be stored in a smart -contract. If your source code, the compiler or the platform has a bug, these -funds may be lost. If you want to limit your loss, limit the amount of Ether. +Restrict the amount of Ether (or other tokens) that can be stored in a smart contract. +If your source code, the compiler or the platform has a bug, these funds may be lost. +If you want to limit your loss, limit the amount of Ether. Keep it Small and Modular ========================= -Keep your contracts small and easily understandable. Single out unrelated -functionality in other contracts or into libraries. General recommendations -about source code quality of course apply: Limit the amount of local variables, -the length of functions and so on. Document your functions so that others -can see what your intention was and whether it is different than what the code does. +Keep your contracts small and easily understandable. +Single out unrelated functionality in other contracts or into libraries. +General recommendations about the source code quality of course apply: +Limit the amount of local variables, the length of functions and so on. +Document your functions so that others can see what your intention was +and whether it is different than what the code does. Use the Checks-Effects-Interactions Pattern =========================================== -Most functions will first perform some checks (who called the function, -are the arguments in range, did they send enough Ether, does the person -have tokens, etc.). These checks should be done first. +Most functions will first perform some checks and they should be done first +(who called the function, are the arguments in range, did they send enough Ether, +does the person have tokens, etc.). -As the second step, if all checks passed, effects to the state variables -of the current contract should be made. Interaction with other contracts -should be the very last step in any function. +As the second step, if all checks passed, effects to the state variables of the current contract should be made. +Interaction with other contracts should be the very last step in any function. -Early contracts delayed some effects and waited for external function -calls to return in a non-error state. This is often a serious mistake -because of the re-entrancy problem explained above. +Early contracts delayed some effects and waited for external function calls to return in a non-error state. +This is often a serious mistake because of the reentrancy problem explained above. Note that, also, calls to known contracts might in turn cause calls to unknown contracts, so it is probably better to just always apply this pattern. @@ -437,24 +436,23 @@ unknown contracts, so it is probably better to just always apply this pattern. Include a Fail-Safe Mode ======================== -While making your system fully decentralised will remove any intermediary, -it might be a good idea, especially for new code, to include some kind -of fail-safe mechanism: +While making your system fully decentralized will remove any intermediary, +it might be a good idea, especially for new code, to include some kind of fail-safe mechanism: -You can add a function in your smart contract that performs some -self-checks like "Has any Ether leaked?", +You can add a function in your smart contract that performs some self-checks like "Has any Ether leaked?", "Is the sum of the tokens equal to the balance of the contract?" or similar things. -Keep in mind that you cannot use too much gas for that, so help through off-chain -computations might be needed there. +Keep in mind that you cannot use too much gas for that, +so help through off-chain computations might be needed there. -If the self-check fails, the contract automatically switches into some kind -of "failsafe" mode, which, for example, disables most of the features, hands over -control to a fixed and trusted third party or just converts the contract into -a simple "give me back my money" contract. +If the self-check fails, the contract automatically switches into some kind of "failsafe" mode, +which, for example, disables most of the features, +hands over control to a fixed and trusted third party +or just converts the contract into a simple "give me back my Ether" 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. +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. diff --git a/compiler/docs/smtchecker.rst b/compiler/docs/smtchecker.rst index 6d2f4373..f8085b63 100644 --- a/compiler/docs/smtchecker.rst +++ b/compiler/docs/smtchecker.rst @@ -73,7 +73,7 @@ Tutorial Overflow ======== -.. code-block:: Solidity +.. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0; @@ -97,7 +97,7 @@ Overflow The contract above shows an overflow check example. The SMTChecker does not check underflow and overflow by default for Solidity >=0.8.7, -so we need to use the command line option ``--model-checker-targets "underflow,overflow"`` +so we need to use the command-line option ``--model-checker-targets "underflow,overflow"`` or the JSON option ``settings.modelChecker.targets = ["underflow", "overflow"]``. See :ref:`this section for targets configuration`. Here, it reports the following: @@ -122,7 +122,7 @@ Here, it reports the following: If we add ``require`` statements that filter out overflow cases, the SMTChecker proves that no overflow is reachable (by not reporting warnings): -.. code-block:: Solidity +.. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0; @@ -160,7 +160,7 @@ Since ``f`` is indeed monotonically increasing, the SMTChecker proves that our property is correct. You are encouraged to play with the property and the function definition to see what results come out! -.. code-block:: Solidity +.. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0; @@ -182,7 +182,7 @@ The following code searches for the maximum element of an unrestricted array of numbers, and asserts the property that the found element must be greater or equal every element in the array. -.. code-block:: Solidity +.. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0; @@ -216,7 +216,7 @@ All the properties are correctly proven safe. Feel free to change the properties and/or add restrictions on the array to see different results. For example, changing the code to -.. code-block:: Solidity +.. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0; @@ -268,7 +268,7 @@ Let us place a robot at position (0, 0). The robot can only move diagonally, one and cannot move outside the grid. The robot's state machine can be represented by the smart contract below. -.. code-block:: Solidity +.. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0; @@ -319,7 +319,7 @@ We can also trick the SMTChecker into giving us a path to a certain position we think might be reachable. We can add the property that (2, 4) is *not* reachable, by adding the following function. -.. code-block:: Solidity +.. code-block:: solidity function reach_2_4() public view { assert(!(x == 2 && y == 4)); @@ -368,7 +368,7 @@ In some cases, it is possible to automatically infer properties over state variables that are still true even if the externally called code can do anything, including reenter the caller contract. -.. code-block:: Solidity +.. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0; @@ -412,7 +412,7 @@ is already "locked", so it would not be possible to change the value of ``x``, regardless of what the unknown called code does. If we "forget" to use the ``mutex`` modifier on function ``set``, the -SMTChecker is able to synthesize the behaviour of the externally called code so +SMTChecker is able to synthesize the behavior of the externally called code so that the assertion fails: .. code-block:: text @@ -483,6 +483,14 @@ All targets are checked by default, except underflow and overflow for Solidity > There is no precise heuristic on how and when to split verification targets, but it can be useful especially when dealing with large contracts. +Proved Targets +============== + +If there are any proved targets, the SMTChecker issues one warning per engine stating +how many targets were proved. If the user wishes to see all the specific +proved targets, the CLI option ``--model-checker-show-proved`` and +the JSON option ``settings.modelChecker.showProved = true`` can be used. + Unproved Targets ================ @@ -491,6 +499,23 @@ how many unproved targets there are. If the user wishes to see all the specific unproved targets, the CLI option ``--model-checker-show-unproved`` and the JSON option ``settings.modelChecker.showUnproved = true`` can be used. +Unsupported Language Features +============================= + +Certain Solidity language features are not completely supported by the SMT +encoding that the SMTChecker applies, for example assembly blocks. +The unsupported construct is abstracted via overapproximation to preserve +soundness, meaning any properties reported safe are safe even though this +feature is unsupported. +However such abstraction may cause false positives when the target properties +depend on the precise behavior of the unsupported feature. +If the encoder encounters such cases it will by default report a generic warning +stating how many unsupported features it has seen. +If the user wishes to see all the specific unsupported features, the CLI option +``--model-checker-show-unsupported`` and the JSON option +``settings.modelChecker.showUnsupported = true`` can be used, where their default +value is ``false``. + Verified Contracts ================== @@ -518,13 +543,196 @@ which has the following form: "source2.sol": ["contract2", "contract3"] } +Trusted External Calls +====================== + +By default, the SMTChecker does not assume that compile-time available code +is the same as the runtime code for external calls. Take the following contracts +as an example: + +.. code-block:: solidity + + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.8.0; + + contract Ext { + uint public x; + function setX(uint _x) public { x = _x; } + } + contract MyContract { + function callExt(Ext _e) public { + _e.setX(42); + assert(_e.x() == 42); + } + } + +When ``MyContract.callExt`` is called, an address is given as the argument. +At deployment time, we cannot know for sure that address ``_e`` actually +contains a deployment of contract ``Ext``. +Therefore, the SMTChecker will warn that the assertion above can be violated, +which is true, if ``_e`` contains another contract than ``Ext``. + +However, it can be useful to treat these external calls as trusted, for example, +to test that different implementations of an interface conform to the same property. +This means assuming that address ``_e`` indeed was deployed as contract ``Ext``. +This mode can be enabled via the CLI option ``--model-checker-ext-calls=trusted`` +or the JSON field ``settings.modelChecker.extCalls: "trusted"``. + +Please be aware that enabling this mode can make the SMTChecker analysis much more +computationally costly. + +An important part of this mode is that it is applied to contract types and high +level external calls to contracts, and not low level calls such as ``call`` and +``delegatecall``. The storage of an address is stored per contract type, and +the SMTChecker assumes that an externally called contract has the type of the +caller expression. Therefore, casting an ``address`` or a contract to +different contract types will yield different storage values and can give +unsound results if the assumptions are inconsistent, such as the example below: + +.. code-block:: solidity + + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.8.0; + + contract D { + constructor(uint _x) { x = _x; } + uint public x; + function setX(uint _x) public { x = _x; } + } + + contract E { + constructor() { x = 2; } + uint public x; + function setX(uint _x) public { x = _x; } + } + + contract C { + function f() public { + address d = address(new D(42)); + + // `d` was deployed as `D`, so its `x` should be 42 now. + assert(D(d).x() == 42); // should hold + assert(D(d).x() == 43); // should fail + + // E and D have the same interface, so the following + // call would also work at runtime. + // However, the change to `E(d)` is not reflected in `D(d)`. + E(d).setX(1024); + + // Reading from `D(d)` now will show old values. + // The assertion below should fail at runtime, + // but succeeds in this mode's analysis (unsound). + assert(D(d).x() == 42); + // The assertion below should succeed at runtime, + // but fails in this mode's analysis (false positive). + assert(D(d).x() == 1024); + } + } + +Due to the above, make sure that the trusted external calls to a certain +variable of ``address`` or ``contract`` type always have the same caller +expression type. + +It is also helpful to cast the called contract's variable as the type of the +most derived type in case of inheritance. + +.. code-block:: solidity + + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.8.0; + + interface Token { + function balanceOf(address _a) external view returns (uint); + function transfer(address _to, uint _amt) external; + } + + contract TokenCorrect is Token { + mapping (address => uint) balance; + constructor(address _a, uint _b) { + balance[_a] = _b; + } + function balanceOf(address _a) public view override returns (uint) { + return balance[_a]; + } + function transfer(address _to, uint _amt) public override { + require(balance[msg.sender] >= _amt); + balance[msg.sender] -= _amt; + balance[_to] += _amt; + } + } + + contract Test { + function property_transfer(address _token, address _to, uint _amt) public { + require(_to != address(this)); + + TokenCorrect t = TokenCorrect(_token); + + uint xPre = t.balanceOf(address(this)); + require(xPre >= _amt); + uint yPre = t.balanceOf(_to); + + t.transfer(_to, _amt); + uint xPost = t.balanceOf(address(this)); + uint yPost = t.balanceOf(_to); + + assert(xPost == xPre - _amt); + assert(yPost == yPre + _amt); + } + } + +Note that in function ``property_transfer``, the external calls are +performed on variable ``t``. + +Another caveat of this mode are calls to state variables of contract type +outside the analyzed contract. In the code below, even though ``B`` deploys +``A``, it is also possible for the address stored in ``B.a`` to be called by +anyone outside of ``B`` in between transactions to ``B`` itself. To reflect the +possible changes to ``B.a``, the encoding allows an unbounded number of calls +to be made to ``B.a`` externally. The encoding will keep track of ``B.a``'s +storage, therefore assertion (2) should hold. However, currently the encoding +allows such calls to be made from ``B`` conceptually, therefore assertion (3) +fails. Making the encoding stronger logically is an extension of the trusted +mode and is under development. Note that the encoding does not keep track of +storage for ``address`` variables, therefore if ``B.a`` had type ``address`` +the encoding would assume that its storage does not change in between +transactions to ``B``. + +.. code-block:: solidity + + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.8.0; + + contract A { + uint public x; + address immutable public owner; + constructor() { + owner = msg.sender; + } + function setX(uint _x) public { + require(msg.sender == owner); + x = _x; + } + } + + contract B { + A a; + constructor() { + a = new A(); + assert(a.x() == 0); // (1) should hold + } + function g() public view { + assert(a.owner() == address(this)); // (2) should hold + assert(a.x() == 0); // (3) should hold, but fails due to a false positive + } + } + Reported Inferred Inductive Invariants ====================================== For properties that were proved safe with the CHC engine, the SMTChecker can retrieve inductive invariants that were inferred by the Horn solver as part of the proof. -Currently two types of invariants can be reported to the user: +Currently only two types of invariants can be reported to the user: - Contract Invariants: these are properties over the contract's state variables that are true before and after every possible transaction that the contract may ever run. For example, ``x >= y``, where ``x`` and ``y`` are a contract's state variables. @@ -543,7 +751,7 @@ and modulo operations inside Horn rules. Because of that, by default the Solidity division and modulo operations are encoded using the constraint ``a = b * d + m`` where ``d = a / b`` and ``m = a % b``. However, other solvers, such as Eldarica, prefer the syntactically precise operations. -The command line flag ``--model-checker-div-mod-no-slacks`` and the JSON option +The command-line flag ``--model-checker-div-mod-no-slacks`` and the JSON option ``settings.modelChecker.divModNoSlacks`` can be used to toggle the encoding depending on the used solver preferences. @@ -614,25 +822,26 @@ which is primarily an SMT solver and makes `Spacer `_ which does both. The user can choose which solvers should be used, if available, via the CLI -option ``--model-checker-solvers {all,cvc4,smtlib2,z3}`` or the JSON option +option ``--model-checker-solvers {all,cvc4,eld,smtlib2,z3}`` or the JSON option ``settings.modelChecker.solvers=[smtlib2,z3]``, where: - ``cvc4`` is only available if the ``solc`` binary is compiled with it. Only BMC uses ``cvc4``. +- ``eld`` is used via its binary which must be installed in the system. Only CHC uses ``eld``, and only if ``z3`` is not enabled. - ``smtlib2`` outputs SMT/Horn queries in the `smtlib2 `_ format. These can be used together with the compiler's `callback mechanism `_ so that any solver binary from the system can be employed to synchronously return the results of the queries to the compiler. - This is currently the only way to use Eldarica, for example, since it does not have a C++ API. This can be used by both BMC and CHC depending on which solvers are called. - ``z3`` is available - if ``solc`` is compiled with it; - - if a dynamic ``z3`` library of version 4.8.x is installed in a Linux system (from Solidity 0.7.6); - - statically in ``soljson.js`` (from Solidity 0.6.9), that is, the Javascript binary of the compiler. + - if a dynamic ``z3`` library of version >=4.8.x is installed in a Linux system (from Solidity 0.7.6); + - statically in ``soljson.js`` (from Solidity 0.6.9), that is, the JavaScript binary of the compiler. .. note:: z3 version 4.8.16 broke ABI compatibility with previous versions and cannot be used with solc <=0.8.13. If you are using z3 >=4.8.16 please use solc - >=0.8.14. + >=0.8.14, and conversely, only use older z3 with older solc releases. + We also recommend using the latest z3 release which is what SMTChecker also does. Since both BMC and CHC use ``z3``, and ``z3`` is available in a greater variety of environments, including in the browser, most users will almost never need to be diff --git a/compiler/docs/solidity_logo.svg b/compiler/docs/solidity_logo.svg new file mode 100644 index 00000000..86b9f499 --- /dev/null +++ b/compiler/docs/solidity_logo.svg @@ -0,0 +1,27 @@ + + + + +Vector 1 +Created with Sketch. + + + + + + + + + + + + + diff --git a/compiler/docs/structure-of-a-contract.rst b/compiler/docs/structure-of-a-contract.rst index 740cce86..51d99a80 100644 --- a/compiler/docs/structure-of-a-contract.rst +++ b/compiler/docs/structure-of-a-contract.rst @@ -112,11 +112,11 @@ Events are convenience interfaces with the EVM logging facilities. .. code-block:: solidity // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.4.21 <0.9.0; + pragma solidity ^0.8.22; - contract SimpleAuction { - event HighestBidIncreased(address bidder, uint amount); // Event + event HighestBidIncreased(address bidder, uint amount); // Event + contract SimpleAuction { function bid() public payable { // ... emit HighestBidIncreased(msg.sender, msg.value); // Triggering event diff --git a/compiler/docs/style-guide.rst b/compiler/docs/style-guide.rst index 75de18ac..427ede5e 100644 --- a/compiler/docs/style-guide.rst +++ b/compiler/docs/style-guide.rst @@ -16,19 +16,19 @@ Many projects will implement their own style guides. In the event of conflicts, project specific style guides take precedence. The structure and many of the recommendations within this style guide were -taken from python's -`pep8 style guide `_. +taken from Python's +`pep8 style guide `_. The goal of this guide is *not* to be the right way or the best way to write -Solidity code. The goal of this guide is *consistency*. A quote from python's -`pep8 `_ +Solidity code. The goal of this guide is *consistency*. A quote from Python's +`pep8 `_ captures this concept well. .. note:: A style guide is about consistency. Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is most important. - But most importantly: **know when to be inconsistent** -- sometimes the style guide just doesn't apply. When in doubt, use your best judgment. Look at other examples and decide what looks best. And don't hesitate to ask! + But most importantly: **know when to be inconsistent** -- sometimes the style guide just doesn't apply. When in doubt, use your best judgment. Look at other examples and decide what looks best. And do not hesitate to ask! *********** @@ -233,7 +233,7 @@ Yes: bytes32[] options ); - LongAndLotsOfArgs( + emit LongAndLotsOfArgs( sender, recipient, publicKey, @@ -251,7 +251,7 @@ No: uint256 amount, bytes32[] options); - LongAndLotsOfArgs(sender, + emit LongAndLotsOfArgs(sender, recipient, publicKey, amount, @@ -448,7 +448,7 @@ No: y = 2; longVariable = 3; -Don't include a whitespace in the receive and fallback functions: +Do not include a whitespace in the receive and fallback functions: Yes: @@ -1045,27 +1045,55 @@ No: Order of Layout *************** -Layout contract elements in the following order: +Contract elements should be laid out in the following order: 1. Pragma statements 2. Import statements -3. Interfaces -4. Libraries -5. Contracts +3. Events +4. Errors +5. Interfaces +6. Libraries +7. Contracts Inside each contract, library or interface, use the following order: 1. Type declarations 2. State variables 3. Events -4. Modifiers -5. Functions +4. Errors +5. Modifiers +6. Functions .. note:: It might be clearer to declare types close to their use in events or state variables. +Yes: + +.. code-block:: solidity + + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.8.4 <0.9.0; + + abstract contract Math { + error DivideByZero(); + function divide(int256 numerator, int256 denominator) public virtual returns (uint256); + } + +No: + +.. code-block:: solidity + + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.8.4 <0.9.0; + + abstract contract Math { + function divide(int256 numerator, int256 denominator) public virtual returns (uint256); + error DivideByZero(); + } + + ****************** Naming Conventions ****************** @@ -1130,15 +1158,15 @@ Yes: contract Owned { address public owner; - constructor() { - owner = msg.sender; - } - modifier onlyOwner { require(msg.sender == owner); _; } + constructor() { + owner = msg.sender; + } + function transferOwnership(address newOwner) public onlyOwner { owner = newOwner; } @@ -1169,15 +1197,15 @@ No: contract owned { address public owner; - constructor() { - owner = msg.sender; - } - modifier onlyOwner { require(msg.sender == owner); _; } + constructor() { + owner = msg.sender; + } + function transferOwnership(address newOwner) public onlyOwner { owner = newOwner; } @@ -1258,6 +1286,21 @@ Avoiding Naming Collisions This convention is suggested when the desired name collides with that of an existing state variable, function, built-in or otherwise reserved name. +Underscore Prefix for Non-external Functions and Variables +========================================================== + +* ``_singleLeadingUnderscore`` + +This convention is suggested for non-external functions and state variables (``private`` or ``internal``). State variables without a specified visibility are ``internal`` by default. + +When designing a smart contract, the public-facing API (functions that can be called by any account) +is an important consideration. +Leading underscores allow you to immediately recognize the intent of such functions, +but more importantly, if you change a function from non-external to external (including ``public``) +and rename it accordingly, this forces you to review every call site while renaming. +This can be an important manual check against unintended external functions +and a common source of security vulnerabilities (avoid find-replace-all tooling for this change). + .. _style_guide_natspec: ******* diff --git a/compiler/docs/types/conversion.rst b/compiler/docs/types/conversion.rst index e6c68df4..dafb336b 100644 --- a/compiler/docs/types/conversion.rst +++ b/compiler/docs/types/conversion.rst @@ -44,7 +44,7 @@ Explicit Conversions If the compiler does not allow implicit conversion but you are confident a conversion will work, an explicit type conversion is sometimes possible. This may -result in unexpected behaviour and allows you to bypass some security +result in unexpected behavior and allows you to bypass some security features of the compiler, so be sure to test that the result is what you want and expect! @@ -130,6 +130,7 @@ If the array is shorter than the target type, it will be padded with zeros at th } } +.. index:: ! literal;conversion, literal;rational, literal;hexadecimal number .. _types-conversion-literals: Conversions between Literals and Elementary Types @@ -152,6 +153,8 @@ that is large enough to represent it without truncation: converted to an integer type. From 0.8.0, such explicit conversions are as strict as implicit conversions, i.e., they are only allowed if the literal fits in the resulting range. +.. index:: literal;string, literal;hexadecimal + Fixed-Size Byte Arrays ---------------------- @@ -182,6 +185,8 @@ if their number of characters matches the size of the bytes type: bytes2 e = "x"; // not allowed bytes2 f = "xyz"; // not allowed +.. index:: literal;address + Addresses --------- diff --git a/compiler/docs/types/mapping-types.rst b/compiler/docs/types/mapping-types.rst index c59d6e84..454caaf0 100644 --- a/compiler/docs/types/mapping-types.rst +++ b/compiler/docs/types/mapping-types.rst @@ -4,12 +4,13 @@ Mapping Types ============= -Mapping types use the syntax ``mapping(KeyType => ValueType)`` and variables -of mapping type are declared using the syntax ``mapping(KeyType => ValueType) VariableName``. -The ``KeyType`` can be any -built-in value type, ``bytes``, ``string``, or any contract or enum type. Other user-defined -or complex types, such as mappings, structs or array types are not allowed. -``ValueType`` can be any type, including mappings, arrays and structs. +Mapping types use the syntax ``mapping(KeyType KeyName? => ValueType ValueName?)`` and variables of +mapping type are declared using the syntax ``mapping(KeyType KeyName? => ValueType ValueName?) +VariableName``. The ``KeyType`` can be any built-in value type, ``bytes``, ``string``, or any +contract or enum type. Other user-defined or complex types, such as mappings, structs or array types +are not allowed. ``ValueType`` can be any type, including mappings, arrays and structs. ``KeyName`` +and ``ValueName`` are optional (so ``mapping(KeyType => ValueType)`` works as well) and can be any +valid identifier that is not a type. You can think of mappings as `hash tables `_, which are virtually initialised such that every possible key exists and is mapped to a value whose @@ -29,8 +30,10 @@ of contract functions that are publicly visible. These restrictions are also true for arrays and structs that contain mappings. You can mark state variables of mapping type as ``public`` and Solidity creates a -:ref:`getter ` for you. The ``KeyType`` becomes a parameter for the getter. -If ``ValueType`` is a value type or a struct, the getter returns ``ValueType``. +:ref:`getter ` for you. The ``KeyType`` becomes a parameter +with name ``KeyName`` (if specified) for the getter. +If ``ValueType`` is a value type or a struct, the getter returns ``ValueType`` with +name ``ValueName`` (if specified). If ``ValueType`` is an array or a mapping, the getter has one parameter for each ``KeyType``, recursively. @@ -64,6 +67,25 @@ contract that returns the value at the specified address. The example below is a simplified version of an `ERC20 token `_. ``_allowances`` is an example of a mapping type inside another mapping type. + +In the example below, the optional ``KeyName`` and ``ValueName`` are provided for the mapping. +It does not affect any contract functionality or bytecode, it only sets the ``name`` field +for the inputs and outputs in the ABI for the mapping's getter. + +.. code-block:: solidity + + // SPDX-License-Identifier: GPL-3.0 + pragma solidity ^0.8.18; + + contract MappingExampleWithNames { + mapping(address user => uint balance) public balances; + + function update(uint newBalance) public { + balances[msg.sender] = newBalance; + } + } + + The example below uses ``_allowances`` to record the amount someone else is allowed to withdraw from your account. .. code-block:: solidity @@ -73,8 +95,8 @@ The example below uses ``_allowances`` to record the amount someone else is allo contract MappingExample { - mapping (address => uint256) private _balances; - mapping (address => mapping (address => uint256)) private _allowances; + mapping(address => uint256) private _balances; + mapping(address => mapping(address => uint256)) private _allowances; event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); diff --git a/compiler/docs/types/reference-types.rst b/compiler/docs/types/reference-types.rst index a6b5728c..d2e9300e 100644 --- a/compiler/docs/types/reference-types.rst +++ b/compiler/docs/types/reference-types.rst @@ -47,8 +47,8 @@ non-persistent area where function arguments are stored, and behaves mostly like .. _data-location-assignment: -Data location and assignment behaviour -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Data location and assignment behavior +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Data locations are not only relevant for persistency of data, but also for the semantics of assignments: @@ -133,9 +133,13 @@ It is possible to mark state variable arrays ``public`` and have Solidity create The numeric index becomes a required parameter for the getter. Accessing an array past its end causes a failing assertion. Methods ``.push()`` and ``.push(value)`` can be used -to append a new element at the end of the array, where ``.push()`` appends a zero-initialized element and returns +to append a new element at the end of a dynamically-sized array, where ``.push()`` appends a zero-initialized element and returns a reference to it. +.. note:: + Dynamically-sized arrays can only be resized in storage. + In memory, such arrays can be of arbitrary size but the size cannot be changed once an array is allocated. + .. index:: ! string, ! bytes .. _strings: @@ -231,7 +235,7 @@ with the :ref:`default value`. } } -.. index:: ! array;literals, ! inline;arrays +.. index:: ! literal;array, ! inline;arrays Array Literals ^^^^^^^^^^^^^^ @@ -368,7 +372,7 @@ Array Members .. note:: In EVM versions before Byzantium, it was not possible to access - dynamic arrays return from function calls. If you call functions + dynamic arrays returned from function calls. If you call functions that return dynamic arrays, make sure to use an EVM that is set to Byzantium mode. @@ -578,10 +582,10 @@ and the assignment will effectively garble the length of ``x``. To be safe, only enlarge bytes arrays by at most one element during a single assignment and do not simultaneously index-access the array in the same statement. -While the above describes the behaviour of dangling storage references in the +While the above describes the behavior of dangling storage references in the current version of the compiler, any code with dangling references should be -considered to have *undefined behaviour*. In particular, this means that -any future version of the compiler may change the behaviour of code that +considered to have *undefined behavior*. In particular, this means that +any future version of the compiler may change the behavior of code that involves dangling references. Be sure to avoid dangling references in your code! @@ -637,7 +641,7 @@ Array slices are useful to ABI-decode secondary data passed in function paramete /// after doing basic validation on the address argument. function forward(bytes calldata payload) external { bytes4 sig = bytes4(payload[:4]); - // Due to truncating behaviour, bytes4(payload) performs identically. + // Due to truncating behavior, bytes4(payload) performs identically. // bytes4 sig = bytes4(payload); if (sig == bytes4(keccak256("setOwner(address)"))) { address owner = abi.decode(payload[4:], (address)); @@ -682,11 +686,11 @@ shown in the following example: uint fundingGoal; uint numFunders; uint amount; - mapping (uint => Funder) funders; + mapping(uint => Funder) funders; } uint numCampaigns; - mapping (uint => Campaign) campaigns; + mapping(uint => Campaign) campaigns; function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) { campaignID = numCampaigns++; // campaignID is return variable diff --git a/compiler/docs/types/value-types.rst b/compiler/docs/types/value-types.rst index 1e8e3e7f..d44d2fd7 100644 --- a/compiler/docs/types/value-types.rst +++ b/compiler/docs/types/value-types.rst @@ -4,8 +4,7 @@ Value Types =========== -The following types are also called value types because variables of these -types will always be passed by value, i.e. they are always copied when they +The following are called value types because their variables will always be passed by value, i.e. they are always copied when they are used as function arguments or in assignments. .. index:: ! bool, ! true, ! false @@ -47,7 +46,7 @@ access the minimum and maximum value representable by the type. Integers in Solidity are restricted to a certain range. For example, with ``uint32``, this is ``0`` up to ``2**32 - 1``. There are two modes in which arithmetic is performed on these types: The "wrapping" or "unchecked" mode and the "checked" mode. - By default, arithmetic is always "checked", which mean that if the result of an operation falls outside the value range + By default, arithmetic is always "checked", meaning that if an operation's result falls outside the value range of the type, the call is reverted through a :ref:`failing assertion`. You can switch to "unchecked" mode using ``unchecked { ... }``. More details can be found in the section about :ref:`unchecked `. @@ -141,7 +140,7 @@ Exponentiation Exponentiation is only available for unsigned types in the exponent. The resulting type of an exponentiation is always equal to the type of the base. Please take care that it is -large enough to hold the result and prepare for potential assertion failures or wrapping behaviour. +large enough to hold the result and prepare for potential assertion failures or wrapping behavior. .. note:: In checked mode, exponentiation only uses the comparatively cheap ``exp`` opcode for small bases. @@ -182,7 +181,7 @@ Operators: Address ------- -The address type comes in two flavours, which are largely identical: +The address type comes in two largely identical flavors: - ``address``: Holds a 20 byte value (size of an Ethereum address). - ``address payable``: Same as ``address``, but with the additional members ``transfer`` and ``send``. @@ -258,13 +257,13 @@ reverts on failure. * ``send`` -Send is the low-level counterpart of ``transfer``. If the execution fails, the current contract will not stop with an exception, but ``send`` will return ``false``. +``send`` is the low-level counterpart of ``transfer``. If the execution fails, the current contract will not stop with an exception, but ``send`` will return ``false``. .. warning:: There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024 (this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better: - use a pattern where the recipient withdraws the money. + use a pattern where the recipient withdraws the Ether. * ``call``, ``delegatecall`` and ``staticcall`` @@ -424,7 +423,7 @@ Dynamically-sized byte array ``string``: Dynamically-sized UTF-8-encoded string, see :ref:`arrays`. Not a value-type! -.. index:: address, literal;address +.. index:: address, ! literal;address .. _address_literals: @@ -440,7 +439,7 @@ an error. You can prepend (for integer types) or append (for bytesNN types) zero .. note:: The mixed-case address checksum format is defined in `EIP-55 `_. -.. index:: literal, literal;rational +.. index:: integer, rational number, ! literal;rational .. _rational_literals: @@ -518,7 +517,7 @@ regardless of the type of the right (exponent) operand. uint128 a = 1; uint128 b = 2.5 + a + 0.5; -.. index:: literal, literal;string, string +.. index:: ! literal;string, string .. _string_literals: String Literals and Types @@ -565,6 +564,8 @@ character sequence ``abcdef``. Any Unicode line terminator which is not a newline (i.e. LF, VF, FF, CR, NEL, LS, PS) is considered to terminate the string literal. Newline only terminates the string literal if it is not preceded by a ``\``. +.. index:: ! literal;unicode + Unicode Literals ---------------- @@ -575,7 +576,7 @@ They also support the very same escape sequences as regular string literals. string memory a = unicode"Hello 😃"; -.. index:: literal, bytes +.. index:: ! literal;hexadecimal, bytes Hexadecimal Literals -------------------- @@ -589,7 +590,8 @@ of the hexadecimal sequence. Multiple hexadecimal literals separated by whitespace are concatenated into a single literal: ``hex"00112233" hex"44556677"`` is equivalent to ``hex"0011223344556677"`` -Hexadecimal literals behave like :ref:`string literals ` and have the same convertibility restrictions. +Hexadecimal literals in some ways behave like :ref:`string literals ` but are not +implicitly convertible to the ``string`` type. .. index:: enum @@ -653,18 +655,18 @@ smallest and respectively largest value of the given enum. .. _user-defined-value-types: -User Defined Value Types +User-defined Value Types ------------------------ -A user defined value type allows creating a zero cost abstraction over an elementary value type. +A user-defined value type allows creating a zero cost abstraction over an elementary value type. This is similar to an alias, but with stricter type requirements. -A user defined value type is defined using ``type C is V``, where ``C`` is the name of the newly +A user-defined value type is defined using ``type C is V``, where ``C`` is the name of the newly introduced type and ``V`` has to be a built-in value type (the "underlying type"). The function ``C.wrap`` is used to convert from the underlying type to the custom type. Similarly, the function ``C.unwrap`` is used to convert from the custom type to the underlying type. -The type ``C`` does not have any operators or bound member functions. In particular, even the +The type ``C`` does not have any operators or attached member functions. In particular, even the operator ``==`` is not defined. Explicit and implicit conversions to and from other types are disallowed. @@ -680,7 +682,7 @@ type with 18 decimals and a minimal library to do arithmetic operations on the t // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.8; - // Represent a 18 decimal, 256 bit wide fixed point type using a user defined value type. + // Represent a 18 decimal, 256 bit wide fixed point type using a user-defined value type. type UFixed256x18 is uint256; /// A minimal library to do fixed point operations on UFixed256x18. @@ -770,6 +772,16 @@ confusing, but in essence, if a function is ``payable``, this means that it 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. +To clarify, rejecting ether is more restrictive than not rejecting ether. +This means you can override a payable function with a non-payable but not the +other way around. + +Additionally, When you define a ``non-payable`` function pointer, +the compiler does not enforce that the pointed function will actually reject ether. +Instead, it enforces that the function pointer is never used to send ether. +Which makes it possible to assign a ``payable`` function pointer to a ``non-payable`` +function pointer ensuring both types behave the same way, i.e, both cannot be used +to send ether. If a function type variable is not initialised, calling it results in a :ref:`Panic error`. The same happens if you call a function after using ``delete`` @@ -789,6 +801,24 @@ This includes private, internal and public functions of both contracts and libra functions. External function types, on the other hand, are only compatible with public and external contract functions. + +.. note:: + External functions with ``calldata`` parameters are incompatible with external function types with ``calldata`` parameters. + They are compatible with the corresponding types with ``memory`` parameters instead. + For example, there is no function that can be pointed at by a value of type ``function (string calldata) external`` while + ``function (string memory) external`` can point at both ``function f(string memory) external {}`` and + ``function g(string calldata) external {}``. + This is because for both locations the arguments are passed to the function in the same way. + The caller cannot pass its calldata directly to an external function and always ABI-encodes the arguments into memory. + Marking the parameters as ``calldata`` only affects the implementation of the external function and is + meaningless in a function pointer on the caller's side. + +.. warning:: + Comparison of internal function pointers can have unexpected results in the legacy pipeline with the optimizer enabled, + as it can collapse identical functions into one, which will then lead to said function pointers comparing as equal instead of not. + Such comparisons are not advised, and will lead to the compiler issuing a warning, until the next breaking release (0.9.0), + when the warning will be upgraded to an error, thereby making such comparisons disallowed. + Libraries are excluded because they require a ``delegatecall`` and use :ref:`a different ABI convention for their selectors `. Functions declared in interfaces do not have definitions so pointing at them does not make sense either. diff --git a/compiler/docs/units-and-global-variables.rst b/compiler/docs/units-and-global-variables.rst index b1939629..a0422c32 100644 --- a/compiler/docs/units-and-global-variables.rst +++ b/compiler/docs/units-and-global-variables.rst @@ -1,8 +1,10 @@ +.. index:: ! denomination + ************************************** Units and Globally Available Variables ************************************** -.. index:: wei, finney, szabo, gwei, ether +.. index:: ! wei, ! finney, ! szabo, ! gwei, ! ether, ! denomination;ether Ether Units =========== @@ -21,7 +23,7 @@ The only effect of the subdenomination suffix is a multiplication by a power of .. note:: The denominations ``finney`` and ``szabo`` have been removed in version 0.7.0. -.. index:: time, seconds, minutes, hours, days, weeks, years +.. index:: ! seconds, ! minutes, ! hours, ! days, ! weeks, ! years, ! denomination;time Time Units ========== @@ -52,7 +54,7 @@ interpret a function parameter in days, you can in the following way: function f(uint start, uint daysAfter) public { if (block.timestamp >= start + daysAfter * 1 days) { - // ... + // ... } } @@ -65,19 +67,24 @@ There are special variables and functions which always exist in the global namespace and are mainly used to provide information about the blockchain or are general-use utility functions. -.. index:: abi, block, coinbase, difficulty, encode, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, gas price, origin +.. index:: abi, block, coinbase, difficulty, prevrandao, encode, number, block;number, timestamp, block;timestamp, block;basefee, block;blobbasefee, msg, data, gas, sender, value, gas price, origin Block and Transaction Properties -------------------------------- - ``blockhash(uint blockNumber) returns (bytes32)``: hash of the given block when ``blocknumber`` is one of the 256 most recent blocks; otherwise returns zero +- ``blobhash(uint index) returns (bytes32)``: versioned hash of the ``index``-th blob associated with the current transaction. + A versioned hash consists of a single byte representing the version (currently ``0x01``), followed by the last 31 bytes + of the SHA256 hash of the KZG commitment (`EIP-4844 `_). - ``block.basefee`` (``uint``): current block's base fee (`EIP-3198 `_ and `EIP-1559 `_) +- ``block.blobbasefee`` (``uint``): current block's blob base fee (`EIP-7516 `_ and `EIP-4844 `_) - ``block.chainid`` (``uint``): current chain id - ``block.coinbase`` (``address payable``): current block miner's address -- ``block.difficulty`` (``uint``): current block difficulty +- ``block.difficulty`` (``uint``): current block difficulty (``EVM < Paris``). For other EVM versions it behaves as a deprecated alias for ``block.prevrandao`` (`EIP-4399 `_ ) - ``block.gaslimit`` (``uint``): current block gaslimit - ``block.number`` (``uint``): current block number +- ``block.prevrandao`` (``uint``): random number provided by the beacon chain (``EVM >= Paris``) - ``block.timestamp`` (``uint``): current block timestamp as seconds since unix epoch - ``gasleft() returns (uint256)``: remaining gas - ``msg.data`` (``bytes calldata``): complete calldata @@ -104,7 +111,7 @@ Block and Transaction Properties Both the timestamp and the block hash can be influenced by miners to some degree. Bad actors in the mining community can for example run a casino payout function on a chosen hash - and just retry a different hash if they did not receive any money. + and just retry a different hash if they did not receive any compensation, e.g. Ether. The current block timestamp must be strictly larger than the timestamp of the last block, but the only guarantee is that it will be somewhere between the timestamps of two @@ -231,8 +238,8 @@ Mathematical and Cryptographic Functions for _transaction_ signatures (see `EIP-2 `_), but the ecrecover function remained unchanged. - This is usually not a problem unless you require signatures to be unique or - use them to identify items. OpenZeppelin have a `ECDSA helper library `_ that you can use as a wrapper for ``ecrecover`` without this issue. + This is usually not a problem unless you require signatures to be unique or use them to identify items. + OpenZeppelin has an `ECDSA helper library `_ that you can use as a wrapper for ``ecrecover`` without this issue. .. note:: @@ -279,7 +286,7 @@ For more information, see the section on :ref:`address`. There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024 (this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better: - Use a pattern where the recipient withdraws the money. + Use a pattern where the recipient withdraws the Ether. .. warning:: Due to the fact that the EVM considers a call to a non-existing contract to always succeed, @@ -310,13 +317,16 @@ For more information, see the section on :ref:`address`. semantics than ``delegatecall``. -.. index:: this, selfdestruct +.. index:: this, selfdestruct, super -Contract Related +Contract-related ---------------- ``this`` (current contract's type) - the current contract, explicitly convertible to :ref:`address` + The current contract, explicitly convertible to :ref:`address` + +``super`` + A contract one level higher in the inheritance hierarchy ``selfdestruct(address payable recipient)`` Destroy the current contract, sending its funds to the given :ref:`address` @@ -326,11 +336,13 @@ Contract Related - the receiving contract's receive function is not executed. - the contract is only really destroyed at the end of the transaction and ``revert`` s might "undo" the destruction. - - - Furthermore, all functions of the current contract are callable directly including the current function. +.. warning:: + From version 0.8.18 and up, the use of ``selfdestruct`` in both Solidity and Yul will trigger a + deprecation warning, since the ``SELFDESTRUCT`` opcode will eventually undergo breaking changes in behavior + as stated in `EIP-6049 `_. + .. note:: Prior to version 0.5.0, there was a function called ``suicide`` with the same semantics as ``selfdestruct``. @@ -372,7 +384,7 @@ The following properties are available for a contract type ``C``: In addition to the properties above, the following properties are available for an interface type ``I``: -``type(I).interfaceId``: +``type(I).interfaceId`` A ``bytes4`` value containing the `EIP-165 `_ interface identifier of the given interface ``I``. This identifier is defined as the ``XOR`` of all function selectors defined within the interface itself - excluding all inherited functions. @@ -394,4 +406,4 @@ These keywords are reserved in Solidity. They might become part of the syntax in ``define``, ``final``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``, ``mutable``, ``null``, ``of``, ``partial``, ``promise``, ``reference``, ``relocatable``, ``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``typedef``, ``typeof``, -``var``. \ No newline at end of file +``var``. diff --git a/compiler/docs/using-the-compiler.rst b/compiler/docs/using-the-compiler.rst index d6bf94a2..97abbdba 100644 --- a/compiler/docs/using-the-compiler.rst +++ b/compiler/docs/using-the-compiler.rst @@ -15,7 +15,7 @@ Using the Commandline Compiler Basic Usage ----------- -One of the build targets of the Solidity repository is ``solc``, the solidity commandline compiler. +One of the build targets of the Solidity repository is ``solc``, the Solidity commandline compiler. Using ``solc --help`` provides you with an explanation of all options. The compiler can produce various outputs, ranging from simple binaries and assembly over an abstract syntax tree (parse tree) to estimations of gas usage. If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast-compact-json --asm sourceFile.sol``. @@ -54,7 +54,7 @@ or ../ ` are treated as relative to the directories specified us Furthermore, the part of the path added via these options will not appear in the contract metadata. For security reasons the compiler has :ref:`restrictions on what directories it can access `. -Directories of source files specified on the command line and target paths of +Directories of source files specified on the command-line and target paths of remappings are automatically allowed to be accessed by the file reader, but everything else is rejected by default. Additional paths (and their subdirectories) can be allowed via the @@ -114,15 +114,15 @@ Setting the EVM Version to Target ********************************* When you compile your contract code you can specify the Ethereum virtual machine -version to compile for to avoid particular features or behaviours. +version to compile for to avoid particular features or behaviors. .. warning:: Compiling for the wrong EVM version can result in wrong, strange and failing - behaviour. Please ensure, especially if running a private chain, that you + behavior. Please ensure, especially if running a private chain, that you use matching EVM versions. -On the command line, you can select the EVM version as follows: +On the command-line, you can select the EVM version as follows: .. code-block:: shell @@ -147,20 +147,20 @@ Target Options Below is a list of target EVM versions and the compiler-relevant changes introduced at each version. Backward compatibility is not guaranteed between each version. -- ``homestead`` +- ``homestead`` (*support deprecated*) - (oldest version) -- ``tangerineWhistle`` +- ``tangerineWhistle`` (*support deprecated*) - Gas cost for access to other accounts increased, relevant for gas estimation and the optimizer. - All gas sent by default for external calls, previously a certain amount had to be retained. -- ``spuriousDragon`` +- ``spuriousDragon`` (*support deprecated*) - Gas cost for the ``exp`` opcode increased, relevant for gas estimation and the optimizer. -- ``byzantium`` +- ``byzantium`` (*support deprecated*) - Opcodes ``returndatacopy``, ``returndatasize`` and ``staticcall`` are available in assembly. - The ``staticcall`` opcode is used when calling non-library view or pure functions, which prevents the functions from modifying state at the EVM level, i.e., even applies when you use invalid type conversions. - It is possible to access dynamic data returned from function calls. - ``revert`` opcode introduced, which means that ``revert()`` will not waste gas. - ``constantinople`` - - Opcodes ``create2`, ``extcodehash``, ``shl``, ``shr`` and ``sar`` are available in assembly. + - Opcodes ``create2``, ``extcodehash``, ``shl``, ``shr`` and ``sar`` are available in assembly. - Shifting operators use shifting opcodes and thus need less gas. - ``petersburg`` - The compiler behaves the same way as with constantinople. @@ -170,9 +170,17 @@ at each version. Backward compatibility is not guaranteed between each version. - Gas costs for ``SLOAD``, ``*CALL``, ``BALANCE``, ``EXT*`` and ``SELFDESTRUCT`` increased. The compiler assumes cold gas costs for such operations. This is relevant for gas estimation and the optimizer. -- ``london`` (**default**) +- ``london`` - The block's base fee (`EIP-3198 `_ and `EIP-1559 `_) can be accessed via the global ``block.basefee`` or ``basefee()`` in inline assembly. - +- ``paris`` + - Introduces ``prevrandao()`` and ``block.prevrandao``, and changes the semantics of the now deprecated ``block.difficulty``, disallowing ``difficulty()`` in inline assembly (see `EIP-4399 `_). +- ``shanghai`` (**default**) + - Smaller code size and gas savings due to the introduction of ``push0`` (see `EIP-3855 `_). +- ``cancun`` + - The block's blob base fee (`EIP-7516 `_ and `EIP-4844 `_) can be accessed via the global ``block.blobbasefee`` or ``blobbasefee()`` in inline assembly. + - Introduces ``blobhash()`` in inline assembly and a corresponding global function to retrieve versioned hashes of blobs associated with the transaction (see `EIP-4844 `_). + - Opcode ``mcopy`` is available in assembly (see `EIP-5656 `_). + - Opcodes ``tstore`` and ``tload`` are available in assembly (see `EIP-1153 `_). .. index:: ! standard JSON, ! --standard-json .. _compiler-api: @@ -200,7 +208,7 @@ Input Description .. code-block:: javascript { - // Required: Source code language. Currently supported are "Solidity" and "Yul". + // Required: Source code language. Currently supported are "Solidity", "Yul", "SolidityAST" (experimental), "EVMAssembly" (experimental). "language": "Solidity", // Required "sources": @@ -224,7 +232,7 @@ Input Description "bzzr://56ab...", "ipfs://Qma...", "/tmp/path/to/file.sol" - // If files are used, their directories should be added to the command line via + // If files are used, their directories should be added to the command-line via // `--allow-paths `. ] }, @@ -234,6 +242,33 @@ Input Description "keccak256": "0x234...", // Required (unless "urls" is used): literal contents of the source file "content": "contract destructible is owned { function shutdown() { if (msg.sender == owner) selfdestruct(owner); } }" + }, + "myFile.sol_json.ast": + { + // If language is set to "SolidityAST", an AST needs to be supplied under the "ast" key + // and there can be only one source file present. + // The format is the same as used by the `ast` output. + // Note that importing ASTs is experimental and in particular that: + // - importing invalid ASTs can produce undefined results and + // - no proper error reporting is available on invalid ASTs. + // Furthermore, note that the AST import only consumes the fields of the AST as + // produced by the compiler in "stopAfter": "parsing" mode and then re-performs + // analysis, so any analysis-based annotations of the AST are ignored upon import. + "ast": { ... } + }, + "myFile_evm.json": + { + // If language is set to "EVMAssembly", an EVM Assembly JSON object needs to be supplied + // under the "assemblyJson" key and there can be only one source file present. + // The format is the same as used by the `evm.legacyAssembly` output or `--asm-json` + // output on the command line. + // Note that importing EVM assembly is experimental. + "assemblyJson": + { + ".code": [ ... ], + ".data": { ... }, // optional + "sourceList": [ ... ] // optional (if no `source` node was defined in any `.code` object) + } } }, // Optional @@ -261,9 +296,9 @@ Input Description // The peephole optimizer is always on if no details are given, // use details to switch it off. "peephole": true, - // The inliner is always on if no details are given, - // use details to switch it off. - "inliner": true, + // The inliner is always off if no details are given, + // use details to switch it on. + "inliner": false, // The unused jumpdest remover is always on if no details are given, // use details to switch it off. "jumpdestRemover": true, @@ -276,6 +311,9 @@ Input Description "cse": false, // Optimize representation of literal numbers and strings in code. "constantOptimizer": false, + // Use unchecked arithmetic when incrementing the counter of for loops + // under certain circumstances. It is always on if no details are given. + "simpleCounterForLoopUncheckedIncrement": true, // The new Yul optimizer. Mostly operates on the code of ABI coder v2 // and inline assembly. // It is activated together with the global optimizer setting @@ -287,16 +325,27 @@ Input Description // Improve allocation of stack slots for variables, can free up stack slots early. // Activated by default if the Yul optimizer is activated. "stackAllocation": true, - // Select optimization steps to be applied. - // Optional, the optimizer will use the default sequence if omitted. + // Select optimization steps to be applied. It is also possible to modify both the + // optimization sequence and the clean-up sequence. Instructions for each sequence + // are separated with the ":" delimiter and the values are provided in the form of + // optimization-sequence:clean-up-sequence. For more information see + // "The Optimizer > Selecting Optimizations". + // This field is optional, and if not provided, the default sequences for both + // optimization and clean-up are used. If only one of the sequences is provided + // the other will not be run. + // If only the delimiter ":" is provided then neither the optimization nor the clean-up + // sequence will be run. + // If set to an empty value, only the default clean-up sequence is used and + // no optimization steps are applied. "optimizerSteps": "dhfoDgvulfnTUtnIf..." } } }, // Version of the EVM to compile for. // Affects type checking and code generation. Can be homestead, - // tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg, istanbul or berlin - "evmVersion": "byzantium", + // tangerineWhistle, spuriousDragon, byzantium, constantinople, + // petersburg, istanbul, berlin, london, paris or shanghai (default) + "evmVersion": "shanghai", // Optional: Change compilation pipeline to go through the Yul intermediate representation. // This is false by default. "viaIR": true, @@ -323,6 +372,9 @@ Input Description }, // Metadata settings (optional) "metadata": { + // The CBOR metadata is appended at the end of the bytecode by default. + // Setting this to false omits the metadata from the runtime and deploy time code. + "appendCBOR": true, // Use only literal content and not URLs (false by default) "useLiteralContent": true, // Use the given hash method for the metadata hash that is appended to the bytecode. @@ -334,7 +386,7 @@ Input Description // Addresses of the libraries. If not all libraries are given here, // it can result in unlinked objects whose output data is different. "libraries": { - // The top level key is the the name of the source file where the library is used. + // The top level key is the name of the source file where the library is used. // If remappings are used, this source file should match the global path // after remappings were applied. // If this key is an empty string, that refers to a global level. @@ -366,7 +418,9 @@ Input Description // userdoc - User documentation (natspec) // metadata - Metadata // ir - Yul intermediate representation of the code before optimization + // irAst - AST of Yul intermediate representation of the code before optimization // irOptimized - Intermediate representation after optimization + // irOptimizedAst - AST of intermediate representation after optimization // storageLayout - Slots, offsets and types of the contract's state variables. // evm.assembly - New assembly format // evm.legacyAssembly - Old-style assembly format in JSON @@ -380,10 +434,8 @@ Input Description // evm.deployedBytecode.immutableReferences - Map from AST ids to bytecode ranges that reference immutables // evm.methodIdentifiers - The list of function hashes // evm.gasEstimates - Function gas estimates - // ewasm.wast - Ewasm in WebAssembly S-expressions format - // ewasm.wasm - Ewasm in WebAssembly binary format // - // Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select every + // Note that using `evm`, `evm.bytecode`, etc. will select every // target part of that output. Additionally, `*` can be used as a wildcard to request everything. // "outputSelection": { @@ -419,10 +471,18 @@ Input Description "divModNoSlacks": false, // Choose which model checker engine to use: all (default), bmc, chc, none. "engine": "chc", + // Choose whether external calls should be considered trusted in case the + // code of the called function is available at compile-time. + // For details see the SMTChecker section. + "extCalls": "trusted", // Choose which types of invariants should be reported to the user: contract, reentrancy. "invariants": ["contract", "reentrancy"], + // Choose whether to output all proved targets. The default is `false`. + "showProved": true, // Choose whether to output all unproved targets. The default is `false`. "showUnproved": true, + // Choose whether to output all unsupported language features. The default is `false`. + "showUnsupported": true, // Choose which solvers should be used, if available. // See the Formal Verification section for the solvers description. "solvers": ["cvc4", "smtlib2", "z3"], @@ -469,7 +529,7 @@ Output Description // Mandatory: Error type, such as "TypeError", "InternalCompilerError", "Exception", etc. // See below for complete list of types. "type": "TypeError", - // Mandatory: Component where the error originated, such as "general", "ewasm", etc. + // Mandatory: Component where the error originated, such as "general" etc. "component": "general", // Mandatory ("error", "warning" or "info", but please note that this may be extended in the future) "severity": "error", @@ -506,8 +566,14 @@ Output Description "userdoc": {}, // Developer documentation (natspec) "devdoc": {}, - // Intermediate representation (string) + // Intermediate representation before optimization (string) "ir": "", + // AST of intermediate representation before optimization + "irAst": {/* ... */}, + // Intermediate representation after optimization (string) + "irOptimized": "", + // AST of intermediate representation after optimization + "irOptimizedAst": {/* ... */}, // See the Storage Layout documentation. "storageLayout": {"storage": [/* ... */], "types": {/* ... */} }, // EVM-related outputs @@ -585,13 +651,6 @@ Output Description "heavyLifting()": "infinite" } } - }, - // Ewasm related outputs - "ewasm": { - // S-expressions format - "wast": "", - // Binary format (hex string) - "wasm": "" } } } @@ -614,231 +673,6 @@ Error Types 10. ``Exception``: Unknown failure during compilation - this should be reported as an issue. 11. ``CompilerError``: Invalid use of the compiler stack - this should be reported as an issue. 12. ``FatalError``: Fatal error not processed correctly - this should be reported as an issue. -13. ``YulException``: Error during Yul Code generation - this should be reported as an issue. +13. ``YulException``: Error during Yul code generation - this should be reported as an issue. 14. ``Warning``: A warning, which didn't stop the compilation, but should be addressed if possible. 15. ``Info``: Information that the compiler thinks the user might find useful, but is not dangerous and does not necessarily need to be addressed. - - -.. _compiler-tools: - -Compiler Tools -************** - -solidity-upgrade ----------------- - -``solidity-upgrade`` can help you to semi-automatically upgrade your contracts -to breaking language changes. While it does not and cannot implement all -required changes for every breaking release, it still supports the ones, that -would need plenty of repetitive manual adjustments otherwise. - -.. note:: - - ``solidity-upgrade`` carries out a large part of the work, but your - contracts will most likely need further manual adjustments. We recommend - using a version control system for your files. This helps reviewing and - eventually rolling back the changes made. - -.. warning:: - - ``solidity-upgrade`` is not considered to be complete or free from bugs, so - please use with care. - -How it Works -~~~~~~~~~~~~ - -You can pass (a) Solidity source file(s) to ``solidity-upgrade [files]``. If -these make use of ``import`` statement which refer to files outside the -current source file's directory, you need to specify directories that -are allowed to read and import files from, by passing -``--allow-paths [directory]``. You can ignore missing files by passing -``--ignore-missing``. - -``solidity-upgrade`` is based on ``libsolidity`` and can parse, compile and -analyse your source files, and might find applicable source upgrades in them. - -Source upgrades are considered to be small textual changes to your source code. -They are applied to an in-memory representation of the source files -given. The corresponding source file is updated by default, but you can pass -``--dry-run`` to simulate to whole upgrade process without writing to any file. - -The upgrade process itself has two phases. In the first phase source files are -parsed, and since it is not possible to upgrade source code on that level, -errors are collected and can be logged by passing ``--verbose``. No source -upgrades available at this point. - -In the second phase, all sources are compiled and all activated upgrade analysis -modules are run alongside compilation. By default, all available modules are -activated. Please read the documentation on -:ref:`available modules ` for further details. - - -This can result in compilation errors that may -be fixed by source upgrades. If no errors occur, no source upgrades are being -reported and you're done. -If errors occur and some upgrade module reported a source upgrade, the first -reported one gets applied and compilation is triggered again for all given -source files. The previous step is repeated as long as source upgrades are -reported. If errors still occur, you can log them by passing ``--verbose``. -If no errors occur, your contracts are up to date and can be compiled with -the latest version of the compiler. - -.. _upgrade-modules: - -Available Upgrade Modules -~~~~~~~~~~~~~~~~~~~~~~~~~ - -+----------------------------+---------+--------------------------------------------------+ -| Module | Version | Description | -+============================+=========+==================================================+ -| ``constructor`` | 0.5.0 | Constructors must now be defined using the | -| | | ``constructor`` keyword. | -+----------------------------+---------+--------------------------------------------------+ -| ``visibility`` | 0.5.0 | Explicit function visibility is now mandatory, | -| | | defaults to ``public``. | -+----------------------------+---------+--------------------------------------------------+ -| ``abstract`` | 0.6.0 | The keyword ``abstract`` has to be used if a | -| | | contract does not implement all its functions. | -+----------------------------+---------+--------------------------------------------------+ -| ``virtual`` | 0.6.0 | Functions without implementation outside an | -| | | interface have to be marked ``virtual``. | -+----------------------------+---------+--------------------------------------------------+ -| ``override`` | 0.6.0 | When overriding a function or modifier, the new | -| | | keyword ``override`` must be used. | -+----------------------------+---------+--------------------------------------------------+ -| ``dotsyntax`` | 0.7.0 | The following syntax is deprecated: | -| | | ``f.gas(...)()``, ``f.value(...)()`` and | -| | | ``(new C).value(...)()``. Replace these calls by | -| | | ``f{gas: ..., value: ...}()`` and | -| | | ``(new C){value: ...}()``. | -+----------------------------+---------+--------------------------------------------------+ -| ``now`` | 0.7.0 | The ``now`` keyword is deprecated. Use | -| | | ``block.timestamp`` instead. | -+----------------------------+---------+--------------------------------------------------+ -| ``constructor-visibility`` | 0.7.0 | Removes visibility of constructors. | -| | | | -+----------------------------+---------+--------------------------------------------------+ - -Please read :doc:`0.5.0 release notes <050-breaking-changes>`, -:doc:`0.6.0 release notes <060-breaking-changes>`, -:doc:`0.7.0 release notes <070-breaking-changes>` and :doc:`0.8.0 release notes <080-breaking-changes>` for further details. - -Synopsis -~~~~~~~~ - -.. code-block:: none - - Usage: solidity-upgrade [options] contract.sol - - Allowed options: - --help Show help message and exit. - --version Show version and exit. - --allow-paths path(s) - Allow a given path for imports. A list of paths can be - supplied by separating them with a comma. - --ignore-missing Ignore missing files. - --modules module(s) Only activate a specific upgrade module. A list of - modules can be supplied by separating them with a comma. - --dry-run Apply changes in-memory only and don't write to input - file. - --verbose Print logs, errors and changes. Shortens output of - upgrade patches. - --unsafe Accept *unsafe* changes. - - - -Bug Reports / Feature Requests -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you found a bug or if you have a feature request, please -`file an issue `_ on Github. - - -Example -~~~~~~~ - -Assume that you have the following contract in ``Source.sol``: - -.. code-block:: Solidity - - pragma solidity >=0.6.0 <0.6.4; - // This will not compile after 0.7.0 - // SPDX-License-Identifier: GPL-3.0 - contract C { - // FIXME: remove constructor visibility and make the contract abstract - constructor() internal {} - } - - contract D { - uint time; - - function f() public payable { - // FIXME: change now to block.timestamp - time = now; - } - } - - contract E { - D d; - - // FIXME: remove constructor visibility - constructor() public {} - - function g() public { - // FIXME: change .value(5) => {value: 5} - d.f.value(5)(); - } - } - - - -Required Changes -^^^^^^^^^^^^^^^^ - -The above contract will not compile starting from 0.7.0. To bring the contract up to date with the -current Solidity version, the following upgrade modules have to be executed: -``constructor-visibility``, ``now`` and ``dotsyntax``. Please read the documentation on -:ref:`available modules ` for further details. - - -Running the Upgrade -^^^^^^^^^^^^^^^^^^^ - -It is recommended to explicitly specify the upgrade modules by using ``--modules`` argument. - -.. code-block:: bash - - solidity-upgrade --modules constructor-visibility,now,dotsyntax Source.sol - -The command above applies all changes as shown below. Please review them carefully (the pragmas will -have to be updated manually.) - -.. code-block:: Solidity - - // SPDX-License-Identifier: GPL-3.0 - pragma solidity >=0.7.0 <0.9.0; - abstract contract C { - // FIXME: remove constructor visibility and make the contract abstract - constructor() {} - } - - contract D { - uint time; - - function f() public payable { - // FIXME: change now to block.timestamp - time = block.timestamp; - } - } - - contract E { - D d; - - // FIXME: remove constructor visibility - constructor() {} - - function g() public { - // FIXME: change .value(5) => {value: 5} - d.f{value: 5}(); - } - } diff --git a/compiler/docs/yul.rst b/compiler/docs/yul.rst index 7ce01016..3cf2785f 100644 --- a/compiler/docs/yul.rst +++ b/compiler/docs/yul.rst @@ -9,13 +9,9 @@ Yul Yul (previously also called JULIA or IULIA) is an intermediate language that can be compiled to bytecode for different backends. -Support for EVM 1.0, EVM 1.5 and Ewasm is planned, and it is designed to -be a usable common denominator of all three -platforms. It can already be used in stand-alone mode and -for "inline assembly" inside Solidity -and there is an experimental implementation of the Solidity compiler -that uses Yul as an intermediate language. Yul is a good target for -high-level optimisation stages that can benefit all target platforms equally. +It can be used in stand-alone mode and for "inline assembly" inside Solidity. +The compiler uses Yul as an intermediate language in the IR-based code generator ("new codegen" or "IR-based codegen"). +Yul is a good target for high-level optimisation stages that can benefit all target platforms equally. Motivation and High-level Description ===================================== @@ -89,7 +85,6 @@ It can be compiled using ``solc --strict-assembly``. The builtin functions It is also possible to implement the same function using a for-loop instead of with recursion. Here, ``lt(a, b)`` computes whether ``a`` is less than ``b``. -less-than comparison. .. code-block:: yul @@ -157,7 +152,7 @@ where an object is expected. Inside a code block, the following elements can be used (see the later sections for more details): -- literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters) +- literals, e.g. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters) - calls to builtin functions, e.g. ``add(1, mload(0))`` - variable declarations, e.g. ``let x := 7``, ``let x := add(y, 3)`` or ``let x`` (initial value of 0 is assigned) - identifiers (variables), e.g. ``add(3, x)`` @@ -171,6 +166,8 @@ Inside a code block, the following elements can be used Multiple syntactical elements can follow each other simply separated by whitespace, i.e. there is no terminating ``;`` or newline required. +.. index:: ! literal;in Yul + Literals -------- @@ -243,7 +240,7 @@ they have to be assigned to local variables. For built-in functions of the EVM, functional expressions can be directly translated to a stream of opcodes: You just read the expression from right to left to obtain the -opcodes. In the case of the first line in the example, this +opcodes. In the case of the second line in the example, this is ``PUSH1 3 PUSH1 0x80 MLOAD ADD PUSH1 0x80 MSTORE``. For calls to user-defined functions, the arguments are also @@ -744,7 +741,7 @@ EVM Dialect ----------- The default dialect of Yul currently is the EVM dialect for the currently selected version of the EVM. -with a version of the EVM. The only type available in this dialect +The only type available in this dialect is ``u256``, the 256-bit native type of the Ethereum Virtual Machine. Since it is the default type of this dialect, it can be omitted. @@ -755,11 +752,12 @@ This document does not want to be a full description of the Ethereum virtual mac Please refer to a different document if you are interested in the precise semantics. Opcodes marked with ``-`` do not return a result and all others return exactly one value. -Opcodes marked with ``F``, ``H``, ``B``, ``C``, ``I`` and ``L`` are present since Frontier, Homestead, -Byzantium, Constantinople, Istanbul or London respectively. +Opcodes marked with ``F``, ``H``, ``B``, ``C``, ``I``, ``L``, ``P`` and ``N`` are present since Frontier, +Homestead, Byzantium, Constantinople, Istanbul, London, Paris or Cancun respectively. In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to -but not including position ``b`` and ``storage[p]`` signifies the storage contents at slot ``p``. +but not including position ``b``, ``storage[p]`` signifies the storage contents at slot ``p``, and +similarly, ``transientStorage[p]`` signifies the transient storage contents at slot ``p``. Since Yul manages local variables and control-flow, opcodes that interfere with these features are not available. This includes @@ -836,6 +834,10 @@ the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels a +-------------------------+-----+---+-----------------------------------------------------------------+ | sstore(p, v) | `-` | F | storage[p] := v | +-------------------------+-----+---+-----------------------------------------------------------------+ +| tload(p) | | N | transientStorage[p] | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| tstore(p, v) | `-` | N | transientStorage[p] := v | ++-------------------------+-----+---+-----------------------------------------------------------------+ | msize() | | F | size of memory, i.e. largest accessed memory index | +-------------------------+-----+---+-----------------------------------------------------------------+ | gas() | | F | gas still available to execution | @@ -868,6 +870,8 @@ the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels a +-------------------------+-----+---+-----------------------------------------------------------------+ | returndatacopy(t, f, s) | `-` | B | copy s bytes from returndata at position f to mem at position t | +-------------------------+-----+---+-----------------------------------------------------------------+ +| mcopy(t, f, s) | `-` | N | copy s bytes from mem at position f to mem at position t | ++-------------------------+-----+---+-----------------------------------------------------------------+ | extcodehash(a) | | C | code hash of address a | +-------------------------+-----+---+-----------------------------------------------------------------+ | create(v, p, n) | | F | create new contract with code mem[p...(p+n)) and send v wei | @@ -903,37 +907,44 @@ the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels a | revert(p, s) | `-` | B | end execution, revert state changes, return data mem[p...(p+s)) | +-------------------------+-----+---+-----------------------------------------------------------------+ | selfdestruct(a) | `-` | F | end execution, destroy current contract and send funds to a | +| | | | (deprecated) | +-------------------------+-----+---+-----------------------------------------------------------------+ | invalid() | `-` | F | end execution with invalid instruction | +-------------------------+-----+---+-----------------------------------------------------------------+ -| log0(p, s) | `-` | F | log without topics and data mem[p...(p+s)) | +| log0(p, s) | `-` | F | log data mem[p...(p+s)) | +-------------------------+-----+---+-----------------------------------------------------------------+ -| log1(p, s, t1) | `-` | F | log with topic t1 and data mem[p...(p+s)) | +| log1(p, s, t1) | `-` | F | log data mem[p...(p+s)) with topic t1 | +-------------------------+-----+---+-----------------------------------------------------------------+ -| log2(p, s, t1, t2) | `-` | F | log with topics t1, t2 and data mem[p...(p+s)) | +| log2(p, s, t1, t2) | `-` | F | log data mem[p...(p+s)) with topics t1, t2 | +-------------------------+-----+---+-----------------------------------------------------------------+ -| log3(p, s, t1, t2, t3) | `-` | F | log with topics t1, t2, t3 and data mem[p...(p+s)) | +| log3(p, s, t1, t2, t3) | `-` | F | log data mem[p...(p+s)) with topics t1, t2, t3 | +-------------------------+-----+---+-----------------------------------------------------------------+ -| log4(p, s, t1, t2, t3, | `-` | F | log with topics t1, t2, t3, t4 and data mem[p...(p+s)) | +| log4(p, s, t1, t2, t3, | `-` | F | log data mem[p...(p+s)) with topics t1, t2, t3, t4 | | t4) | | | | +-------------------------+-----+---+-----------------------------------------------------------------+ | chainid() | | I | ID of the executing chain (EIP-1344) | +-------------------------+-----+---+-----------------------------------------------------------------+ | basefee() | | L | current block's base fee (EIP-3198 and EIP-1559) | +-------------------------+-----+---+-----------------------------------------------------------------+ +| blobbasefee() | | N | current block's blob base fee (EIP-7516 and EIP-4844) | ++-------------------------+-----+---+-----------------------------------------------------------------+ | origin() | | F | transaction sender | +-------------------------+-----+---+-----------------------------------------------------------------+ | gasprice() | | F | gas price of the transaction | +-------------------------+-----+---+-----------------------------------------------------------------+ | blockhash(b) | | F | hash of block nr b - only for last 256 blocks excluding current | +-------------------------+-----+---+-----------------------------------------------------------------+ +| blobhash(i) | | N | versioned hash of transaction's i-th blob | ++-------------------------+-----+---+-----------------------------------------------------------------+ | coinbase() | | F | current mining beneficiary | +-------------------------+-----+---+-----------------------------------------------------------------+ | timestamp() | | F | timestamp of the current block in seconds since the epoch | +-------------------------+-----+---+-----------------------------------------------------------------+ | number() | | F | current block number | +-------------------------+-----+---+-----------------------------------------------------------------+ -| difficulty() | | F | difficulty of the current block | +| difficulty() | | F | difficulty of the current block (see note below) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| prevrandao() | | P | randomness provided by the beacon chain (see note below) | +-------------------------+-----+---+-----------------------------------------------------------------+ | gaslimit() | | F | block gas limit of the current block | +-------------------------+-----+---+-----------------------------------------------------------------+ @@ -948,6 +959,20 @@ the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels a You need to use the ``returndatasize`` opcode to check which part of this memory area contains the return data. The remaining bytes will retain their values as of before the call. +.. note:: + The ``difficulty()`` instruction is disallowed in EVM version >= Paris. + With the Paris network upgrade the semantics of the instruction that was previously called + ``difficulty`` have been changed and the instruction was renamed to ``prevrandao``. + It can now return arbitrary values in the full 256-bit range, whereas the highest recorded + difficulty value within Ethash was ~54 bits. + This change is described in `EIP-4399 `_. + Please note that irrelevant to which EVM version is selected in the compiler, the semantics of + instructions depend on the final chain of deployment. + +.. warning:: + From version 0.8.18 and up, the use of ``selfdestruct`` in both Solidity and Yul will trigger a + deprecation warning, since the ``SELFDESTRUCT`` opcode will eventually undergo breaking changes in behavior + as stated in `EIP-6049 `_. In some internal dialects, there are additional functions: @@ -981,7 +1006,7 @@ Its first and only argument must be a string literal and uniquely represents the Identifiers can be arbitrary but when the compiler produces Yul code from Solidity sources, it uses a library name qualified with the name of the source unit that defines that library. To link the code with a particular library address, the same identifier must be provided to the -``--libraries`` option on the command line. +``--libraries`` option on the command-line. For example this code @@ -1062,12 +1087,12 @@ or even opcodes unknown to the Solidity compiler, care has to be taken when using ``verbatim`` together with the optimizer. Even when the optimizer is switched off, the code generator has to determine the stack layout, which means that e.g. using ``verbatim`` to modify -the stack height can lead to undefined behaviour. +the stack height can lead to undefined behavior. The following is a non-exhaustive list of restrictions on verbatim bytecode that are not checked by the compiler. Violations of these restrictions can result in -undefined behaviour. +undefined behavior. - Control-flow should not jump into or out of verbatim blocks, but it can jump within the same verbatim block. @@ -1174,14 +1199,13 @@ An example Yul Object is shown below: datacopy(offset, dataoffset("Contract2"), size) // constructor parameter is a single number 0x1234 mstore(add(offset, size), 0x1234) - pop(create(offset, add(size, 32), 0)) + pop(create(0, offset, add(size, 32))) // now return the runtime object (the currently // executing code is the constructor code) size := datasize("Contract1_deployed") offset := allocate(size) - // This will turn into a memory->memory copy for Ewasm and - // a codecopy for EVM + // This will turn into a codecopy for EVM datacopy(offset, dataoffset("Contract1_deployed"), size) return(offset, size) } @@ -1245,65 +1269,8 @@ In Solidity mode, the Yul optimizer is activated together with the regular optim Optimization Step Sequence -------------------------- -By default the Yul optimizer applies its predefined sequence of optimization steps to the generated assembly. -You can override this sequence and supply your own using the ``--yul-optimizations`` option: - -.. code-block:: sh - - solc --optimize --ir-optimized --yul-optimizations 'dhfoD[xarrscLMcCTU]uljmul' - -The order of steps is significant and affects the quality of the output. -Moreover, applying a step may uncover new optimization opportunities for others that were already -applied so repeating steps is often beneficial. -By enclosing part of the sequence in square brackets (``[]``) you tell the optimizer to repeatedly -apply that part until it no longer improves the size of the resulting assembly. -You can use brackets multiple times in a single sequence but they cannot be nested. - -The following optimization steps are available: - -============ =============================== -Abbreviation Full name -============ =============================== -``f`` ``BlockFlattener`` -``l`` ``CircularReferencesPruner`` -``c`` ``CommonSubexpressionEliminator`` -``C`` ``ConditionalSimplifier`` -``U`` ``ConditionalUnsimplifier`` -``n`` ``ControlFlowSimplifier`` -``D`` ``DeadCodeEliminator`` -``v`` ``EquivalentFunctionCombiner`` -``e`` ``ExpressionInliner`` -``j`` ``ExpressionJoiner`` -``s`` ``ExpressionSimplifier`` -``x`` ``ExpressionSplitter`` -``I`` ``ForLoopConditionIntoBody`` -``O`` ``ForLoopConditionOutOfBody`` -``o`` ``ForLoopInitRewriter`` -``i`` ``FullInliner`` -``g`` ``FunctionGrouper`` -``h`` ``FunctionHoister`` -``F`` ``FunctionSpecializer`` -``T`` ``LiteralRematerialiser`` -``L`` ``LoadResolver`` -``M`` ``LoopInvariantCodeMotion`` -``r`` ``RedundantAssignEliminator`` -``R`` ``ReasoningBasedSimplifier`` - highly experimental -``m`` ``Rematerialiser`` -``V`` ``SSAReverser`` -``a`` ``SSATransform`` -``t`` ``StructuralSimplifier`` -``u`` ``UnusedPruner`` -``p`` ``UnusedFunctionParameterPruner`` -``d`` ``VarDeclInitializer`` -============ =============================== - -Some steps depend on properties ensured by ``BlockFlattener``, ``FunctionGrouper``, ``ForLoopInitRewriter``. -For this reason the Yul optimizer always applies them before applying any steps supplied by the user. - -The ReasoningBasedSimplifier is an optimizer step that is currently not enabled -in the default set of steps. It uses an SMT solver to simplify arithmetic expressions -and boolean conditions. It has not received thorough testing or validation yet and can produce -non-reproducible results, so please use with care! +Detailed information regarding the optimization sequence as well as a list of abbreviations is +available in the :ref:`optimizer docs `. .. _erc20yul: diff --git a/compiler/lib/stdlib_sol.tvm b/compiler/lib/stdlib_sol.tvm deleted file mode 100644 index b710f010..00000000 --- a/compiler/lib/stdlib_sol.tvm +++ /dev/null @@ -1,2629 +0,0 @@ -.globl insert_pubkey -.type insert_pubkey, @function -CALL $insert_pubkey_macro$ - -.macro insert_pubkey_macro -.loc stdlib.sol, 14 -SWAP -CTOS -.loc stdlib.sol, 15 -NEWC -.loc stdlib.sol, 18 -OVER -LDI 1 -POP S3 -PUSHCONT { - .loc stdlib.sol, 19 - STSLICECONST 1 - .loc stdlib.sol, 20 - OVER - LDU 32 - POP S3 - .loc stdlib.sol, 21 - SWAP - STSLICECONST 1 - STU 32 -} -PUSHCONT { - .loc stdlib.sol, 23 - STSLICECONST 0 -} -IFELSE -.loc stdlib.sol, 27 -OVER -LDI 1 -POP S3 -PUSHCONT { - .loc stdlib.sol, 28 - STSLICECONST 1 - .loc stdlib.sol, 29 - OVER - LDI 1 - LDI 1 - POP S4 - .loc stdlib.sol, 30 - XCHG S2 - STI 1 - STI 1 -} -PUSHCONT { - .loc stdlib.sol, 32 - STSLICECONST 0 -} -IFELSE -.loc stdlib.sol, 36 -OVER -LDDICT -POP S3 -SWAP -STDICT -.loc stdlib.sol, 40 -NEWDICT -.loc stdlib.sol, 41 -PUSH S2 -LDI 1 -POP S4 -PUSHCONT { - .loc stdlib.sol, 42 - PUSH S2 - LDREFRTOS - SWAP - POP S4 - .loc stdlib.sol, 43 - PLDDICT - NIP - .loc stdlib.sol, 0 -} -IF -.loc stdlib.sol, 45 -ROLL 3 -PUSHINT 0 -ROTREV -NEWC -STU 256 -ROTREV -PUSHINT 64 -DICTUSETB -.loc stdlib.sol, 47 -NEWC -STDICT -.loc stdlib.sol, 48 -OVER -STSLICECONST 1 -POP S2 -.loc stdlib.sol, 49 -STBREFR -.loc stdlib.sol, 53 -OVER -LDDICT -POP S3 -SWAP -STDICT -.loc stdlib.sol, 55 -SWAP -SEMPTY -THROWIFNOT 55 -.loc stdlib.sol, 57 -ENDC -.loc stdlib.sol, 0 - -.globl replay_protection -.type replay_protection, @function -CALL $replay_protection_macro$ - -.macro replay_protection_macro -.loc stdlib.sol, 61 -GETGLOB 3 -OVER -LESS -THROWIFNOT 52 -.loc stdlib.sol, 62 -DUP -NOW -PUSHINT 1000 -MUL -PUSHINT 1800000 -ADD -LESS -THROWIFNOT 52 -.loc stdlib.sol, 63 -SETGLOB 3 -.loc stdlib.sol, 0 - -.globl __tonToGas -.type __tonToGas, @function -CALL $__tonToGas_macro$ - -.macro __tonToGas_macro -.loc stdlib.sol, 67 -PUSHPOW2 16 -SWAP -CALLREF { - CALL $__gasGasPrice_macro$ -} -MULDIV -.loc stdlib.sol, 0 - -.globl __gasToTon -.type __gasToTon, @function -CALL $__gasToTon_macro$ - -.macro __gasToTon_macro -.loc stdlib.sol, 71 -CALLREF { - CALL $__gasGasPrice_macro$ -} -PUSHPOW2 16 -MULDIVC -.loc stdlib.sol, 0 - -.globl __gasGasPrice -.type __gasGasPrice, @function -CALL $__gasGasPrice_macro$ - -.macro __gasGasPrice_macro -.loc stdlib.sol, 75 -DUP -EQINT 0 -OVER -EQINT -1 -OR -THROWIFNOT 67 -.loc stdlib.sol, 76 -PUSHINT 20 -PUSHINT 21 -CONDSEL -CONFIGPARAM -DUP -PUSHCONT { - PUSHREF { - } - SWAP -} -IFNOT -.loc stdlib.sol, 77 -THROWIFNOT 68 -.loc stdlib.sol, 78 -CTOS -.loc stdlib.sol, 79 -LDU 8 -LDU 64 -LDU 64 -LDU 8 -PLDU 64 -BLKDROP2 4, 1 -.loc stdlib.sol, 0 - -.globl __exp -.type __exp, @function -CALL $__exp_macro$ - -.macro __exp_macro -.loc stdlib.sol, 83 -PUSHINT 1 -.loc stdlib.sol, 84 -PUSHCONT { - OVER - NEQINT 0 -} -PUSHCONT { - .loc stdlib.sol, 85 - OVER - MODPOW2 1 - PUSHCONT { - .loc stdlib.sol, 86 - PUSH S2 - MUL - .loc stdlib.sol, 87 - OVER - DEC - } - PUSHCONT { - .loc stdlib.sol, 89 - PUSH2 S2, S2 - MUL - POP S3 - .loc stdlib.sol, 90 - OVER - RSHIFT 1 - } - IFELSE - POP S2 - .loc stdlib.sol, 0 -} -WHILE -.loc stdlib.sol, 93 -BLKDROP2 2, 1 -.loc stdlib.sol, 0 - -.globl parseInteger -.type parseInteger, @function -CALL $parseInteger_macro$ - -.macro parseInteger_macro -.loc stdlib.sol, 99 -TUPLE 0 -.loc stdlib.sol, 100 -PUSH S2 -PUSHCONT { - .loc stdlib.sol, 101 - PUSHINT 0 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - .loc stdlib.sol, 102 - BLKDROP2 2, 1 - .loc stdlib.sol, 0 -} -IFNOTJMP -.loc stdlib.sol, 104 -PUSHINT 0 -.loc stdlib.sol, 105 -PUSHCONT { - PUSH S3 - NEQINT 0 -} -PUSHCONT { - .loc stdlib.sol, 106 - OVER2 - DIVMOD - POP S2 - POP S4 - .loc stdlib.sol, 107 - DUP2 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S2 - .loc stdlib.sol, 0 -} -WHILE -DROP -BLKDROP2 2, 1 -.loc stdlib.sol, 0 - -.globl convertIntToDecStr_short -.type convertIntToDecStr_short, @function -CALL $convertIntToDecStr_short_macro$ - -.macro convertIntToDecStr_short_macro -.loc stdlib.sol, 112 -ROTREV -PUSH S2 -ABS -PUSHINT 0 -DUP -ROLL 5 -LESSINT 0 -CALLREF { - CALL $convertIntToDecStr_macro$ -} -.loc stdlib.sol, 0 - -.globl convertIntToDecStr -.type convertIntToDecStr, @function -CALL $convertIntToDecStr_macro$ - -.macro convertIntToDecStr_macro -.loc stdlib.sol, 116 -PUSH S4 -BREMBITS -RSHIFT 3 -.loc stdlib.sol, 117 -DUP -PUSHCONT { - .loc stdlib.sol, 118 - BLKPUSH 2, 6 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S7 - .loc stdlib.sol, 119 - NEWC - POP S6 - .loc stdlib.sol, 120 - DROP - PUSHINT 127 - .loc stdlib.sol, 0 -} -IFNOT -.loc stdlib.sol, 122 -ROT -PUSHINT 48 -PUSHINT 32 -CONDSEL -.loc stdlib.sol, 123 -ROT -PUSHCONT { - .loc stdlib.sol, 124 - PUSH S4 - STSLICECONST x2d - POP S5 - .loc stdlib.sol, 0 -} -IF -.loc stdlib.sol, 125 -OVER -DEC -POP S2 -.loc stdlib.sol, 126 -OVER -PUSHCONT { - .loc stdlib.sol, 127 - BLKPUSH 2, 5 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S6 - .loc stdlib.sol, 128 - NEWC - POP S5 - .loc stdlib.sol, 129 - PUSHINT 127 - POP S2 - .loc stdlib.sol, 0 -} -IFNOT -.loc stdlib.sol, 131 -ROLL 3 -PUSHINT 10 -CALLREF { - CALL $parseInteger_macro$ -} -.loc stdlib.sol, 132 -DUP -CALLREF { - DUP - TLEN - DUP - PUSHCONT { - DEC - PUSHPOW2DEC 8 - MUL - SWAP - LAST - TLEN - ADD - } - PUSHCONT { - NIP - } - IFELSE -} -.loc stdlib.sol, 133 -PUSH S4 -PUSHCONT { - .loc stdlib.sol, 134 - PUSH2 S4, S0 - LESS - PUSH S5 - GTINT 127 - OR - THROWIF 66 - .loc stdlib.sol, 136 - PUSH2 S4, S0 - SUB - .loc stdlib.sol, 137 - PUSH2 S0, S4 - LEQ - PUSHCONT { - .loc stdlib.sol, 138 - DUP - PUSHCONT { - .loc stdlib.sol, 139 - PUSH2 S6, S3 - STUR 8 - POP S7 - .loc stdlib.sol, 0 - } - REPEAT - .loc stdlib.sol, 141 - PUSH2 S4, S0 - SUB - POP S5 - .loc stdlib.sol, 142 - PUSH S4 - PUSHCONT { - .loc stdlib.sol, 143 - BLKPUSH 2, 7 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S8 - .loc stdlib.sol, 144 - NEWC - POP S7 - .loc stdlib.sol, 145 - PUSHINT 127 - POP S5 - .loc stdlib.sol, 0 - } - IFNOT - } - PUSHCONT { - .loc stdlib.sol, 148 - PUSH S4 - PUSHCONT { - .loc stdlib.sol, 149 - PUSH2 S6, S3 - STUR 8 - POP S7 - .loc stdlib.sol, 0 - } - REPEAT - .loc stdlib.sol, 151 - BLKPUSH 2, 7 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S8 - .loc stdlib.sol, 152 - NEWC - POP S7 - .loc stdlib.sol, 153 - PUSH2 S0, S4 - SUB - PUSHCONT { - .loc stdlib.sol, 154 - PUSH2 S6, S3 - STUR 8 - POP S7 - .loc stdlib.sol, 0 - } - REPEAT - .loc stdlib.sol, 156 - PUSHINT 127 - OVER - SUB - PUSH S5 - ADD - POP S5 - } - IFELSE - .loc stdlib.sol, 0 - DROP -} -IF -.loc stdlib.sol, 159 -PUSH2 S0, S3 -LEQ -PUSHCONT { - .loc stdlib.sol, 160 - DUP - PUSHCONT { - .loc stdlib.sol, 161 - OVER - CALLREF { - TPOP - TPOP - ROTREV - DUP - TLEN - PUSHCONT { - TPUSH - } - PUSHCONT { - DROP - } - IFELSE - } - POP S3 - .loc stdlib.sol, 162 - PUSH S6 - PUSHINT 48 - ROT - ADD - STUR 8 - POP S6 - .loc stdlib.sol, 0 - } - REPEAT -} -PUSHCONT { - .loc stdlib.sol, 165 - PUSH S3 - PUSHCONT { - .loc stdlib.sol, 166 - OVER - CALLREF { - TPOP - TPOP - ROTREV - DUP - TLEN - PUSHCONT { - TPUSH - } - PUSHCONT { - DROP - } - IFELSE - } - POP S3 - .loc stdlib.sol, 167 - PUSH S6 - PUSHINT 48 - ROT - ADD - STUR 8 - POP S6 - .loc stdlib.sol, 0 - } - REPEAT - .loc stdlib.sol, 169 - BLKPUSH 2, 6 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S7 - .loc stdlib.sol, 170 - NEWC - POP S6 - .loc stdlib.sol, 171 - PUSH2 S0, S3 - SUB - PUSHCONT { - .loc stdlib.sol, 172 - OVER - CALLREF { - TPOP - TPOP - ROTREV - DUP - TLEN - PUSHCONT { - TPUSH - } - PUSHCONT { - DROP - } - IFELSE - } - POP S3 - .loc stdlib.sol, 173 - PUSH S6 - PUSHINT 48 - ROT - ADD - STUR 8 - POP S6 - .loc stdlib.sol, 0 - } - REPEAT -} -IFELSE -.loc stdlib.sol, 177 -BLKDROP 5 -.loc stdlib.sol, 0 - -.globl convertAddressToHexString -.type convertAddressToHexString, @function -CALL $convertAddressToHexString_macro$ - -.macro convertAddressToHexString_macro -.loc stdlib.sol, 181 -REWRITESTDADDR -.loc stdlib.sol, 182 -OVER2 -ROLL 3 -CALLREF { - CALL $convertIntToHexStr_short_macro$ -} -POP S3 -POP S3 -.loc stdlib.sol, 183 -OVER -BREMBITS -.loc stdlib.sol, 184 -GTINT 8 -PUSHCONT { - .loc stdlib.sol, 187 - BLKPUSH 2, 2 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S3 - .loc stdlib.sol, 188 - NEWC - POP S2 - .loc stdlib.sol, 189 -} -IFNOT -OVER -STSLICECONST x3a -POP S2 -.loc stdlib.sol, 191 -PUSHINT 64 -TRUE -DUP -FALSE -CALLREF { - CALL $convertIntToHexStr_macro$ -} -.loc stdlib.sol, 0 - -.globl convertFixedPointToString -.type convertFixedPointToString, @function -CALL $convertFixedPointToString_macro$ - -.macro convertFixedPointToString_macro -.loc stdlib.sol, 195 -OVER -ABS -.loc stdlib.sol, 196 -PUSHINT 10 -PUSH3 S2, S0, S2 -OR -THROWIFNOT 69 -CALLREF { - CALL $__exp_macro$ -} -DIVMOD -.loc stdlib.sol, 197 -BLKPUSH 2, 5 -ROLL 3 -PUSHINT 0 -DUP -ROLL 7 -SGN -LESSINT 0 -CALLREF { - CALL $convertIntToDecStr_macro$ -} -POP S4 -POP S4 -.loc stdlib.sol, 198 -PUSH S2 -BREMBITS -.loc stdlib.sol, 199 -GTINT 8 -PUSHCONT { - .loc stdlib.sol, 202 - OVER2 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S4 - .loc stdlib.sol, 203 - NEWC - POP S3 - .loc stdlib.sol, 204 -} -IFNOT -PUSH S2 -STSLICECONST x2e -POP S3 -.loc stdlib.sol, 206 -SWAP -TRUE -FALSE -CALLREF { - CALL $convertIntToDecStr_macro$ -} -.loc stdlib.sol, 0 - -.globl convertIntToHexStr_short -.type convertIntToHexStr_short, @function -CALL $convertIntToHexStr_short_macro$ - -.macro convertIntToHexStr_short_macro -.loc stdlib.sol, 210 -ROTREV -PUSH S2 -ABS -PUSHINT 0 -DUP -TRUE -ROLL 6 -LESSINT 0 -CALLREF { - CALL $convertIntToHexStr_macro$ -} -.loc stdlib.sol, 0 - -.globl convertIntToHexStr -.type convertIntToHexStr, @function -CALL $convertIntToHexStr_macro$ - -.macro convertIntToHexStr_macro -.loc stdlib.sol, 214 -PUSH S5 -BREMBITS -RSHIFT 3 -.loc stdlib.sol, 215 -DUP -PUSHCONT { - .loc stdlib.sol, 216 - BLKPUSH 2, 7 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S8 - .loc stdlib.sol, 217 - NEWC - POP S7 - .loc stdlib.sol, 218 - DROP - PUSHINT 127 - .loc stdlib.sol, 0 -} -IFNOT -.loc stdlib.sol, 220 -ROLL 3 -PUSHINT 48 -PUSHINT 32 -CONDSEL -.loc stdlib.sol, 221 -ROT -PUSHCONT { - .loc stdlib.sol, 222 - PUSH S5 - STSLICECONST x2d - POP S6 - .loc stdlib.sol, 0 -} -IF -.loc stdlib.sol, 223 -OVER -DEC -POP S2 -.loc stdlib.sol, 224 -OVER -PUSHCONT { - .loc stdlib.sol, 225 - BLKPUSH 2, 6 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S7 - .loc stdlib.sol, 226 - NEWC - POP S6 - .loc stdlib.sol, 227 - PUSHINT 127 - POP S2 - .loc stdlib.sol, 0 -} -IFNOT -.loc stdlib.sol, 229 -ROLL 4 -PUSHINT 16 -CALLREF { - CALL $parseInteger_macro$ -} -.loc stdlib.sol, 230 -DUP -CALLREF { - DUP - TLEN - DUP - PUSHCONT { - DEC - PUSHPOW2DEC 8 - MUL - SWAP - LAST - TLEN - ADD - } - PUSHCONT { - NIP - } - IFELSE -} -.loc stdlib.sol, 232 -PUSH S5 -PUSHCONT { - .loc stdlib.sol, 233 - PUSH2 S5, S0 - LESS - PUSH S6 - GTINT 127 - OR - THROWIF 69 - .loc stdlib.sol, 235 - PUSH2 S5, S0 - SUB - .loc stdlib.sol, 236 - PUSH2 S0, S4 - LEQ - PUSHCONT { - .loc stdlib.sol, 237 - DUP - PUSHCONT { - .loc stdlib.sol, 238 - PUSH2 S7, S3 - STUR 8 - POP S8 - .loc stdlib.sol, 0 - } - REPEAT - .loc stdlib.sol, 240 - PUSH2 S4, S0 - SUB - POP S5 - .loc stdlib.sol, 241 - PUSH S4 - PUSHCONT { - .loc stdlib.sol, 242 - BLKPUSH 2, 8 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S9 - .loc stdlib.sol, 243 - NEWC - POP S8 - .loc stdlib.sol, 244 - PUSHINT 127 - POP S5 - .loc stdlib.sol, 0 - } - IFNOT - } - PUSHCONT { - .loc stdlib.sol, 247 - PUSH S4 - PUSHCONT { - .loc stdlib.sol, 248 - PUSH2 S7, S3 - STUR 8 - POP S8 - .loc stdlib.sol, 0 - } - REPEAT - .loc stdlib.sol, 250 - BLKPUSH 2, 8 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S9 - .loc stdlib.sol, 251 - NEWC - POP S8 - .loc stdlib.sol, 252 - PUSH2 S0, S4 - SUB - PUSHCONT { - .loc stdlib.sol, 253 - PUSH2 S7, S3 - STUR 8 - POP S8 - .loc stdlib.sol, 0 - } - REPEAT - .loc stdlib.sol, 255 - PUSHINT 127 - OVER - SUB - PUSH S5 - ADD - POP S5 - } - IFELSE - .loc stdlib.sol, 0 - DROP -} -IF -.loc stdlib.sol, 258 -PUSH2 S0, S3 -LEQ -PUSHCONT { - .loc stdlib.sol, 259 - DUP - PUSHCONT { - .loc stdlib.sol, 260 - OVER - CALLREF { - TPOP - TPOP - ROTREV - DUP - TLEN - PUSHCONT { - TPUSH - } - PUSHCONT { - DROP - } - IFELSE - } - POP S3 - .loc stdlib.sol, 261 - DUP - LESSINT 10 - PUSHCONT { - .loc stdlib.sol, 262 - PUSH S7 - PUSHINT 48 - } - PUSHCONT { - .loc stdlib.sol, 264 - PUSH2 S7, S5 - PUSHINT 87 - PUSHINT 55 - CONDSEL - } - IFELSE - PUSH S2 - ADD - STUR 8 - POP S8 - .loc stdlib.sol, 0 - DROP - } - REPEAT -} -PUSHCONT { - .loc stdlib.sol, 267 - PUSH S3 - PUSHCONT { - .loc stdlib.sol, 268 - OVER - CALLREF { - TPOP - TPOP - ROTREV - DUP - TLEN - PUSHCONT { - TPUSH - } - PUSHCONT { - DROP - } - IFELSE - } - POP S3 - .loc stdlib.sol, 269 - DUP - LESSINT 10 - PUSHCONT { - .loc stdlib.sol, 270 - PUSH S7 - PUSHINT 48 - } - PUSHCONT { - .loc stdlib.sol, 272 - PUSH2 S7, S5 - PUSHINT 87 - PUSHINT 55 - CONDSEL - } - IFELSE - PUSH S2 - ADD - STUR 8 - POP S8 - .loc stdlib.sol, 0 - DROP - } - REPEAT - .loc stdlib.sol, 274 - BLKPUSH 2, 7 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S8 - .loc stdlib.sol, 275 - NEWC - POP S7 - .loc stdlib.sol, 276 - PUSH2 S0, S3 - SUB - PUSHCONT { - .loc stdlib.sol, 277 - OVER - CALLREF { - TPOP - TPOP - ROTREV - DUP - TLEN - PUSHCONT { - TPUSH - } - PUSHCONT { - DROP - } - IFELSE - } - POP S3 - .loc stdlib.sol, 278 - DUP - LESSINT 10 - PUSHCONT { - .loc stdlib.sol, 279 - PUSH S7 - PUSHINT 48 - } - PUSHCONT { - .loc stdlib.sol, 281 - PUSH2 S7, S5 - PUSHINT 87 - PUSHINT 55 - CONDSEL - } - IFELSE - PUSH S2 - ADD - STUR 8 - POP S8 - .loc stdlib.sol, 0 - DROP - } - REPEAT -} -IFELSE -.loc stdlib.sol, 284 -BLKDROP 6 -.loc stdlib.sol, 0 - -.globl storeStringInBuilders -.type storeStringInBuilders, @function -CALL $storeStringInBuilders_macro$ - -.macro storeStringInBuilders_macro -.loc stdlib.sol, 288 -OVER -BREMBITS -ADDCONST -7 -.loc stdlib.sol, 289 -OVER -SBITREFS -.loc stdlib.sol, 290 -DUP -PUSHCONT { - .loc stdlib.sol, 291 - PUSH S3 - PUSHINT 0 - PUSH S2 - SSKIPFIRST - POP S4 - .loc stdlib.sol, 0 -} -IF -.loc stdlib.sol, 292 -DROP -OVER -LEQ -.loc stdlib.sol, 293 -PUSHCONT { - .loc stdlib.sol, 296 - DUP2 - LDSLICEX - POP S3 - .loc stdlib.sol, 297 - PUSH S3 - STSLICE - POP S3 - .loc stdlib.sol, 298 - OVER2 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S4 - .loc stdlib.sol, 299 - NEWC - POP S3 - .loc stdlib.sol, 300 -} -IFNOT -PUSH2 S1, S2 -STSLICE -BLKDROP2 3, 1 -.loc stdlib.sol, 0 - -.globl assembleList -.type assembleList, @function -CALL $assembleList_macro$ - -.macro assembleList_macro -.loc stdlib.sol, 306 -PUSHCONT { - OVER - TLEN - ISZERO - NOT -} -PUSHCONT { - .loc stdlib.sol, 307 - OVER - CALLREF { - TPOP - TPOP - ROTREV - DUP - TLEN - PUSHCONT { - TPUSH - } - PUSHCONT { - DROP - } - IFELSE - } - POP S3 - .loc stdlib.sol, 308 - STBREF - .loc stdlib.sol, 0 -} -WHILE -.loc stdlib.sol, 311 -ENDC -NIP -.loc stdlib.sol, 0 - -.globl __stoi -.type __stoi, @function -CALL $__stoi_macro$ - -.macro __stoi_macro -.loc stdlib.sol, 315 -CTOS -.loc stdlib.sol, 316 -DUP -SBITS -LESSINT 8 -PUSHCONT { - .loc stdlib.sol, 317 - DROP - NULL - .loc stdlib.sol, 0 -} -IFJMP -.loc stdlib.sol, 319 -BLKPUSH 2, 0 -.loc stdlib.sol, 320 -LDU 8 -POP S2 -.loc stdlib.sol, 321 -DUP -EQINT 45 -.loc stdlib.sol, 322 -PUSHINT 0 -.loc stdlib.sol, 323 -PUSH S3 -SBITS -.loc stdlib.sol, 324 -PUSH2 S2, S0 -GTINT 15 -AND -PUSHCONT { - .loc stdlib.sol, 325 - PUSH S4 - LDU 8 - LDU 8 - POP S7 - POP S3 - POP S4 - .loc stdlib.sol, 0 -} -IF -.loc stdlib.sol, 327 -PUSH S2 -NOT -SWAP -GTINT 7 -AND -PUSHCONT { - .loc stdlib.sol, 328 - PUSH S3 - LDU 8 - POP S5 - NIP - .loc stdlib.sol, 0 -} -IF -.loc stdlib.sol, 330 -ROT -EQINT 48 -SWAP -EQINT 120 -AND -.loc stdlib.sol, 332 -OVER -PUSHCONT { - .loc stdlib.sol, 333 - PUSH S3 - LDU 8 - POP S5 - DROP - .loc stdlib.sol, 0 -} -IF -.loc stdlib.sol, 334 -DUP -PUSHCONT { - .loc stdlib.sol, 335 - PUSH S3 - LDU 8 - LDU 8 - POP S6 - DROP2 - .loc stdlib.sol, 0 -} -IF -.loc stdlib.sol, 337 -PUSHINT 0 -.loc stdlib.sol, 339 -PUSH S4 -SBITS -RSHIFT 3 -.loc stdlib.sol, 341 -FALSE ; decl return flag -ROLL 3 -PUSHCONT { - .loc stdlib.sol, 342 - FALSE ; decl return flag - PUSH S2 - PUSHCONT { - .loc stdlib.sol, 343 - PUSH S6 - LDU 8 - POP S8 - .loc stdlib.sol, 344 - PUSH S4 - MULCONST 16 - POP S5 - .loc stdlib.sol, 345 - DUP - GTINT 47 - OVER - LESSINT 58 - AND - PUSHCONT { - .loc stdlib.sol, 346 - DUP - ADDCONST -48 - PUSH S5 - ADD - POP S5 - .loc stdlib.sol, 0 - } - PUSHCONT { - DUP - GTINT 64 - OVER - LESSINT 71 - AND - PUSHCONT { - .loc stdlib.sol, 348 - DUP - ADDCONST -55 - PUSH S5 - ADD - POP S5 - .loc stdlib.sol, 0 - } - PUSHCONT { - DUP - GTINT 96 - OVER - LESSINT 103 - AND - PUSHCONT { - .loc stdlib.sol, 350 - DUP - ADDCONST -87 - PUSH S5 - ADD - POP S5 - .loc stdlib.sol, 0 - } - PUSHCONT { - .loc stdlib.sol, 352 - BLKDROP 8 - NULL - PUSHINT 4 - RETALT - .loc stdlib.sol, 0 - } - IFELSE - } - IFELSE - } - IFELSE - DROP - .loc stdlib.sol, 0 - } - REPEATBRK - DUP - IFRET - DROP - .loc stdlib.sol, 0 -} -PUSHCONT { - .loc stdlib.sol, 356 - FALSE ; decl return flag - PUSH S2 - PUSHCONT { - .loc stdlib.sol, 357 - PUSH S6 - LDU 8 - POP S8 - .loc stdlib.sol, 358 - DUP - LESSINT 48 - OVER - GTINT 57 - OR - PUSHCONT { - BLKDROP 8 - NULL - PUSHINT 4 - RETALT - } - IFJMP - .loc stdlib.sol, 360 - PUSH S4 - MULCONST 10 - POP S5 - .loc stdlib.sol, 361 - ADDCONST -48 - PUSH S4 - ADD - POP S4 - .loc stdlib.sol, 0 - } - REPEATBRK - DUP - IFRET - DROP - .loc stdlib.sol, 0 -} -IFELSE -IFRET -.loc stdlib.sol, 364 -DROP -SWAP -PUSHCONT { - .loc stdlib.sol, 365 - NEGATE - .loc stdlib.sol, 0 -} -IF -.loc stdlib.sol, 366 -BLKDROP2 2, 1 -.loc stdlib.sol, 0 - -.globl strToList -.type strToList, @function -CALL $strToList_macro$ - -.macro strToList_macro -.loc stdlib.sol, 370 -TUPLE 0 -.loc stdlib.sol, 371 -SWAP -CTOS -.loc stdlib.sol, 372 -PUSHCONT { - DUP - SREFS - NEQINT 0 -} -PUSHCONT { - .loc stdlib.sol, 373 - LDREFRTOS - .loc stdlib.sol, 375 - SWAP - NEWC - STSLICE - .loc stdlib.sol, 376 - PUXC S2, S-1 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - POP S2 - .loc stdlib.sol, 0 -} -WHILE -.loc stdlib.sol, 380 -NEWC -STSLICE -.loc stdlib.sol, 0 - -.globl bytes_substr -.type bytes_substr, @function -CALL $bytes_substr_macro$ - -.macro bytes_substr_macro -.loc stdlib.sol, 385 -PUSH S3 -PUSHPOW2DEC 32 -CDATASIZE -DROP -NIP -.loc stdlib.sol, 386 -RSHIFT 3 -.loc stdlib.sol, 387 -SWAP -PUSHCONT { - .loc stdlib.sol, 388 - DUP - POP S2 - .loc stdlib.sol, 0 -} -IF -.loc stdlib.sol, 389 -PUSH2 S1, S2 -GEQ -THROWIFNOT 70 -.loc stdlib.sol, 390 -XCPU S1, S2 -SUB -.loc stdlib.sol, 391 -SWAP -BLKPUSH 2, 2 -ADD -GEQ -THROWIFNOT 70 -.loc stdlib.sol, 392 -CALLREF { - CALL $__subCell_macro$ -} -.loc stdlib.sol, 0 - -.globl __substr -.type __substr, @function -CALL $__substr_macro$ - -.macro __substr_macro -.loc stdlib.sol, 396 -PUSH S2 -PUSHPOW2DEC 32 -CDATASIZE -DROP -NIP -.loc stdlib.sol, 397 -RSHIFT 3 -.loc stdlib.sol, 398 -OVER -EQINT -1 -PUSHCONT { - .loc stdlib.sol, 399 - PUSH2 S0, S2 - SUB - FITS 256 - POP S2 - .loc stdlib.sol, 0 -} -IF -.loc stdlib.sol, 400 -BLKPUSH 2, 2 -UFITS 256 -ADD -GEQ -THROWIFNOT 70 -.loc stdlib.sol, 401 -UFITS 256 -CALLREF { - CALL $__subCell_macro$ -} -.loc stdlib.sol, 0 - -.globl __subCell -.type __subCell, @function -CALL $__subCell_macro$ - -.macro __subCell_macro -.loc stdlib.sol, 404 -PUSHREF { -} -.loc stdlib.sol, 405 -OVER -PUSHCONT { - .loc stdlib.sol, 406 - BLKDROP2 3, 1 - .loc stdlib.sol, 0 -} -IFNOTJMP -.loc stdlib.sol, 408 -DROP -SWAP -PUSHINT 127 -DIVMOD -.loc stdlib.sol, 409 -ROLL 3 -CTOS -.loc stdlib.sol, 410 -ROT -PUSHCONT { - .loc stdlib.sol, 411 - LDREFRTOS - NIP - .loc stdlib.sol, 0 -} -REPEAT -.loc stdlib.sol, 413 -SWAP -MULCONST 8 -SDSKIPFIRST -.loc stdlib.sol, 414 -TUPLE 0 -.loc stdlib.sol, 415 -NEWC -.loc stdlib.sol, 416 -PUSHCONT { - PUSH S3 - NEQINT 0 -} -PUSHCONT { - .loc stdlib.sol, 417 - OVER2 - SBITS - RSHIFT 3 - PUSH S2 - BREMBITS - RSHIFT 3 - MIN - MIN - .loc stdlib.sol, 418 - PUSH3 S1, S3, S0 - LSHIFT 3 - UFITS 16 - LDSLICEX - POP S6 - STSLICER - POP S2 - .loc stdlib.sol, 419 - PUSH S4 - SUBR - POP S4 - .loc stdlib.sol, 420 - PUSH S3 - PUSHCONT { - .loc stdlib.sol, 421 - PUSH S2 - SBITS - PUSHCONT { - .loc stdlib.sol, 422 - PUSH S2 - LDREFRTOS - NIP - POP S3 - .loc stdlib.sol, 0 - } - IFNOT - .loc stdlib.sol, 424 - DUP - BREMBITS - LESSINT 8 - PUSHCONT { - .loc stdlib.sol, 425 - CALLREF { - XCPU S1, S0 - TLEN - PUSHCONT { - TPOP - DUP - TLEN - PUSHPOW2DEC 8 - SUB - PUSHCONT { - TPUSH - TUPLE 0 - } - IFNOT - } - PUSHCONT { - TUPLE 0 - } - IFELSE - ROT - TPUSH - TPUSH - } - .loc stdlib.sol, 426 - NEWC - .loc stdlib.sol, 0 - } - IF - .loc stdlib.sol, 0 - } - IF - .loc stdlib.sol, 0 -} -WHILE -.loc stdlib.sol, 430 -CALLREF { - CALL $assembleList_macro$ -} -BLKDROP2 2, 1 -.loc stdlib.sol, 0 - -.globl compareLongStrings -.type compareLongStrings, @function -CALL $compareLongStrings_macro$ - -.macro compareLongStrings_macro -.loc stdlib.sol, 437 -SWAP -CTOS -.loc stdlib.sol, 438 -SWAP -CTOS -.loc stdlib.sol, 439 -FALSE ; decl return flag -PUSHCONT { - .loc stdlib.sol, 440 - BLKPUSH 2, 2 - SDLEXCMP - .loc stdlib.sol, 441 - DUP - PUSHCONT { - .loc stdlib.sol, 442 - BLKDROP2 3, 1 - PUSHINT 4 - RETALT - .loc stdlib.sol, 0 - } - IFJMP - .loc stdlib.sol, 444 - DROP - PUSH S2 - SREFS - .loc stdlib.sol, 445 - PUSH S2 - SREFS - .loc stdlib.sol, 446 - DUP2 - GREATER - PUSHCONT { - BLKDROP 5 - PUSHINT 1 - PUSHINT 4 - RETALT - } - IFJMP - .loc stdlib.sol, 448 - PUSH2 S0, S1 - GREATER - PUSHCONT { - BLKDROP 5 - PUSHINT -1 - PUSHINT 4 - RETALT - } - IFJMP - .loc stdlib.sol, 450 - ADD - PUSHCONT { - BLKDROP 3 - PUSHINT 0 - PUSHINT 4 - RETALT - } - IFNOTJMP - .loc stdlib.sol, 452 - PUSH S2 - LDREFRTOS - NIP - POP S3 - .loc stdlib.sol, 453 - OVER - LDREFRTOS - NIP - POP S2 - .loc stdlib.sol, 0 -} -AGAINBRK -IFRET -.loc stdlib.sol, 455 -DROP2 -PUSHINT 0 -.loc stdlib.sol, 0 - -.globl concatenateStrings -.type concatenateStrings, @function -CALL $concatenateStrings_macro$ - -.macro concatenateStrings_macro -.loc stdlib.sol, 459 -SWAP -CALLREF { - CALL $strToList_macro$ -} -.loc stdlib.sol, 460 -ROT -CTOS -.loc stdlib.sol, 461 -BLKPUSH 3, 2 -CALLREF { - CALL $storeStringInBuilders_macro$ -} -POP S3 -POP S3 -.loc stdlib.sol, 462 -PUSHCONT { - DUP - PUSHINT 1 - SCHKREFSQ -} -PUSHCONT { - .loc stdlib.sol, 463 - LDREFRTOS - NIP - .loc stdlib.sol, 464 - BLKPUSH 3, 2 - CALLREF { - CALL $storeStringInBuilders_macro$ - } - POP S3 - POP S3 - .loc stdlib.sol, 0 -} -WHILE -.loc stdlib.sol, 466 -DROP -CALLREF { - CALL $assembleList_macro$ -} -.loc stdlib.sol, 0 - -.globl __strchr -.type __strchr, @function -CALL $__strchr_macro$ - -.macro __strchr_macro -.loc stdlib.sol, 469 -NULL -.loc stdlib.sol, 470 -PUSHINT 0 -.loc stdlib.sol, 471 -ROLL 3 -CTOS -NULL -FALSE ; decl return flag -PUSHCONT { - PUSH S2 - SEMPTY - NOT -} -PUSHCONT { - PUSH S2 - LDUQ 8 - PUSHCONT { - PLDREF - CTOS - LDU 8 - } - IFNOT - POP S4 - POP S2 - .loc stdlib.sol, 472 - PUSH2 S1, S5 - EQUAL - PUSHCONT { - .loc stdlib.sol, 473 - ROLL 3 - BLKDROP2 5, 1 - PUSHINT 4 - RETALT - .loc stdlib.sol, 0 - } - IFJMP - .loc stdlib.sol, 475 - PUSH S3 - INC - POP S4 - .loc stdlib.sol, 0 -} -WHILEBRK -IFRET -BLKDROP 3 -NIP -.loc stdlib.sol, 0 - -.globl __strrchr -.type __strrchr, @function -CALL $__strrchr_macro$ - -.macro __strrchr_macro -.loc stdlib.sol, 479 -NULL -.loc stdlib.sol, 480 -PUSHINT 0 -.loc stdlib.sol, 481 -ROLL 3 -CTOS -NULL -PUSHCONT { - OVER - SEMPTY - NOT -} -PUSHCONT { - OVER - LDUQ 8 - PUSHCONT { - PLDREF - CTOS - LDU 8 - } - IFNOT - POP S3 - NIP - .loc stdlib.sol, 482 - PUSH2 S0, S4 - EQUAL - PUSHCONT { - .loc stdlib.sol, 483 - PUSH S2 - POP S4 - .loc stdlib.sol, 0 - } - IF - .loc stdlib.sol, 485 - PUSH S2 - INC - POP S3 - .loc stdlib.sol, 0 -} -WHILE -BLKDROP 3 -NIP -.loc stdlib.sol, 0 - -.globl __strstr -.type __strstr, @function -CALL $__strstr_macro$ - -.macro __strstr_macro -.loc stdlib.sol, 514 -NULL -.loc stdlib.sol, 515 -PUSH S2 -CTOS -.loc stdlib.sol, 516 -PUSH S2 -CTOS -.loc stdlib.sol, 517 -DUP -PUSHPOW2DEC 32 -SDATASIZE -DROP -NIP -.loc stdlib.sol, 518 -PUSH S2 -PUSHPOW2DEC 32 -SDATASIZE -DROP -NIP -.loc stdlib.sol, 519 -DUP2 -GREATER -PUSHCONT { - .loc stdlib.sol, 520 - ROLL 4 - BLKDROP2 6, 1 - .loc stdlib.sol, 0 -} -IFJMP -.loc stdlib.sol, 522 -OVER -RSHIFT 3 -POP S2 -.loc stdlib.sol, 523 -RSHIFT 3 -.loc stdlib.sol, 524 -PUSH2 S0, S2 -.loc stdlib.sol, 525 -LDU 8 -POP S5 -.loc stdlib.sol, 526 -FALSE ; decl return flag -PUSHCONT { - PUSH2 S3, S4 - GEQ -} -PUSHCONT { - .loc stdlib.sol, 527 - PUSH S6 - LDU 8 - POP S8 - .loc stdlib.sol, 528 - PUSH S7 - SBITREFS - .loc stdlib.sol, 529 - OVER - LESSINT 8 - OVER - GTINT 0 - AND - PUSHCONT { - .loc stdlib.sol, 530 - PUSH S9 - LDREFRTOS - NIP - POP S10 - .loc stdlib.sol, 0 - } - IF - .loc stdlib.sol, 532 - PUSH2 S2, S4 - EQUAL - PUSHCONT { - .loc stdlib.sol, 533 - BLKPUSH 2, 9 - PUSHCONT { - .loc stdlib.sol, 491 - OVER - SBITS - .loc stdlib.sol, 492 - OVER - SBITS - .loc stdlib.sol, 493 - FALSE ; decl return flag - PUSHCONT { - PUSH S2 - NEQINT 0 - PUSH S2 - NEQINT 0 - AND - } - PUSHCONT { - .loc stdlib.sol, 494 - BLKPUSH 2, 2 - MIN - .loc stdlib.sol, 495 - PUSH2 S5, S0 - LDSLICEX - POP S7 - .loc stdlib.sol, 496 - PUSH2 S5, S1 - LDSLICEX - POP S7 - .loc stdlib.sol, 497 - PUSH2 S5, S2 - SUB - POP S6 - .loc stdlib.sol, 498 - ROT - PUSH S4 - SUBR - POP S4 - .loc stdlib.sol, 499 - SDEQ - PUSHCONT { - .loc stdlib.sol, 500 - BLKDROP 5 - FALSE - PUSHINT 4 - RETALT - .loc stdlib.sol, 0 - } - IFNOTJMP - .loc stdlib.sol, 502 - PUSH S2 - EQINT 0 - PUSH S5 - SREFS - NEQINT 0 - AND - PUSHCONT { - .loc stdlib.sol, 503 - PUSH S4 - LDREFRTOS - NIP - POP S5 - .loc stdlib.sol, 504 - PUSH S4 - SBITS - POP S3 - .loc stdlib.sol, 0 - } - IF - .loc stdlib.sol, 506 - OVER - EQINT 0 - PUSH S4 - SREFS - NEQINT 0 - AND - PUSHCONT { - .loc stdlib.sol, 507 - PUSH S3 - LDREFRTOS - NIP - POP S4 - .loc stdlib.sol, 508 - PUSH S3 - SBITS - POP S2 - .loc stdlib.sol, 0 - } - IF - .loc stdlib.sol, 0 - } - WHILEBRK - IFRET - .loc stdlib.sol, 511 - BLKDROP 4 - TRUE - .loc stdlib.sol, 490 - } - CALLX - .loc stdlib.sol, 0 - PUSHCONT { - .loc stdlib.sol, 534 - BLKSWAP 2, 5 - SUBR - UFITS 32 - POP S9 - .loc stdlib.sol, 535 - ROLL 8 - BLKDROP2 10, 1 - PUSHINT 4 - RETALT - .loc stdlib.sol, 0 - } - IFJMP - } - IF - .loc stdlib.sol, 538 - BLKDROP 3 - PUSH S3 - DEC - POP S4 - .loc stdlib.sol, 0 -} -WHILEBRK -IFRET -.loc stdlib.sol, 540 -BLKDROP 6 -BLKDROP2 2, 1 -.loc stdlib.sol, 0 - -.globl __toLowerCase -.type __toLowerCase, @function -CALL $__toLowerCase_macro$ - -.macro __toLowerCase_macro -.loc stdlib.sol, 549 -NEWC -NULL -PAIR -.loc stdlib.sol, 550 -PUSHINT 0 -.loc stdlib.sol, 551 -ROT -CTOS -NULL -PUSHCONT { - OVER - SEMPTY - NOT -} -PUSHCONT { - OVER - LDUQ 8 - PUSHCONT { - PLDREF - CTOS - LDU 8 - } - IFNOT - POP S3 - NIP - .loc stdlib.sol, 552 - BLKPUSH 2, 0 - .loc stdlib.sol, 553 - GTINT 64 - OVER - LESSINT 91 - AND - PUSHCONT { - .loc stdlib.sol, 554 - ADDCONST 32 - .loc stdlib.sol, 0 - } - IF - .loc stdlib.sol, 556 - PUSH2 S4, S4 - FIRST - XCHG S1, S2 - STU 8 - SETINDEX 0 - POP S4 - .loc stdlib.sol, 557 - PUSH S2 - INC - POP S3 - .loc stdlib.sol, 558 - PUSH S2 - EQINT 127 - PUSHCONT { - .loc stdlib.sol, 559 - NEWC - .loc stdlib.sol, 560 - PUSH S4 - PAIR - POP S4 - .loc stdlib.sol, 0 - } - IF - .loc stdlib.sol, 0 -} -WHILE -BLKDROP 3 -.loc stdlib.sol, 563 -DUP -FIRST -.loc stdlib.sol, 564 -PUSHCONT { - OVER - SECOND - ISNULL - NOT -} -PUSHCONT { - .loc stdlib.sol, 565 - OVER - SECOND - DUP - ISNULL - THROWIF 63 - POP S2 - .loc stdlib.sol, 566 - OVER - FIRST - .loc stdlib.sol, 567 - STBREF - .loc stdlib.sol, 0 -} -WHILE -.loc stdlib.sol, 570 -ENDC -NIP -.loc stdlib.sol, 0 - -.globl __toUpperCase -.type __toUpperCase, @function -CALL $__toUpperCase_macro$ - -.macro __toUpperCase_macro -.loc stdlib.sol, 574 -NEWC -NULL -PAIR -.loc stdlib.sol, 575 -PUSHINT 0 -.loc stdlib.sol, 576 -ROT -CTOS -NULL -PUSHCONT { - OVER - SEMPTY - NOT -} -PUSHCONT { - OVER - LDUQ 8 - PUSHCONT { - PLDREF - CTOS - LDU 8 - } - IFNOT - POP S3 - NIP - .loc stdlib.sol, 577 - BLKPUSH 2, 0 - .loc stdlib.sol, 578 - GTINT 96 - OVER - LESSINT 123 - AND - PUSHCONT { - .loc stdlib.sol, 579 - ADDCONST -32 - .loc stdlib.sol, 0 - } - IF - .loc stdlib.sol, 581 - PUSH2 S4, S4 - FIRST - XCHG S1, S2 - STU 8 - SETINDEX 0 - POP S4 - .loc stdlib.sol, 582 - PUSH S2 - INC - POP S3 - .loc stdlib.sol, 583 - PUSH S2 - EQINT 127 - PUSHCONT { - .loc stdlib.sol, 584 - NEWC - .loc stdlib.sol, 585 - PUSH S4 - PAIR - POP S4 - .loc stdlib.sol, 0 - } - IF - .loc stdlib.sol, 0 -} -WHILE -BLKDROP 3 -.loc stdlib.sol, 588 -DUP -FIRST -.loc stdlib.sol, 589 -PUSHCONT { - OVER - SECOND - ISNULL - NOT -} -PUSHCONT { - .loc stdlib.sol, 590 - OVER - SECOND - DUP - ISNULL - THROWIF 63 - POP S2 - .loc stdlib.sol, 591 - OVER - FIRST - .loc stdlib.sol, 592 - STBREF - .loc stdlib.sol, 0 -} -WHILE -.loc stdlib.sol, 595 -ENDC -NIP -.loc stdlib.sol, 0 - -.globl stateInitHash -.type stateInitHash, @function -CALL $stateInitHash_macro$ - -.macro stateInitHash_macro -.loc stdlib.sol, 599 -NEWC -.loc stdlib.sol, 601 -STSLICECONST x020134 -.loc stdlib.sol, 613 -ROT -STUR 16 -.loc stdlib.sol, 614 -STU 16 -.loc stdlib.sol, 616 -ROT -STUR 256 -.loc stdlib.sol, 617 -STU 256 -.loc stdlib.sol, 618 -ENDC -CTOS -SHA256U -.loc stdlib.sol, 0 - diff --git a/compiler/liblangutil/CMakeLists.txt b/compiler/liblangutil/CMakeLists.txt index 31210220..f8a36dd8 100644 --- a/compiler/liblangutil/CMakeLists.txt +++ b/compiler/liblangutil/CMakeLists.txt @@ -25,7 +25,6 @@ set(sources SourceReferenceFormatter.h Token.cpp Token.h - UndefMacros.h UniqueErrorReporter.h TVMVersion.h diff --git a/compiler/liblangutil/CharStream.cpp b/compiler/liblangutil/CharStream.cpp index 6ecd85bf..c6b58ef6 100644 --- a/compiler/liblangutil/CharStream.cpp +++ b/compiler/liblangutil/CharStream.cpp @@ -51,7 +51,6 @@ #include #include -using namespace std; using namespace solidity; using namespace solidity::langutil; @@ -79,21 +78,21 @@ char CharStream::setPosition(size_t _location) return get(); } -string CharStream::lineAtPosition(int _position) const +std::string CharStream::lineAtPosition(int _position) const { // if _position points to \n, it returns the line before the \n - using size_type = string::size_type; - size_type searchStart = min(m_source.size(), size_type(_position)); + using size_type = std::string::size_type; + size_type searchStart = std::min(m_source.size(), size_type(_position)); if (searchStart > 0) searchStart--; size_type lineStart = m_source.rfind('\n', searchStart); - if (lineStart == string::npos) + if (lineStart == std::string::npos) lineStart = 0; else lineStart++; - string line = m_source.substr( + std::string line = m_source.substr( lineStart, - min(m_source.find('\n', lineStart), m_source.size()) - lineStart + std::min(m_source.find('\n', lineStart), m_source.size()) - lineStart ); if (!line.empty() && line.back() == '\r') line.pop_back(); @@ -102,9 +101,9 @@ string CharStream::lineAtPosition(int _position) const LineColumn CharStream::translatePositionToLineColumn(int _position) const { - using size_type = string::size_type; - using diff_type = string::difference_type; - size_type searchPosition = min(m_source.size(), size_type(_position)); + using size_type = std::string::size_type; + using diff_type = std::string::difference_type; + size_type searchPosition = std::min(m_source.size(), size_type(_position)); int lineNumber = static_cast(count(m_source.begin(), m_source.begin() + diff_type(searchPosition), '\n')); size_type lineStart; if (searchPosition == 0) @@ -112,24 +111,24 @@ LineColumn CharStream::translatePositionToLineColumn(int _position) const else { lineStart = m_source.rfind('\n', searchPosition - 1); - lineStart = lineStart == string::npos ? 0 : lineStart + 1; + lineStart = lineStart == std::string::npos ? 0 : lineStart + 1; } return LineColumn{lineNumber, static_cast(searchPosition - lineStart)}; } -string_view CharStream::text(SourceLocation const& _location) const +std::string_view CharStream::text(SourceLocation const& _location) const { if (!_location.hasText()) return {}; solAssert(_location.sourceName && *_location.sourceName == m_name, ""); solAssert(static_cast(_location.end) <= m_source.size(), ""); - return string_view{m_source}.substr( + return std::string_view{m_source}.substr( static_cast(_location.start), static_cast(_location.end - _location.start) ); } -string CharStream::singleLineSnippet(string const& _sourceCode, SourceLocation const& _location) +std::string CharStream::singleLineSnippet(std::string const& _sourceCode, SourceLocation const& _location) { if (!_location.hasText()) return {}; @@ -137,39 +136,39 @@ string CharStream::singleLineSnippet(string const& _sourceCode, SourceLocation c if (static_cast(_location.start) >= _sourceCode.size()) return {}; - string cut = _sourceCode.substr(static_cast(_location.start), static_cast(_location.end - _location.start)); + std::string cut = _sourceCode.substr(static_cast(_location.start), static_cast(_location.end - _location.start)); auto newLinePos = cut.find_first_of("\n\r"); - if (newLinePos != string::npos) + if (newLinePos != std::string::npos) cut = cut.substr(0, newLinePos) + "..."; return cut; } -optional CharStream::translateLineColumnToPosition(LineColumn const& _lineColumn) const +std::optional CharStream::translateLineColumnToPosition(LineColumn const& _lineColumn) const { return translateLineColumnToPosition(m_source, _lineColumn); } -optional CharStream::translateLineColumnToPosition(std::string const& _text, LineColumn const& _input) +std::optional CharStream::translateLineColumnToPosition(std::string const& _text, LineColumn const& _input) { if (_input.line < 0) - return nullopt; + return std::nullopt; size_t offset = 0; for (int i = 0; i < _input.line; i++) { offset = _text.find('\n', offset); if (offset == _text.npos) - return nullopt; + return std::nullopt; offset++; // Skip linefeed. } size_t endOfLine = _text.find('\n', offset); - if (endOfLine == string::npos) + if (endOfLine == std::string::npos) endOfLine = _text.size(); if (offset + static_cast(_input.column) > endOfLine) - return nullopt; + return std::nullopt; return offset + static_cast(_input.column); } diff --git a/compiler/liblangutil/DebugInfoSelection.cpp b/compiler/liblangutil/DebugInfoSelection.cpp index 6ff023c0..88e09cdb 100644 --- a/compiler/liblangutil/DebugInfoSelection.cpp +++ b/compiler/liblangutil/DebugInfoSelection.cpp @@ -30,7 +30,6 @@ #include -using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::util; @@ -50,7 +49,7 @@ DebugInfoSelection const DebugInfoSelection::Only(bool DebugInfoSelection::* _me return result; } -optional DebugInfoSelection::fromString(string_view _input) +std::optional DebugInfoSelection::fromString(std::string_view _input) { // TODO: Make more stuff constexpr and make it a static_assert(). solAssert(componentMap().count("all") == 0, ""); @@ -61,11 +60,11 @@ optional DebugInfoSelection::fromString(string_view _input) if (_input == "none") return None(); - return fromComponents(_input | ranges::views::split(',') | ranges::to>); + return fromComponents(_input | ranges::views::split(',') | ranges::to>); } -optional DebugInfoSelection::fromComponents( - vector const& _componentNames, +std::optional DebugInfoSelection::fromComponents( + std::vector const& _componentNames, bool _acceptWildcards ) { @@ -75,16 +74,16 @@ optional DebugInfoSelection::fromComponents( for (auto const& component: _componentNames) { if (component == "*") - return (_acceptWildcards ? make_optional(DebugInfoSelection::All()) : nullopt); + return (_acceptWildcards ? std::make_optional(DebugInfoSelection::All()) : std::nullopt); if (!selection.enable(component)) - return nullopt; + return std::nullopt; } return selection; } -bool DebugInfoSelection::enable(string _component) +bool DebugInfoSelection::enable(std::string _component) { auto memberIt = componentMap().find(boost::trim_copy(_component)); if (memberIt == componentMap().end()) @@ -146,9 +145,9 @@ bool DebugInfoSelection::operator==(DebugInfoSelection const& _other) const noex return true; } -ostream& langutil::operator<<(ostream& _stream, DebugInfoSelection const& _selection) +std::ostream& langutil::operator<<(std::ostream& _stream, DebugInfoSelection const& _selection) { - vector selectedComponentNames; + std::vector selectedComponentNames; for (auto const& [name, member]: _selection.componentMap()) if (_selection.*member) selectedComponentNames.push_back(name); diff --git a/compiler/liblangutil/EVMVersion.cpp b/compiler/liblangutil/EVMVersion.cpp new file mode 100644 index 00000000..438932a1 --- /dev/null +++ b/compiler/liblangutil/EVMVersion.cpp @@ -0,0 +1,64 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * EVM versioning. + */ + +#include +#include + +using namespace solidity; +using namespace solidity::evmasm; +using namespace solidity::langutil; + +bool EVMVersion::hasOpcode(Instruction _opcode) const +{ + switch (_opcode) + { + case Instruction::RETURNDATACOPY: + case Instruction::RETURNDATASIZE: + return supportsReturndata(); + case Instruction::STATICCALL: + return hasStaticCall(); + case Instruction::SHL: + case Instruction::SHR: + case Instruction::SAR: + return hasBitwiseShifting(); + case Instruction::CREATE2: + return hasCreate2(); + case Instruction::EXTCODEHASH: + return hasExtCodeHash(); + case Instruction::CHAINID: + return hasChainID(); + case Instruction::SELFBALANCE: + return hasSelfBalance(); + case Instruction::BASEFEE: + return hasBaseFee(); + case Instruction::BLOBHASH: + return hasBlobHash(); + case Instruction::BLOBBASEFEE: + return hasBlobBaseFee(); + case Instruction::MCOPY: + return hasMcopy(); + case Instruction::TSTORE: + case Instruction::TLOAD: + return supportsTransientStorage(); + default: + return true; + } +} diff --git a/compiler/liblangutil/EVMVersion.h b/compiler/liblangutil/EVMVersion.h index 3b36d39c..4887159a 100644 --- a/compiler/liblangutil/EVMVersion.h +++ b/compiler/liblangutil/EVMVersion.h @@ -21,12 +21,19 @@ #pragma once +#include #include #include #include +namespace solidity::evmasm +{ +/// Virtual machine bytecode instruction. Forward declared from libevmasm/Instruction.h +enum class Instruction: uint8_t; +} + namespace solidity::langutil { @@ -50,10 +57,13 @@ class EVMVersion: static EVMVersion istanbul() { return {Version::Istanbul}; } static EVMVersion berlin() { return {Version::Berlin}; } static EVMVersion london() { return {Version::London}; } + static EVMVersion paris() { return {Version::Paris}; } + static EVMVersion shanghai() { return {Version::Shanghai}; } + static EVMVersion cancun() { return {Version::Cancun}; } static std::optional fromString(std::string const& _version) { - for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium(), constantinople(), petersburg(), istanbul(), berlin(), london()}) + for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium(), constantinople(), petersburg(), istanbul(), berlin(), london(), paris(), shanghai(), cancun()}) if (_version == v.name()) return v; return std::nullopt; @@ -75,6 +85,9 @@ class EVMVersion: case Version::Istanbul: return "istanbul"; case Version::Berlin: return "berlin"; case Version::London: return "london"; + case Version::Paris: return "paris"; + case Version::Shanghai: return "shanghai"; + case Version::Cancun: return "cancun"; } return "INVALID"; } @@ -88,17 +101,23 @@ class EVMVersion: bool hasChainID() const { return *this >= istanbul(); } bool hasSelfBalance() const { return *this >= istanbul(); } bool hasBaseFee() const { return *this >= london(); } + bool hasBlobBaseFee() const { return *this >= cancun(); } + bool hasPrevRandao() const { return *this >= paris(); } + bool hasPush0() const { return *this >= shanghai(); } + bool hasBlobHash() const { return *this >= cancun(); } + bool hasMcopy() const { return *this >= cancun(); } + bool supportsTransientStorage() const { return *this >= cancun(); } /// Whether we have to retain the costs for the call opcode itself (false), /// or whether we can just forward easily all remaining gas (true). bool canOverchargeGasForCall() const { return *this >= tangerineWhistle(); } private: - enum class Version { Homestead, TangerineWhistle, SpuriousDragon, Byzantium, Constantinople, Petersburg, Istanbul, Berlin, London }; + enum class Version { Homestead, TangerineWhistle, SpuriousDragon, Byzantium, Constantinople, Petersburg, Istanbul, Berlin, London, Paris, Shanghai, Cancun }; EVMVersion(Version _version): m_version(_version) {} - Version m_version = Version::London; + Version m_version = Version::Shanghai; }; } diff --git a/compiler/liblangutil/ErrorReporter.cpp b/compiler/liblangutil/ErrorReporter.cpp index f99247b3..298e4f63 100644 --- a/compiler/liblangutil/ErrorReporter.cpp +++ b/compiler/liblangutil/ErrorReporter.cpp @@ -25,7 +25,6 @@ #include #include -using namespace std; using namespace solidity; using namespace solidity::langutil; @@ -37,7 +36,7 @@ ErrorReporter& ErrorReporter::operator=(ErrorReporter const& _errorReporter) return *this; } -void ErrorReporter::warning(ErrorId _error, string const& _description) +void ErrorReporter::warning(ErrorId _error, std::string const& _description) { error(_error, Error::Type::Warning, SourceLocation(), _description); } @@ -45,7 +44,7 @@ void ErrorReporter::warning(ErrorId _error, string const& _description) void ErrorReporter::warning( ErrorId _error, SourceLocation const& _location, - string const& _description + std::string const& _description ) { error(_error, Error::Type::Warning, _location, _description); @@ -54,27 +53,27 @@ void ErrorReporter::warning( void ErrorReporter::warning( ErrorId _error, SourceLocation const& _location, - string const& _description, + std::string const& _description, SecondarySourceLocation const& _secondaryLocation ) { error(_error, Error::Type::Warning, _location, _secondaryLocation, _description); } -void ErrorReporter::error(ErrorId _errorId, Error::Type _type, SourceLocation const& _location, string const& _description) +void ErrorReporter::error(ErrorId _errorId, Error::Type _type, SourceLocation const& _location, std::string const& _description) { if (checkForExcessiveErrors(_type)) return; - m_errorList.push_back(make_shared(_errorId, _type, _description, _location)); + m_errorList.push_back(std::make_shared(_errorId, _type, _description, _location)); } -void ErrorReporter::error(ErrorId _errorId, Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description) +void ErrorReporter::error(ErrorId _errorId, Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, std::string const& _description) { if (checkForExcessiveErrors(_type)) return; - m_errorList.push_back(make_shared(_errorId, _type, _description, _location, _secondaryLocation)); + m_errorList.push_back(std::make_shared(_errorId, _type, _description, _location, _secondaryLocation)); } bool ErrorReporter::hasExcessiveErrors() const @@ -89,7 +88,7 @@ bool ErrorReporter::checkForExcessiveErrors(Error::Type _type) m_warningCount++; if (m_warningCount == c_maxWarningsAllowed) - m_errorList.push_back(make_shared(4591_error, Error::Type::Warning, "There are more than 256 warnings. Ignoring the rest.")); + m_errorList.push_back(std::make_shared(4591_error, Error::Type::Warning, "There are more than 256 warnings. Ignoring the rest.")); if (m_warningCount >= c_maxWarningsAllowed) return true; @@ -99,7 +98,7 @@ bool ErrorReporter::checkForExcessiveErrors(Error::Type _type) m_infoCount++; if (m_infoCount == c_maxInfosAllowed) - m_errorList.push_back(make_shared(2833_error, Error::Type::Info, "There are more than 256 infos. Ignoring the rest.")); + m_errorList.push_back(std::make_shared(2833_error, Error::Type::Info, "There are more than 256 infos. Ignoring the rest.")); if (m_infoCount >= c_maxInfosAllowed) return true; @@ -110,7 +109,7 @@ bool ErrorReporter::checkForExcessiveErrors(Error::Type _type) if (m_errorCount > c_maxErrorsAllowed) { - m_errorList.push_back(make_shared(4013_error, Error::Type::Warning, "There are more than 256 errors. Aborting.")); + m_errorList.push_back(std::make_shared(4013_error, Error::Type::Warning, "There are more than 256 errors. Aborting.")); BOOST_THROW_EXCEPTION(FatalError()); } } @@ -118,13 +117,13 @@ bool ErrorReporter::checkForExcessiveErrors(Error::Type _type) return false; } -void ErrorReporter::fatalError(ErrorId _error, Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description) +void ErrorReporter::fatalError(ErrorId _error, Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, std::string const& _description) { error(_error, _type, _location, _secondaryLocation, _description); BOOST_THROW_EXCEPTION(FatalError()); } -void ErrorReporter::fatalError(ErrorId _error, Error::Type _type, SourceLocation const& _location, string const& _description) +void ErrorReporter::fatalError(ErrorId _error, Error::Type _type, SourceLocation const& _location, std::string const& _description) { error(_error, _type, _location, _description); BOOST_THROW_EXCEPTION(FatalError()); @@ -140,7 +139,7 @@ void ErrorReporter::clear() m_errorList.clear(); } -void ErrorReporter::declarationError(ErrorId _error, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description) +void ErrorReporter::declarationError(ErrorId _error, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, std::string const& _description) { error( _error, @@ -151,7 +150,7 @@ void ErrorReporter::declarationError(ErrorId _error, SourceLocation const& _loca ); } -void ErrorReporter::declarationError(ErrorId _error, SourceLocation const& _location, string const& _description) +void ErrorReporter::declarationError(ErrorId _error, SourceLocation const& _location, std::string const& _description) { error( _error, @@ -170,7 +169,7 @@ void ErrorReporter::fatalDeclarationError(ErrorId _error, SourceLocation const& _description); } -void ErrorReporter::parserError(ErrorId _error, SourceLocation const& _location, string const& _description) +void ErrorReporter::parserError(ErrorId _error, SourceLocation const& _location, std::string const& _description) { error( _error, @@ -180,7 +179,7 @@ void ErrorReporter::parserError(ErrorId _error, SourceLocation const& _location, ); } -void ErrorReporter::fatalParserError(ErrorId _error, SourceLocation const& _location, string const& _description) +void ErrorReporter::fatalParserError(ErrorId _error, SourceLocation const& _location, std::string const& _description) { fatalError( _error, @@ -190,7 +189,7 @@ void ErrorReporter::fatalParserError(ErrorId _error, SourceLocation const& _loca ); } -void ErrorReporter::syntaxError(ErrorId _error, SourceLocation const& _location, string const& _description) +void ErrorReporter::syntaxError(ErrorId _error, SourceLocation const& _location, std::string const& _description) { error( _error, @@ -200,7 +199,7 @@ void ErrorReporter::syntaxError(ErrorId _error, SourceLocation const& _location, ); } -void ErrorReporter::typeError(ErrorId _error, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description) +void ErrorReporter::typeError(ErrorId _error, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, std::string const& _description) { error( _error, @@ -211,7 +210,7 @@ void ErrorReporter::typeError(ErrorId _error, SourceLocation const& _location, S ); } -void ErrorReporter::typeError(ErrorId _error, SourceLocation const& _location, string const& _description) +void ErrorReporter::typeError(ErrorId _error, SourceLocation const& _location, std::string const& _description) { error( _error, @@ -222,7 +221,7 @@ void ErrorReporter::typeError(ErrorId _error, SourceLocation const& _location, s } -void ErrorReporter::fatalTypeError(ErrorId _error, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description) +void ErrorReporter::fatalTypeError(ErrorId _error, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, std::string const& _description) { fatalError( _error, @@ -233,7 +232,7 @@ void ErrorReporter::fatalTypeError(ErrorId _error, SourceLocation const& _locati ); } -void ErrorReporter::fatalTypeError(ErrorId _error, SourceLocation const& _location, string const& _description) +void ErrorReporter::fatalTypeError(ErrorId _error, SourceLocation const& _location, std::string const& _description) { fatalError( _error, @@ -243,7 +242,7 @@ void ErrorReporter::fatalTypeError(ErrorId _error, SourceLocation const& _locati ); } -void ErrorReporter::docstringParsingError(ErrorId _error, SourceLocation const& _location, string const& _description) +void ErrorReporter::docstringParsingError(ErrorId _error, SourceLocation const& _location, std::string const& _description) { error( _error, @@ -256,13 +255,13 @@ void ErrorReporter::docstringParsingError(ErrorId _error, SourceLocation const& void ErrorReporter::info( ErrorId _error, SourceLocation const& _location, - string const& _description + std::string const& _description ) { error(_error, Error::Type::Info, _location, _description); } -void ErrorReporter::info(ErrorId _error, string const& _description) +void ErrorReporter::info(ErrorId _error, std::string const& _description) { error(_error, Error::Type::Info, SourceLocation(), _description); } diff --git a/compiler/liblangutil/ErrorReporter.h b/compiler/liblangutil/ErrorReporter.h index 67fc76e8..6901e87b 100644 --- a/compiler/liblangutil/ErrorReporter.h +++ b/compiler/liblangutil/ErrorReporter.h @@ -30,7 +30,8 @@ #include #include -#include +#include +#include namespace solidity::langutil { @@ -106,9 +107,8 @@ class ErrorReporter std::initializer_list const descs = { _descriptions... }; solAssert(descs.size() > 0, "Need error descriptions!"); - auto filterEmpty = boost::adaptors::filtered([](std::string const& _s) { return !_s.empty(); }); - - std::string errorStr = util::joinHumanReadable(descs | filterEmpty, " "); + auto nonEmpty = [](std::string const& _s) { return !_s.empty(); }; + std::string errorStr = util::joinHumanReadable(descs | ranges::views::filter(nonEmpty) | ranges::to_vector, " "); error(_error, Error::Type::TypeError, _location, errorStr); } diff --git a/compiler/liblangutil/Exceptions.cpp b/compiler/liblangutil/Exceptions.cpp index 2595c775..108acd12 100644 --- a/compiler/liblangutil/Exceptions.cpp +++ b/compiler/liblangutil/Exceptions.cpp @@ -26,10 +26,29 @@ #include #include -using namespace std; using namespace solidity; using namespace solidity::langutil; +std::map const Error::m_errorTypeNames = { + {Error::Type::IOError, "IOError"}, + {Error::Type::FatalError, "FatalError"}, + {Error::Type::JSONError, "JSONError"}, + {Error::Type::InternalCompilerError, "InternalCompilerError"}, + {Error::Type::CompilerError, "CompilerError"}, + {Error::Type::Exception, "Exception"}, + {Error::Type::CodeGenerationError, "CodeGenerationError"}, + {Error::Type::DeclarationError, "DeclarationError"}, + {Error::Type::DocstringParsingError, "DocstringParsingError"}, + {Error::Type::ParserError, "ParserError"}, + {Error::Type::SyntaxError, "SyntaxError"}, + {Error::Type::TypeError, "TypeError"}, + {Error::Type::UnimplementedFeatureError, "UnimplementedFeatureError"}, + {Error::Type::YulException, "YulException"}, + {Error::Type::SMTLogicException, "SMTLogicException"}, + {Error::Type::Warning, "Warning"}, + {Error::Type::Info, "Info"}, +}; + Error::Error( ErrorId _errorId, Error::Type _type, std::string const& _description, @@ -39,34 +58,6 @@ Error::Error( m_errorId(_errorId), m_type(_type) { - switch (m_type) - { - case Type::CodeGenerationError: - m_typeName = "CodeGenerationError"; - break; - case Type::DeclarationError: - m_typeName = "DeclarationError"; - break; - case Type::DocstringParsingError: - m_typeName = "DocstringParsingError"; - break; - case Type::Info: - m_typeName = "Info"; - break; - case Type::ParserError: - m_typeName = "ParserError"; - break; - case Type::SyntaxError: - m_typeName = "SyntaxError"; - break; - case Type::TypeError: - m_typeName = "TypeError"; - break; - case Type::Warning: - m_typeName = "Warning"; - break; - } - if (_location.isValid()) *this << errinfo_sourceLocation(_location); if (!_secondaryLocation.infos.empty()) @@ -84,15 +75,3 @@ SecondarySourceLocation const* Error::secondarySourceLocation() const noexcept { return boost::get_error_info(*this); } - -optional Error::severityFromString(string _input) -{ - boost::algorithm::to_lower(_input); - boost::algorithm::trim(_input); - - for (Severity severity: {Severity::Error, Severity::Warning, Severity::Info}) - if (_input == formatErrorSeverityLowercase(severity)) - return severity; - - return nullopt; -} diff --git a/compiler/liblangutil/Exceptions.h b/compiler/liblangutil/Exceptions.h index fb6f23cf..b0701f99 100644 --- a/compiler/liblangutil/Exceptions.h +++ b/compiler/liblangutil/Exceptions.h @@ -31,11 +31,13 @@ #include #include #include +#include +#include #include #include +#include #include -#include namespace solidity::langutil { @@ -168,21 +170,31 @@ class Error: virtual public util::Exception public: enum class Type { + Info, + Warning, CodeGenerationError, DeclarationError, DocstringParsingError, ParserError, TypeError, SyntaxError, - Warning, - Info + IOError, + FatalError, + JSONError, + InternalCompilerError, + CompilerError, + Exception, + UnimplementedFeatureError, + YulException, + SMTLogicException, }; enum class Severity { - Error, + // NOTE: We rely on these being ordered from least to most severe. + Info, Warning, - Info + Error, }; Error( @@ -195,7 +207,7 @@ class Error: virtual public util::Exception ErrorId errorId() const { return m_errorId; } Type type() const { return m_type; } - std::string const& typeName() const { return m_typeName; } + Severity severity() const { return errorSeverity(m_type); } SourceLocation const* sourceLocation() const noexcept; SecondarySourceLocation const* secondarySourceLocation() const noexcept; @@ -211,11 +223,19 @@ class Error: virtual public util::Exception static constexpr Severity errorSeverity(Type _type) { - if (_type == Type::Info) - return Severity::Info; - if (_type == Type::Warning) - return Severity::Warning; - return Severity::Error; + switch (_type) + { + case Type::Info: return Severity::Info; + case Type::Warning: return Severity::Warning; + default: return Severity::Error; + } + } + + static constexpr Severity errorSeverityOrType(std::variant _typeOrSeverity) + { + if (std::holds_alternative(_typeOrSeverity)) + return errorSeverity(std::get(_typeOrSeverity)); + return std::get(_typeOrSeverity); } static bool isError(Severity _severity) @@ -237,36 +257,50 @@ class Error: virtual public util::Exception } static std::string formatErrorSeverity(Severity _severity) - { - if (_severity == Severity::Info) - return "Info"; - if (_severity == Severity::Warning) - return "Warning"; - solAssert(isError(_severity), ""); - return "Error"; - } - - static std::string formatErrorSeverityLowercase(Severity _severity) { switch (_severity) { - case Severity::Info: - return "info"; - case Severity::Warning: - return "warning"; - case Severity::Error: - solAssert(isError(_severity), ""); - return "error"; + case Severity::Info: return "Info"; + case Severity::Warning: return "Warning"; + case Severity::Error: return "Error"; } - solAssert(false, ""); + util::unreachable(); + } + + static std::string formatErrorType(Type _type) + { + return m_errorTypeNames.at(_type); + } + + static std::optional parseErrorType(std::string _name) + { + static std::map const m_errorTypesByName = util::invertMap(m_errorTypeNames); + + if (m_errorTypesByName.count(_name) == 0) + return std::nullopt; + + return m_errorTypesByName.at(_name); } - static std::optional severityFromString(std::string _input); + static std::string formatTypeOrSeverity(std::variant _typeOrSeverity) + { + if (std::holds_alternative(_typeOrSeverity)) + return formatErrorType(std::get(_typeOrSeverity)); + return formatErrorSeverity(std::get(_typeOrSeverity)); + } + + static std::string formatErrorSeverityLowercase(Severity _severity) + { + std::string severityValue = formatErrorSeverity(_severity); + boost::algorithm::to_lower(severityValue); + return severityValue; + } private: ErrorId m_errorId; Type m_type; - std::string m_typeName; + + static std::map const m_errorTypeNames; }; } diff --git a/compiler/liblangutil/ParserBase.cpp b/compiler/liblangutil/ParserBase.cpp index 91f67861..fde0f27e 100644 --- a/compiler/liblangutil/ParserBase.cpp +++ b/compiler/liblangutil/ParserBase.cpp @@ -25,7 +25,6 @@ #include #include -using namespace std; using namespace solidity; using namespace solidity::langutil; @@ -44,7 +43,7 @@ Token ParserBase::peekNextToken() const return m_scanner->peekNextToken(); } -string ParserBase::currentLiteral() const +std::string ParserBase::currentLiteral() const { return m_scanner->currentLiteral(); } @@ -54,7 +53,7 @@ Token ParserBase::advance() return m_scanner->next(); } -string ParserBase::tokenName(Token _token) +std::string ParserBase::tokenName(Token _token) { if (_token == Token::Identifier) return "identifier"; @@ -75,56 +74,10 @@ void ParserBase::expectToken(Token _value, bool _advance) { Token tok = m_scanner->currentToken(); if (tok != _value) - { - string const expectedToken = ParserBase::tokenName(_value); - if (m_parserErrorRecovery) - parserError(6635_error, "Expected " + expectedToken + " but got " + tokenName(tok)); - else - fatalParserError(2314_error, "Expected " + expectedToken + " but got " + tokenName(tok)); - // Do not advance so that recovery can sync or make use of the current token. - // This is especially useful if the expected token - // is the only one that is missing and is at the end of a construct. - // "{ ... ; }" is such an example. - // ^ - _advance = false; - } - if (_advance) - advance(); -} - -void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentNodeName, bool _advance) -{ - solAssert(m_inParserRecovery, "The function is supposed to be called during parser recovery only."); - - Token tok = m_scanner->currentToken(); - if (tok != _value) - { - SourceLocation errorLoc = currentLocation(); - int startPosition = errorLoc.start; - while (m_scanner->currentToken() != _value && m_scanner->currentToken() != Token::EOS) - advance(); - - string const expectedToken = ParserBase::tokenName(_value); - if (m_scanner->currentToken() == Token::EOS) - { - // rollback to where the token started, and raise exception to be caught at a higher level. - m_scanner->setPosition(static_cast(startPosition)); - string const msg = "In " + _currentNodeName + ", " + expectedToken + "is expected; got " + ParserBase::tokenName(tok) + " instead."; - fatalParserError(1957_error, errorLoc, msg); - } - else - { - parserWarning(3796_error, "Recovered in " + _currentNodeName + " at " + expectedToken + "."); - m_inParserRecovery = false; - } - } - else - { - string expectedToken = ParserBase::tokenName(_value); - parserWarning(3347_error, "Recovered in " + _currentNodeName + " at " + expectedToken + "."); - m_inParserRecovery = false; - } - + fatalParserError( + 2314_error, + "Expected " + ParserBase::tokenName(_value) + " but got " + tokenName(tok) + ); if (_advance) advance(); } @@ -142,32 +95,32 @@ void ParserBase::decreaseRecursionDepth() m_recursionDepth--; } -void ParserBase::parserWarning(ErrorId _error, string const& _description) +void ParserBase::parserWarning(ErrorId _error, std::string const& _description) { m_errorReporter.warning(_error, currentLocation(), _description); } -void ParserBase::parserWarning(ErrorId _error, SourceLocation const& _location, string const& _description) +void ParserBase::parserWarning(ErrorId _error, SourceLocation const& _location, std::string const& _description) { m_errorReporter.warning(_error, _location, _description); } -void ParserBase::parserError(ErrorId _error, SourceLocation const& _location, string const& _description) +void ParserBase::parserError(ErrorId _error, SourceLocation const& _location, std::string const& _description) { m_errorReporter.parserError(_error, _location, _description); } -void ParserBase::parserError(ErrorId _error, string const& _description) +void ParserBase::parserError(ErrorId _error, std::string const& _description) { parserError(_error, currentLocation(), _description); } -void ParserBase::fatalParserError(ErrorId _error, string const& _description) +void ParserBase::fatalParserError(ErrorId _error, std::string const& _description) { fatalParserError(_error, currentLocation(), _description); } -void ParserBase::fatalParserError(ErrorId _error, SourceLocation const& _location, string const& _description) +void ParserBase::fatalParserError(ErrorId _error, SourceLocation const& _location, std::string const& _description) { m_errorReporter.fatalParserError(_error, _location, _description); } diff --git a/compiler/liblangutil/ParserBase.h b/compiler/liblangutil/ParserBase.h index f72400f6..88fc76a9 100644 --- a/compiler/liblangutil/ParserBase.h +++ b/compiler/liblangutil/ParserBase.h @@ -38,14 +38,9 @@ struct ErrorId; class ParserBase { public: - /// Set @a _parserErrorRecovery to true for additional error - /// recovery. This is experimental and intended for use - /// by front-end tools that need partial AST information even - /// when errors occur. - explicit ParserBase(ErrorReporter& errorReporter, bool _parserErrorRecovery = false): m_errorReporter(errorReporter) - { - m_parserErrorRecovery = _parserErrorRecovery; - } + explicit ParserBase(ErrorReporter& errorReporter): + m_errorReporter(errorReporter) + {} virtual ~ParserBase() = default; @@ -70,13 +65,9 @@ class ParserBase ///@{ ///@name Helper functions /// If current token value is not @a _value, throw exception otherwise advance token - // @a if _advance is true and error recovery is in effect. + // if @a _advance is true void expectToken(Token _value, bool _advance = true); - /// Like expectToken but if there is an error ignores tokens until - /// the expected token or EOS is seen. If EOS is encountered, back up to the error point, - /// and throw an exception so that a higher grammar rule has an opportunity to recover. - void expectTokenOrConsumeUntil(Token _value, std::string const& _currentNodeName, bool _advance = true); Token currentToken() const; Token peekNextToken() const; std::string tokenName(Token _token); @@ -108,10 +99,6 @@ class ParserBase ErrorReporter& m_errorReporter; /// Current recursion depth during parsing. size_t m_recursionDepth = 0; - /// True if we are in parser error recovery. Usually this means we are scanning for - /// a synchronization token like ';', or '}'. We use this to reduce cascaded error messages. - bool m_inParserRecovery = false; - bool m_parserErrorRecovery = false; }; } diff --git a/compiler/liblangutil/Scanner.cpp b/compiler/liblangutil/Scanner.cpp index 3602073b..806efa31 100644 --- a/compiler/liblangutil/Scanner.cpp +++ b/compiler/liblangutil/Scanner.cpp @@ -61,12 +61,11 @@ #include #include -using namespace std; namespace solidity::langutil { -string to_string(ScannerError _errorCode) +std::string to_string(ScannerError _errorCode) { switch (_errorCode) { @@ -92,7 +91,7 @@ string to_string(ScannerError _errorCode) } -ostream& operator<<(ostream& os, ScannerError _errorCode) +std::ostream& operator<<(std::ostream& os, ScannerError _errorCode) { return os << to_string(_errorCode); } @@ -275,12 +274,12 @@ namespace /// to the user. static ScannerError validateBiDiMarkup(CharStream& _stream, size_t _startPosition) { - static array, 5> constexpr directionalSequences{ - pair{"\xE2\x80\xAD", 1}, // U+202D (LRO - Left-to-Right Override) - pair{"\xE2\x80\xAE", 1}, // U+202E (RLO - Right-to-Left Override) - pair{"\xE2\x80\xAA", 1}, // U+202A (LRE - Left-to-Right Embedding) - pair{"\xE2\x80\xAB", 1}, // U+202B (RLE - Right-to-Left Embedding) - pair{"\xE2\x80\xAC", -1} // U+202C (PDF - Pop Directional Formatting + static std::array, 5> constexpr directionalSequences{ + std::pair{"\xE2\x80\xAD", 1}, // U+202D (LRO - Left-to-Right Override) + std::pair{"\xE2\x80\xAE", 1}, // U+202E (RLO - Right-to-Left Override) + std::pair{"\xE2\x80\xAA", 1}, // U+202A (LRE - Left-to-Right Embedding) + std::pair{"\xE2\x80\xAB", 1}, // U+202B (RLE - Right-to-Left Embedding) + std::pair{"\xE2\x80\xAC", -1} // U+202C (PDF - Pop Directional Formatting }; size_t endPosition = _stream.position(); @@ -540,8 +539,8 @@ void Scanner::scanToken() Token token; // M and N are for the purposes of grabbing different type sizes - unsigned m{}; - unsigned n{}; + unsigned m = 0; + unsigned n = 0; do { // Remember the position of the next token @@ -666,7 +665,7 @@ void Scanner::scanToken() case '.': // . Number advance(); - if (isDecimalDigit(m_char)) + if (m_kind != ScannerKind::ExperimentalSolidity && isDecimalDigit(m_char)) token = scanNumber('.'); else token = Token::Period; @@ -712,7 +711,7 @@ void Scanner::scanToken() default: if (isIdentifierStart(m_char)) { - tie(token, m, n) = scanIdentifierOrKeyword(); + std::tie(token, m, n) = scanIdentifierOrKeyword(); // Special case for hexadecimal literals if (token == Token::Hex) @@ -757,7 +756,7 @@ void Scanner::scanToken() m_tokens[NextNext].location.end = static_cast(sourcePos()); m_tokens[NextNext].location.sourceName = m_sourceName; m_tokens[NextNext].token = token; - m_tokens[NextNext].extendedTokenInfo = make_tuple(m, n); + m_tokens[NextNext].extendedTokenInfo = std::make_tuple(m, n); } bool Scanner::scanEscape() @@ -1011,7 +1010,7 @@ Token Scanner::scanNumber(char _charSeen) return Token::Number; } -tuple Scanner::scanIdentifierOrKeyword() +std::tuple Scanner::scanIdentifierOrKeyword() { solAssert(isIdentifierStart(m_char), ""); LiteralScope literal(this, LITERAL_TYPE_STRING); @@ -1020,15 +1019,28 @@ tuple Scanner::scanIdentifierOrKeyword() while (isIdentifierPart(m_char) || (m_char == '.' && m_kind == ScannerKind::Yul)) addLiteralCharAndAdvance(); literal.complete(); + auto const token = TokenTraits::fromIdentifierOrKeyword(m_tokens[NextNext].literal); - if (m_kind == ScannerKind::Yul) + switch (m_kind) { + case ScannerKind::Solidity: + // Turn experimental Solidity keywords that are not keywords in legacy Solidity into identifiers. + if (TokenTraits::isExperimentalSolidityOnlyKeyword(std::get<0>(token))) + return std::make_tuple(Token::Identifier, 0, 0); + break; + case ScannerKind::Yul: // Turn Solidity identifier into a Yul keyword if (m_tokens[NextNext].literal == "leave") return std::make_tuple(Token::Leave, 0, 0); // Turn non-Yul keywords into identifiers. if (!TokenTraits::isYulKeyword(std::get<0>(token))) return std::make_tuple(Token::Identifier, 0, 0); + break; + case ScannerKind::ExperimentalSolidity: + // Turn legacy Solidity keywords that are not keywords in experimental Solidity into identifiers. + if (!TokenTraits::isExperimentalSolidityKeyword(std::get<0>(token))) + return std::make_tuple(Token::Identifier, 0, 0); + break; } return token; } diff --git a/compiler/liblangutil/Scanner.h b/compiler/liblangutil/Scanner.h index c45a2ec2..eaa2b3b5 100644 --- a/compiler/liblangutil/Scanner.h +++ b/compiler/liblangutil/Scanner.h @@ -69,7 +69,8 @@ class ParserRecorder; enum class ScannerKind { Solidity, - Yul + Yul, + ExperimentalSolidity }; enum class ScannerError diff --git a/compiler/liblangutil/SemVerHandler.cpp b/compiler/liblangutil/SemVerHandler.cpp index ceb562ae..2358eec5 100644 --- a/compiler/liblangutil/SemVerHandler.cpp +++ b/compiler/liblangutil/SemVerHandler.cpp @@ -27,18 +27,20 @@ #include #include +#include -using namespace std; +using namespace std::string_literals; using namespace solidity; using namespace solidity::langutil; +using namespace solidity::util; -SemVerMatchExpressionParser::SemVerMatchExpressionParser(vector _tokens, vector _literals): +SemVerMatchExpressionParser::SemVerMatchExpressionParser(std::vector _tokens, std::vector _literals): m_tokens(std::move(_tokens)), m_literals(std::move(_literals)) { solAssert(m_tokens.size() == m_literals.size(), ""); } -SemVerVersion::SemVerVersion(string const& _versionString) +SemVerVersion::SemVerVersion(std::string const& _versionString) { auto i = _versionString.begin(); auto end = _versionString.end(); @@ -52,7 +54,7 @@ SemVerVersion::SemVerVersion(string const& _versionString) if (level < 2) { if (i == end || *i != '.') - BOOST_THROW_EXCEPTION(SemVerError()); + solThrow(SemVerError, "Invalid versionString: "s + _versionString); else ++i; } @@ -61,16 +63,16 @@ SemVerVersion::SemVerVersion(string const& _versionString) { auto prereleaseStart = ++i; while (i != end && *i != '+') ++i; - prerelease = string(prereleaseStart, i); + prerelease = std::string(prereleaseStart, i); } if (i != end && *i == '+') { auto buildStart = ++i; while (i != end) ++i; - build = string(buildStart, i); + build = std::string(buildStart, i); } if (i != end) - BOOST_THROW_EXCEPTION(SemVerError()); + solThrow(SemVerError, "Invalid versionString "s + _versionString); } bool SemVerMatchExpression::MatchComponent::matches(SemVerVersion const& _version) const @@ -156,12 +158,12 @@ bool SemVerMatchExpression::matches(SemVerVersion const& _version) const return false; } -optional SemVerMatchExpressionParser::parse() +SemVerMatchExpression SemVerMatchExpressionParser::parse() { reset(); if (m_tokens.empty()) - return nullopt; + solThrow(SemVerError, "Empty version pragma."); try { @@ -171,14 +173,19 @@ optional SemVerMatchExpressionParser::parse() if (m_pos >= m_tokens.size()) break; if (currentToken() != Token::Or) - BOOST_THROW_EXCEPTION(SemVerError()); + { + solThrow( + SemVerError, + "You can only combine version ranges using the || operator." + ); + } nextToken(); } } - catch (SemVerError const&) + catch (SemVerError const& e) { reset(); - return nullopt; + throw e; } return m_expression; @@ -265,14 +272,22 @@ unsigned SemVerMatchExpressionParser::parseVersionPart() { c = currentChar(); if (v * 10 < v || v * 10 + static_cast(c - '0') < v * 10) - BOOST_THROW_EXCEPTION(SemVerError()); + solThrow(SemVerError, "Integer too large to be used in a version number."); v = v * 10 + static_cast(c - '0'); nextChar(); } return v; } + else if (c == char(-1)) + solThrow(SemVerError, "Expected version number but reached end of pragma."); else - BOOST_THROW_EXCEPTION(SemVerError()); + solThrow( + SemVerError, fmt::format( + "Expected the start of a version number but instead found character '{}'. " + "Version number is invalid or the pragma is not terminated with a semicolon.", + c + ) + ); } char SemVerMatchExpressionParser::currentChar() const diff --git a/compiler/liblangutil/SemVerHandler.h b/compiler/liblangutil/SemVerHandler.h index bce4d736..cf344502 100644 --- a/compiler/liblangutil/SemVerHandler.h +++ b/compiler/liblangutil/SemVerHandler.h @@ -25,17 +25,18 @@ #include #include +#include #include -#include #include #include namespace solidity::langutil { -class SemVerError: public util::Exception +struct SemVerError: public util::Exception { + }; #undef major @@ -87,8 +88,8 @@ class SemVerMatchExpressionParser public: SemVerMatchExpressionParser(std::vector _tokens, std::vector _literals); - /// Returns an expression if it was parseable, or nothing otherwise. - std::optional parse(); + /// Returns an expression if it was parsable, or throws a SemVerError otherwise. + SemVerMatchExpression parse(); private: void reset(); diff --git a/compiler/liblangutil/SourceLocation.cpp b/compiler/liblangutil/SourceLocation.cpp index 72f2506e..61fe40f9 100644 --- a/compiler/liblangutil/SourceLocation.cpp +++ b/compiler/liblangutil/SourceLocation.cpp @@ -25,14 +25,13 @@ using namespace solidity; using namespace solidity::langutil; -using namespace std; -SourceLocation solidity::langutil::parseSourceLocation(string const& _input, vector> const& _sourceNames) +SourceLocation solidity::langutil::parseSourceLocation(std::string const& _input, std::vector> const& _sourceNames) { // Expected input: "start:length:sourceindex" enum SrcElem: size_t { Start, Length, Index }; - vector pos; + std::vector pos; boost::algorithm::split(pos, _input, boost::is_any_of(":")); diff --git a/compiler/liblangutil/SourceReferenceExtractor.cpp b/compiler/liblangutil/SourceReferenceExtractor.cpp index 373ad6b1..9389a77a 100644 --- a/compiler/liblangutil/SourceReferenceExtractor.cpp +++ b/compiler/liblangutil/SourceReferenceExtractor.cpp @@ -22,20 +22,20 @@ #include #include +#include -using namespace std; using namespace solidity; using namespace solidity::langutil; SourceReferenceExtractor::Message SourceReferenceExtractor::extract( CharStreamProvider const& _charStreamProvider, util::Exception const& _exception, - string _severity + std::variant _typeOrSeverity ) { SourceLocation const* location = boost::get_error_info(_exception); - string const* message = boost::get_error_info(_exception); + std::string const* message = boost::get_error_info(_exception); SourceReference primary = extract(_charStreamProvider, location, message ? *message : ""); std::vector secondary; @@ -44,16 +44,16 @@ SourceReferenceExtractor::Message SourceReferenceExtractor::extract( for (auto const& info: secondaryLocation->infos) secondary.emplace_back(extract(_charStreamProvider, &info.second, info.first)); - return Message{std::move(primary), _severity, std::move(secondary), nullopt}; + return Message{std::move(primary), _typeOrSeverity, std::move(secondary), std::nullopt}; } SourceReferenceExtractor::Message SourceReferenceExtractor::extract( CharStreamProvider const& _charStreamProvider, - Error const& _error + Error const& _error, + std::variant _typeOrSeverity ) { - string severity = Error::formatErrorSeverity(Error::errorSeverity(_error.type())); - Message message = extract(_charStreamProvider, _error, severity); + Message message = extract(_charStreamProvider, static_cast(_error), _typeOrSeverity); message.errorId = _error.errorId(); return message; } @@ -77,7 +77,7 @@ SourceReference SourceReferenceExtractor::extract( LineColumn end = charStream.translatePositionToLineColumn(_location->end); bool const isMultiline = start.line != end.line; - string line = charStream.lineAtPosition(_location->start); + std::string line = charStream.lineAtPosition(_location->start); int locationLength = isMultiline ? @@ -87,7 +87,7 @@ SourceReference SourceReferenceExtractor::extract( if (locationLength > 150) { auto const lhs = static_cast(start.column) + 35; - string::size_type const rhs = (isMultiline ? line.length() : static_cast(end.column)) - 35; + std::string::size_type const rhs = (isMultiline ? line.length() : static_cast(end.column)) - 35; line = line.substr(0, lhs) + " ... " + line.substr(rhs); end.column = start.column + 75; locationLength = 75; @@ -97,9 +97,9 @@ SourceReference SourceReferenceExtractor::extract( { int const len = static_cast(line.length()); line = line.substr( - static_cast(max(0, start.column - 35)), - static_cast(min(start.column, 35)) + static_cast( - min(locationLength + 35, len - start.column) + static_cast(std::max(0, start.column - 35)), + static_cast(std::min(start.column, 35)) + static_cast( + std::min(locationLength + 35, len - start.column) ) ); if (start.column + locationLength + 35 < len) @@ -118,7 +118,7 @@ SourceReference SourceReferenceExtractor::extract( interest, isMultiline, line, - min(start.column, static_cast(line.length())), - min(end.column, static_cast(line.length())) + std::min(start.column, static_cast(line.length())), + std::min(end.column, static_cast(line.length())) }; } diff --git a/compiler/liblangutil/SourceReferenceExtractor.h b/compiler/liblangutil/SourceReferenceExtractor.h index 3b418fcf..3f7751f0 100644 --- a/compiler/liblangutil/SourceReferenceExtractor.h +++ b/compiler/liblangutil/SourceReferenceExtractor.h @@ -24,6 +24,7 @@ #include #include #include +#include namespace solidity::langutil { @@ -55,12 +56,21 @@ namespace SourceReferenceExtractor struct Message { SourceReference primary; - std::string severity; // "Error", "Warning", "Info", ... + std::variant _typeOrSeverity; std::vector secondary; std::optional errorId; }; - Message extract(CharStreamProvider const& _charStreamProvider, util::Exception const& _exception, std::string _severity); + Message extract( + CharStreamProvider const& _charStreamProvider, + util::Exception const& _exception, + std::variant _typeOrSeverity + ); + Message extract( + CharStreamProvider const& _charStreamProvider, + Error const& _error, + std::variant _typeOrSeverity + ); Message extract(CharStreamProvider const& _charStreamProvider, Error const& _error); SourceReference extract(CharStreamProvider const& _charStreamProvider, SourceLocation const* _location, std::string message = ""); } diff --git a/compiler/liblangutil/SourceReferenceFormatter.cpp b/compiler/liblangutil/SourceReferenceFormatter.cpp index a38c5e61..c48cb328 100644 --- a/compiler/liblangutil/SourceReferenceFormatter.cpp +++ b/compiler/liblangutil/SourceReferenceFormatter.cpp @@ -26,8 +26,8 @@ #include #include #include +#include -using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::util; @@ -55,6 +55,28 @@ std::string SourceReferenceFormatter::formatErrorInformation(Error const& _error ); } +char const* SourceReferenceFormatter::errorTextColor(Error::Severity _severity) +{ + switch (_severity) + { + case Error::Severity::Error: return RED; + case Error::Severity::Warning: return YELLOW; + case Error::Severity::Info: return WHITE; + } + util::unreachable(); +} + +char const* SourceReferenceFormatter::errorHighlightColor(Error::Severity _severity) +{ + switch (_severity) + { + case Error::Severity::Error: return RED_BACKGROUND; + case Error::Severity::Warning: return ORANGE_BACKGROUND_256; + case Error::Severity::Info: return GRAY_BACKGROUND; + } + util::unreachable(); +} + AnsiColorized SourceReferenceFormatter::normalColored() const { return AnsiColorized(m_stream, m_colored, {WHITE}); @@ -65,26 +87,14 @@ AnsiColorized SourceReferenceFormatter::frameColored() const return AnsiColorized(m_stream, m_colored, {BOLD, BLUE}); } -AnsiColorized SourceReferenceFormatter::errorColored(optional _severity) const +AnsiColorized SourceReferenceFormatter::errorColored(std::ostream& _stream, bool _colored, Error::Severity _severity) { - // We used to color messages of any severity as errors so this seems like a good default - // for cases where severity cannot be determined. - char const* textColor = RED; - - if (_severity.has_value()) - switch (_severity.value()) - { - case Error::Severity::Error: textColor = RED; break; - case Error::Severity::Warning: textColor = YELLOW; break; - case Error::Severity::Info: textColor = WHITE; break; - } - - return AnsiColorized(m_stream, m_colored, {BOLD, textColor}); + return AnsiColorized(_stream, _colored, {BOLD, errorTextColor(_severity)}); } -AnsiColorized SourceReferenceFormatter::messageColored() const +AnsiColorized SourceReferenceFormatter::messageColored(std::ostream& _stream, bool _colored) { - return AnsiColorized(m_stream, m_colored, {BOLD, WHITE}); + return AnsiColorized(_stream, _colored, {BOLD, WHITE}); } AnsiColorized SourceReferenceFormatter::secondaryColored() const @@ -114,15 +124,15 @@ void SourceReferenceFormatter::printSourceLocation(SourceReference const& _ref) return; // No line available, nothing else to print } - string line = std::to_string(_ref.position.line + 1); // one-based line number as string - string leftpad = string(line.size(), ' '); + std::string line = std::to_string(_ref.position.line + 1); // one-based line number as string + std::string leftpad = std::string(line.size(), ' '); // line 0: source name m_stream << leftpad; frameColored() << "-->"; m_stream << ' ' << _ref.sourceName << ':' << line << ':' << (_ref.position.column + 1) << ":\n"; - string_view text = _ref.text; + std::string_view text = _ref.text; if (m_charStreamProvider.charStream(_ref.sourceName).isImportedFromAST()) return; @@ -176,15 +186,26 @@ void SourceReferenceFormatter::printSourceLocation(SourceReference const& _ref) } } -void SourceReferenceFormatter::printExceptionInformation(SourceReferenceExtractor::Message const& _msg) +void SourceReferenceFormatter::printPrimaryMessage( + std::ostream& _stream, + std::string _message, + std::variant _typeOrSeverity, + std::optional _errorId, + bool _colored, + bool _withErrorIds +) { - // exception header line - optional severity = Error::severityFromString(_msg.severity); - errorColored(severity) << _msg.severity; - if (m_withErrorIds && _msg.errorId.has_value()) - errorColored(severity) << " (" << _msg.errorId.value().error << ")"; - messageColored() << ": " << _msg.primary.message << '\n'; + errorColored(_stream, _colored, Error::errorSeverityOrType(_typeOrSeverity)) << Error::formatTypeOrSeverity(_typeOrSeverity); + + if (_withErrorIds && _errorId.has_value()) + errorColored(_stream, _colored, Error::errorSeverityOrType(_typeOrSeverity)) << " (" << _errorId.value().error << ")"; + messageColored(_stream, _colored) << ": " << _message << '\n'; +} + +void SourceReferenceFormatter::printExceptionInformation(SourceReferenceExtractor::Message const& _msg) +{ + printPrimaryMessage(m_stream, _msg.primary.message, _msg._typeOrSeverity, _msg.errorId, m_colored, m_withErrorIds); printSourceLocation(_msg.primary); for (auto const& secondary: _msg.secondary) @@ -197,7 +218,12 @@ void SourceReferenceFormatter::printExceptionInformation(SourceReferenceExtracto m_stream << '\n'; } -void SourceReferenceFormatter::printExceptionInformation(util::Exception const& _exception, std::string const& _severity) +void SourceReferenceFormatter::printExceptionInformation(util::Exception const& _exception, Error::Type _type) +{ + printExceptionInformation(SourceReferenceExtractor::extract(m_charStreamProvider, _exception, _type)); +} + +void SourceReferenceFormatter::printExceptionInformation(util::Exception const& _exception, Error::Severity _severity) { printExceptionInformation(SourceReferenceExtractor::extract(m_charStreamProvider, _exception, _severity)); } @@ -210,5 +236,11 @@ void SourceReferenceFormatter::printErrorInformation(ErrorList const& _errors) void SourceReferenceFormatter::printErrorInformation(Error const& _error) { - printExceptionInformation(SourceReferenceExtractor::extract(m_charStreamProvider, _error)); + SourceReferenceExtractor::Message message = + SourceReferenceExtractor::extract( + m_charStreamProvider, + _error, + Error::errorSeverity(_error.type()) + ); + printExceptionInformation(message); } diff --git a/compiler/liblangutil/SourceReferenceFormatter.h b/compiler/liblangutil/SourceReferenceFormatter.h index e18400b6..7163199d 100644 --- a/compiler/liblangutil/SourceReferenceFormatter.h +++ b/compiler/liblangutil/SourceReferenceFormatter.h @@ -46,19 +46,52 @@ class SourceReferenceFormatter bool _colored, bool _withErrorIds ): - m_stream(_stream), m_charStreamProvider(_charStreamProvider), m_colored(_colored), m_withErrorIds(_withErrorIds) + m_stream(_stream), + m_charStreamProvider(_charStreamProvider), + m_colored(_colored), + m_withErrorIds(_withErrorIds) {} + // WARNING: Use the xyzErrorInformation() variants over xyzExceptionInformation() when you + // do have access to an Error instance. Error is implicitly convertible to util::Exception + // but the conversion loses the error ID. + /// Prints source location if it is given. void printSourceLocation(SourceReference const& _ref); void printExceptionInformation(SourceReferenceExtractor::Message const& _msg); - void printExceptionInformation(util::Exception const& _exception, std::string const& _severity); + void printExceptionInformation(util::Exception const& _exception, Error::Type _type); + void printExceptionInformation(util::Exception const& _exception, Error::Severity _severity); void printErrorInformation(langutil::ErrorList const& _errors); void printErrorInformation(Error const& _error); static std::string formatExceptionInformation( util::Exception const& _exception, - std::string const& _name, + Error::Type _type, + CharStreamProvider const& _charStreamProvider, + bool _colored = false + ) + { + std::ostringstream errorOutput; + SourceReferenceFormatter formatter(errorOutput, _charStreamProvider, _colored, false /* _withErrorIds */); + formatter.printExceptionInformation(_exception, _type); + return errorOutput.str(); + } + + static std::string formatExceptionInformation( + util::Exception const& _exception, + Error::Severity _severity, + CharStreamProvider const& _charStreamProvider, + bool _colored = false + ) + { + std::ostringstream errorOutput; + SourceReferenceFormatter formatter(errorOutput, _charStreamProvider, _colored, false /* _withErrorIds */); + formatter.printExceptionInformation(_exception, _severity); + return errorOutput.str(); + } + + static std::string formatErrorInformation( + Error const& _error, CharStreamProvider const& _charStreamProvider, bool _colored = false, bool _withErrorIds = false @@ -66,33 +99,56 @@ class SourceReferenceFormatter { std::ostringstream errorOutput; SourceReferenceFormatter formatter(errorOutput, _charStreamProvider, _colored, _withErrorIds); - formatter.printExceptionInformation(_exception, _name); + formatter.printErrorInformation(_error); return errorOutput.str(); } static std::string formatErrorInformation( - Error const& _error, - CharStreamProvider const& _charStreamProvider + langutil::ErrorList const& _errors, + CharStreamProvider const& _charStreamProvider, + bool _colored = false, + bool _withErrorIds = false ) { - return formatExceptionInformation( - _error, - Error::formatErrorSeverity(Error::errorSeverity(_error.type())), - _charStreamProvider - ); + std::ostringstream errorOutput; + SourceReferenceFormatter formatter(errorOutput, _charStreamProvider, _colored, _withErrorIds); + formatter.printErrorInformation(_errors); + return errorOutput.str(); } static std::string formatErrorInformation(Error const& _error, CharStream const& _charStream); + static void printPrimaryMessage( + std::ostream& _stream, + std::string _message, + std::variant _typeOrSeverity, + std::optional _errorId = std::nullopt, + bool _colored = false, + bool _withErrorIds = false + ); + + /// The default text color for printing error messages of a given severity in the terminal. + /// Assumes a dark background color. + static char const* errorTextColor(Error::Severity _severity); + + /// The default background color for highlighting source fragments corresponding to an error + /// of a given severity in the terminal. Assumes a light text color. + /// @note This is *not* meant to be used for the same text in combination with @a errorTextColor(). + /// It's an alternative way to highlight it, while preserving the original text color. + static char const* errorHighlightColor(Error::Severity _severity); + private: util::AnsiColorized normalColored() const; util::AnsiColorized frameColored() const; - util::AnsiColorized errorColored(std::optional _severity) const; - util::AnsiColorized messageColored() const; + util::AnsiColorized errorColored(Error::Severity _severity) const { return errorColored(m_stream, m_colored, _severity); } + util::AnsiColorized messageColored() const { return messageColored(m_stream, m_colored); } util::AnsiColorized secondaryColored() const; util::AnsiColorized highlightColored() const; util::AnsiColorized diagColored() const; + static util::AnsiColorized errorColored(std::ostream& _stream, bool _colored, langutil::Error::Severity _severity); + static util::AnsiColorized messageColored(std::ostream& _stream, bool _colored); + private: std::ostream& m_stream; CharStreamProvider const& m_charStreamProvider; diff --git a/compiler/liblangutil/Token.cpp b/compiler/liblangutil/Token.cpp index 6c0f0c58..60fcfbeb 100644 --- a/compiler/liblangutil/Token.cpp +++ b/compiler/liblangutil/Token.cpp @@ -46,9 +46,6 @@ #include - -using namespace std; - namespace solidity::langutil { @@ -72,25 +69,26 @@ std::string ElementaryTypeNameToken::toString(bool const& tokenValue) const void ElementaryTypeNameToken::assertDetails(Token _baseType, unsigned const& _first, unsigned const& _second) { - solAssert(TokenTraits::isElementaryTypeName(_baseType), "Expected elementary type name: " + string(TokenTraits::toString(_baseType))); + solAssert(TokenTraits::isElementaryTypeName(_baseType), "Expected elementary type name: " + std::string(TokenTraits::toString(_baseType))); if (_baseType == Token::BytesM) { solAssert(_second == 0, "There should not be a second size argument to type bytesM."); - solAssert(_first <= 32, "No elementary type bytes" + to_string(_first) + "."); + solAssert(_first <= 32, "No elementary type bytes" + std::to_string(_first) + "."); } else if (_baseType == Token::UIntM || _baseType == Token::IntM) { - solAssert(_second == 0, "There should not be a second size argument to type " + string(TokenTraits::toString(_baseType)) + "."); + unsigned bitLength = _baseType == Token::UInt ? 256 : 257; + solAssert(_second == 0, "There should not be a second size argument to type " + std::string(TokenTraits::toString(_baseType)) + "."); solAssert( - _first > 0 && _first <= 256, - "No elementary type " + string(TokenTraits::toString(_baseType)) + to_string(_first) + "." + _first <= bitLength, + "No elementary type " + std::string(TokenTraits::toString(_baseType)) + std::to_string(_first) + "." ); } else if (_baseType == Token::UFixedMxN || _baseType == Token::FixedMxN) { solAssert( - _first >= 8 && _first <= 256 && _first % 8 == 0 && _second <= 80, - "No elementary type " + string(TokenTraits::toString(_baseType)) + to_string(_first) + "x" + to_string(_second) + "." + _first >= 8 && _first <= 256 && _second <= 80, + "No elementary type " + std::string(TokenTraits::toString(_baseType)) + std::to_string(_first) + "x" + std::to_string(_second) + "." ); } else if (_baseType == Token::VarUintM || _baseType == Token::VarIntM) @@ -141,29 +139,29 @@ std::string friendlyName(Token tok) } -static Token keywordByName(string const& _name) +static Token keywordByName(std::string const& _name) { // The following macros are used inside TOKEN_LIST and cause non-keyword tokens to be ignored // and keywords to be put inside the keywords variable. #define KEYWORD(name, string, precedence) {string, Token::name}, #define TOKEN(name, string, precedence) - static map const keywords({TOKEN_LIST(TOKEN, KEYWORD)}); + static std::map const keywords({TOKEN_LIST(TOKEN, KEYWORD)}); #undef KEYWORD #undef TOKEN auto it = keywords.find(_name); return it == keywords.end() ? Token::Identifier : it->second; } -bool isYulKeyword(string const& _literal) +bool isYulKeyword(std::string const& _literal) { return _literal == "leave" || isYulKeyword(keywordByName(_literal)); } -tuple fromIdentifierOrKeyword(string const& _literal) +std::tuple fromIdentifierOrKeyword(std::string const& _literal) { // Used for `bytesM`, `uintM`, `intM`, `fixedMxN`, `ufixedMxN`. // M/N must be shortest representation. M can never be 0. N can be zero. - auto parseSize = [](string::const_iterator _begin, string::const_iterator _end) -> int + auto parseSize = [](std::string::const_iterator _begin, std::string::const_iterator _end) -> int { // No number. if (distance(_begin, _end) == 0) @@ -190,7 +188,7 @@ tuple fromIdentifierOrKeyword(string const& _ auto positionM = find_if(_literal.begin(), _literal.end(), util::isDigit); if (positionM != _literal.end()) { - string baseType(_literal.begin(), positionM); + std::string baseType(_literal.begin(), positionM); auto positionX = find_if_not(positionM, _literal.end(), util::isDigit); int m = parseSize(positionM, positionX); Token keyword = keywordByName(baseType); @@ -199,23 +197,24 @@ tuple fromIdentifierOrKeyword(string const& _ if (keyword == Token::VarUint || keyword == Token::VarInt) { if (m == 16 || m == 32) { if (keyword == Token::VarUint) - return make_tuple(Token::VarUintM, m, 0); - return make_tuple(Token::VarIntM, m, 0); + return std::make_tuple(Token::VarUintM, m, 0); + return std::make_tuple(Token::VarIntM, m, 0); } } else if (keyword == Token::Bytes) { if (0 < m && m <= 32 && positionX == _literal.end()) - return make_tuple(Token::BytesM, m, 0); + return std::make_tuple(Token::BytesM, m, 0); } else if (keyword == Token::UInt || keyword == Token::Int) { - if (0 < m && m <= 256 && positionX == _literal.end()) + int bitLength = keyword == Token::UInt ? 256 : 257; + if (0 < m && m <= bitLength && positionX == _literal.end()) { if (keyword == Token::UInt) - return make_tuple(Token::UIntM, m, 0); + return std::make_tuple(Token::UIntM, m, 0); else - return make_tuple(Token::IntM, m, 0); + return std::make_tuple(Token::IntM, m, 0); } } else if (keyword == Token::UFixed || keyword == Token::Fixed) @@ -228,20 +227,20 @@ tuple fromIdentifierOrKeyword(string const& _ ) { int n = parseSize(positionX + 1, _literal.end()); if ( - 8 <= m && m <= 256 && m % 8 == 0 && + 8 <= m && m <= 256 && 0 <= n && n <= 80 ) { if (keyword == Token::UFixed) - return make_tuple(Token::UFixedMxN, m, n); + return std::make_tuple(Token::UFixedMxN, m, n); else - return make_tuple(Token::FixedMxN, m, n); + return std::make_tuple(Token::FixedMxN, m, n); } } } - return make_tuple(Token::Identifier, 0, 0); + return std::make_tuple(Token::Identifier, 0, 0); } - return make_tuple(keywordByName(_literal), 0, 0); + return std::make_tuple(keywordByName(_literal), 0, 0); } } diff --git a/compiler/liblangutil/Token.h b/compiler/liblangutil/Token.h index 2553a74e..5515e18b 100644 --- a/compiler/liblangutil/Token.h +++ b/compiler/liblangutil/Token.h @@ -42,8 +42,6 @@ #pragma once -#include - #include #include #include @@ -179,8 +177,9 @@ namespace solidity::langutil K(Mapping, "mapping", 0) \ K(Modifier, "modifier", 0) \ K(New, "new", 0) \ + K(NoStorage, "nostorage", 0) \ K(Optional, "optional", 0) \ - K(TvmVector, "vector", 0) \ + K(TvmVector, "vector", 0) \ K(Override, "override", 0) \ K(Payable, "payable", 0) \ K(Public, "public", 0) \ @@ -258,6 +257,7 @@ namespace solidity::langutil K(VarInt, "varInt", 0) \ T(VarIntM, "varIntM", 0) \ K(VarUint, "varUint", 0) \ + K(coins, "coins", 0) \ T(VarUintM, "varUintM", 0) \ T(TypesEnd, nullptr, 0) /* used as type enum end marker */ \ \ @@ -310,6 +310,16 @@ namespace solidity::langutil /* Yul-specific tokens, but not keywords. */ \ T(Leave, "leave", 0) \ \ + T(NonExperimentalEnd, nullptr, 0) /* used as non-experimental enum end marker */ \ + /* Experimental Solidity specific keywords. */ \ + K(Class, "class", 0) \ + K(Instantiation, "instantiation", 0) \ + K(Integer, "Integer", 0) \ + K(Itself, "itself", 0) \ + K(StaticAssert, "static_assert", 0) \ + K(Builtin, "__builtin", 0) \ + T(ExperimentalEnd, nullptr, 0) /* used as experimental enum end marker */ \ + \ /* Illegal token - not able to scan. */ \ T(Illegal, "ILLEGAL", 0) \ \ @@ -332,7 +342,7 @@ namespace TokenTraits constexpr size_t count() { return static_cast(Token::NUM_TOKENS); } // Predicates - constexpr bool isElementaryTypeName(Token tok) { return Token::Int <= tok && tok < Token::TypesEnd; } + constexpr bool isElementaryTypeName(Token _token) { return Token::Int <= _token && _token < Token::TypesEnd; } constexpr bool isAssignmentOp(Token tok) { return Token::Assign <= tok && tok <= Token::AssignMod; } constexpr bool isBinaryOp(Token op) { return Token::Comma <= op && op <= Token::Exp; } constexpr bool isCommutativeOp(Token op) { return op == Token::BitOr || op == Token::BitXor || op == Token::BitAnd || @@ -342,7 +352,7 @@ namespace TokenTraits constexpr bool isBitOp(Token op) { return (Token::BitOr <= op && op <= Token::BitAnd) || op == Token::BitNot; } constexpr bool isBooleanOp(Token op) { return (Token::Or <= op && op <= Token::And) || op == Token::Not; } - constexpr bool isUnaryOp(Token op) { return (Token::Not <= op && op <= Token::Delete) || op == Token::Add || op == Token::Sub; } + constexpr bool isUnaryOp(Token op) { return (Token::Not <= op && op <= Token::Delete) || op == Token::Sub; } constexpr bool isCountOp(Token op) { return op == Token::Inc || op == Token::Dec; } constexpr bool isShiftOp(Token op) { return (Token::SHL <= op) && (op <= Token::SHR); } constexpr bool isVariableVisibilitySpecifier(Token op) { @@ -355,6 +365,7 @@ namespace TokenTraits return op == Token::Pure || op == Token::View || op == Token::Payable; } + // TODO RENAME constexpr bool isTonSubdenomination(Token op) { return (Token::SubNano <= op && op <= Token::SubGEver); } constexpr bool isTimeSubdenomination(Token op) { return op == Token::SubSecond || op == Token::SubMinute || op == Token::SubHour || op == Token::SubDay || op == Token::SubWeek || op == Token::SubYear; } constexpr bool isReservedKeyword(Token op) { return (Token::After <= op && op <= Token::Var); } @@ -366,6 +377,48 @@ namespace TokenTraits tok == Token::TrueLiteral || tok == Token::FalseLiteral || tok == Token::HexStringLiteral || tok == Token::Hex; } + constexpr bool isBuiltinTypeClassName(Token _token) + { + return + _token == Token::Integer || + (isBinaryOp(_token) && _token != Token::Comma) || + isCompareOp(_token) || + isUnaryOp(_token) || + (isAssignmentOp(_token) && _token != Token::Assign); + } + + constexpr bool isExperimentalSolidityKeyword(Token token) + { + return + token == Token::Assembly || + token == Token::Contract || + token == Token::External || + token == Token::Fallback || + token == Token::Pragma || + token == Token::Import || + token == Token::As || + token == Token::Function || + token == Token::Let || + token == Token::Return || + token == Token::Type || + token == Token::If || + token == Token::Else || + token == Token::Do || + token == Token::While || + token == Token::For || + token == Token::Continue || + token == Token::Break || + (token > Token::NonExperimentalEnd && token< Token::ExperimentalEnd); + } + + constexpr bool isExperimentalSolidityOnlyKeyword(Token _token) + { + // TODO: use token > Token::NonExperimentalEnd && token < Token::ExperimentalEnd + // as soon as other experimental tokens are added. For now the comparison generates + // a warning from clang because it is always false. + return _token > Token::NonExperimentalEnd && _token < Token::ExperimentalEnd; + } + bool isYulKeyword(std::string const& _literal); Token AssignmentToBinaryOp(Token op); diff --git a/compiler/liblangutil/UndefMacros.h b/compiler/liblangutil/UndefMacros.h deleted file mode 100644 index e3dde6d3..00000000 --- a/compiler/liblangutil/UndefMacros.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ -// SPDX-License-Identifier: GPL-3.0 -/** @file UndefMacros.h - * @author Lefteris - * @date 2015 - * - * This header should be used to #undef some really evil macros defined by - * windows.h which result in conflict with our Token.h - */ -#pragma once - -#if defined(_MSC_VER) || defined(__MINGW32__) - -#undef DELETE -#undef IN -#undef VOID -#undef THIS -#undef CONST - -// Conflicting define on MinGW in windows.h -// windows.h(19): #define interface struct -#ifdef interface -#undef interface -#endif - -#elif defined(DELETE) || defined(IN) || defined(VOID) || defined(THIS) || defined(CONST) || defined(interface) - -#error "The preceding macros in this header file are reserved for V8's "\ -"TOKEN_LIST. Please add a platform specific define above to undefine "\ -"overlapping macros." - -#endif diff --git a/compiler/liblangutil/UniqueErrorReporter.h b/compiler/liblangutil/UniqueErrorReporter.h index ebcb8d97..902068b8 100644 --- a/compiler/liblangutil/UniqueErrorReporter.h +++ b/compiler/liblangutil/UniqueErrorReporter.h @@ -33,6 +33,11 @@ class UniqueErrorReporter public: UniqueErrorReporter(): m_errorReporter(m_uniqueErrors) {} + void append(UniqueErrorReporter const& _other) + { + m_errorReporter.append(_other.m_errorReporter.errors()); + } + void warning(ErrorId _error, SourceLocation const& _location, std::string const& _description) { if (!seen(_error, _location, _description)) diff --git a/compiler/libsmtutil/CHCSmtLib2Interface.cpp b/compiler/libsmtutil/CHCSmtLib2Interface.cpp index c9b5f605..ef7c56ac 100644 --- a/compiler/libsmtutil/CHCSmtLib2Interface.cpp +++ b/compiler/libsmtutil/CHCSmtLib2Interface.cpp @@ -31,21 +31,22 @@ #include #include -using namespace std; using namespace solidity; using namespace solidity::util; using namespace solidity::frontend; using namespace solidity::smtutil; CHCSmtLib2Interface::CHCSmtLib2Interface( - map const& _queryResponses, + std::map const& _queryResponses, ReadCallback::Callback _smtCallback, - optional _queryTimeout + SMTSolverChoice _enabledSolvers, + std::optional _queryTimeout ): CHCSolverInterface(_queryTimeout), - m_smtlib2(make_unique(_queryResponses, _smtCallback, m_queryTimeout)), + m_smtlib2(std::make_unique(_queryResponses, _smtCallback, m_queryTimeout)), m_queryResponses(std::move(_queryResponses)), - m_smtCallback(_smtCallback) + m_smtCallback(_smtCallback), + m_enabledSolvers(_enabledSolvers) { reset(); } @@ -64,8 +65,8 @@ void CHCSmtLib2Interface::registerRelation(Expression const& _expr) smtAssert(_expr.sort->kind == Kind::Function); if (!m_variables.count(_expr.name)) { - auto fSort = dynamic_pointer_cast(_expr.sort); - string domain = toSmtLibSort(fSort->domain); + auto fSort = std::dynamic_pointer_cast(_expr.sort); + std::string domain = toSmtLibSort(fSort->domain); // Relations are predicates which have implicit codomain Bool. m_variables.insert(_expr.name); write( @@ -87,25 +88,10 @@ void CHCSmtLib2Interface::addRule(Expression const& _expr, std::string const& /* ); } -tuple CHCSmtLib2Interface::query(Expression const& _block) +std::tuple CHCSmtLib2Interface::query(Expression const& _block) { - string accumulated{}; - swap(m_accumulatedOutput, accumulated); - solAssert(m_smtlib2, ""); - writeHeader(); - for (auto const& decl: m_smtlib2->userSorts() | ranges::views::values) - write(decl); - m_accumulatedOutput += accumulated; - - string queryRule = "(assert\n(forall " + forall() + "\n" + - "(=> " + _block.name + " false)" - "))"; - string response = querySolver( - m_accumulatedOutput + - queryRule + - "\n(check-sat)" - ); - swap(m_accumulatedOutput, accumulated); + std::string query = dumpQuery(_block); + std::string response = querySolver(query); CheckResult result; // TODO proper parsing @@ -121,7 +107,7 @@ tuple CHCSmtLib2Interface return {result, Expression(true), {}}; } -void CHCSmtLib2Interface::declareVariable(string const& _name, SortPointer const& _sort) +void CHCSmtLib2Interface::declareVariable(std::string const& _name, SortPointer const& _sort) { smtAssert(_sort); if (_sort->kind == Kind::Function) @@ -133,32 +119,25 @@ void CHCSmtLib2Interface::declareVariable(string const& _name, SortPointer const } } -string CHCSmtLib2Interface::toSmtLibSort(Sort const& _sort) +std::string CHCSmtLib2Interface::toSmtLibSort(Sort const& _sort) { if (!m_sortNames.count(&_sort)) m_sortNames[&_sort] = m_smtlib2->toSmtLibSort(_sort); return m_sortNames.at(&_sort); } -string CHCSmtLib2Interface::toSmtLibSort(vector const& _sorts) +std::string CHCSmtLib2Interface::toSmtLibSort(std::vector const& _sorts) { - string ssort("("); + std::string ssort("("); for (auto const& sort: _sorts) ssort += toSmtLibSort(*sort) + " "; ssort += ")"; return ssort; } -void CHCSmtLib2Interface::writeHeader() -{ - if (m_queryTimeout) - write("(set-option :timeout " + to_string(*m_queryTimeout) + ")"); - write("(set-logic HORN)\n"); -} - -string CHCSmtLib2Interface::forall() +std::string CHCSmtLib2Interface::forall() { - string vars("("); + std::string vars("("); for (auto const& [name, sort]: m_smtlib2->variables()) { solAssert(sort, ""); @@ -169,17 +148,17 @@ string CHCSmtLib2Interface::forall() return vars; } -void CHCSmtLib2Interface::declareFunction(string const& _name, SortPointer const& _sort) +void CHCSmtLib2Interface::declareFunction(std::string const& _name, SortPointer const& _sort) { smtAssert(_sort); smtAssert(_sort->kind == Kind::Function); // TODO Use domain and codomain as key as well if (!m_variables.count(_name)) { - auto fSort = dynamic_pointer_cast(_sort); + auto fSort = std::dynamic_pointer_cast(_sort); smtAssert(fSort->codomain); - string domain = toSmtLibSort(fSort->domain); - string codomain = toSmtLibSort(*fSort->codomain); + std::string domain = toSmtLibSort(fSort->domain); + std::string codomain = toSmtLibSort(*fSort->codomain); m_variables.insert(_name); write( "(declare-fun |" + @@ -193,22 +172,54 @@ void CHCSmtLib2Interface::declareFunction(string const& _name, SortPointer const } } -void CHCSmtLib2Interface::write(string _data) +void CHCSmtLib2Interface::write(std::string _data) { m_accumulatedOutput += std::move(_data) + "\n"; } -string CHCSmtLib2Interface::querySolver(string const& _input) +std::string CHCSmtLib2Interface::querySolver(std::string const& _input) { util::h256 inputHash = util::keccak256(_input); if (m_queryResponses.count(inputHash)) return m_queryResponses.at(inputHash); + + smtAssert(m_enabledSolvers.smtlib2 || m_enabledSolvers.eld); if (m_smtCallback) { auto result = m_smtCallback(ReadCallback::kindString(ReadCallback::Kind::SMTQuery), _input); if (result.success) return result.responseOrErrorMessage; } + m_unhandledQueries.push_back(_input); return "unknown\n"; } + +std::string CHCSmtLib2Interface::dumpQuery(Expression const& _expr) +{ + std::stringstream s; + + s + << createHeaderAndDeclarations() + << m_accumulatedOutput << std::endl + << createQueryAssertion(_expr.name) << std::endl + << "(check-sat)" << std::endl; + + return s.str(); +} + +std::string CHCSmtLib2Interface::createHeaderAndDeclarations() { + std::stringstream s; + if (m_queryTimeout) + s << "(set-option :timeout " + std::to_string(*m_queryTimeout) + ")\n"; + s << "(set-logic HORN)" << std::endl; + + for (auto const& decl: m_smtlib2->userSorts() | ranges::views::values) + s << decl << std::endl; + + return s.str(); +} + +std::string CHCSmtLib2Interface::createQueryAssertion(std::string name) { + return "(assert\n(forall " + forall() + "\n" + "(=> " + name + " false)))"; +} diff --git a/compiler/libsmtutil/CHCSmtLib2Interface.h b/compiler/libsmtutil/CHCSmtLib2Interface.h index a7dfb769..0f3a68ee 100644 --- a/compiler/libsmtutil/CHCSmtLib2Interface.h +++ b/compiler/libsmtutil/CHCSmtLib2Interface.h @@ -35,6 +35,7 @@ class CHCSmtLib2Interface: public CHCSolverInterface explicit CHCSmtLib2Interface( std::map const& _queryResponses = {}, frontend::ReadCallback::Callback _smtCallback = {}, + SMTSolverChoice _enabledSolvers = SMTSolverChoice::All(), std::optional _queryTimeout = {} ); @@ -50,6 +51,8 @@ class CHCSmtLib2Interface: public CHCSolverInterface void declareVariable(std::string const& _name, SortPointer const& _sort) override; + std::string dumpQuery(Expression const& _expr); + std::vector unhandledQueries() const { return m_unhandledQueries; } SMTLib2Interface* smtlib2Interface() const { return m_smtlib2.get(); } @@ -65,6 +68,9 @@ class CHCSmtLib2Interface: public CHCSolverInterface void write(std::string _data); + std::string createQueryAssertion(std::string name); + std::string createHeaderAndDeclarations(); + /// Communicates with the solver via the callback. Throws SMTSolverError on error. std::string querySolver(std::string const& _input); @@ -78,6 +84,7 @@ class CHCSmtLib2Interface: public CHCSolverInterface std::vector m_unhandledQueries; frontend::ReadCallback::Callback m_smtCallback; + SMTSolverChoice m_enabledSolvers; std::map m_sortNames; }; diff --git a/compiler/libsmtutil/CVC4Interface.cpp b/compiler/libsmtutil/CVC4Interface.cpp index ff356c02..f53c92ab 100644 --- a/compiler/libsmtutil/CVC4Interface.cpp +++ b/compiler/libsmtutil/CVC4Interface.cpp @@ -23,12 +23,11 @@ #include -using namespace std; using namespace solidity; using namespace solidity::util; using namespace solidity::smtutil; -CVC4Interface::CVC4Interface(optional _queryTimeout): +CVC4Interface::CVC4Interface(std::optional _queryTimeout): SolverInterface(_queryTimeout), m_solver(&m_context) { @@ -56,7 +55,7 @@ void CVC4Interface::pop() m_solver.pop(); } -void CVC4Interface::declareVariable(string const& _name, SortPointer const& _sort) +void CVC4Interface::declareVariable(std::string const& _name, SortPointer const& _sort) { smtAssert(_sort, ""); m_variables[_name] = m_context.mkVar(_name.c_str(), cvc4Sort(*_sort)); @@ -86,10 +85,10 @@ void CVC4Interface::addAssertion(Expression const& _expr) } } -pair> CVC4Interface::check(vector const& _expressionsToEvaluate) +std::pair> CVC4Interface::check(std::vector const& _expressionsToEvaluate) { CheckResult result; - vector values; + std::vector values; try { switch (m_solver.checkSat().isSat()) @@ -119,7 +118,7 @@ pair> CVC4Interface::check(vector const& values.clear(); } - return make_pair(result, values); + return std::make_pair(result, values); } CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) @@ -128,13 +127,13 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) if (_expr.arguments.empty() && m_variables.count(_expr.name)) return m_variables.at(_expr.name); - vector arguments; + std::vector arguments; for (auto const& arg: _expr.arguments) arguments.push_back(toCVC4Expr(arg)); try { - string const& n = _expr.name; + std::string const& n = _expr.name; // Function application if (!arguments.empty() && m_variables.count(_expr.name)) return m_context.mkExpr(CVC4::kind::APPLY_UF, m_variables.at(n), arguments); @@ -145,7 +144,7 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) return m_context.mkConst(true); else if (n == "false") return m_context.mkConst(false); - else if (auto sortSort = dynamic_pointer_cast(_expr.sort)) + else if (auto sortSort = std::dynamic_pointer_cast(_expr.sort)) return m_context.mkVar(n, cvc4Sort(*sortSort->inner)); else try @@ -224,7 +223,7 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) } else if (n == "bv2int") { - auto intSort = dynamic_pointer_cast(_expr.sort); + auto intSort = std::dynamic_pointer_cast(_expr.sort); smtAssert(intSort, ""); auto nat = m_context.mkExpr(CVC4::kind::BITVECTOR_TO_NAT, arguments[0]); if (!intSort->isSigned) @@ -239,7 +238,7 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) m_context.mkExpr( CVC4::kind::EQUAL, m_context.mkExpr(CVC4::kind::BITVECTOR_EXTRACT, extractOp, arguments[0]), - m_context.mkConst(CVC4::BitVector(1, size_t(0))) + m_context.mkConst(CVC4::BitVector(1, uint64_t{0})) ), nat, m_context.mkExpr( @@ -254,13 +253,13 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) return m_context.mkExpr(CVC4::kind::STORE, arguments[0], arguments[1], arguments[2]); else if (n == "const_array") { - shared_ptr sortSort = std::dynamic_pointer_cast(_expr.arguments[0].sort); + std::shared_ptr sortSort = std::dynamic_pointer_cast(_expr.arguments[0].sort); smtAssert(sortSort, ""); return m_context.mkConst(CVC4::ArrayStoreAll(cvc4Sort(*sortSort->inner), arguments[1])); } else if (n == "tuple_get") { - shared_ptr tupleSort = std::dynamic_pointer_cast(_expr.arguments[0].sort); + std::shared_ptr tupleSort = std::dynamic_pointer_cast(_expr.arguments[0].sort); smtAssert(tupleSort, ""); CVC4::DatatypeType tt = m_context.mkTupleType(cvc4Sort(tupleSort->components)); CVC4::Datatype const& dt = tt.getDatatype(); @@ -270,7 +269,7 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) } else if (n == "tuple_constructor") { - shared_ptr tupleSort = std::dynamic_pointer_cast(_expr.sort); + std::shared_ptr tupleSort = std::dynamic_pointer_cast(_expr.sort); smtAssert(tupleSort, ""); CVC4::DatatypeType tt = m_context.mkTupleType(cvc4Sort(tupleSort->components)); CVC4::Datatype const& dt = tt.getDatatype(); @@ -328,9 +327,9 @@ CVC4::Type CVC4Interface::cvc4Sort(Sort const& _sort) return m_context.integerType(); } -vector CVC4Interface::cvc4Sort(vector const& _sorts) +std::vector CVC4Interface::cvc4Sort(std::vector const& _sorts) { - vector cvc4Sorts; + std::vector cvc4Sorts; for (auto const& _sort: _sorts) cvc4Sorts.push_back(cvc4Sort(*_sort)); return cvc4Sorts; diff --git a/compiler/libsmtutil/SMTLib2Interface.cpp b/compiler/libsmtutil/SMTLib2Interface.cpp index 3e117913..ac5645bf 100644 --- a/compiler/libsmtutil/SMTLib2Interface.cpp +++ b/compiler/libsmtutil/SMTLib2Interface.cpp @@ -33,16 +33,15 @@ #include #include -using namespace std; using namespace solidity; using namespace solidity::util; using namespace solidity::frontend; using namespace solidity::smtutil; SMTLib2Interface::SMTLib2Interface( - map _queryResponses, + std::map _queryResponses, ReadCallback::Callback _smtCallback, - optional _queryTimeout + std::optional _queryTimeout ): SolverInterface(_queryTimeout), m_queryResponses(std::move(_queryResponses)), @@ -59,7 +58,7 @@ void SMTLib2Interface::reset() m_userSorts.clear(); write("(set-option :produce-models true)"); if (m_queryTimeout) - write("(set-option :timeout " + to_string(*m_queryTimeout) + ")"); + write("(set-option :timeout " + std::to_string(*m_queryTimeout) + ")"); write("(set-logic ALL)"); } @@ -74,7 +73,7 @@ void SMTLib2Interface::pop() m_accumulatedOutput.pop_back(); } -void SMTLib2Interface::declareVariable(string const& _name, SortPointer const& _sort) +void SMTLib2Interface::declareVariable(std::string const& _name, SortPointer const& _sort) { smtAssert(_sort, ""); if (_sort->kind == Kind::Function) @@ -86,16 +85,16 @@ void SMTLib2Interface::declareVariable(string const& _name, SortPointer const& _ } } -void SMTLib2Interface::declareFunction(string const& _name, SortPointer const& _sort) +void SMTLib2Interface::declareFunction(std::string const& _name, SortPointer const& _sort) { smtAssert(_sort, ""); smtAssert(_sort->kind == Kind::Function, ""); // TODO Use domain and codomain as key as well if (!m_variables.count(_name)) { - auto const& fSort = dynamic_pointer_cast(_sort); - string domain = toSmtLibSort(fSort->domain); - string codomain = toSmtLibSort(*fSort->codomain); + auto const& fSort = std::dynamic_pointer_cast(_sort); + std::string domain = toSmtLibSort(fSort->domain); + std::string codomain = toSmtLibSort(*fSort->codomain); m_variables.emplace(_name, _sort); write( "(declare-fun |" + @@ -114,9 +113,9 @@ void SMTLib2Interface::addAssertion(Expression const& _expr) write("(assert " + toSExpr(_expr) + ")"); } -pair> SMTLib2Interface::check(vector const& _expressionsToEvaluate) +std::pair> SMTLib2Interface::check(std::vector const& _expressionsToEvaluate) { - string response = querySolver( + std::string response = querySolver( boost::algorithm::join(m_accumulatedOutput, "\n") + checkSatAndGetValuesCommand(_expressionsToEvaluate) ); @@ -132,13 +131,13 @@ pair> SMTLib2Interface::check(vector con else result = CheckResult::ERROR; - vector values; + std::vector values; if (result == CheckResult::SATISFIABLE && !_expressionsToEvaluate.empty()) values = parseValues(find(response.cbegin(), response.cend(), '\n'), response.cend()); - return make_pair(result, values); + return std::make_pair(result, values); } -string SMTLib2Interface::toSExpr(Expression const& _expr) +std::string SMTLib2Interface::toSExpr(Expression const& _expr) { if (_expr.arguments.empty()) return _expr.name; @@ -148,16 +147,16 @@ string SMTLib2Interface::toSExpr(Expression const& _expr) { size_t size = std::stoul(_expr.arguments[1].name); auto arg = toSExpr(_expr.arguments.front()); - auto int2bv = "(_ int2bv " + to_string(size) + ")"; + auto int2bv = "(_ int2bv " + std::to_string(size) + ")"; // Some solvers treat all BVs as unsigned, so we need to manually apply 2's complement if needed. - sexpr += string("ite ") + + sexpr += std::string("ite ") + "(>= " + arg + " 0) " + "(" + int2bv + " " + arg + ") " + "(bvneg (" + int2bv + " (- " + arg + ")))"; } else if (_expr.name == "bv2int") { - auto intSort = dynamic_pointer_cast(_expr.sort); + auto intSort = std::dynamic_pointer_cast(_expr.sort); smtAssert(intSort, ""); auto arg = toSExpr(_expr.arguments.front()); @@ -166,13 +165,13 @@ string SMTLib2Interface::toSExpr(Expression const& _expr) if (!intSort->isSigned) return nat; - auto bvSort = dynamic_pointer_cast(_expr.arguments.front().sort); + auto bvSort = std::dynamic_pointer_cast(_expr.arguments.front().sort); smtAssert(bvSort, ""); - auto size = to_string(bvSort->size); - auto pos = to_string(bvSort->size - 1); + auto size = std::to_string(bvSort->size); + auto pos = std::to_string(bvSort->size - 1); // Some solvers treat all BVs as unsigned, so we need to manually apply 2's complement if needed. - sexpr += string("ite ") + + sexpr += std::string("ite ") + "(= ((_ extract " + pos + " " + pos + ")" + arg + ") #b0) " + nat + " " + "(- (bv2nat (bvneg " + arg + ")))"; @@ -182,7 +181,7 @@ string SMTLib2Interface::toSExpr(Expression const& _expr) smtAssert(_expr.arguments.size() == 2, ""); auto sortSort = std::dynamic_pointer_cast(_expr.arguments.at(0).sort); smtAssert(sortSort, ""); - auto arraySort = dynamic_pointer_cast(sortSort->inner); + auto arraySort = std::dynamic_pointer_cast(sortSort->inner); smtAssert(arraySort, ""); sexpr += "(as const " + toSmtLibSort(*arraySort) + ") "; sexpr += toSExpr(_expr.arguments.at(1)); @@ -190,14 +189,14 @@ string SMTLib2Interface::toSExpr(Expression const& _expr) else if (_expr.name == "tuple_get") { smtAssert(_expr.arguments.size() == 2, ""); - auto tupleSort = dynamic_pointer_cast(_expr.arguments.at(0).sort); + auto tupleSort = std::dynamic_pointer_cast(_expr.arguments.at(0).sort); size_t index = std::stoul(_expr.arguments.at(1).name); smtAssert(index < tupleSort->members.size(), ""); sexpr += "|" + tupleSort->members.at(index) + "| " + toSExpr(_expr.arguments.at(0)); } else if (_expr.name == "tuple_constructor") { - auto tupleSort = dynamic_pointer_cast(_expr.sort); + auto tupleSort = std::dynamic_pointer_cast(_expr.sort); smtAssert(tupleSort, ""); sexpr += "|" + tupleSort->name + "|"; for (auto const& arg: _expr.arguments) @@ -213,7 +212,7 @@ string SMTLib2Interface::toSExpr(Expression const& _expr) return sexpr; } -string SMTLib2Interface::toSmtLibSort(Sort const& _sort) +std::string SMTLib2Interface::toSmtLibSort(Sort const& _sort) { switch (_sort.kind) { @@ -222,7 +221,7 @@ string SMTLib2Interface::toSmtLibSort(Sort const& _sort) case Kind::Bool: return "Bool"; case Kind::BitVector: - return "(_ BitVec " + to_string(dynamic_cast(_sort).size) + ")"; + return "(_ BitVec " + std::to_string(dynamic_cast(_sort).size) + ")"; case Kind::Array: { auto const& arraySort = dynamic_cast(_sort); @@ -232,11 +231,11 @@ string SMTLib2Interface::toSmtLibSort(Sort const& _sort) case Kind::Tuple: { auto const& tupleSort = dynamic_cast(_sort); - string tupleName = "|" + tupleSort.name + "|"; + std::string tupleName = "|" + tupleSort.name + "|"; auto isName = [&](auto entry) { return entry.first == tupleName; }; if (ranges::find_if(m_userSorts, isName) == m_userSorts.end()) { - string decl("(declare-datatypes ((" + tupleName + " 0)) (((" + tupleName); + std::string decl("(declare-datatypes ((" + tupleName + " 0)) (((" + tupleName); smtAssert(tupleSort.members.size() == tupleSort.components.size(), ""); for (unsigned i = 0; i < tupleSort.members.size(); ++i) decl += " (|" + tupleSort.members.at(i) + "| " + toSmtLibSort(*tupleSort.components.at(i)) + ")"; @@ -252,24 +251,24 @@ string SMTLib2Interface::toSmtLibSort(Sort const& _sort) } } -string SMTLib2Interface::toSmtLibSort(vector const& _sorts) +std::string SMTLib2Interface::toSmtLibSort(std::vector const& _sorts) { - string ssort("("); + std::string ssort("("); for (auto const& sort: _sorts) ssort += toSmtLibSort(*sort) + " "; ssort += ")"; return ssort; } -void SMTLib2Interface::write(string _data) +void SMTLib2Interface::write(std::string _data) { smtAssert(!m_accumulatedOutput.empty(), ""); m_accumulatedOutput.back() += std::move(_data) + "\n"; } -string SMTLib2Interface::checkSatAndGetValuesCommand(vector const& _expressionsToEvaluate) +std::string SMTLib2Interface::checkSatAndGetValuesCommand(std::vector const& _expressionsToEvaluate) { - string command; + std::string command; if (_expressionsToEvaluate.empty()) command = "(check-sat)\n"; else @@ -279,22 +278,22 @@ string SMTLib2Interface::checkSatAndGetValuesCommand(vector const& _ { auto const& e = _expressionsToEvaluate.at(i); smtAssert(e.sort->kind == Kind::Int || e.sort->kind == Kind::Bool, "Invalid sort for expression to evaluate."); - command += "(declare-const |EVALEXPR_" + to_string(i) + "| " + (e.sort->kind == Kind::Int ? "Int" : "Bool") + ")\n"; - command += "(assert (= |EVALEXPR_" + to_string(i) + "| " + toSExpr(e) + "))\n"; + command += "(declare-const |EVALEXPR_" + std::to_string(i) + "| " + (e.sort->kind == Kind::Int ? "Int" : "Bool") + ")\n"; + command += "(assert (= |EVALEXPR_" + std::to_string(i) + "| " + toSExpr(e) + "))\n"; } command += "(check-sat)\n"; command += "(get-value ("; for (size_t i = 0; i < _expressionsToEvaluate.size(); i++) - command += "|EVALEXPR_" + to_string(i) + "| "; + command += "|EVALEXPR_" + std::to_string(i) + "| "; command += "))\n"; } return command; } -vector SMTLib2Interface::parseValues(string::const_iterator _start, string::const_iterator _end) +std::vector SMTLib2Interface::parseValues(std::string::const_iterator _start, std::string::const_iterator _end) { - vector values; + std::vector values; while (_start < _end) { auto valStart = find(_start, _end, ' '); @@ -308,7 +307,7 @@ vector SMTLib2Interface::parseValues(string::const_iterator _start, stri return values; } -string SMTLib2Interface::querySolver(string const& _input) +std::string SMTLib2Interface::querySolver(std::string const& _input) { h256 inputHash = keccak256(_input); if (m_queryResponses.count(inputHash)) @@ -322,3 +321,9 @@ string SMTLib2Interface::querySolver(string const& _input) m_unhandledQueries.push_back(_input); return "unknown\n"; } + +std::string SMTLib2Interface::dumpQuery(std::vector const& _expressionsToEvaluate) +{ + return boost::algorithm::join(m_accumulatedOutput, "\n") + + checkSatAndGetValuesCommand(_expressionsToEvaluate); +} diff --git a/compiler/libsmtutil/SMTLib2Interface.h b/compiler/libsmtutil/SMTLib2Interface.h index 9454d551..9ef8e94a 100644 --- a/compiler/libsmtutil/SMTLib2Interface.h +++ b/compiler/libsmtutil/SMTLib2Interface.h @@ -68,6 +68,8 @@ class SMTLib2Interface: public SolverInterface std::vector> const& userSorts() const { return m_userSorts; } + std::string dumpQuery(std::vector const& _expressionsToEvaluate); + private: void declareFunction(std::string const& _name, SortPointer const& _sort); diff --git a/compiler/libsmtutil/SMTPortfolio.cpp b/compiler/libsmtutil/SMTPortfolio.cpp index 77a404a4..76c69d01 100644 --- a/compiler/libsmtutil/SMTPortfolio.cpp +++ b/compiler/libsmtutil/SMTPortfolio.cpp @@ -26,29 +26,30 @@ #endif #include -using namespace std; using namespace solidity; using namespace solidity::util; using namespace solidity::frontend; using namespace solidity::smtutil; SMTPortfolio::SMTPortfolio( - map _smtlib2Responses, + std::map _smtlib2Responses, frontend::ReadCallback::Callback _smtCallback, [[maybe_unused]] SMTSolverChoice _enabledSolvers, - optional _queryTimeout + std::optional _queryTimeout, + bool _printQuery ): SolverInterface(_queryTimeout) { + solAssert(!_printQuery || _enabledSolvers == smtutil::SMTSolverChoice::SMTLIB2(), "Only SMTLib2 solver can be enabled to print queries"); if (_enabledSolvers.smtlib2) - m_solvers.emplace_back(make_unique(std::move(_smtlib2Responses), std::move(_smtCallback), m_queryTimeout)); + m_solvers.emplace_back(std::make_unique(std::move(_smtlib2Responses), std::move(_smtCallback), m_queryTimeout)); #ifdef HAVE_Z3 if (_enabledSolvers.z3 && Z3Interface::available()) - m_solvers.emplace_back(make_unique(m_queryTimeout)); + m_solvers.emplace_back(std::make_unique(m_queryTimeout)); #endif #ifdef HAVE_CVC4 if (_enabledSolvers.cvc4) - m_solvers.emplace_back(make_unique(m_queryTimeout)); + m_solvers.emplace_back(std::make_unique(m_queryTimeout)); #endif } @@ -70,7 +71,7 @@ void SMTPortfolio::pop() s->pop(); } -void SMTPortfolio::declareVariable(string const& _name, SortPointer const& _sort) +void SMTPortfolio::declareVariable(std::string const& _name, SortPointer const& _sort) { smtAssert(_sort, ""); for (auto const& s: m_solvers) @@ -113,14 +114,14 @@ void SMTPortfolio::addAssertion(Expression const& _expr) * * If all solvers return ERROR, the result is ERROR. */ -pair> SMTPortfolio::check(vector const& _expressionsToEvaluate) +std::pair> SMTPortfolio::check(std::vector const& _expressionsToEvaluate) { CheckResult lastResult = CheckResult::ERROR; - vector finalValues; + std::vector finalValues; for (auto const& s: m_solvers) { CheckResult result; - vector values; + std::vector values; tie(result, values) = s->check(_expressionsToEvaluate); if (solverAnswered(result)) { @@ -138,10 +139,10 @@ pair> SMTPortfolio::check(vector const& else if (result == CheckResult::UNKNOWN && lastResult == CheckResult::ERROR) lastResult = result; } - return make_pair(lastResult, finalValues); + return std::make_pair(lastResult, finalValues); } -vector SMTPortfolio::unhandledQueries() +std::vector SMTPortfolio::unhandledQueries() { // This code assumes that the constructor guarantees that // SmtLib2Interface is in position 0, if enabled. @@ -155,3 +156,12 @@ bool SMTPortfolio::solverAnswered(CheckResult result) { return result == CheckResult::SATISFIABLE || result == CheckResult::UNSATISFIABLE; } + +std::string SMTPortfolio::dumpQuery(std::vector const& _expressionsToEvaluate) +{ + // This code assumes that the constructor guarantees that + // SmtLib2Interface is in position 0, if enabled. + auto smtlib2 = dynamic_cast(m_solvers.front().get()); + solAssert(smtlib2, "Must use SMTLib2 solver to dump queries"); + return smtlib2->dumpQuery(_expressionsToEvaluate); +} diff --git a/compiler/libsmtutil/SMTPortfolio.h b/compiler/libsmtutil/SMTPortfolio.h index 3d82756c..cdb33368 100644 --- a/compiler/libsmtutil/SMTPortfolio.h +++ b/compiler/libsmtutil/SMTPortfolio.h @@ -46,7 +46,8 @@ class SMTPortfolio: public SolverInterface std::map _smtlib2Responses = {}, frontend::ReadCallback::Callback _smtCallback = {}, SMTSolverChoice _enabledSolvers = SMTSolverChoice::All(), - std::optional _queryTimeout = {} + std::optional _queryTimeout = {}, + bool _printQuery = false ); void reset() override; @@ -62,6 +63,9 @@ class SMTPortfolio: public SolverInterface std::vector unhandledQueries() override; size_t solvers() override { return m_solvers.size(); } + + std::string dumpQuery(std::vector const& _expressionsToEvaluate); + private: static bool solverAnswered(CheckResult result); diff --git a/compiler/libsmtutil/SolverInterface.h b/compiler/libsmtutil/SolverInterface.h index ca4c497d..4a21f184 100644 --- a/compiler/libsmtutil/SolverInterface.h +++ b/compiler/libsmtutil/SolverInterface.h @@ -42,28 +42,23 @@ namespace solidity::smtutil struct SMTSolverChoice { bool cvc4 = false; + bool eld = false; bool smtlib2 = false; bool z3 = false; - static constexpr SMTSolverChoice All() noexcept { return {true, true, true}; } - static constexpr SMTSolverChoice CVC4() noexcept { return {true, false, false}; } - static constexpr SMTSolverChoice SMTLIB2() noexcept { return {false, true, false}; } - static constexpr SMTSolverChoice Z3() noexcept { return {false, false, true}; } - static constexpr SMTSolverChoice None() noexcept { return {false, false, false}; } + static constexpr SMTSolverChoice All() noexcept { return {true, true, true, true}; } + static constexpr SMTSolverChoice CVC4() noexcept { return {true, false, false, false}; } + static constexpr SMTSolverChoice ELD() noexcept { return {false, true, false, false}; } + static constexpr SMTSolverChoice SMTLIB2() noexcept { return {false, false, true, false}; } + static constexpr SMTSolverChoice Z3() noexcept { return {false, false, false, true}; } + static constexpr SMTSolverChoice None() noexcept { return {false, false, false, false}; } static std::optional fromString(std::string const& _solvers) { SMTSolverChoice solvers; - if (_solvers == "all") - { - smtAssert(solvers.setSolver("cvc4"), ""); - smtAssert(solvers.setSolver("smtlib2"), ""); - smtAssert(solvers.setSolver("z3"), ""); - } - else - for (auto&& s: _solvers | ranges::views::split(',') | ranges::to>()) - if (!solvers.setSolver(s)) - return {}; + for (auto&& s: _solvers | ranges::views::split(',') | ranges::to>()) + if (!solvers.setSolver(s)) + return {}; return solvers; } @@ -71,6 +66,7 @@ struct SMTSolverChoice SMTSolverChoice& operator&=(SMTSolverChoice const& _other) { cvc4 &= _other.cvc4; + eld &= _other.eld; smtlib2 &= _other.smtlib2; z3 &= _other.z3; return *this; @@ -87,17 +83,20 @@ struct SMTSolverChoice bool operator==(SMTSolverChoice const& _other) const noexcept { return cvc4 == _other.cvc4 && + eld == _other.eld && smtlib2 == _other.smtlib2 && z3 == _other.z3; } bool setSolver(std::string const& _solver) { - static std::set const solvers{"cvc4", "smtlib2", "z3"}; + static std::set const solvers{"cvc4", "eld", "smtlib2", "z3"}; if (!solvers.count(_solver)) return false; if (_solver == "cvc4") cvc4 = true; + if (_solver == "eld") + eld = true; else if (_solver == "smtlib2") smtlib2 = true; else if (_solver == "z3") @@ -106,8 +105,8 @@ struct SMTSolverChoice } bool none() const noexcept { return !some(); } - bool some() const noexcept { return cvc4 || smtlib2 || z3; } - bool all() const noexcept { return cvc4 && smtlib2 && z3; } + bool some() const noexcept { return cvc4 || eld || smtlib2 || z3; } + bool all() const noexcept { return cvc4 && eld && smtlib2 && z3; } }; enum class CheckResult @@ -124,7 +123,7 @@ class Expression explicit Expression(std::shared_ptr _sort, std::string _name = ""): Expression(std::move(_name), {}, _sort) {} explicit Expression(std::string _name, std::vector _arguments, SortPointer _sort): name(std::move(_name)), arguments(std::move(_arguments)), sort(std::move(_sort)) {} - Expression(size_t _number): Expression(std::to_string(_number), {}, SortProvider::sintSort) {} + Expression(size_t _number): Expression(std::to_string(_number), {}, SortProvider::uintSort) {} Expression(u256 const& _number): Expression(_number.str(), {}, SortProvider::sintSort) {} Expression(s256 const& _number): Expression( _number >= 0 ? _number.str() : "-", @@ -190,7 +189,10 @@ class Expression static Expression ite(Expression _condition, Expression _trueValue, Expression _falseValue) { - smtAssert(*_trueValue.sort == *_falseValue.sort, ""); + if (_trueValue.sort->kind == Kind::Int) + smtAssert(_trueValue.sort->kind == _falseValue.sort->kind, ""); + else + smtAssert(*_trueValue.sort == *_falseValue.sort, ""); SortPointer sort = _trueValue.sort; return Expression("ite", std::vector{ std::move(_condition), std::move(_trueValue), std::move(_falseValue) @@ -214,7 +216,10 @@ class Expression std::shared_ptr arraySort = std::dynamic_pointer_cast(_array.sort); smtAssert(arraySort, ""); smtAssert(_index.sort, ""); - smtAssert(*arraySort->domain == *_index.sort, ""); + if (arraySort->domain->kind == Kind::Int) + smtAssert(arraySort->domain->kind == _index.sort->kind, ""); + else + smtAssert(*arraySort->domain == *_index.sort, ""); return Expression( "select", std::vector{std::move(_array), std::move(_index)}, @@ -231,7 +236,10 @@ class Expression smtAssert(_index.sort, ""); smtAssert(_element.sort, ""); smtAssert(*arraySort->domain == *_index.sort, ""); - smtAssert(*arraySort->range == *_element.sort, ""); + if (arraySort->domain->kind == Kind::Int) + smtAssert(arraySort->range->kind == _element.sort->kind, ""); + else + smtAssert(*arraySort->range == *_element.sort, ""); return Expression( "store", std::vector{std::move(_array), std::move(_index), std::move(_element)}, @@ -246,7 +254,10 @@ class Expression auto arraySort = std::dynamic_pointer_cast(sortSort->inner); smtAssert(sortSort && arraySort, ""); smtAssert(_value.sort, ""); - smtAssert(*arraySort->range == *_value.sort, ""); + if (arraySort->domain->kind == Kind::Int) + smtAssert(arraySort->range->kind == _value.sort->kind, ""); + else + smtAssert(*arraySort->range == *_value.sort, ""); return Expression( "const_array", std::vector{std::move(_sort), std::move(_value)}, diff --git a/compiler/libsmtutil/Sorts.cpp b/compiler/libsmtutil/Sorts.cpp index 543c7ba0..b45bb303 100644 --- a/compiler/libsmtutil/Sorts.cpp +++ b/compiler/libsmtutil/Sorts.cpp @@ -19,22 +19,20 @@ #include -using namespace std; - namespace solidity::smtutil { -shared_ptr const SortProvider::boolSort{make_shared(Kind::Bool)}; -shared_ptr const SortProvider::uintSort{make_shared(false)}; -shared_ptr const SortProvider::sintSort{make_shared(true)}; +std::shared_ptr const SortProvider::boolSort{std::make_shared(Kind::Bool)}; +std::shared_ptr const SortProvider::uintSort{std::make_shared(false)}; +std::shared_ptr const SortProvider::sintSort{std::make_shared(true)}; -shared_ptr SortProvider::intSort(bool _signed) +std::shared_ptr SortProvider::intSort(bool _signed) { if (_signed) return sintSort; return uintSort; } -shared_ptr const SortProvider::bitVectorSort{make_shared(256)}; +std::shared_ptr const SortProvider::bitVectorSort{std::make_shared(256)}; } diff --git a/compiler/libsmtutil/Sorts.h b/compiler/libsmtutil/Sorts.h index 107dc1f2..419ee9dc 100644 --- a/compiler/libsmtutil/Sorts.h +++ b/compiler/libsmtutil/Sorts.h @@ -57,9 +57,14 @@ struct IntSort: public Sort isSigned(_signed) {} - bool operator==(IntSort const& _other) const + bool operator==(Sort const& _other) const override { - return Sort::operator==(_other) && isSigned == _other.isSigned; + if (!Sort::operator==(_other)) + return false; + + auto otherIntSort = dynamic_cast(&_other); + smtAssert(otherIntSort); + return isSigned == otherIntSort->isSigned; } bool isSigned; @@ -72,9 +77,14 @@ struct BitVectorSort: public Sort size(_size) {} - bool operator==(BitVectorSort const& _other) const + bool operator==(Sort const& _other) const override { - return Sort::operator==(_other) && size == _other.size; + if (!Sort::operator==(_other)) + return false; + + auto otherBitVectorSort = dynamic_cast(&_other); + smtAssert(otherBitVectorSort); + return size == otherBitVectorSort->size; } unsigned size; diff --git a/compiler/libsmtutil/Z3CHCInterface.cpp b/compiler/libsmtutil/Z3CHCInterface.cpp index bfbc9841..a25890e8 100644 --- a/compiler/libsmtutil/Z3CHCInterface.cpp +++ b/compiler/libsmtutil/Z3CHCInterface.cpp @@ -23,21 +23,20 @@ #include #include -using namespace std; using namespace solidity; using namespace solidity::smtutil; -Z3CHCInterface::Z3CHCInterface(optional _queryTimeout): +Z3CHCInterface::Z3CHCInterface(std::optional _queryTimeout): CHCSolverInterface(_queryTimeout), - m_z3Interface(make_unique(m_queryTimeout)), + m_z3Interface(std::make_unique(m_queryTimeout)), m_context(m_z3Interface->context()), m_solver(*m_context) { Z3_get_version( - &get<0>(m_version), - &get<1>(m_version), - &get<2>(m_version), - &get<3>(m_version) + &std::get<0>(m_version), + &std::get<1>(m_version), + &std::get<2>(m_version), + &std::get<3>(m_version) ); // These need to be set globally. @@ -51,7 +50,7 @@ Z3CHCInterface::Z3CHCInterface(optional _queryTimeout): setSpacerOptions(); } -void Z3CHCInterface::declareVariable(string const& _name, SortPointer const& _sort) +void Z3CHCInterface::declareVariable(std::string const& _name, SortPointer const& _sort) { smtAssert(_sort, ""); m_z3Interface->declareVariable(_name, _sort); @@ -62,7 +61,7 @@ void Z3CHCInterface::registerRelation(Expression const& _expr) m_solver.register_relation(m_z3Interface->functions().at(_expr.name)); } -void Z3CHCInterface::addRule(Expression const& _expr, string const& _name) +void Z3CHCInterface::addRule(Expression const& _expr, std::string const& _name) { z3::expr rule = m_z3Interface->toZ3Expr(_expr); if (m_z3Interface->constants().empty()) @@ -77,7 +76,7 @@ void Z3CHCInterface::addRule(Expression const& _expr, string const& _name) } } -tuple Z3CHCInterface::query(Expression const& _expr) +std::tuple Z3CHCInterface::query(Expression const& _expr) { CheckResult result; try @@ -90,7 +89,7 @@ tuple Z3CHCInterface::que result = CheckResult::SATISFIABLE; // z3 version 4.8.8 modified Spacer to also return // proofs containing nonlinear clauses. - if (m_version >= tuple(4, 8, 8, 0)) + if (m_version >= std::tuple(4, 8, 8, 0)) { auto proof = m_solver.get_answer(); return {result, Expression(true), cexGraph(proof)}; @@ -113,7 +112,7 @@ tuple Z3CHCInterface::que } catch (z3::exception const& _err) { - set msgs{ + std::set msgs{ /// Resource limit (rlimit) exhausted. "max. resource limit exceeded", /// User given timeout exhausted. @@ -178,13 +177,13 @@ CHCSolverInterface::CexGraph Z3CHCInterface::cexGraph(z3::expr const& _proof) CexGraph graph; - stack proofStack; + std::stack proofStack; proofStack.push(_proof.arg(0)); auto const& root = proofStack.top(); graph.nodes.emplace(root.id(), m_z3Interface->fromZ3Expr(fact(root))); - set visited; + std::set visited; visited.insert(root.id()); while (!proofStack.empty()) @@ -227,16 +226,16 @@ z3::expr Z3CHCInterface::fact(z3::expr const& _node) return _node.arg(_node.num_args() - 1); } -string Z3CHCInterface::name(z3::expr const& _predicate) +std::string Z3CHCInterface::name(z3::expr const& _predicate) { smtAssert(_predicate.is_app(), ""); return _predicate.decl().name().str(); } -vector Z3CHCInterface::arguments(z3::expr const& _predicate) +std::vector Z3CHCInterface::arguments(z3::expr const& _predicate) { smtAssert(_predicate.is_app(), ""); - vector args; + std::vector args; for (unsigned i = 0; i < _predicate.num_args(); ++i) args.emplace_back(_predicate.arg(i).to_string()); return args; diff --git a/compiler/libsmtutil/Z3Interface.cpp b/compiler/libsmtutil/Z3Interface.cpp index 4abeffa1..a16e7566 100644 --- a/compiler/libsmtutil/Z3Interface.cpp +++ b/compiler/libsmtutil/Z3Interface.cpp @@ -26,7 +26,6 @@ #include #endif -using namespace std; using namespace solidity::smtutil; using namespace solidity::util; @@ -69,7 +68,7 @@ void Z3Interface::pop() m_solver.pop(); } -void Z3Interface::declareVariable(string const& _name, SortPointer const& _sort) +void Z3Interface::declareVariable(std::string const& _name, SortPointer const& _sort) { smtAssert(_sort, ""); if (_sort->kind == Kind::Function) @@ -80,7 +79,7 @@ void Z3Interface::declareVariable(string const& _name, SortPointer const& _sort) m_constants.emplace(_name, m_context.constant(_name.c_str(), z3Sort(*_sort))); } -void Z3Interface::declareFunction(string const& _name, Sort const& _sort) +void Z3Interface::declareFunction(std::string const& _name, Sort const& _sort) { smtAssert(_sort.kind == Kind::Function, ""); FunctionSort fSort = dynamic_cast(_sort); @@ -95,10 +94,10 @@ void Z3Interface::addAssertion(Expression const& _expr) m_solver.add(toZ3Expr(_expr)); } -pair> Z3Interface::check(vector const& _expressionsToEvaluate) +std::pair> Z3Interface::check(std::vector const& _expressionsToEvaluate) { CheckResult result; - vector values; + std::vector values; try { switch (m_solver.check()) @@ -123,7 +122,7 @@ pair> Z3Interface::check(vector const& _ } catch (z3::exception const& _err) { - set msgs{ + std::set msgs{ /// Resource limit (rlimit) exhausted. "max. resource limit exceeded", /// User given timeout exhausted. @@ -137,7 +136,7 @@ pair> Z3Interface::check(vector const& _ values.clear(); } - return make_pair(result, values); + return std::make_pair(result, values); } z3::expr Z3Interface::toZ3Expr(Expression const& _expr) @@ -150,7 +149,7 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) try { - string const& n = _expr.name; + std::string const& n = _expr.name; if (m_functions.count(n)) return m_functions.at(n)(arguments); else if (m_constants.count(n)) @@ -166,7 +165,7 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) return m_context.bool_val(false); else if (_expr.sort->kind == Kind::Sort) { - auto sortSort = dynamic_pointer_cast(_expr.sort); + auto sortSort = std::dynamic_pointer_cast(_expr.sort); smtAssert(sortSort, ""); return m_context.constant(n.c_str(), z3Sort(*sortSort->inner)); } @@ -233,7 +232,7 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) } else if (n == "bv2int") { - auto intSort = dynamic_pointer_cast(_expr.sort); + auto intSort = std::dynamic_pointer_cast(_expr.sort); smtAssert(intSort, ""); return z3::bv2int(arguments[0], intSort->isSigned); } @@ -243,9 +242,9 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) return z3::store(arguments[0], arguments[1], arguments[2]); else if (n == "const_array") { - shared_ptr sortSort = std::dynamic_pointer_cast(_expr.arguments[0].sort); + std::shared_ptr sortSort = std::dynamic_pointer_cast(_expr.arguments[0].sort); smtAssert(sortSort, ""); - auto arraySort = dynamic_pointer_cast(sortSort->inner); + auto arraySort = std::dynamic_pointer_cast(sortSort->inner); smtAssert(arraySort && arraySort->domain, ""); return z3::const_array(z3Sort(*arraySort->domain), arguments[1]); } @@ -285,7 +284,7 @@ Expression Z3Interface::fromZ3Expr(z3::expr const& _expr) if (_expr.is_quantifier()) { - string quantifierName; + std::string quantifierName; if (_expr.is_exists()) quantifierName = "exists"; else if (_expr.is_forall()) @@ -297,7 +296,7 @@ Expression Z3Interface::fromZ3Expr(z3::expr const& _expr) return Expression(quantifierName, {fromZ3Expr(_expr.body())}, sort); } smtAssert(_expr.is_app(), ""); - vector arguments; + std::vector arguments; for (unsigned i = 0; i < _expr.num_args(); ++i) arguments.push_back(fromZ3Expr(_expr.arg(i))); @@ -344,6 +343,12 @@ Expression Z3Interface::fromZ3Expr(z3::expr const& _expr) return arguments[0] % arguments[1]; else if (kind == Z3_OP_XOR) return arguments[0] ^ arguments[1]; + else if (kind == Z3_OP_BOR) + return arguments[0] | arguments[1]; + else if (kind == Z3_OP_BAND) + return arguments[0] & arguments[1]; + else if (kind == Z3_OP_BXOR) + return arguments[0] ^ arguments[1]; else if (kind == Z3_OP_BNOT) return !arguments[0]; else if (kind == Z3_OP_BSHL) @@ -364,12 +369,12 @@ Expression Z3Interface::fromZ3Expr(z3::expr const& _expr) return Expression::store(arguments[0], arguments[1], arguments[2]); else if (kind == Z3_OP_CONST_ARRAY) { - auto sortSort = make_shared(fromZ3Sort(_expr.get_sort())); + auto sortSort = std::make_shared(fromZ3Sort(_expr.get_sort())); return Expression::const_array(Expression(sortSort), arguments[0]); } else if (kind == Z3_OP_DT_CONSTRUCTOR) { - auto sortSort = make_shared(fromZ3Sort(_expr.get_sort())); + auto sortSort = std::make_shared(fromZ3Sort(_expr.get_sort())); return Expression::tuple_constructor(Expression(sortSort), arguments); } else if (kind == Z3_OP_DT_ACCESSOR) @@ -406,12 +411,12 @@ z3::sort Z3Interface::z3Sort(Sort const& _sort) case Kind::Tuple: { auto const& tupleSort = dynamic_cast(_sort); - vector cMembers; + std::vector cMembers; for (auto const& member: tupleSort.members) cMembers.emplace_back(member.c_str()); /// Using this instead of the function below because with that one /// we can't use `&sorts[0]` here. - vector sorts; + std::vector sorts; for (auto const& sort: tupleSort.components) sorts.push_back(z3Sort(*sort)); z3::func_decl_vector projs(m_context); @@ -433,7 +438,7 @@ z3::sort Z3Interface::z3Sort(Sort const& _sort) return m_context.int_sort(); } -z3::sort_vector Z3Interface::z3Sort(vector const& _sorts) +z3::sort_vector Z3Interface::z3Sort(std::vector const& _sorts) { z3::sort_vector z3Sorts(m_context); for (auto const& _sort: _sorts) @@ -448,27 +453,27 @@ SortPointer Z3Interface::fromZ3Sort(z3::sort const& _sort) if (_sort.is_int()) return SortProvider::sintSort; if (_sort.is_bv()) - return make_shared(_sort.bv_size()); + return std::make_shared(_sort.bv_size()); if (_sort.is_array()) - return make_shared(fromZ3Sort(_sort.array_domain()), fromZ3Sort(_sort.array_range())); + return std::make_shared(fromZ3Sort(_sort.array_domain()), fromZ3Sort(_sort.array_range())); if (_sort.is_datatype()) { auto name = _sort.name().str(); auto constructor = z3::func_decl(m_context, Z3_get_tuple_sort_mk_decl(m_context, _sort)); - vector memberNames; - vector memberSorts; + std::vector memberNames; + std::vector memberSorts; for (unsigned i = 0; i < constructor.arity(); ++i) { auto accessor = z3::func_decl(m_context, Z3_get_tuple_sort_field_decl(m_context, _sort, i)); memberNames.push_back(accessor.name().str()); memberSorts.push_back(fromZ3Sort(accessor.range())); } - return make_shared(name, memberNames, memberSorts); + return std::make_shared(name, memberNames, memberSorts); } smtAssert(false, ""); } -vector Z3Interface::fromZ3Sort(z3::sort_vector const& _sorts) +std::vector Z3Interface::fromZ3Sort(z3::sort_vector const& _sorts) { return applyMap(_sorts, [this](auto const& sort) { return fromZ3Sort(sort); }); } diff --git a/compiler/libsmtutil/Z3Loader.cpp b/compiler/libsmtutil/Z3Loader.cpp index 86b17aa0..0211f600 100644 --- a/compiler/libsmtutil/Z3Loader.cpp +++ b/compiler/libsmtutil/Z3Loader.cpp @@ -27,7 +27,6 @@ #endif #include -using namespace std; using namespace solidity; using namespace solidity::smtutil; @@ -41,7 +40,7 @@ void* Z3Loader::loadSymbol(char const* _name) const { smtAssert(m_handle, "Attempted to use dynamically loaded Z3, even though it is not available."); void* sym = dlsym(m_handle, _name); - smtAssert(sym, string("Symbol \"") + _name + "\" not found in libz3.so"); + smtAssert(sym, std::string("Symbol \"") + _name + "\" not found in libz3.so"); return sym; } @@ -59,7 +58,7 @@ bool Z3Loader::available() const Z3Loader::Z3Loader() { - string libname{"libz3.so." + to_string(Z3_MAJOR_VERSION) + "." + to_string(Z3_MINOR_VERSION)}; + std::string libname{"libz3.so." + std::to_string(Z3_MAJOR_VERSION) + "." + std::to_string(Z3_MINOR_VERSION)}; m_handle = dlmopen(LM_ID_NEWLM, libname.c_str(), RTLD_NOW); } diff --git a/compiler/libsolc/libsolc.cpp b/compiler/libsolc/libsolc.cpp index db589a52..b6b1763d 100644 --- a/compiler/libsolc/libsolc.cpp +++ b/compiler/libsolc/libsolc.cpp @@ -32,7 +32,6 @@ #include "license.h" -using namespace std; using namespace solidity; using namespace solidity::util; @@ -43,21 +42,21 @@ using solidity::frontend::StandardCompiler; namespace { -// The strings in this list must not be resized after they have been added here (via solidity_alloc()), because +// The std::strings in this list must not be resized after they have been added here (via solidity_alloc()), because // this may potentially change the pointer that was passed to the caller from solidity_alloc(). -static list solidityAllocations; +static std::list solidityAllocations; /// Find the equivalent to @p _data in the list of allocations of solidity_alloc(), /// removes it from the list and returns its value. /// /// If any invalid argument is being passed, it is considered a programming error /// on the caller-side and hence, will call abort() then. -string takeOverAllocation(char const* _data) +std::string takeOverAllocation(char const* _data) { for (auto iter = begin(solidityAllocations); iter != end(solidityAllocations); ++iter) if (iter->data() == _data) { - string chunk = std::move(*iter); + std::string chunk = std::move(*iter); solidityAllocations.erase(iter); return chunk; } @@ -65,11 +64,11 @@ string takeOverAllocation(char const* _data) abort(); } -/// Resizes a std::string to the proper length based on the occurrence of a zero terminator. -void truncateCString(string& _data) +/// Resizes a std::std::string to the proper length based on the occurrence of a zero terminator. +void truncateCString(std::string& _data) { size_t pos = _data.find('\0'); - if (pos != string::npos) + if (pos != std::string::npos) _data.resize(pos); } @@ -78,7 +77,7 @@ ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback, vo ReadCallback::Callback readCallback; if (_readCallback) { - readCallback = [=](string const& _kind, string const& _data) + readCallback = [=](std::string const& _kind, std::string const& _data) { char* contents_c = nullptr; char* error_c = nullptr; @@ -107,7 +106,7 @@ ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback, vo return readCallback; } -string compile(string _input, CStyleReadFileCallback _readCallback, void* _readContext) +std::string compile(std::string _input, CStyleReadFileCallback _readCallback, void* _readContext) { StandardCompiler compiler(wrapReadCallback(_readCallback, _readContext)); return compiler.compile(std::move(_input)); @@ -119,7 +118,7 @@ extern "C" { extern char const* solidity_license() noexcept { - static string fullLicenseText = otherLicenses + licenseText; + static std::string fullLicenseText = otherLicenses + licenseText; return fullLicenseText.c_str(); } @@ -202,7 +201,7 @@ extern void file_reader_add_or_update_file(void *p, const char* path, const char extern char* file_reader_source_unit_name(void *p, const char* path) noexcept { FileReader *fileReader = (FileReader *)p; - string name = fileReader->cliPathToSourceUnitName(boost::filesystem::path(path)); + std::string name = fileReader->cliPathToSourceUnitName(boost::filesystem::path(path)); #ifdef FILE_READER_DEBUG cout << "file_reader_source_unit_name " << path << " " << name << endl; #endif diff --git a/compiler/libsolidity/CMakeLists.txt b/compiler/libsolidity/CMakeLists.txt index 3f433b62..2c79fed0 100644 --- a/compiler/libsolidity/CMakeLists.txt +++ b/compiler/libsolidity/CMakeLists.txt @@ -63,6 +63,7 @@ set(sources ast/CallGraph.cpp ast/CallGraph.h ast/ExperimentalFeatures.h + ast/UserDefinableOperators.h ast/Types.cpp ast/Types.h ast/TypeProvider.cpp @@ -78,10 +79,14 @@ set(sources interface/Natspec.h interface/OptimiserSettings.h interface/ReadFile.h + interface/SMTSolverCommand.cpp + interface/SMTSolverCommand.h interface/StandardCompiler.cpp interface/StandardCompiler.h interface/Version.cpp interface/Version.h + lsp/DocumentHoverHandler.cpp + lsp/DocumentHoverHandler.h lsp/FileRepository.cpp lsp/FileRepository.h lsp/GotoDefinition.cpp @@ -103,7 +108,27 @@ set(sources parsing/Parser.cpp parsing/Parser.h parsing/Token.h - + experimental/analysis/Analysis.cpp + experimental/analysis/Analysis.h + experimental/analysis/DebugWarner.cpp + experimental/analysis/DebugWarner.h + experimental/analysis/FunctionDependencyAnalysis.cpp + experimental/analysis/FunctionDependencyAnalysis.h + experimental/analysis/TypeClassRegistration.cpp + experimental/analysis/TypeClassRegistration.h + experimental/analysis/TypeInference.cpp + experimental/analysis/TypeInference.h + experimental/analysis/TypeRegistration.cpp + experimental/analysis/TypeRegistration.h + experimental/analysis/SyntaxRestrictor.cpp + experimental/analysis/SyntaxRestrictor.h + experimental/ast/FunctionCallGraph.h + experimental/ast/Type.cpp + experimental/ast/Type.h + experimental/ast/TypeSystem.cpp + experimental/ast/TypeSystem.h + experimental/ast/TypeSystemHelper.cpp + experimental/ast/TypeSystemHelper.h codegen/DictOperations.cpp codegen/DictOperations.hpp @@ -151,4 +176,4 @@ set(sources ) add_library(solidity ${sources}) -target_link_libraries(solidity PUBLIC langutil solutil Boost::boost Boost::filesystem Boost::system fmt::fmt-header-only) +target_link_libraries(solidity PUBLIC langutil solutil Boost::boost Boost::filesystem Boost::system fmt::fmt-header-only Threads::Threads) diff --git a/compiler/libsolidity/analysis/ConstantEvaluator.cpp b/compiler/libsolidity/analysis/ConstantEvaluator.cpp index 7fde36ec..8e18183a 100644 --- a/compiler/libsolidity/analysis/ConstantEvaluator.cpp +++ b/compiler/libsolidity/analysis/ConstantEvaluator.cpp @@ -29,7 +29,6 @@ #include -using namespace std; using namespace solidity; using namespace solidity::frontend; using namespace solidity::langutil; @@ -47,9 +46,9 @@ bool fitsPrecisionExp(bigint const& _base, bigint const& _exp) solAssert(_base > 0, ""); - size_t const bitsMax = 4096; + std::size_t const bitsMax = 4096; - size_t mostSignificantBaseBit = static_cast(boost::multiprecision::msb(_base)); + std::size_t mostSignificantBaseBit = static_cast(boost::multiprecision::msb(_base)); if (mostSignificantBaseBit == 0) // _base == 1 return true; if (mostSignificantBaseBit > bitsMax) // _base >= 2 ^ 4096 @@ -68,7 +67,7 @@ bool fitsPrecisionBase2(bigint const& _mantissa, uint32_t _expBase2) } -optional ConstantEvaluator::evaluateBinaryOperator(Token _operator, rational const& _left, rational const& _right) +std::optional ConstantEvaluator::evaluateBinaryOperator(Token _operator, rational const& _left, rational const& _right) { bool fractional = _left.denominator() != 1 || _right.denominator() != 1; switch (_operator) @@ -76,17 +75,17 @@ optional ConstantEvaluator::evaluateBinaryOperator(Token _operator, ra //bit operations will only be enabled for integers and fixed types that resemble integers case Token::BitOr: if (fractional) - return nullopt; + return std::nullopt; else return _left.numerator() | _right.numerator(); case Token::BitXor: if (fractional) - return nullopt; + return std::nullopt; else return _left.numerator() ^ _right.numerator(); case Token::BitAnd: if (fractional) - return nullopt; + return std::nullopt; else return _left.numerator() & _right.numerator(); case Token::Add: return _left + _right; @@ -94,12 +93,12 @@ optional ConstantEvaluator::evaluateBinaryOperator(Token _operator, ra case Token::Mul: return _left * _right; case Token::Div: if (_right == rational(0)) - return nullopt; + return std::nullopt; else return _left / _right; case Token::Mod: if (_right == rational(0)) - return nullopt; + return std::nullopt; else if (fractional) { rational tempValue = _left / _right; @@ -111,7 +110,7 @@ optional ConstantEvaluator::evaluateBinaryOperator(Token _operator, ra case Token::Exp: { if (_right.denominator() != 1) - return nullopt; + return std::nullopt; bigint const& exp = _right.numerator(); // x ** 0 = 1 @@ -127,13 +126,13 @@ optional ConstantEvaluator::evaluateBinaryOperator(Token _operator, ra } else { - if (abs(exp) > numeric_limits::max()) - return nullopt; // This will need too much memory to represent. + if (abs(exp) > std::numeric_limits::max()) + return std::nullopt; // This will need too much memory to represent. uint32_t absExp = bigint(abs(exp)).convert_to(); if (!fitsPrecisionExp(abs(_left.numerator()), absExp) || !fitsPrecisionExp(abs(_left.denominator()), absExp)) - return nullopt; + return std::nullopt; static auto const optimizedPow = [](bigint const& _base, uint32_t _exponent) -> bigint { if (_base == 1) @@ -158,18 +157,18 @@ optional ConstantEvaluator::evaluateBinaryOperator(Token _operator, ra case Token::SHL: { if (fractional) - return nullopt; + return std::nullopt; else if (_right < 0) - return nullopt; - else if (_right > numeric_limits::max()) - return nullopt; + return std::nullopt; + else if (_right > std::numeric_limits::max()) + return std::nullopt; if (_left.numerator() == 0) return 0; else { uint32_t exponent = _right.numerator().convert_to(); if (!fitsPrecisionBase2(abs(_left.numerator()), exponent)) - return nullopt; + return std::nullopt; return _left.numerator() * boost::multiprecision::pow(bigint(2), exponent); } break; @@ -179,11 +178,11 @@ optional ConstantEvaluator::evaluateBinaryOperator(Token _operator, ra case Token::SAR: { if (fractional) - return nullopt; + return std::nullopt; else if (_right < 0) - return nullopt; - else if (_right > numeric_limits::max()) - return nullopt; + return std::nullopt; + else if (_right > std::numeric_limits::max()) + return std::nullopt; if (_left.numerator() == 0) return 0; else @@ -209,60 +208,60 @@ optional ConstantEvaluator::evaluateBinaryOperator(Token _operator, ra break; } default: - return nullopt; + return std::nullopt; } } -optional ConstantEvaluator::evaluateUnaryOperator(Token _operator, rational const& _input) +std::optional ConstantEvaluator::evaluateUnaryOperator(Token _operator, rational const& _input) { switch (_operator) { case Token::BitNot: if (_input.denominator() != 1) - return nullopt; + return std::nullopt; else return ~_input.numerator(); case Token::Sub: return -_input; default: - return nullopt; + return std::nullopt; } } namespace { -optional convertType(rational const& _value, Type const& _type) +std::optional convertType(rational const& _value, Type const& _type) { if (_type.category() == Type::Category::RationalNumber) return TypedRational{TypeProvider::rationalNumber(_value), _value}; else if (auto const* integerType = dynamic_cast(&_type)) { if (_value > integerType->maxValue() || _value < integerType->minValue()) - return nullopt; + return std::nullopt; else return TypedRational{&_type, _value.numerator() / _value.denominator()}; } else - return nullopt; + return std::nullopt; } -optional convertType(optional const& _value, Type const& _type) +std::optional convertType(std::optional const& _value, Type const& _type) { - return _value ? convertType(_value->value, _type) : nullopt; + return _value ? convertType(_value->value, _type) : std::nullopt; } -optional constantToTypedValue(Type const& _type) +std::optional constantToTypedValue(Type const& _type) { if (_type.category() == Type::Category::RationalNumber) return TypedRational{&_type, dynamic_cast(_type).value()}; else - return nullopt; + return std::nullopt; } } -optional ConstantEvaluator::evaluate( +std::optional ConstantEvaluator::evaluate( langutil::ErrorReporter& _errorReporter, Expression const& _expr ) @@ -271,7 +270,7 @@ optional ConstantEvaluator::evaluate( } -optional ConstantEvaluator::evaluate(ASTNode const& _node) +std::optional ConstantEvaluator::evaluate(ASTNode const& _node) { if (!m_values.count(&_node)) { @@ -280,7 +279,7 @@ optional ConstantEvaluator::evaluate(ASTNode const& _node) solAssert(varDecl->isConstant(), ""); // In some circumstances, we do not yet have a type for the variable. if (!varDecl->value() || !varDecl->type()) - m_values[&_node] = nullopt; + m_values[&_node] = std::nullopt; else { m_depth++; @@ -298,7 +297,7 @@ optional ConstantEvaluator::evaluate(ASTNode const& _node) { expression->accept(*this); if (!m_values.count(&_node)) - m_values[&_node] = nullopt; + m_values[&_node] = std::nullopt; } } return m_values.at(&_node); @@ -306,7 +305,7 @@ optional ConstantEvaluator::evaluate(ASTNode const& _node) void ConstantEvaluator::endVisit(UnaryOperation const& _operation) { - optional value = evaluate(_operation.subExpression()); + std::optional value = evaluate(_operation.subExpression()); if (!value) return; @@ -317,9 +316,9 @@ void ConstantEvaluator::endVisit(UnaryOperation const& _operation) if (!value) return; - if (optional result = evaluateUnaryOperator(_operation.getOperator(), value->value)) + if (std::optional result = evaluateUnaryOperator(_operation.getOperator(), value->value)) { - optional convertedValue = convertType(*result, *resultType); + std::optional convertedValue = convertType(*result, *resultType); if (!convertedValue) m_errorReporter.fatalTypeError( 3667_error, @@ -332,8 +331,8 @@ void ConstantEvaluator::endVisit(UnaryOperation const& _operation) void ConstantEvaluator::endVisit(BinaryOperation const& _operation) { - optional left = evaluate(_operation.leftExpression()); - optional right = evaluate(_operation.rightExpression()); + std::optional left = evaluate(_operation.leftExpression()); + std::optional right = evaluate(_operation.rightExpression()); if (!left || !right) return; @@ -349,7 +348,7 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation) 6020_error, _operation.location(), "Operator " + - string(TokenTraits::toString(_operation.getOperator())) + + std::string(TokenTraits::toString(_operation.getOperator())) + " not compatible with types " + left->type->toString() + " and " + @@ -363,9 +362,9 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation) if (!left || !right) return; - if (optional value = evaluateBinaryOperator(_operation.getOperator(), left->value, right->value)) + if (std::optional value = evaluateBinaryOperator(_operation.getOperator(), left->value, right->value)) { - optional convertedValue = convertType(*value, *resultType); + std::optional convertedValue = convertType(*value, *resultType); if (!convertedValue) m_errorReporter.fatalTypeError( 2643_error, diff --git a/compiler/libsolidity/analysis/ContractLevelChecker.cpp b/compiler/libsolidity/analysis/ContractLevelChecker.cpp index 0883e7ee..048193d2 100644 --- a/compiler/libsolidity/analysis/ContractLevelChecker.cpp +++ b/compiler/libsolidity/analysis/ContractLevelChecker.cpp @@ -28,9 +28,10 @@ #include #include +#include + #include -using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; @@ -47,10 +48,10 @@ bool hasEqualExternalCallableParameters(T const& _a, B const& _b) } template -map> filterDeclarations( - map> const& _declarations) +std::map> filterDeclarations( + std::map> const& _declarations) { - map> filteredDeclarations; + std::map> filteredDeclarations; for (auto const& [name, overloads]: _declarations) for (auto const* declaration: overloads) if (auto typedDeclaration = dynamic_cast(declaration)) @@ -102,7 +103,7 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co { /// Checks that two functions with the same name defined in this contract have different /// argument types and that there is at most one constructor. - map> functions; + std::map> functions; FunctionDefinition const* constructor = nullptr; FunctionDefinition const* fallback = nullptr; FunctionDefinition const* receive = nullptr; @@ -178,7 +179,7 @@ void ContractLevelChecker::checkDuplicateEvents(ContractDefinition const& _contr { /// Checks that two events with the same name defined in this contract have different /// argument types - map> events; + std::map> events; for (auto const* contract: _contract.annotation().linearizedBaseContracts) for (EventDefinition const* event: contract->events()) events[event->name()].push_back(event); @@ -208,12 +209,12 @@ void ContractLevelChecker::checkReceiveFunction(ContractDefinition const& _contr } template -void ContractLevelChecker::findDuplicateDefinitions(map> const& _definitions) +void ContractLevelChecker::findDuplicateDefinitions(std::map> const& _definitions) { for (auto const& it: _definitions) { - vector const& overloads = it.second; - set reported; + std::vector const& overloads = it.second; + std::set reported; for (size_t i = 0; i < overloads.size() && !reported.count(i); ++i) { SecondarySourceLocation ssl; @@ -241,15 +242,15 @@ void ContractLevelChecker::findDuplicateDefinitions(map> const if (ssl.infos.size() > 0) { ErrorId error; - string message; - if constexpr (is_same_v) + std::string message; + if constexpr (std::is_same_v) { error = 1686_error; message = "Function with same name and parameter types defined twice."; } else { - static_assert(is_same_v, "Expected \"FunctionDefinition const*\" or \"EventDefinition const*\""); + static_assert(std::is_same_v, "Expected \"FunctionDefinition const*\" or \"EventDefinition const*\""); error = 5883_error; message = "Event with same name and parameter types defined twice."; } @@ -271,7 +272,7 @@ void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _c { // Collects functions, static variable getters and modifiers. If they // override (unimplemented) base class ones, they are replaced. - set proxies; + std::set proxies; auto registerProxy = [&proxies](OverrideProxy const& _overrideProxy) { @@ -299,9 +300,6 @@ void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _c } // Set to not fully implemented if at least one flag is false. - // Note that `_contract.annotation().unimplementedDeclarations` has already been - // pre-filled by `checkBaseConstructorArguments`. - // for (auto const& proxy: proxies) if (proxy.unimplemented()) _contract.annotation().unimplementedDeclarations->push_back(proxy.declaration()); @@ -339,7 +337,7 @@ void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _c void ContractLevelChecker::checkBaseConstructorArguments(ContractDefinition const& _contract) { - vector const& bases = _contract.annotation().linearizedBaseContracts; + std::vector const& bases = _contract.annotation().linearizedBaseContracts; // Determine the arguments that are used for the base constructors. for (ContractDefinition const* contract: bases) @@ -377,11 +375,27 @@ void ContractLevelChecker::checkBaseConstructorArguments(ContractDefinition cons // check that we get arguments for all base constructors that need it. // If not mark the contract as abstract (not fully implemented) - for (ContractDefinition const* contract: bases) - if (FunctionDefinition const* constructor = contract->constructor()) - if (contract != &_contract && !constructor->parameters().empty()) - if (!_contract.annotation().baseConstructorArguments.count(constructor)) - _contract.annotation().unimplementedDeclarations->push_back(constructor); + if (_contract.contractKind() == ContractKind::Contract && !_contract.abstract()) + for (ContractDefinition const* baseContract: bases) + if (FunctionDefinition const* baseConstructor = baseContract->constructor()) + if ( + baseContract != &_contract && + !baseConstructor->parameters().empty() && + _contract.annotation().baseConstructorArguments.count(baseConstructor) == 0 + ) + m_errorReporter.typeError( + 3415_error, + _contract.location(), + SecondarySourceLocation{}.append( + "Base constructor parameters:", + baseConstructor->parameterList().location() + ), + fmt::format( + "No arguments passed to the base constructor. " + "Specify the arguments or mark \"{}\" as abstract.", + *_contract.annotation().canonicalName + ) + ); } void ContractLevelChecker::annotateBaseConstructorArguments( @@ -430,7 +444,7 @@ void ContractLevelChecker::annotateBaseConstructorArguments( void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _contract) { - map>> externalDeclarations; + std::map>> externalDeclarations; for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts) { for (FunctionDefinition const* f: contract->definedFunctions()) @@ -467,7 +481,7 @@ void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _c void ContractLevelChecker::checkHashCollisions(ContractDefinition const& _contract) { - set> hashes; + std::set> hashes; for (auto const& it: _contract.interfaceFunctionList()) { util::FixedHash<4> const& hash = it.first; @@ -475,7 +489,7 @@ void ContractLevelChecker::checkHashCollisions(ContractDefinition const& _contra m_errorReporter.fatalTypeError( 1860_error, _contract.location(), - string("Function signature hash collision for ") + it.second->externalSignature() + std::string("Function signature hash collision for ") + it.second->externalSignature() ); hashes.insert(hash); } diff --git a/compiler/libsolidity/analysis/ControlFlowAnalyzer.cpp b/compiler/libsolidity/analysis/ControlFlowAnalyzer.cpp index 557be380..f4e9d33a 100644 --- a/compiler/libsolidity/analysis/ControlFlowAnalyzer.cpp +++ b/compiler/libsolidity/analysis/ControlFlowAnalyzer.cpp @@ -25,7 +25,6 @@ #include -using namespace std; using namespace std::placeholders; using namespace solidity::langutil; using namespace solidity::frontend; @@ -44,7 +43,7 @@ void ControlFlowAnalyzer::analyze(FunctionDefinition const& _function, ContractD if (!_function.isImplemented()) return; - optional mostDerivedContractName; + std::optional mostDerivedContractName; // The name of the most derived contract only required if it differs from // the functions contract @@ -61,13 +60,13 @@ void ControlFlowAnalyzer::analyze(FunctionDefinition const& _function, ContractD } -void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit, bool _emptyBody, optional _contractName) +void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit, bool _emptyBody, std::optional _contractName) { struct NodeInfo { - set unassignedVariablesAtEntry; - set unassignedVariablesAtExit; - set uninitializedVariableAccesses; + std::set unassignedVariablesAtEntry; + std::set unassignedVariablesAtExit; + std::set uninitializedVariableAccesses; /// Propagate the information from another node to this node. /// To be used to propagate information from a node to its exit nodes. /// Returns true, if new variables were added and thus the current node has @@ -84,8 +83,8 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod ; } }; - map nodeInfos; - set nodesToTraverse; + std::map nodeInfos; + std::set nodesToTraverse; nodesToTraverse.insert(_entry); // Walk all paths starting from the nodes in ``nodesToTraverse`` until ``NodeInfo::propagateFrom`` @@ -105,10 +104,6 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod case VariableOccurrence::Kind::Assignment: unassignedVariables.erase(&variableOccurrence.declaration()); break; - case VariableOccurrence::Kind::InlineAssembly: - // We consider all variables referenced in inline assembly as accessed. - // So far any reference is enough, but we might want to actually analyze - // the control flow in the assembly at some point. case VariableOccurrence::Kind::Access: case VariableOccurrence::Kind::Return: break; @@ -131,7 +126,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod auto const& exitInfo = nodeInfos[_exit]; if (!exitInfo.uninitializedVariableAccesses.empty()) { - vector uninitializedAccessesOrdered( + std::vector uninitializedAccessesOrdered( exitInfo.uninitializedVariableAccesses.begin(), exitInfo.uninitializedVariableAccesses.end() ); diff --git a/compiler/libsolidity/analysis/ControlFlowBuilder.cpp b/compiler/libsolidity/analysis/ControlFlowBuilder.cpp index a8179e5b..9665e71c 100644 --- a/compiler/libsolidity/analysis/ControlFlowBuilder.cpp +++ b/compiler/libsolidity/analysis/ControlFlowBuilder.cpp @@ -19,10 +19,8 @@ #include #include -using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; -using namespace std; ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow, ContractDefinition const* _contract): m_nodeContainer(_nodeContainer), @@ -34,13 +32,13 @@ ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, Funct } -unique_ptr ControlFlowBuilder::createFunctionFlow( +std::unique_ptr ControlFlowBuilder::createFunctionFlow( CFG::NodeContainer& _nodeContainer, FunctionDefinition const& _function, ContractDefinition const* _contract ) { - auto functionFlow = make_unique(); + auto functionFlow = std::make_unique(); functionFlow->entry = _nodeContainer.newNode(); functionFlow->exit = _nodeContainer.newNode(); functionFlow->revert = _nodeContainer.newNode(); @@ -61,17 +59,53 @@ bool ControlFlowBuilder::visit(BinaryOperation const& _operation) case Token::And: { visitNode(_operation); + solAssert(*_operation.annotation().userDefinedFunction == nullptr); appendControlFlow(_operation.leftExpression()); auto nodes = splitFlow<2>(); nodes[0] = createFlow(nodes[0], _operation.rightExpression()); mergeFlow(nodes, nodes[1]); - return false; } default: - return ASTConstVisitor::visit(_operation); + { + if (*_operation.annotation().userDefinedFunction != nullptr) + { + visitNode(_operation); + _operation.leftExpression().accept(*this); + _operation.rightExpression().accept(*this); + + m_currentNode->functionDefinition = *_operation.annotation().userDefinedFunction; + + auto nextNode = newLabel(); + + connect(m_currentNode, nextNode); + m_currentNode = nextNode; + return false; + } + } + } + return ASTConstVisitor::visit(_operation); +} + +bool ControlFlowBuilder::visit(UnaryOperation const& _operation) +{ + solAssert(!!m_currentNode); + + if (*_operation.annotation().userDefinedFunction != nullptr) + { + visitNode(_operation); + _operation.subExpression().accept(*this); + m_currentNode->functionDefinition = *_operation.annotation().userDefinedFunction; + + auto nextNode = newLabel(); + + connect(m_currentNode, nextNode); + m_currentNode = nextNode; + return false; } + + return ASTConstVisitor::visit(_operation); } bool ControlFlowBuilder::visit(Conditional const& _conditional) @@ -333,8 +367,7 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) _functionCall.expression().accept(*this); ASTNode::listAccept(_functionCall.arguments(), *this); - solAssert(!m_currentNode->functionCall); - m_currentNode->functionCall = &_functionCall; + m_currentNode->functionDefinition = ASTNode::resolveFunctionCall(_functionCall, m_contract); auto nextNode = newLabel(); @@ -351,6 +384,8 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) bool ControlFlowBuilder::visit(ModifierInvocation const& _modifierInvocation) { + solAssert(m_contract, "Free functions cannot have modifiers"); + if (auto arguments = _modifierInvocation.arguments()) for (auto& argument: *arguments) appendControlFlow(*argument); @@ -444,12 +479,6 @@ bool ControlFlowBuilder::visit(FunctionTypeName const& _functionTypeName) return false; } -bool ControlFlowBuilder::visit(InlineAssembly const& /*_inlineAssembly*/) -{ - solAssert(!!m_currentNode, ""); - return true; -} - bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration) { solAssert(!!m_currentNode, ""); diff --git a/compiler/libsolidity/analysis/ControlFlowBuilder.h b/compiler/libsolidity/analysis/ControlFlowBuilder.h index 10e8e636..44e7a973 100644 --- a/compiler/libsolidity/analysis/ControlFlowBuilder.h +++ b/compiler/libsolidity/analysis/ControlFlowBuilder.h @@ -37,18 +37,19 @@ class ControlFlowBuilder: private ASTConstVisitor static std::unique_ptr createFunctionFlow( CFG::NodeContainer& _nodeContainer, FunctionDefinition const& _function, - ContractDefinition const* _contract = nullptr + ContractDefinition const* _contract ); private: explicit ControlFlowBuilder( CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow, - ContractDefinition const* _contract = nullptr + ContractDefinition const* _contract ); // Visits for constructing the control flow. bool visit(BinaryOperation const& _operation) override; + bool visit(UnaryOperation const& _operation) override; bool visit(Conditional const& _conditional) override; bool visit(TryStatement const& _tryStatement) override; bool visit(IfStatement const& _ifStatement) override; @@ -69,7 +70,6 @@ class ControlFlowBuilder: private ASTConstVisitor // Visits for filling variable occurrences. bool visit(FunctionTypeName const& _functionTypeName) override; - bool visit(InlineAssembly const& _inlineAssembly) override; bool visit(VariableDeclaration const& _variableDeclaration) override; bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; bool visit(Identifier const& _identifier) override; diff --git a/compiler/libsolidity/analysis/ControlFlowGraph.cpp b/compiler/libsolidity/analysis/ControlFlowGraph.cpp index ca36b421..518743ef 100644 --- a/compiler/libsolidity/analysis/ControlFlowGraph.cpp +++ b/compiler/libsolidity/analysis/ControlFlowGraph.cpp @@ -20,7 +20,6 @@ #include -using namespace std; using namespace solidity::langutil; using namespace solidity::frontend; @@ -34,7 +33,11 @@ bool CFG::constructFlow(ASTNode const& _astRoot) bool CFG::visit(FunctionDefinition const& _function) { if (_function.isImplemented() && _function.isFree()) - m_functionControlFlow[{nullptr, &_function}] = ControlFlowBuilder::createFunctionFlow(m_nodeContainer, _function); + m_functionControlFlow[{nullptr, &_function}] = ControlFlowBuilder::createFunctionFlow( + m_nodeContainer, + _function, + nullptr /* _contract */ + ); return false; } diff --git a/compiler/libsolidity/analysis/ControlFlowGraph.h b/compiler/libsolidity/analysis/ControlFlowGraph.h index 7383783f..7e519205 100644 --- a/compiler/libsolidity/analysis/ControlFlowGraph.h +++ b/compiler/libsolidity/analysis/ControlFlowGraph.h @@ -48,7 +48,6 @@ class VariableOccurrence Access, Return, Assignment, - InlineAssembly }; VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional _occurrence = {}): m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(std::move(_occurrence)) @@ -98,9 +97,8 @@ struct CFGNode std::vector entries; /// Exit nodes. All CFG nodes to which control flow may continue after this node. std::vector exits; - /// Function call done by this node - FunctionCall const* functionCall = nullptr; - + /// Resolved definition of the function called by this node + FunctionDefinition const* functionDefinition = nullptr; /// Variable occurrences in the node. std::vector variableOccurrences; // Source location of this control flow block. diff --git a/compiler/libsolidity/analysis/ControlFlowRevertPruner.cpp b/compiler/libsolidity/analysis/ControlFlowRevertPruner.cpp index ae9c18a1..c96ad2b0 100644 --- a/compiler/libsolidity/analysis/ControlFlowRevertPruner.cpp +++ b/compiler/libsolidity/analysis/ControlFlowRevertPruner.cpp @@ -81,27 +81,23 @@ void ControlFlowRevertPruner::findRevertStates() if (_node == functionFlow.exit) foundExit = true; - if (auto const* functionCall = _node->functionCall) + auto const* resolvedFunction = _node->functionDefinition; + if (resolvedFunction && resolvedFunction->isImplemented()) { - auto const* resolvedFunction = ASTNode::resolveFunctionCall(*functionCall, item.contract); - - if (resolvedFunction && resolvedFunction->isImplemented()) + CFG::FunctionContractTuple calledFunctionTuple{ + findScopeContract(*resolvedFunction, item.contract), + resolvedFunction + }; + switch (m_functions.at(calledFunctionTuple)) { - CFG::FunctionContractTuple calledFunctionTuple{ - findScopeContract(*resolvedFunction, item.contract), - resolvedFunction - }; - switch (m_functions.at(calledFunctionTuple)) - { - case RevertState::Unknown: - wakeUp[calledFunctionTuple].insert(item); - foundUnknown = true; - return; - case RevertState::AllPathsRevert: - return; - case RevertState::HasNonRevertingPath: - break; - } + case RevertState::Unknown: + wakeUp[calledFunctionTuple].insert(item); + foundUnknown = true; + return; + case RevertState::AllPathsRevert: + return; + case RevertState::HasNonRevertingPath: + break; } } @@ -135,30 +131,26 @@ void ControlFlowRevertPruner::modifyFunctionFlows() FunctionFlow const& functionFlow = m_cfg.functionFlow(*item.first.function, item.first.contract); solidity::util::BreadthFirstSearch{{functionFlow.entry}}.run( [&](CFGNode* _node, auto&& _addChild) { - if (auto const* functionCall = _node->functionCall) - { - auto const* resolvedFunction = ASTNode::resolveFunctionCall(*functionCall, item.first.contract); - - if (resolvedFunction && resolvedFunction->isImplemented()) - switch (m_functions.at({findScopeContract(*resolvedFunction, item.first.contract), resolvedFunction})) - { - case RevertState::Unknown: - [[fallthrough]]; - case RevertState::AllPathsRevert: - // If the revert states of the functions do not - // change anymore, we treat all "unknown" states as - // "reverting", since they can only be caused by - // recursion. - for (CFGNode * node: _node->exits) - ranges::remove(node->entries, _node); - - _node->exits = {functionFlow.revert}; - functionFlow.revert->entries.push_back(_node); - return; - default: - break; - } - } + auto const* resolvedFunction = _node->functionDefinition; + if (resolvedFunction && resolvedFunction->isImplemented()) + switch (m_functions.at({findScopeContract(*resolvedFunction, item.first.contract), resolvedFunction})) + { + case RevertState::Unknown: + [[fallthrough]]; + case RevertState::AllPathsRevert: + // If the revert states of the functions do not + // change anymore, we treat all "unknown" states as + // "reverting", since they can only be caused by + // recursion. + for (CFGNode * node: _node->exits) + ranges::remove(node->entries, _node); + + _node->exits = {functionFlow.revert}; + functionFlow.revert->entries.push_back(_node); + return; + default: + break; + } for (CFGNode* exit: _node->exits) _addChild(exit); diff --git a/compiler/libsolidity/analysis/DeclarationContainer.cpp b/compiler/libsolidity/analysis/DeclarationContainer.cpp index b6cd3eef..df6e664b 100644 --- a/compiler/libsolidity/analysis/DeclarationContainer.cpp +++ b/compiler/libsolidity/analysis/DeclarationContainer.cpp @@ -29,7 +29,6 @@ #include #include -using namespace std; using namespace solidity; using namespace solidity::frontend; @@ -41,7 +40,7 @@ Declaration const* DeclarationContainer::conflictingDeclaration( if (!_name) _name = &_declaration.name(); solAssert(!_name->empty(), ""); - vector declarations; + std::vector declarations; if (m_declarations.count(*_name)) declarations += m_declarations.at(*_name); if (m_invisibleDeclarations.count(*_name)) @@ -127,7 +126,7 @@ bool DeclarationContainer::registerDeclaration( m_homonymCandidates.emplace_back(*_name, _location ? _location : &_declaration.location()); } - vector& decls = _invisible ? m_invisibleDeclarations[*_name] : m_declarations[*_name]; + std::vector& decls = _invisible ? m_invisibleDeclarations[*_name] : m_declarations[*_name]; if (!util::contains(decls, &_declaration)) decls.push_back(&_declaration); return true; @@ -142,13 +141,13 @@ bool DeclarationContainer::registerDeclaration( return registerDeclaration(_declaration, nullptr, nullptr, _invisible, _update); } -vector DeclarationContainer::resolveName( +std::vector DeclarationContainer::resolveName( ASTString const& _name, ResolvingSettings _settings ) const { solAssert(!_name.empty(), "Attempt to resolve empty name."); - vector result; + std::vector result; if (m_declarations.count(_name)) { @@ -172,24 +171,24 @@ vector DeclarationContainer::resolveName( return result; } -vector DeclarationContainer::similarNames(ASTString const& _name) const +std::vector DeclarationContainer::similarNames(ASTString const& _name) const { // because the function below has quadratic runtime - it will not magically improve once a better algorithm is discovered ;) // since 80 is the suggested line length limit, we use 80^2 as length threshold static size_t const MAXIMUM_LENGTH_THRESHOLD = 80 * 80; - vector similar; + std::vector similar; size_t maximumEditDistance = _name.size() > 3 ? 2 : _name.size() / 2; for (auto const& declaration: m_declarations) { - string const& declarationName = declaration.first; + std::string const& declarationName = declaration.first; if (util::stringWithinDistance(_name, declarationName, maximumEditDistance, MAXIMUM_LENGTH_THRESHOLD)) similar.push_back(declarationName); } for (auto const& declaration: m_invisibleDeclarations) { - string const& declarationName = declaration.first; + std::string const& declarationName = declaration.first; if (util::stringWithinDistance(_name, declarationName, maximumEditDistance, MAXIMUM_LENGTH_THRESHOLD)) similar.push_back(declarationName); } @@ -200,7 +199,7 @@ vector DeclarationContainer::similarNames(ASTString const& _name) con return similar; } -void DeclarationContainer::populateHomonyms(back_insert_iterator _it) const +void DeclarationContainer::populateHomonyms(std::back_insert_iterator _it) const { for (DeclarationContainer const* innerContainer: m_innerContainers) innerContainer->populateHomonyms(_it); @@ -210,7 +209,7 @@ void DeclarationContainer::populateHomonyms(back_insert_iterator _it) ResolvingSettings settings; settings.recursive = true; settings.alsoInvisible = true; - vector const& declarations = m_enclosingContainer->resolveName(name, std::move(settings)); + std::vector const& declarations = m_enclosingContainer->resolveName(name, std::move(settings)); if (!declarations.empty()) _it = make_pair(location, declarations); } diff --git a/compiler/libsolidity/analysis/DeclarationTypeChecker.cpp b/compiler/libsolidity/analysis/DeclarationTypeChecker.cpp index be37cf61..ca4319cd 100644 --- a/compiler/libsolidity/analysis/DeclarationTypeChecker.cpp +++ b/compiler/libsolidity/analysis/DeclarationTypeChecker.cpp @@ -29,7 +29,6 @@ #include -using namespace std; using namespace solidity::langutil; using namespace solidity::frontend; @@ -169,7 +168,7 @@ void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName) m_errorReporter.fatalTypeError( 5172_error, _typeName.location(), - "Name has to refer to a struct, enum or contract." + "Name has to refer to a user-defined type." ); } } @@ -221,14 +220,54 @@ void DeclarationTypeChecker::endVisit(Mapping const& _mapping) // key type is checked in TVMTypeChecker::visit(const Mapping &_mapping) Type const* keyType = _mapping.keyType().annotation().type; + ASTString keyName = _mapping.keyName(); + Type const* valueType = _mapping.valueType().annotation().type; + ASTString valueName = _mapping.valueName(); - // Convert key type to memory. - keyType = TypeProvider::withLocationIfReference(keyType); + _mapping.annotation().type = TypeProvider::mapping(keyType, keyName, valueType, valueName); - // Convert value type to storage reference. - valueType = TypeProvider::withLocationIfReference(valueType); - _mapping.annotation().type = TypeProvider::mapping(keyType, valueType); + // Check if parameter names are conflicting. + if (!keyName.empty()) + { + auto childMappingType = dynamic_cast(valueType); + ASTString currentValueName = valueName; + bool loop = true; + while (loop) + { + bool isError = false; + // Value type is a mapping. + if (childMappingType) + { + // Compare top mapping's key name with child mapping's key name. + ASTString childKeyName = childMappingType->keyName(); + if (keyName == childKeyName) + isError = true; + + auto valueType = childMappingType->valueType(); + currentValueName = childMappingType->valueName(); + childMappingType = dynamic_cast(valueType); + } + else + { + // Compare top mapping's key name with the value name. + if (keyName == currentValueName) + isError = true; + + loop = false; // We arrived at the end of mapping recursion. + } + + // Report error. + if (isError) + { + m_errorReporter.declarationError( + 1809_error, + _mapping.location(), + "Conflicting parameter name \"" + keyName + "\" in mapping." + ); + } + } + } } void DeclarationTypeChecker::endVisit(Optional const& _optional) @@ -272,10 +311,10 @@ void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName) if (Expression const* length = _typeName.length()) { - optional lengthValue; + std::optional lengthValue; if (length->annotation().type && length->annotation().type->category() == Type::Category::RationalNumber) lengthValue = dynamic_cast(*length->annotation().type).value(); - else if (optional value = ConstantEvaluator::evaluate(m_errorReporter, *length)) + else if (std::optional value = ConstantEvaluator::evaluate(m_errorReporter, *length)) lengthValue = value->value; if (!lengthValue) @@ -331,12 +370,6 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable) ); Type const* type = _variable.typeName().annotation().type; - if (auto ref = dynamic_cast(type)) - { - bool isPointer = !_variable.isStateVariable(); - type = TypeProvider::withLocation(ref, isPointer); - } - _variable.annotation().type = type; } @@ -354,7 +387,7 @@ bool DeclarationTypeChecker::visit(UsingForDirective const& _usingFor) m_errorReporter.typeError( 4167_error, function->location(), - "Only file-level functions and library functions can be bound to a type in a \"using\" statement" + "Only file-level functions and library functions can be attached to a type in a \"using\" statement" ); } else diff --git a/compiler/libsolidity/analysis/DocStringAnalyser.cpp b/compiler/libsolidity/analysis/DocStringAnalyser.cpp index 5cff34f2..d4afadf2 100644 --- a/compiler/libsolidity/analysis/DocStringAnalyser.cpp +++ b/compiler/libsolidity/analysis/DocStringAnalyser.cpp @@ -30,7 +30,6 @@ #include -using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; @@ -38,7 +37,7 @@ using namespace solidity::frontend; namespace { -void copyMissingTags(set const& _baseFunctions, StructurallyDocumentedAnnotation& _target, FunctionType const* _functionType = nullptr) +void copyMissingTags(std::set const& _baseFunctions, StructurallyDocumentedAnnotation& _target, FunctionType const* _functionType = nullptr) { // Only copy if there is exactly one direct base function. if (_baseFunctions.size() != 1) @@ -50,7 +49,7 @@ void copyMissingTags(set const& _baseFunctions, Stru for (auto it = sourceDoc.docTags.begin(); it != sourceDoc.docTags.end();) { - string const& tag = it->first; + std::string const& tag = it->first; // Don't copy tag "inheritdoc", custom tags or already existing tags if (tag == "inheritdoc" || _target.docTags.count(tag) || boost::starts_with(tag, "custom")) { @@ -68,7 +67,7 @@ void copyMissingTags(set const& _baseFunctions, Stru if (_functionType && tag == "return") { size_t docParaNameEndPos = content.content.find_first_of(" \t"); - string const docParameterName = content.content.substr(0, docParaNameEndPos); + std::string const docParameterName = content.content.substr(0, docParaNameEndPos); if ( _functionType->returnParameterNames().size() > n && @@ -80,10 +79,10 @@ void copyMissingTags(set const& _baseFunctions, Stru baseFunction.returnParameters().size() > n && baseFunction.returnParameters().at(n)->name().empty(); - string paramName = _functionType->returnParameterNames().at(n); + std::string paramName = _functionType->returnParameterNames().at(n); content.content = (paramName.empty() ? "" : std::move(paramName) + " ") + ( - string::npos == docParaNameEndPos || baseHasNoName ? + std::string::npos == docParaNameEndPos || baseHasNoName ? content.content : content.content.substr(docParaNameEndPos + 1) ); @@ -95,7 +94,7 @@ void copyMissingTags(set const& _baseFunctions, Stru } } -CallableDeclaration const* findBaseCallable(set const& _baseFunctions, int64_t _contractId) +CallableDeclaration const* findBaseCallable(std::set const& _baseFunctions, int64_t _contractId) { for (CallableDeclaration const* baseFuncCandidate: _baseFunctions) if (baseFuncCandidate->annotation().contract->id() == _contractId) @@ -181,7 +180,7 @@ void DocStringAnalyser::handleCallable( } CallableDeclaration const* DocStringAnalyser::resolveInheritDoc( - set const& _baseFuncs, + std::set const& _baseFuncs, StructurallyDocumented const& _node, StructurallyDocumentedAnnotation& _annotation ) diff --git a/compiler/libsolidity/analysis/DocStringTagParser.cpp b/compiler/libsolidity/analysis/DocStringTagParser.cpp index 1dc2711e..5127b779 100644 --- a/compiler/libsolidity/analysis/DocStringTagParser.cpp +++ b/compiler/libsolidity/analysis/DocStringTagParser.cpp @@ -37,7 +37,6 @@ #include #include -using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; @@ -67,7 +66,7 @@ bool DocStringTagParser::validateDocStringsUsingTypes(SourceUnit const& _sourceU if (tagName == "return") { returnTagsVisited++; - vector returnParameterNames; + std::vector returnParameterNames; if (auto const* varDecl = dynamic_cast(&_node)) { @@ -82,8 +81,8 @@ bool DocStringTagParser::validateDocStringsUsingTypes(SourceUnit const& _sourceU else continue; - string content = tagValue.content; - string firstWord = content.substr(0, content.find_first_of(" \t")); + std::string content = tagValue.content; + std::string firstWord = content.substr(0, content.find_first_of(" \t")); if (returnTagsVisited > returnParameterNames.size()) m_errorReporter.docstringParsingError( @@ -94,7 +93,7 @@ bool DocStringTagParser::validateDocStringsUsingTypes(SourceUnit const& _sourceU ); else { - string const& parameter = returnParameterNames.at(returnTagsVisited - 1); + std::string const& parameter = returnParameterNames.at(returnTagsVisited - 1); if (!parameter.empty() && parameter != firstWord) m_errorReporter.docstringParsingError( 5856_error, @@ -113,7 +112,7 @@ bool DocStringTagParser::validateDocStringsUsingTypes(SourceUnit const& _sourceU bool DocStringTagParser::visit(ContractDefinition const& _contract) { - static set const validTags = set{"author", "title", "dev", "notice"}; + static std::set const validTags = std::set{"author", "title", "dev", "notice"}; parseDocStrings(_contract, _contract.annotation(), validTags, "contracts"); return true; @@ -163,88 +162,13 @@ bool DocStringTagParser::visit(ErrorDefinition const& _error) return true; } -bool DocStringTagParser::visit(InlineAssembly const& _assembly) -{ - if (!_assembly.documentation()) - return true; - StructuredDocumentation documentation{-1, _assembly.location(), _assembly.documentation()}; - ErrorList errors; - ErrorReporter errorReporter{errors}; - auto docTags = DocStringParser{documentation, errorReporter}.parse(); - - if (!errors.empty()) - { - SecondarySourceLocation ssl; - for (auto const& error: errors) - if (error->comment()) - ssl.append( - *error->comment(), - _assembly.location() - ); - m_errorReporter.warning( - 7828_error, - _assembly.location(), - "Inline assembly has invalid NatSpec documentation.", - ssl - ); - } - - for (auto const& [tagName, tagValue]: docTags) - { - if (tagName == "solidity") - { - vector values; - boost::split(values, tagValue.content, isWhiteSpace); - - set valuesSeen; - set duplicates; - for (auto const& value: values | ranges::views::filter(not_fn(&string::empty))) - if (valuesSeen.insert(value).second) - { - if (value == "memory-safe-assembly") - { - if (_assembly.annotation().markedMemorySafe) - m_errorReporter.warning( - 8544_error, - _assembly.location(), - "Inline assembly marked as memory safe using both a NatSpec tag and an assembly flag. " - "If you are not concerned with backwards compatibility, only use the assembly flag, " - "otherwise only use the NatSpec tag." - ); - _assembly.annotation().markedMemorySafe = true; - } - else - m_errorReporter.warning( - 8787_error, - _assembly.location(), - "Unexpected value for @solidity tag in inline assembly: " + value - ); - } - else if (duplicates.insert(value).second) - m_errorReporter.warning( - 4377_error, - _assembly.location(), - "Value for @solidity tag in inline assembly specified multiple times: " + value - ); - } - else - m_errorReporter.warning( - 6269_error, - _assembly.location(), - "Unexpected NatSpec tag \"" + tagName + "\" with value \"" + tagValue.content + "\" in inline assembly." - ); - } - - return true; -} - void DocStringTagParser::checkParameters( CallableDeclaration const& _callable, StructurallyDocumented const& _node, StructurallyDocumentedAnnotation& _annotation ) { - set validParams; + std::set validParams; for (auto const& p: _callable.parameters()) validParams.insert(p->name()); if (_callable.returnParameterList()) @@ -268,7 +192,7 @@ void DocStringTagParser::handleConstructor( StructurallyDocumentedAnnotation& _annotation ) { - static set const validTags = set{"author", "dev", "notice", "param"}; + static std::set const validTags = std::set{"author", "dev", "notice", "param"}; parseDocStrings(_node, _annotation, validTags, "constructor"); checkParameters(_callable, _node, _annotation); } @@ -279,10 +203,10 @@ void DocStringTagParser::handleCallable( StructurallyDocumentedAnnotation& _annotation ) { - static set const validEventTags = set{"dev", "notice", "return", "param"}; - static set const validErrorTags = set{"dev", "notice", "param"}; - static set const validModifierTags = set{"dev", "notice", "param", "inheritdoc"}; - static set const validTags = set{"dev", "notice", "return", "param", "inheritdoc"}; + static std::set const validEventTags = std::set{"dev", "notice", "return", "param"}; + static std::set const validErrorTags = std::set{"dev", "notice", "param"}; + static std::set const validModifierTags = std::set{"dev", "notice", "param", "inheritdoc"}; + static std::set const validTags = std::set{"dev", "notice", "return", "param", "inheritdoc"}; if (dynamic_cast(&_callable)) parseDocStrings(_node, _annotation, validEventTags, "events"); @@ -299,8 +223,8 @@ void DocStringTagParser::handleCallable( void DocStringTagParser::parseDocStrings( StructurallyDocumented const& _node, StructurallyDocumentedAnnotation& _annotation, - set const& _validTags, - string const& _nodeName + std::set const& _validTags, + std::string const& _nodeName ) { if (!_node.documentation()) @@ -310,7 +234,7 @@ void DocStringTagParser::parseDocStrings( for (auto const& [tagName, tagValue]: _annotation.docTags) { - string_view static constexpr customPrefix("custom:"); + std::string_view static constexpr customPrefix("custom:"); if (tagName == "custom" || tagName == "custom:") m_errorReporter.docstringParsingError( 6564_error, @@ -319,7 +243,7 @@ void DocStringTagParser::parseDocStrings( ); else if (boost::starts_with(tagName, customPrefix) && tagName.size() > customPrefix.size()) { - regex static const customRegex("^custom:[a-z][a-z-]*$"); + std::regex static const customRegex("^custom:[a-z][a-z-]*$"); if (!regex_match(tagName, customRegex)) m_errorReporter.docstringParsingError( 2968_error, diff --git a/compiler/libsolidity/analysis/DocStringTagParser.h b/compiler/libsolidity/analysis/DocStringTagParser.h index 248befcb..84fde6c6 100644 --- a/compiler/libsolidity/analysis/DocStringTagParser.h +++ b/compiler/libsolidity/analysis/DocStringTagParser.h @@ -48,7 +48,6 @@ class DocStringTagParser: private ASTConstVisitor bool visit(ModifierDefinition const& _modifier) override; bool visit(EventDefinition const& _event) override; bool visit(ErrorDefinition const& _error) override; - bool visit(InlineAssembly const& _assembly) override; void checkParameters( CallableDeclaration const& _callable, diff --git a/compiler/libsolidity/analysis/FunctionCallGraph.cpp b/compiler/libsolidity/analysis/FunctionCallGraph.cpp index e941c99c..1d01f1a6 100644 --- a/compiler/libsolidity/analysis/FunctionCallGraph.cpp +++ b/compiler/libsolidity/analysis/FunctionCallGraph.cpp @@ -24,7 +24,6 @@ #include #include -using namespace std; using namespace solidity::frontend; using namespace solidity::util; @@ -72,7 +71,7 @@ CallGraph FunctionCallGraphBuilder::buildDeployedGraph( FunctionCallGraphBuilder builder(_contract); solAssert(builder.m_currentNode == CallGraph::Node(CallGraph::SpecialNode::Entry), ""); - auto getSecondElement = [](auto const& _tuple){ return get<1>(_tuple); }; + auto getSecondElement = [](auto const& _tuple){ return std::get<1>(_tuple); }; // Create graph for all publicly reachable functions for (FunctionTypePointer functionType: _contract.interfaceFunctionList() | ranges::views::transform(getSecondElement)) @@ -96,14 +95,14 @@ CallGraph FunctionCallGraphBuilder::buildDeployedGraph( // All functions present in internal dispatch at creation time could potentially be pointers // assigned to state variables and as such may be reachable after deployment as well. builder.m_currentNode = CallGraph::SpecialNode::InternalDispatch; - set defaultNode; + std::set defaultNode; for (CallGraph::Node const& dispatchTarget: util::valueOrDefault(_creationGraph.edges, CallGraph::SpecialNode::InternalDispatch, defaultNode)) { - solAssert(!holds_alternative(dispatchTarget), ""); - solAssert(get(dispatchTarget) != nullptr, ""); + solAssert(!std::holds_alternative(dispatchTarget), ""); + solAssert(std::get(dispatchTarget) != nullptr, ""); // Visit the callable to add not only it but also everything it calls too - builder.functionReferenced(*get(dispatchTarget), false); + builder.functionReferenced(*std::get(dispatchTarget), false); } builder.m_currentNode = CallGraph::SpecialNode::Entry; @@ -204,6 +203,20 @@ bool FunctionCallGraphBuilder::visit(MemberAccess const& _memberAccess) return true; } +bool FunctionCallGraphBuilder::visit(BinaryOperation const& _binaryOperation) +{ + if (*_binaryOperation.annotation().userDefinedFunction != nullptr) + functionReferenced(**_binaryOperation.annotation().userDefinedFunction, true /* called directly */); + return true; +} + +bool FunctionCallGraphBuilder::visit(UnaryOperation const& _unaryOperation) +{ + if (*_unaryOperation.annotation().userDefinedFunction != nullptr) + functionReferenced(**_unaryOperation.annotation().userDefinedFunction, true /* called directly */); + return true; +} + bool FunctionCallGraphBuilder::visit(ModifierInvocation const& _modifierInvocation) { if (auto const* modifier = dynamic_cast(_modifierInvocation.name().annotation().referencedDeclaration)) @@ -248,10 +261,10 @@ void FunctionCallGraphBuilder::processQueue() while (!m_visitQueue.empty()) { m_currentNode = m_visitQueue.front(); - solAssert(holds_alternative(m_currentNode), ""); + solAssert(std::holds_alternative(m_currentNode), ""); m_visitQueue.pop_front(); - get(m_currentNode)->accept(*this); + std::get(m_currentNode)->accept(*this); } m_currentNode = CallGraph::SpecialNode::Entry; @@ -267,7 +280,7 @@ void FunctionCallGraphBuilder::functionReferenced(CallableDeclaration const& _ca if (_calledDirectly) { solAssert( - holds_alternative(m_currentNode) || m_graph.edges.count(m_currentNode) > 0, + std::holds_alternative(m_currentNode) || m_graph.edges.count(m_currentNode) > 0, "Adding an edge from a node that has not been visited yet." ); @@ -279,10 +292,10 @@ void FunctionCallGraphBuilder::functionReferenced(CallableDeclaration const& _ca enqueueCallable(_callable); } -ostream& solidity::frontend::operator<<(ostream& _out, CallGraph::Node const& _node) +std::ostream& solidity::frontend::operator<<(std::ostream& _out, CallGraph::Node const& _node) { - if (holds_alternative(_node)) - switch (get(_node)) + if (std::holds_alternative(_node)) + switch (std::get(_node)) { case CallGraph::SpecialNode::InternalDispatch: _out << "InternalDispatch"; @@ -295,19 +308,19 @@ ostream& solidity::frontend::operator<<(ostream& _out, CallGraph::Node const& _n } else { - solAssert(holds_alternative(_node), ""); + solAssert(std::holds_alternative(_node), ""); - auto const* callableDeclaration = get(_node); + auto const* callableDeclaration = std::get(_node); solAssert(callableDeclaration, ""); auto const* function = dynamic_cast(callableDeclaration); auto const* event = dynamic_cast(callableDeclaration); auto const* modifier = dynamic_cast(callableDeclaration); - auto typeToString = [](auto const& _var) -> string { return _var->type()->toString(true); }; - vector parameters = callableDeclaration->parameters() | ranges::views::transform(typeToString) | ranges::to>(); + auto typeToString = [](auto const& _var) -> std::string { return _var->type()->toString(true); }; + std::vector parameters = callableDeclaration->parameters() | ranges::views::transform(typeToString) | ranges::to>(); - string scopeName; + std::string scopeName; if (!function || !function->isFree()) { solAssert(callableDeclaration->annotation().scope, ""); diff --git a/compiler/libsolidity/analysis/FunctionCallGraph.h b/compiler/libsolidity/analysis/FunctionCallGraph.h index 3ea1f5b9..a4e5787a 100644 --- a/compiler/libsolidity/analysis/FunctionCallGraph.h +++ b/compiler/libsolidity/analysis/FunctionCallGraph.h @@ -72,6 +72,8 @@ class FunctionCallGraphBuilder: private ASTConstVisitor bool visit(EmitStatement const& _emitStatement) override; bool visit(Identifier const& _identifier) override; bool visit(MemberAccess const& _memberAccess) override; + bool visit(BinaryOperation const& _binaryOperation) override; + bool visit(UnaryOperation const& _unaryOperation) override; bool visit(ModifierInvocation const& _modifierInvocation) override; bool visit(NewExpression const& _newExpression) override; diff --git a/compiler/libsolidity/analysis/GlobalContext.cpp b/compiler/libsolidity/analysis/GlobalContext.cpp index 51a75292..1976525e 100644 --- a/compiler/libsolidity/analysis/GlobalContext.cpp +++ b/compiler/libsolidity/analysis/GlobalContext.cpp @@ -29,8 +29,6 @@ #include #include -using namespace std; - namespace solidity::frontend { @@ -67,6 +65,7 @@ int magicVariableToID(std::string const& _name) else if (_name == "tx") return -26; else if (_name == "type") return -27; else if (_name == "this") return -28; + else if (_name == "blobhash") return -29; else if (_name == "gasToValue") return -60; else if (_name == "valueToGas") return -61; else if (_name == "bitSize") return -62; @@ -75,13 +74,13 @@ int magicVariableToID(std::string const& _name) solAssert(false, "Unknown magic variable: \"" + _name + "\"."); } -inline vector> constructMagicVariables() +inline std::vector> constructMagicVariables(langutil::EVMVersion _evmVersion) { - static auto const magicVarDecl = [](string const& _name, Type const* _type) { - return make_shared(magicVariableToID(_name), _name, _type); + static auto const magicVarDecl = [](std::string const& _name, Type const* _type) { + return std::make_shared(magicVariableToID(_name), _name, _type); }; - return { + std::vector> magicVariableDeclarations = { magicVarDecl("abi", TypeProvider::magic(MagicType::Kind::ABI)), magicVarDecl("addmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, StateMutability::Pure)), magicVarDecl("assert", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Assert, StateMutability::Pure)), @@ -103,8 +102,8 @@ inline vector> constructMagicVariable magicVarDecl("stoi", TypeProvider::function( TypePointers{TypeProvider::stringStorage()}, TypePointers{TypeProvider::optional(TypeProvider::integer(256, IntegerType::Modifier::Signed))}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::Stoi, StateMutability::Pure) ), @@ -126,11 +125,19 @@ inline vector> constructMagicVariable magicVarDecl("bitSize", TypeProvider::function({"int"}, {"uint16"}, FunctionType::Kind::BitSize, StateMutability::Pure)), magicVarDecl("uBitSize", TypeProvider::function({"uint"}, {"uint16"}, FunctionType::Kind::UBitSize, StateMutability::Pure)), }; + + if (_evmVersion >= langutil::EVMVersion::cancun()) + magicVariableDeclarations.push_back( + magicVarDecl("blobhash", TypeProvider::function(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlobHash, StateMutability::View)) + ); + + return magicVariableDeclarations; } } -GlobalContext::GlobalContext(): m_magicVariables{constructMagicVariables()} +GlobalContext::GlobalContext(langutil::EVMVersion _evmVersion): + m_magicVariables{constructMagicVariables(_evmVersion)} { } @@ -139,9 +146,9 @@ void GlobalContext::setCurrentContract(ContractDefinition const& _contract) m_currentContract = &_contract; } -vector GlobalContext::declarations() const +std::vector GlobalContext::declarations() const { - vector declarations; + std::vector declarations; declarations.reserve(m_magicVariables.size()); for (ASTPointer const& variable: m_magicVariables) declarations.push_back(variable.get()); @@ -156,7 +163,7 @@ MagicVariableDeclaration const* GlobalContext::currentThis() const if (m_currentContract) type = TypeProvider::contract(*m_currentContract); m_thisPointer[m_currentContract] = - make_shared(magicVariableToID("this"), "this", type); + std::make_shared(magicVariableToID("this"), "this", type); } return m_thisPointer[m_currentContract].get(); } @@ -169,7 +176,7 @@ MagicVariableDeclaration const* GlobalContext::currentSuper() const if (m_currentContract) type = TypeProvider::typeType(TypeProvider::contract(*m_currentContract, true)); m_superPointer[m_currentContract] = - make_shared(magicVariableToID("super"), "super", type); + std::make_shared(magicVariableToID("super"), "super", type); } return m_superPointer[m_currentContract].get(); } diff --git a/compiler/libsolidity/analysis/GlobalContext.h b/compiler/libsolidity/analysis/GlobalContext.h index 69344537..3aa5e76e 100644 --- a/compiler/libsolidity/analysis/GlobalContext.h +++ b/compiler/libsolidity/analysis/GlobalContext.h @@ -23,6 +23,7 @@ #pragma once +#include #include #include #include @@ -47,7 +48,7 @@ class GlobalContext GlobalContext(GlobalContext const&) = delete; GlobalContext& operator=(GlobalContext const&) = delete; - GlobalContext(); + GlobalContext(langutil::EVMVersion _evmVersion); void setCurrentContract(ContractDefinition const& _contract); void resetCurrentContract() { m_currentContract = nullptr; } MagicVariableDeclaration const* currentThis() const; diff --git a/compiler/libsolidity/analysis/ImmutableValidator.cpp b/compiler/libsolidity/analysis/ImmutableValidator.cpp index 485aa3fa..0f5a2ee2 100644 --- a/compiler/libsolidity/analysis/ImmutableValidator.cpp +++ b/compiler/libsolidity/analysis/ImmutableValidator.cpp @@ -18,8 +18,6 @@ #include -#include - #include using namespace solidity::frontend; @@ -27,256 +25,44 @@ using namespace solidity::langutil; void ImmutableValidator::analyze() { - m_inCreationContext = true; - auto linearizedContracts = m_mostDerivedContract.annotation().linearizedBaseContracts | ranges::views::reverse; - for (ContractDefinition const* contract: linearizedContracts) - for (VariableDeclaration const* stateVar: contract->stateVariables()) - if (stateVar->value()) - stateVar->value()->accept(*this); - - for (ContractDefinition const* contract: linearizedContracts) - for (std::shared_ptr const& inheritSpec: contract->baseContracts()) - if (auto args = inheritSpec->arguments()) - ASTNode::listAccept(*args, *this); - for (ContractDefinition const* contract: linearizedContracts) { - for (VariableDeclaration const* stateVar: contract->stateVariables()) - if (stateVar->value()) - m_initializedStateVariables.emplace(stateVar); + for (FunctionDefinition const* function: contract->definedFunctions()) + function->accept(*this); - if (contract->constructor()) - visitCallableIfNew(*contract->constructor()); + for (ModifierDefinition const* modifier: contract->functionModifiers()) + modifier->accept(*this); } - - m_inCreationContext = false; - - for (ContractDefinition const* contract: linearizedContracts) - { - for (auto funcDef: contract->definedFunctions()) - visitCallableIfNew(*funcDef); - - for (auto modDef: contract->functionModifiers()) - visitCallableIfNew(*modDef); - } - - checkAllVariablesInitialized(m_mostDerivedContract.location()); } -bool ImmutableValidator::visit(Assignment const& _assignment) -{ - // Need to visit values first (rhs) as they might access other immutables. - _assignment.rightHandSide().accept(*this); - _assignment.leftHandSide().accept(*this); - return false; -} - - bool ImmutableValidator::visit(FunctionDefinition const& _functionDefinition) { - return analyseCallable(_functionDefinition); -} - -bool ImmutableValidator::visit(ModifierDefinition const& _modifierDefinition) -{ - return analyseCallable(_modifierDefinition); -} - -bool ImmutableValidator::visit(MemberAccess const& _memberAccess) -{ - _memberAccess.expression().accept(*this); - - if (auto contractType = dynamic_cast(_memberAccess.expression().annotation().type)) - if (!contractType->isSuper()) - // external access, no analysis needed. - return false; - - if (auto varDecl = dynamic_cast(_memberAccess.annotation().referencedDeclaration)) - analyseVariableReference(*varDecl, _memberAccess); - else if (auto funcType = dynamic_cast(_memberAccess.annotation().type)) - if (funcType->kind() == FunctionType::Kind::Internal && funcType->hasDeclaration()) - visitCallableIfNew(funcType->declaration()); - - return false; -} - -bool ImmutableValidator::visit(IfStatement const& _ifStatement) -{ - bool prevInBranch = m_inBranch; - - _ifStatement.condition().accept(*this); - - m_inBranch = true; - _ifStatement.trueStatement().accept(*this); - - if (auto falseStatement = _ifStatement.falseStatement()) - falseStatement->accept(*this); - - m_inBranch = prevInBranch; - - return false; -} - -bool ImmutableValidator::visit(WhileStatement const& _whileStatement) -{ - bool prevInLoop = m_inLoop; - m_inLoop = true; - - _whileStatement.condition().accept(*this); - _whileStatement.body().accept(*this); - - m_inLoop = prevInLoop; - - return false; + return !_functionDefinition.isConstructor(); } -void ImmutableValidator::endVisit(IdentifierPath const& _identifierPath) +void ImmutableValidator::endVisit(MemberAccess const& _memberAccess) { - if (auto const callableDef = dynamic_cast(_identifierPath.annotation().referencedDeclaration)) - visitCallableIfNew( - *_identifierPath.annotation().requiredLookup == VirtualLookup::Virtual ? - callableDef->resolveVirtual(m_mostDerivedContract) : - *callableDef - ); - - solAssert(!dynamic_cast(_identifierPath.annotation().referencedDeclaration), ""); + analyseVariableReference(_memberAccess.annotation().referencedDeclaration, _memberAccess); } void ImmutableValidator::endVisit(Identifier const& _identifier) { - if (auto const callableDef = dynamic_cast(_identifier.annotation().referencedDeclaration)) - visitCallableIfNew(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual ? callableDef->resolveVirtual(m_mostDerivedContract) : *callableDef); - if (auto const varDecl = dynamic_cast(_identifier.annotation().referencedDeclaration)) - analyseVariableReference(*varDecl, _identifier); + analyseVariableReference(_identifier.annotation().referencedDeclaration, _identifier); } -void ImmutableValidator::endVisit(Return const& _return) +void ImmutableValidator::analyseVariableReference(Declaration const* _reference, Expression const& _expression) { - if (m_currentConstructor != nullptr) - checkAllVariablesInitialized(_return.location()); -} - -bool ImmutableValidator::analyseCallable(CallableDeclaration const& _callableDeclaration) -{ - ScopedSaveAndRestore constructorGuard{m_currentConstructor, {}}; - ScopedSaveAndRestore constructorContractGuard{m_currentConstructorContract, {}}; - - if (FunctionDefinition const* funcDef = dynamic_cast(&_callableDeclaration)) - { - ASTNode::listAccept(funcDef->modifiers(), *this); - - if (funcDef->isConstructor()) - { - m_currentConstructorContract = funcDef->annotation().contract; - m_currentConstructor = funcDef; - } - - if (funcDef->isImplemented()) - funcDef->body().accept(*this); - } - else if (ModifierDefinition const* modDef = dynamic_cast(&_callableDeclaration)) - if (modDef->isImplemented()) - modDef->body().accept(*this); - - return false; -} - -void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _variableReference, Expression const& _expression) -{ - if (!_variableReference.isStateVariable() || !_variableReference.immutable()) + auto const* variable = dynamic_cast(_reference); + if (!variable || !variable->isStateVariable() || !variable->immutable()) return; // If this is not an ordinary assignment, we write and read at the same time. - bool write = _expression.annotation().willBeWrittenTo; - bool read = !_expression.annotation().willBeWrittenTo || !_expression.annotation().lValueOfOrdinaryAssignment; - if (write) - { - if (!m_currentConstructor) - m_errorReporter.typeError( - 1581_error, - _expression.location(), - "Cannot write to immutable here: Immutable variables can only be initialized inline or assigned directly in the constructor." - ); - else if (m_currentConstructor->annotation().contract->id() != _variableReference.annotation().contract->id()) - m_errorReporter.typeError( - 7484_error, - _expression.location(), - "Cannot write to immutable here: Immutable variables must be initialized in the constructor of the contract they are defined in." - ); - else if (m_inLoop) - m_errorReporter.typeError( - 6672_error, - _expression.location(), - "Cannot write to immutable here: Immutable variables cannot be initialized inside a loop." - ); - else if (m_inBranch) - m_errorReporter.typeError( - 4599_error, - _expression.location(), - "Cannot write to immutable here: Immutable variables cannot be initialized inside an if statement." - ); - else if (m_initializedStateVariables.count(&_variableReference)) - { - if (!read) - m_errorReporter.typeError( - 1574_error, - _expression.location(), - "Immutable state variable already initialized." - ); - else - m_errorReporter.typeError( - 2718_error, - _expression.location(), - "Immutable variables cannot be modified after initialization." - ); - } - else if (read) - m_errorReporter.typeError( - 3969_error, - _expression.location(), - "Immutable variables must be initialized using an assignment." - ); - m_initializedStateVariables.emplace(&_variableReference); - } - if ( - read && - m_inCreationContext && - !m_initializedStateVariables.count(&_variableReference) - ) + if (_expression.annotation().willBeWrittenTo) m_errorReporter.typeError( - 7733_error, + 1581_error, _expression.location(), - "Immutable variables cannot be read before they are initialized." + "Cannot write to immutable here: Immutable variables can only be initialized inline or assigned directly in the constructor." ); } - -void ImmutableValidator::checkAllVariablesInitialized(solidity::langutil::SourceLocation const& _location) -{ - for (ContractDefinition const* contract: m_mostDerivedContract.annotation().linearizedBaseContracts | ranges::views::reverse) - { - for (VariableDeclaration const* varDecl: contract->stateVariables()) - if (varDecl->immutable()) - if (!util::contains(m_initializedStateVariables, varDecl)) - m_errorReporter.typeError( - 2658_error, - _location, - solidity::langutil::SecondarySourceLocation().append("Not initialized: ", varDecl->location()), - "Construction control flow ends without initializing all immutable state variables." - ); - - // Don't check further than the current c'tors contract - if (contract == m_currentConstructorContract) - break; - } -} - -void ImmutableValidator::visitCallableIfNew(Declaration const& _declaration) -{ - CallableDeclaration const* _callable = dynamic_cast(&_declaration); - solAssert(_callable != nullptr, ""); - - if (m_visitedCallables.emplace(_callable).second) - _declaration.accept(*this); -} diff --git a/compiler/libsolidity/analysis/ImmutableValidator.h b/compiler/libsolidity/analysis/ImmutableValidator.h index 452e91d7..78d85a61 100644 --- a/compiler/libsolidity/analysis/ImmutableValidator.h +++ b/compiler/libsolidity/analysis/ImmutableValidator.h @@ -21,25 +21,15 @@ #include #include -#include - namespace solidity::frontend { /** * Validates access and initialization of immutable variables: - * must be directly initialized in their respective c'tor or inline - * cannot be read before being initialized - * cannot be read when initializing state variables inline - * must be initialized outside loops (only one initialization) - * must be initialized outside ifs (must be initialized unconditionally) - * must be initialized exactly once (no multiple statements) - * must be initialized exactly once (no early return to skip initialization) + * must be directly initialized in a c'tor or inline */ class ImmutableValidator: private ASTConstVisitor { - using CallableDeclarationSet = std::set; - public: ImmutableValidator(langutil::ErrorReporter& _errorReporter, ContractDefinition const& _contractDefinition): m_mostDerivedContract(_contractDefinition), @@ -49,35 +39,15 @@ class ImmutableValidator: private ASTConstVisitor void analyze(); private: - bool visit(Assignment const& _assignment); bool visit(FunctionDefinition const& _functionDefinition); - bool visit(ModifierDefinition const& _modifierDefinition); - bool visit(MemberAccess const& _memberAccess); - bool visit(IfStatement const& _ifStatement); - bool visit(WhileStatement const& _whileStatement); - void endVisit(IdentifierPath const& _identifierPath); + void endVisit(MemberAccess const& _memberAccess); void endVisit(Identifier const& _identifier); - void endVisit(Return const& _return); - - bool analyseCallable(CallableDeclaration const& _callableDeclaration); - void analyseVariableReference(VariableDeclaration const& _variableReference, Expression const& _expression); - void checkAllVariablesInitialized(langutil::SourceLocation const& _location); - - void visitCallableIfNew(Declaration const& _declaration); + void analyseVariableReference(Declaration const* _variableReference, Expression const& _expression); ContractDefinition const& m_mostDerivedContract; - CallableDeclarationSet m_visitedCallables; - - std::set m_initializedStateVariables; langutil::ErrorReporter& m_errorReporter; - - FunctionDefinition const* m_currentConstructor = nullptr; - ContractDefinition const* m_currentConstructorContract = nullptr; - bool m_inLoop = false; - bool m_inBranch = false; - bool m_inCreationContext = true; }; } diff --git a/compiler/libsolidity/analysis/NameAndTypeResolver.cpp b/compiler/libsolidity/analysis/NameAndTypeResolver.cpp index df657365..767e2707 100644 --- a/compiler/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/compiler/libsolidity/analysis/NameAndTypeResolver.cpp @@ -30,7 +30,6 @@ #include #include -using namespace std; using namespace solidity::langutil; namespace solidity::frontend @@ -39,13 +38,15 @@ namespace solidity::frontend NameAndTypeResolver::NameAndTypeResolver( GlobalContext& _globalContext, langutil::EVMVersion _evmVersion, - ErrorReporter& _errorReporter + ErrorReporter& _errorReporter, + bool _experimentalSolidity ): m_evmVersion(_evmVersion), m_errorReporter(_errorReporter), - m_globalContext(_globalContext) + m_globalContext(_globalContext), + m_experimentalSolidity(_experimentalSolidity) { - m_scopes[nullptr] = make_shared(); + m_scopes[nullptr] = std::make_shared(); for (Declaration const* declaration: _globalContext.declarations()) { solAssert(m_scopes[nullptr]->registerDeclaration(*declaration, false, false), "Unable to register global declaration."); @@ -68,14 +69,14 @@ bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode return true; } -bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map const& _sourceUnits) +bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, std::map const& _sourceUnits) { DeclarationContainer& target = *m_scopes.at(&_sourceUnit); bool error = false; for (auto const& node: _sourceUnit.nodes()) if (auto imp = dynamic_cast(node.get())) { - string const& path = *imp->annotation().absolutePath; + std::string const& path = *imp->annotation().absolutePath; // The import resolution in CompilerStack enforces this. solAssert(_sourceUnits.count(path), ""); auto scope = m_scopes.find(_sourceUnits.at(path)); @@ -127,7 +128,7 @@ bool NameAndTypeResolver::resolveNamesAndTypes(SourceUnit& _source) { try { - for (shared_ptr const& node: _source.nodes()) + for (std::shared_ptr const& node: _source.nodes()) { setScope(&_source); if (!resolveNamesAndTypesInternal(*node, true)) @@ -159,7 +160,7 @@ bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration) return true; } -void NameAndTypeResolver::activateVariable(string const& _name) +void NameAndTypeResolver::activateVariable(std::string const& _name) { solAssert(m_currentScope, ""); // Scoped local variables are invisible before activation. @@ -171,15 +172,15 @@ void NameAndTypeResolver::activateVariable(string const& _name) m_currentScope->activateVariable(_name); } -vector NameAndTypeResolver::resolveName(ASTString const& _name, ASTNode const* _scope) const +std::vector NameAndTypeResolver::resolveName(ASTString const& _name, ASTNode const* _scope) const { auto iterator = m_scopes.find(_scope); if (iterator == end(m_scopes)) - return vector({}); + return std::vector({}); return iterator->second->resolveName(_name); } -vector NameAndTypeResolver::nameFromCurrentScope(ASTString const& _name, bool _includeInvisibles) const +std::vector NameAndTypeResolver::nameFromCurrentScope(ASTString const& _name, bool _includeInvisibles) const { ResolvingSettings settings; settings.recursive = true; @@ -187,7 +188,7 @@ vector NameAndTypeResolver::nameFromCurrentScope(ASTString c return m_currentScope->resolveName(_name, std::move(settings)); } -Declaration const* NameAndTypeResolver::pathFromCurrentScope(vector const& _path) const +Declaration const* NameAndTypeResolver::pathFromCurrentScope(std::vector const& _path) const { if (auto declarations = pathFromCurrentScopeWithAllDeclarations(_path); !declarations.empty()) return declarations.back(); @@ -195,16 +196,23 @@ Declaration const* NameAndTypeResolver::pathFromCurrentScope(vector c return nullptr; } -std::vector NameAndTypeResolver::pathFromCurrentScopeWithAllDeclarations(std::vector const& _path) const +std::vector NameAndTypeResolver::pathFromCurrentScopeWithAllDeclarations( + std::vector const& _path, + bool _includeInvisibles +) const { solAssert(!_path.empty(), ""); - vector pathDeclarations; + std::vector pathDeclarations; ResolvingSettings settings; settings.recursive = true; - settings.alsoInvisible = false; + settings.alsoInvisible = _includeInvisibles; settings.onlyVisibleAsUnqualifiedNames = true; - vector candidates = m_currentScope->resolveName(_path.front(), std::move(settings)); + std::vector candidates = m_currentScope->resolveName(_path.front(), settings); + + // inside the loop, use default settings, except for alsoInvisible + settings.recursive = false; + settings.onlyVisibleAsUnqualifiedNames = false; for (size_t i = 1; i < _path.size() && candidates.size() == 1; i++) { @@ -213,7 +221,7 @@ std::vector NameAndTypeResolver::pathFromCurrentScopeWithAll pathDeclarations.push_back(candidates.front()); - candidates = m_scopes.at(candidates.front())->resolveName(_path[i]); + candidates = m_scopes.at(candidates.front())->resolveName(_path[i], settings); } if (candidates.size() == 1) { @@ -298,7 +306,7 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res if (success) { linearizeBaseContracts(*contract); - vector properBases( + std::vector properBases( ++contract->annotation().linearizedBaseContracts.begin(), contract->annotation().linearizedBaseContracts.end() ); @@ -398,7 +406,7 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract) { // order in the lists is from derived to base // list of lists to linearize, the last element is the list of direct bases - list> input(1, list{}); + std::list> input(1, std::list{}); for (ASTPointer const& baseSpecifier: _contract.baseContracts()) { IdentifierPath const& baseName = baseSpecifier->name(); @@ -408,25 +416,25 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract) // "push_front" has the effect that bases mentioned later can overwrite members of bases // mentioned earlier input.back().push_front(base); - vector const& basesBases = base->annotation().linearizedBaseContracts; + std::vector const& basesBases = base->annotation().linearizedBaseContracts; if (basesBases.empty()) m_errorReporter.fatalTypeError(2449_error, baseName.location(), "Definition of base has to precede definition of derived contract"); - input.push_front(list(basesBases.begin(), basesBases.end())); + input.push_front(std::list(basesBases.begin(), basesBases.end())); } input.back().push_front(&_contract); - vector result = cThreeMerge(input); + std::vector result = cThreeMerge(input); if (result.empty()) m_errorReporter.fatalTypeError(5005_error, _contract.location(), "Linearization of inheritance graph impossible"); _contract.annotation().linearizedBaseContracts = result; } template -vector NameAndTypeResolver::cThreeMerge(list>& _toMerge) +std::vector NameAndTypeResolver::cThreeMerge(std::list>& _toMerge) { // returns true iff _candidate appears only as last element of the lists auto appearsOnlyAtHead = [&](T const* _candidate) -> bool { - for (list const& bases: _toMerge) + for (std::list const& bases: _toMerge) { solAssert(!bases.empty(), ""); if (find(++bases.begin(), bases.end(), _candidate) != bases.end()) @@ -437,7 +445,7 @@ vector NameAndTypeResolver::cThreeMerge(list>& _toMerge // returns the next candidate to append to the linearized list or nullptr on failure auto nextCandidate = [&]() -> T const* { - for (list const& bases: _toMerge) + for (std::list const& bases: _toMerge) { solAssert(!bases.empty(), ""); if (appearsOnlyAtHead(bases.front())) @@ -458,26 +466,26 @@ vector NameAndTypeResolver::cThreeMerge(list>& _toMerge } }; - _toMerge.remove_if([](list const& _bases) { return _bases.empty(); }); - vector result; + _toMerge.remove_if([](std::list const& _bases) { return _bases.empty(); }); + std::vector result; while (!_toMerge.empty()) { T const* candidate = nextCandidate(); if (!candidate) - return vector(); + return std::vector(); result.push_back(candidate); removeCandidate(candidate); } return result; } -string NameAndTypeResolver::similarNameSuggestions(ASTString const& _name) const + std::string NameAndTypeResolver::similarNameSuggestions(ASTString const& _name) const { return util::quotedAlternativesList(m_currentScope->similarNames(_name)); } DeclarationRegistrationHelper::DeclarationRegistrationHelper( - map>& _scopes, + std::map>& _scopes, ASTNode& _astRoot, ErrorReporter& _errorReporter, GlobalContext& _globalContext, @@ -495,7 +503,7 @@ DeclarationRegistrationHelper::DeclarationRegistrationHelper( bool DeclarationRegistrationHelper::registerDeclaration( DeclarationContainer& _container, Declaration const& _declaration, - string const* _name, + std::string const* _name, SourceLocation const* _errorLocation, bool _inactive, ErrorReporter& _errorReporter @@ -504,13 +512,13 @@ bool DeclarationRegistrationHelper::registerDeclaration( if (!_errorLocation) _errorLocation = &_declaration.location(); - string name = _name ? *_name : _declaration.name(); + std::string name = _name ? *_name : _declaration.name(); // We use "invisible" for both inactive variables in blocks and for members invisible in contracts. // They cannot both be true at the same time. solAssert(!(_inactive && !_declaration.isVisibleInContract()), ""); - static set illegalNames{"_", "super", "this"}; + static std::set illegalNames{"_", "super", "this"}; if (illegalNames.count(name)) { @@ -573,7 +581,7 @@ bool DeclarationRegistrationHelper::visit(SourceUnit& _sourceUnit) { if (!m_scopes[&_sourceUnit]) // By importing, it is possible that the container already exists. - m_scopes[&_sourceUnit] = make_shared(m_currentScope, m_scopes[m_currentScope].get()); + m_scopes[&_sourceUnit] = std::make_shared(m_currentScope, m_scopes[m_currentScope].get()); return ASTVisitor::visit(_sourceUnit); } @@ -587,7 +595,7 @@ bool DeclarationRegistrationHelper::visit(ImportDirective& _import) SourceUnit const* importee = _import.annotation().sourceUnit; solAssert(!!importee, ""); if (!m_scopes[importee]) - m_scopes[importee] = make_shared(nullptr, m_scopes[nullptr].get()); + m_scopes[importee] = std::make_shared(nullptr, m_scopes[nullptr].get()); m_scopes[&_import] = m_scopes[importee]; ASTVisitor::visit(_import); return false; // Do not recurse into child nodes (Identifier for symbolAliases) @@ -634,7 +642,7 @@ bool DeclarationRegistrationHelper::visitNode(ASTNode& _node) if (auto* annotation = dynamic_cast(&_node.annotation())) { - string canonicalName = dynamic_cast(_node).name(); + std::string canonicalName = dynamic_cast(_node).name(); solAssert(!canonicalName.empty(), ""); for ( @@ -677,7 +685,7 @@ void DeclarationRegistrationHelper::enterNewSubScope(ASTNode& _subScope) { bool newlyAdded = m_scopes.emplace( &_subScope, - make_shared(m_currentScope, m_scopes[m_currentScope].get()) + std::make_shared(m_currentScope, m_scopes[m_currentScope].get()) ).second; solAssert(newlyAdded, "Unable to add new scope."); } diff --git a/compiler/libsolidity/analysis/NameAndTypeResolver.h b/compiler/libsolidity/analysis/NameAndTypeResolver.h index c5bf11b5..52c0c6dd 100644 --- a/compiler/libsolidity/analysis/NameAndTypeResolver.h +++ b/compiler/libsolidity/analysis/NameAndTypeResolver.h @@ -59,7 +59,8 @@ class NameAndTypeResolver NameAndTypeResolver( GlobalContext& _globalContext, langutil::EVMVersion _evmVersion, - langutil::ErrorReporter& _errorReporter + langutil::ErrorReporter& _errorReporter, + bool _experimentalSolidity ); /// Registers all declarations found in the AST node, usually a source unit. /// @returns false in case of error. @@ -96,7 +97,7 @@ class NameAndTypeResolver /// Resolves a path starting from the "current" scope, but also searches parent scopes. /// Should only be called during the initial resolving phase. /// @note Returns an empty vector if any component in the path was non-unique or not found. Otherwise, all declarations along the path are returned. - std::vector pathFromCurrentScopeWithAllDeclarations(std::vector const& _path) const; + std::vector pathFromCurrentScopeWithAllDeclarations(std::vector const& _path, bool _includeInvisibles = false) const; /// Generate and store warnings about declarations with the same name. void warnHomonymDeclarations() const; @@ -107,6 +108,7 @@ class NameAndTypeResolver /// Sets the current scope. void setScope(ASTNode const* _node); + bool experimentalSolidity() const { return m_experimentalSolidity; } private: /// Internal version of @a resolveNamesAndTypes (called from there) throws exceptions on fatal errors. bool resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode = true); @@ -132,6 +134,7 @@ class NameAndTypeResolver DeclarationContainer* m_currentScope = nullptr; langutil::ErrorReporter& m_errorReporter; GlobalContext& m_globalContext; + bool m_experimentalSolidity = false; }; /** diff --git a/compiler/libsolidity/analysis/OverrideChecker.cpp b/compiler/libsolidity/analysis/OverrideChecker.cpp index acfe7c81..b457a1bc 100644 --- a/compiler/libsolidity/analysis/OverrideChecker.cpp +++ b/compiler/libsolidity/analysis/OverrideChecker.cpp @@ -31,7 +31,6 @@ #include -using namespace std; using namespace solidity; using namespace solidity::frontend; using namespace solidity::langutil; @@ -46,7 +45,7 @@ namespace // Helper struct to do a search by name struct MatchByName { - string const& m_name; + std::string const& m_name; bool operator()(OverrideProxy const& _item) { return _item.name() == m_name; @@ -61,7 +60,7 @@ struct MatchByName */ struct OverrideGraph { - OverrideGraph(set const& _baseCallables) + OverrideGraph(std::set const& _baseCallables) { for (auto const& baseFunction: _baseCallables) addEdge(0, visit(baseFunction)); @@ -131,17 +130,17 @@ struct CutVertexFinder run(vInd, _depth + 1); if (m_low[vInd] >= m_depths[_u] && m_parent[_u] != -1) m_cutVertices.insert(m_graph.nodeInv.at(static_cast(_u))); - m_low[_u] = min(m_low[_u], m_low[vInd]); + m_low[_u] = std::min(m_low[_u], m_low[vInd]); } else if (v != m_parent[_u]) - m_low[_u] = min(m_low[_u], m_depths[vInd]); + m_low[_u] = std::min(m_low[_u], m_depths[vInd]); } } }; -vector resolveDirectBaseContracts(ContractDefinition const& _contract) +std::vector resolveDirectBaseContracts(ContractDefinition const& _contract) { - vector resolvedContracts; + std::vector resolvedContracts; for (ASTPointer const& specifier: _contract.baseContracts()) { @@ -155,7 +154,7 @@ vector resolveDirectBaseContracts(ContractDefinition return resolvedContracts; } -vector> sortByContract(vector> const& _list) +std::vector> sortByContract(std::vector> const& _list) { auto sorted = _list; @@ -197,17 +196,17 @@ bool OverrideProxy::operator<(OverrideProxy const& _other) const bool OverrideProxy::isVariable() const { - return holds_alternative(m_item); + return std::holds_alternative(m_item); } bool OverrideProxy::isFunction() const { - return holds_alternative(m_item); + return std::holds_alternative(m_item); } bool OverrideProxy::isModifier() const { - return holds_alternative(m_item); + return std::holds_alternative(m_item); } bool OverrideProxy::CompareBySignature::operator()(OverrideProxy const& _a, OverrideProxy const& _b) const @@ -222,18 +221,18 @@ size_t OverrideProxy::id() const }, m_item); } -shared_ptr OverrideProxy::overrides() const +std::shared_ptr OverrideProxy::overrides() const { return std::visit(GenericVisitor{ [&](auto const* _item) { return _item->overrides(); } }, m_item); } -set OverrideProxy::baseFunctions() const +std::set OverrideProxy::baseFunctions() const { return std::visit(GenericVisitor{ - [&](auto const* _item) -> set { - set ret; + [&](auto const* _item) -> std::set { + std::set ret; for (auto const* f: _item->annotation().baseFunctions) ret.insert(makeOverrideProxy(*f)); return ret; @@ -256,10 +255,10 @@ void OverrideProxy::storeBaseFunction(OverrideProxy const& _base) const }, m_item); } -string const& OverrideProxy::name() const +std::string const& OverrideProxy::name() const { return std::visit(GenericVisitor{ - [&](auto const* _item) -> string const& { return _item->name(); } + [&](auto const* _item) -> std::string const& { return _item->name(); } }, m_item); } @@ -272,7 +271,7 @@ ContractDefinition const& OverrideProxy::contract() const }, m_item); } -string const& OverrideProxy::contractName() const +std::string const& OverrideProxy::contractName() const { return contract().name(); } @@ -357,7 +356,7 @@ SourceLocation const& OverrideProxy::location() const }, m_item); } -string OverrideProxy::astNodeName() const +std::string OverrideProxy::astNodeName() const { return std::visit(GenericVisitor{ [&](FunctionDefinition const*) { return "function"; }, @@ -366,7 +365,7 @@ string OverrideProxy::astNodeName() const }, m_item); } -string OverrideProxy::astNodeNameCapitalized() const +std::string OverrideProxy::astNodeNameCapitalized() const { return std::visit(GenericVisitor{ [&](FunctionDefinition const*) { return "Function"; }, @@ -375,7 +374,7 @@ string OverrideProxy::astNodeNameCapitalized() const }, m_item); } -string OverrideProxy::distinguishingProperty() const +std::string OverrideProxy::distinguishingProperty() const { return std::visit(GenericVisitor{ [&](FunctionDefinition const*) { return "name and parameter types"; }, @@ -418,10 +417,10 @@ OverrideProxy::OverrideComparator const& OverrideProxy::overrideComparator() con { if (!m_comparator) { - m_comparator = make_shared(std::visit(GenericVisitor{ + m_comparator = std::make_shared(std::visit(GenericVisitor{ [&](FunctionDefinition const* _function) { - vector paramTypes; + std::vector paramTypes; for (Type const* t: externalFunctionType()->parameterTypes()) paramTypes.emplace_back(t->richIdentifier()); return OverrideComparator{ @@ -432,7 +431,7 @@ OverrideProxy::OverrideComparator const& OverrideProxy::overrideComparator() con }, [&](VariableDeclaration const* _var) { - vector paramTypes; + std::vector paramTypes; for (Type const* t: externalFunctionType()->parameterTypes()) paramTypes.emplace_back(t->richIdentifier()); return OverrideComparator{ @@ -671,21 +670,21 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr void OverrideChecker::overrideListError( OverrideProxy const& _item, - set _secondary, + std::set _secondary, ErrorId _error, - string const& _message1, - string const& _message2 + std::string const& _message1, + std::string const& _message2 ) { // Using a set rather than a vector so the order is always the same - set names; + std::set names; SecondarySourceLocation ssl; for (Declaration const* c: _secondary) { ssl.append("This contract: ", c->location()); names.insert("\"" + c->name() + "\""); } - string contractSingularPlural = "contract "; + std::string contractSingularPlural = "contract "; if (_secondary.size() > 1) contractSingularPlural = "contracts "; @@ -705,8 +704,8 @@ void OverrideChecker::overrideError( OverrideProxy const& _overriding, OverrideProxy const& _super, ErrorId _error, - string const& _message, - optional const& _secondaryMsg + std::string const& _message, + std::optional const& _secondaryMsg ) { m_errorReporter.typeError( @@ -763,7 +762,7 @@ void OverrideChecker::checkAmbiguousOverrides(ContractDefinition const& _contrac } } -void OverrideChecker::checkAmbiguousOverridesInternal(set _baseCallables, SourceLocation const& _location) const +void OverrideChecker::checkAmbiguousOverridesInternal(std::set _baseCallables, SourceLocation const& _location) const { if (_baseCallables.size() <= 1) return; @@ -796,17 +795,17 @@ void OverrideChecker::checkAmbiguousOverridesInternal(set _baseCa for (OverrideProxy const& baseFunction: _baseCallables) ssl.append("Definition in \"" + baseFunction.contractName() + "\": ", baseFunction.location()); - string callableName = _baseCallables.begin()->astNodeName(); + std::string callableName = _baseCallables.begin()->astNodeName(); if (_baseCallables.begin()->isVariable()) callableName = "function"; - string distinguishigProperty = _baseCallables.begin()->distinguishingProperty(); + std::string distinguishigProperty = _baseCallables.begin()->distinguishingProperty(); bool foundVariable = false; for (auto const& base: _baseCallables) if (base.isVariable()) foundVariable = true; - string message = + std::string message = "Derived contract must override " + callableName + " \"" + _baseCallables.begin()->name() + "\". Two or more base classes define " + callableName + " with same " + distinguishigProperty + "."; @@ -819,9 +818,9 @@ void OverrideChecker::checkAmbiguousOverridesInternal(set _baseCa m_errorReporter.typeError(6480_error, _location, ssl, message); } -set OverrideChecker::resolveOverrideList(OverrideSpecifier const& _overrides) const +std::set OverrideChecker::resolveOverrideList(OverrideSpecifier const& _overrides) const { - set resolved; + std::set resolved; for (ASTPointer const& override: _overrides.overrides()) { @@ -839,7 +838,7 @@ set OverrideChecker::re void OverrideChecker::checkOverrideList(OverrideProxy _item, OverrideProxyBySignatureMultiSet const& _inherited) { - set specifiedContracts = + std::set specifiedContracts = _item.overrides() ? resolveOverrideList(*_item.overrides()) : decltype(specifiedContracts){}; @@ -848,7 +847,7 @@ void OverrideChecker::checkOverrideList(OverrideProxy _item, OverrideProxyBySign if (_item.overrides() && specifiedContracts.size() != _item.overrides()->overrides().size()) { // Sort by contract id to find duplicate for error reporting - vector> list = + std::vector> list = sortByContract(_item.overrides()->overrides()); // Find duplicates and output error @@ -877,7 +876,7 @@ void OverrideChecker::checkOverrideList(OverrideProxy _item, OverrideProxyBySign } } - set expectedContracts; + std::set expectedContracts; // Build list of expected contracts for (auto [begin, end] = _inherited.equal_range(_item); begin != end; begin++) @@ -895,7 +894,7 @@ void OverrideChecker::checkOverrideList(OverrideProxy _item, OverrideProxyBySign _item.astNodeNameCapitalized() + " has override specified but does not override anything." ); - set missingContracts; + std::set missingContracts; // If we expect only one contract, no contract needs to be specified if (expectedContracts.size() > 1) missingContracts = expectedContracts - specifiedContracts; @@ -928,7 +927,7 @@ OverrideChecker::OverrideProxyBySignatureMultiSet const& OverrideChecker::inheri for (auto const* base: resolveDirectBaseContracts(_contract)) { - set functionsInBase; + std::set functionsInBase; for (FunctionDefinition const* fun: base->definedFunctions()) if (!fun->isConstructor()) functionsInBase.emplace(OverrideProxy{fun}); @@ -957,7 +956,7 @@ OverrideChecker::OverrideProxyBySignatureMultiSet const& OverrideChecker::inheri for (auto const* base: resolveDirectBaseContracts(_contract)) { - set modifiersInBase; + std::set modifiersInBase; for (ModifierDefinition const* mod: base->functionModifiers()) modifiersInBase.emplace(OverrideProxy{mod}); diff --git a/compiler/libsolidity/analysis/PostTypeChecker.cpp b/compiler/libsolidity/analysis/PostTypeChecker.cpp index b5b973aa..303635da 100644 --- a/compiler/libsolidity/analysis/PostTypeChecker.cpp +++ b/compiler/libsolidity/analysis/PostTypeChecker.cpp @@ -25,9 +25,10 @@ #include #include +#include + #include -using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; @@ -130,6 +131,17 @@ void PostTypeChecker::endVisit(ModifierInvocation const& _modifierInvocation) callEndVisit(_modifierInvocation); } + +bool PostTypeChecker::visit(ForStatement const& _forStatement) +{ + return callVisit(_forStatement); +} + +void PostTypeChecker::endVisit(ForStatement const& _forStatement) +{ + callEndVisit(_forStatement); +} + namespace { struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker @@ -204,7 +216,7 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker // Iterating through the dependencies needs to be deterministic and thus cannot // depend on the memory layout. // Because of that, we sort by AST node id. - vector dependencies( + std::vector dependencies( m_constVariableDependencies[&_variable].begin(), m_constVariableDependencies[&_variable].end() ); @@ -411,7 +423,7 @@ struct ReservedErrorSelector: public PostTypeChecker::Checker ); else { - uint32_t selector = util::selectorFromSignature32(_error.functionType(true)->externalSignature()); + uint32_t selector = util::selectorFromSignatureU32(_error.functionType(true)->externalSignature()); if (selector == 0 || ~selector == 0) m_errorReporter.syntaxError( 2855_error, @@ -422,15 +434,95 @@ struct ReservedErrorSelector: public PostTypeChecker::Checker } }; +class LValueChecker: public ASTConstVisitor +{ +public: + LValueChecker(Identifier const& _identifier): + m_declaration(_identifier.annotation().referencedDeclaration) + {} + bool willBeWrittenTo() const { return m_willBeWrittenTo; } + void endVisit(Identifier const& _identifier) override + { + if (m_willBeWrittenTo) + return; + + solAssert(_identifier.annotation().referencedDeclaration); + if ( + *_identifier.annotation().referencedDeclaration == *m_declaration && + _identifier.annotation().willBeWrittenTo + ) + m_willBeWrittenTo = true; + } +private: + Declaration const* m_declaration{}; + bool m_willBeWrittenTo = false; +}; + +struct SimpleCounterForLoopChecker: public PostTypeChecker::Checker +{ + SimpleCounterForLoopChecker(ErrorReporter& _errorReporter): Checker(_errorReporter) {} + bool visit(ForStatement const& _forStatement) override + { + _forStatement.annotation().isSimpleCounterLoop = isSimpleCounterLoop(_forStatement); + return true; + } + bool isSimpleCounterLoop(ForStatement const& _forStatement) const + { + auto const* simpleCondition = dynamic_cast(_forStatement.condition()); + if (!simpleCondition || simpleCondition->getOperator() != Token::LessThan || simpleCondition->userDefinedFunctionType()) + return false; + if (!_forStatement.loopExpression()) + return false; + + auto const* simplePostExpression = dynamic_cast(&_forStatement.loopExpression()->expression()); + // This matches both operators ++i and i++ + if (!simplePostExpression || simplePostExpression->getOperator() != Token::Inc || simplePostExpression->userDefinedFunctionType()) + return false; + + auto const* lhsIdentifier = dynamic_cast(&simpleCondition->leftExpression()); + auto const* lhsIntegerType = dynamic_cast(simpleCondition->leftExpression().annotation().type); + auto const* commonIntegerType = dynamic_cast(simpleCondition->annotation().commonType); + + if (!lhsIdentifier || !lhsIntegerType || !commonIntegerType || *lhsIntegerType != *commonIntegerType) + return false; + + auto const* incExpressionIdentifier = dynamic_cast(&simplePostExpression->subExpression()); + if ( + !incExpressionIdentifier || + incExpressionIdentifier->annotation().referencedDeclaration != lhsIdentifier->annotation().referencedDeclaration + ) + return false; + + solAssert(incExpressionIdentifier->annotation().referencedDeclaration); + if ( + auto const* incVariableDeclaration = dynamic_cast( + incExpressionIdentifier->annotation().referencedDeclaration + ); + incVariableDeclaration && + !incVariableDeclaration->isLocalVariable() + ) + return false; + + solAssert(lhsIdentifier); + LValueChecker lValueChecker{*lhsIdentifier}; + simpleCondition->rightExpression().accept(lValueChecker); + if (!lValueChecker.willBeWrittenTo()) + _forStatement.body().accept(lValueChecker); + + return !lValueChecker.willBeWrittenTo(); + } +}; + } PostTypeChecker::PostTypeChecker(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) { - m_checkers.push_back(make_shared(_errorReporter)); - m_checkers.push_back(make_shared(_errorReporter)); - m_checkers.push_back(make_shared(_errorReporter)); - m_checkers.push_back(make_shared(_errorReporter)); - m_checkers.push_back(make_shared(_errorReporter)); - m_checkers.push_back(make_shared(_errorReporter)); + m_checkers.push_back(std::make_shared(_errorReporter)); + m_checkers.push_back(std::make_shared(_errorReporter)); + m_checkers.push_back(std::make_shared(_errorReporter)); + m_checkers.push_back(std::make_shared(_errorReporter)); + m_checkers.push_back(std::make_shared(_errorReporter)); + m_checkers.push_back(std::make_shared(_errorReporter)); + m_checkers.push_back(std::make_shared(_errorReporter)); } diff --git a/compiler/libsolidity/analysis/PostTypeChecker.h b/compiler/libsolidity/analysis/PostTypeChecker.h index 04b4fbe7..178130a3 100644 --- a/compiler/libsolidity/analysis/PostTypeChecker.h +++ b/compiler/libsolidity/analysis/PostTypeChecker.h @@ -97,6 +97,9 @@ class PostTypeChecker: private ASTConstVisitor bool visit(ModifierInvocation const& _modifierInvocation) override; void endVisit(ModifierInvocation const& _modifierInvocation) override; + bool visit(ForStatement const& _forStatement) override; + void endVisit(ForStatement const& _forStatement) override; + template bool callVisit(T const& _node) { diff --git a/compiler/libsolidity/analysis/PostTypeContractLevelChecker.cpp b/compiler/libsolidity/analysis/PostTypeContractLevelChecker.cpp index 38a192c2..6d8dc416 100644 --- a/compiler/libsolidity/analysis/PostTypeContractLevelChecker.cpp +++ b/compiler/libsolidity/analysis/PostTypeContractLevelChecker.cpp @@ -26,7 +26,6 @@ #include #include -using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; @@ -48,11 +47,11 @@ bool PostTypeContractLevelChecker::check(ContractDefinition const& _contract) "" ); - map> errorHashes; + std::map> errorHashes; for (ErrorDefinition const* error: _contract.interfaceErrors()) { - string signature = error->functionType(true)->externalSignature(); - uint32_t hash = util::selectorFromSignature32(signature); + std::string signature = error->functionType(true)->externalSignature(); + uint32_t hash = util::selectorFromSignatureU32(signature); // Fail if there is a different signature for the same hash. if (!errorHashes[hash].empty() && !errorHashes[hash].count(signature)) { diff --git a/compiler/libsolidity/analysis/ReferencesResolver.cpp b/compiler/libsolidity/analysis/ReferencesResolver.cpp index 74e0b367..d5d2f568 100644 --- a/compiler/libsolidity/analysis/ReferencesResolver.cpp +++ b/compiler/libsolidity/analysis/ReferencesResolver.cpp @@ -34,7 +34,6 @@ #include #include -using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; @@ -123,6 +122,21 @@ bool ReferencesResolver::visit(VariableDeclaration const& _varDecl) if (_varDecl.documentation()) resolveInheritDoc(*_varDecl.documentation(), _varDecl.annotation()); + if (m_resolver.experimentalSolidity()) + { + solAssert(!_varDecl.hasTypeName()); + if (_varDecl.typeExpression()) + { + ScopedSaveAndRestore typeContext{m_typeContext, true}; + _varDecl.typeExpression()->accept(*this); + } + if (_varDecl.overrides()) + _varDecl.overrides()->accept(*this); + if (_varDecl.value()) + _varDecl.value()->accept(*this); + return false; + } + return true; } @@ -131,8 +145,10 @@ bool ReferencesResolver::visit(Identifier const& _identifier) auto declarations = m_resolver.nameFromCurrentScope(_identifier.name()); if (declarations.empty()) { - string suggestions = m_resolver.similarNameSuggestions(_identifier.name()); - string errorMessage = "Undeclared identifier."; + if (m_resolver.experimentalSolidity() && m_typeContext) + return false; + std::string suggestions = m_resolver.similarNameSuggestions(_identifier.name()); + std::string errorMessage = "Undeclared identifier."; if (!suggestions.empty()) { if ("\"" + _identifier.name() + "\"" == suggestions) @@ -151,7 +167,7 @@ bool ReferencesResolver::visit(Identifier const& _identifier) bool ReferencesResolver::visit(FunctionDefinition const& _functionDefinition) { - m_returnParameters.push_back(_functionDefinition.returnParameterList().get()); + m_functionDefinitions.push_back(&_functionDefinition); if (_functionDefinition.documentation()) resolveInheritDoc(*_functionDefinition.documentation(), _functionDefinition.annotation()); @@ -161,13 +177,13 @@ bool ReferencesResolver::visit(FunctionDefinition const& _functionDefinition) void ReferencesResolver::endVisit(FunctionDefinition const&) { - solAssert(!m_returnParameters.empty(), ""); - m_returnParameters.pop_back(); + solAssert(!m_functionDefinitions.empty(), ""); + m_functionDefinitions.pop_back(); } bool ReferencesResolver::visit(ModifierDefinition const& _modifierDefinition) { - m_returnParameters.push_back(nullptr); + m_functionDefinitions.push_back(nullptr); if (_modifierDefinition.documentation()) resolveInheritDoc(*_modifierDefinition.documentation(), _modifierDefinition.annotation()); @@ -177,12 +193,13 @@ bool ReferencesResolver::visit(ModifierDefinition const& _modifierDefinition) void ReferencesResolver::endVisit(ModifierDefinition const&) { - solAssert(!m_returnParameters.empty(), ""); - m_returnParameters.pop_back(); + solAssert(!m_functionDefinitions.empty(), ""); + m_functionDefinitions.pop_back(); } void ReferencesResolver::endVisit(IdentifierPath const& _path) { + // Note that library/functions names in "using {} for" directive are resolved separately in visit(UsingForDirective) std::vector declarations = m_resolver.pathFromCurrentScopeWithAllDeclarations(_path.path()); if (declarations.empty()) { @@ -194,18 +211,64 @@ void ReferencesResolver::endVisit(IdentifierPath const& _path) _path.annotation().pathDeclarations = std::move(declarations); } -bool ReferencesResolver::visit(InlineAssembly const& /*_inlineAssembly*/) +bool ReferencesResolver::visit(UsingForDirective const& _usingFor) { + for (ASTPointer const& path: _usingFor.functionsOrLibrary()) + { + // _includeInvisibles is enabled here because external library functions are marked invisible. + // As unintended side-effects other invisible names (eg.: super, this) may be returned as well. + // DeclarationTypeChecker should detect and report such situations. + std::vector declarations = m_resolver.pathFromCurrentScopeWithAllDeclarations(path->path(), true /* _includeInvisibles */); + if (declarations.empty()) + { + std::string libraryOrFunctionNameErrorMessage = + _usingFor.usesBraces() ? + "Identifier is not a function name or not unique." : + "Identifier is not a library name."; + m_errorReporter.fatalDeclarationError( + 9589_error, + path->location(), + libraryOrFunctionNameErrorMessage + ); + break; + } + + path->annotation().referencedDeclaration = declarations.back(); + path->annotation().pathDeclarations = std::move(declarations); + } + + if (_usingFor.typeName()) + _usingFor.typeName()->accept(*this); + return false; } bool ReferencesResolver::visit(Return const& _return) { - solAssert(!m_returnParameters.empty(), ""); - _return.annotation().functionReturnParameters = m_returnParameters.back(); + solAssert(!m_functionDefinitions.empty(), ""); + _return.annotation().function = m_functionDefinitions.back(); + _return.annotation().functionReturnParameters = m_functionDefinitions.back() ? m_functionDefinitions.back()->returnParameterList().get() : nullptr; return true; } +bool ReferencesResolver::visit(BinaryOperation const& _binaryOperation) +{ + if (m_resolver.experimentalSolidity()) + { + _binaryOperation.leftExpression().accept(*this); + if (_binaryOperation.getOperator() == Token::Colon) + { + ScopedSaveAndRestore typeContext(m_typeContext, !m_typeContext); + _binaryOperation.rightExpression().accept(*this); + } + else + _binaryOperation.rightExpression().accept(*this); + return false; + } + else + return ASTConstVisitor::visit(_binaryOperation); +} + void ReferencesResolver::resolveInheritDoc(StructuredDocumentation const& _documentation, StructurallyDocumentedAnnotation& _annotation) { switch (_annotation.docTags.count("inheritdoc")) @@ -214,7 +277,7 @@ void ReferencesResolver::resolveInheritDoc(StructuredDocumentation const& _docum break; case 1: { - string const& name = _annotation.docTags.find("inheritdoc")->second.content; + std::string const& name = _annotation.docTags.find("inheritdoc")->second.content; if (name.empty()) { m_errorReporter.docstringParsingError( @@ -225,7 +288,7 @@ void ReferencesResolver::resolveInheritDoc(StructuredDocumentation const& _docum return; } - vector path; + std::vector path; boost::split(path, name, boost::is_any_of(".")); if (any_of(path.begin(), path.end(), [](auto& _str) { return _str.empty(); })) { diff --git a/compiler/libsolidity/analysis/ReferencesResolver.h b/compiler/libsolidity/analysis/ReferencesResolver.h index c6e4a9cd..97b7efe1 100644 --- a/compiler/libsolidity/analysis/ReferencesResolver.h +++ b/compiler/libsolidity/analysis/ReferencesResolver.h @@ -80,17 +80,20 @@ class ReferencesResolver: private ASTConstVisitor bool visit(ModifierDefinition const& _modifierDefinition) override; void endVisit(ModifierDefinition const& _modifierDefinition) override; void endVisit(IdentifierPath const& _path) override; - bool visit(InlineAssembly const& _inlineAssembly) override; bool visit(Return const& _return) override; + bool visit(UsingForDirective const& _usingFor) override; + bool visit(BinaryOperation const& _binaryOperation) override; void resolveInheritDoc(StructuredDocumentation const& _documentation, StructurallyDocumentedAnnotation& _annotation); langutil::ErrorReporter& m_errorReporter; NameAndTypeResolver& m_resolver; langutil::EVMVersion m_evmVersion; - /// Stack of return parameters. - std::vector m_returnParameters; + /// Stack of function definitions. + std::vector m_functionDefinitions; bool const m_resolveInsideCode; + + bool m_typeContext = false; }; } diff --git a/compiler/libsolidity/analysis/Scoper.cpp b/compiler/libsolidity/analysis/Scoper.cpp index 98716cf0..a14ca455 100644 --- a/compiler/libsolidity/analysis/Scoper.cpp +++ b/compiler/libsolidity/analysis/Scoper.cpp @@ -20,7 +20,6 @@ #include -using namespace std; using namespace solidity; using namespace solidity::frontend; diff --git a/compiler/libsolidity/analysis/StaticAnalyzer.cpp b/compiler/libsolidity/analysis/StaticAnalyzer.cpp index daad4564..07190cf3 100644 --- a/compiler/libsolidity/analysis/StaticAnalyzer.cpp +++ b/compiler/libsolidity/analysis/StaticAnalyzer.cpp @@ -28,7 +28,6 @@ #include #include -using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; @@ -36,43 +35,6 @@ using namespace solidity::frontend; /** * Helper class that determines whether a contract's constructor uses inline assembly. */ -class solidity::frontend::ConstructorUsesAssembly -{ -public: - /// @returns true if and only if the contract's or any of its bases' constructors - /// use inline assembly. - bool check(ContractDefinition const& _contract) - { - for (auto const* base: _contract.annotation().linearizedBaseContracts) - if (checkInternal(*base)) - return true; - return false; - } - - -private: - class Checker: public ASTConstVisitor - { - public: - Checker(FunctionDefinition const& _f) { _f.accept(*this); } - bool visit(InlineAssembly const&) override { assemblySeen = true; return false; } - bool assemblySeen = false; - }; - - bool checkInternal(ContractDefinition const& _contract) - { - if (!m_usesAssembly.count(&_contract)) - { - bool usesAssembly = false; - if (_contract.constructor()) - usesAssembly = Checker{*_contract.constructor()}.assemblySeen; - m_usesAssembly[&_contract] = usesAssembly; - } - return m_usesAssembly[&_contract]; - } - - map m_usesAssembly; -}; StaticAnalyzer::StaticAnalyzer(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) @@ -125,7 +87,7 @@ void StaticAnalyzer::endVisit(FunctionDefinition const& _funDefinition) 5667_error, var.first.second->location(), "Unused " + - string(var.first.second->isTryCatchParameter() ? "try/catch" : "function") + + std::string(var.first.second->isTryCatchParameter() ? "try/catch" : "function") + " parameter. Remove or comment out the variable name to silence this warning." ); } @@ -145,7 +107,7 @@ bool StaticAnalyzer::visit(Identifier const& _identifier) { solAssert(!var->name().empty(), ""); if (var->isLocalVariable()) - m_localVarUseCount[make_pair(var->id(), var)] += 1; + m_localVarUseCount[std::make_pair(var->id(), var)] += 1; } return true; } @@ -157,7 +119,7 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable) solAssert(_variable.isLocalVariable(), ""); if (_variable.name() != "") // This is not a no-op, the entry might pre-exist. - m_localVarUseCount[make_pair(_variable.id(), &_variable)] += 0; + m_localVarUseCount[std::make_pair(_variable.id(), &_variable)] += 0; } return true; @@ -170,7 +132,7 @@ bool StaticAnalyzer::visit(Return const& _return) if (m_currentFunction && _return.expression()) for (auto const& var: m_currentFunction->returnParameters()) if (!var->name().empty()) - m_localVarUseCount[make_pair(var->id(), var.get())] += 1; + m_localVarUseCount[std::make_pair(var->id(), var.get())] += 1; return true; } @@ -202,19 +164,6 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) _memberAccess.location(), "\"block.blockhash()\" has been deprecated in favor of \"blockhash()\"" ); - else if (type->kind() == MagicType::Kind::MetaType && _memberAccess.memberName() == "runtimeCode") - { - if (!m_constructorUsesAssembly) - m_constructorUsesAssembly = make_unique(); - ContractType const& contract = dynamic_cast(*type->typeArgument()); - if (m_constructorUsesAssembly->check(contract.contractDefinition())) - m_errorReporter.warning( - 6417_error, - _memberAccess.location(), - "The constructor of the contract (or its base) uses inline assembly. " - "Because of that, it might be that the deployed bytecode is different from type(...).runtimeCode." - ); - } } if (_memberAccess.memberName() == "callcode") @@ -246,11 +195,6 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) return true; } -bool StaticAnalyzer::visit(InlineAssembly const& /*_inlineAssembly*/) -{ - return true; -} - bool StaticAnalyzer::visit(BinaryOperation const& _operation) { if ( diff --git a/compiler/libsolidity/analysis/StaticAnalyzer.h b/compiler/libsolidity/analysis/StaticAnalyzer.h index dc6263d7..d07e9960 100644 --- a/compiler/libsolidity/analysis/StaticAnalyzer.h +++ b/compiler/libsolidity/analysis/StaticAnalyzer.h @@ -37,9 +37,6 @@ class ErrorReporter; namespace solidity::frontend { -class ConstructorUsesAssembly; - - /** * The module that performs static analysis on the AST. * In this context, static analysis is anything that can produce warnings which can help @@ -70,7 +67,6 @@ class StaticAnalyzer: private ASTConstVisitor bool visit(Identifier const& _identifier) override; bool visit(Return const& _return) override; bool visit(MemberAccess const& _memberAccess) override; - bool visit(InlineAssembly const& _inlineAssembly) override; bool visit(BinaryOperation const& _operation) override; bool visit(FunctionCall const& _functionCall) override; @@ -84,10 +80,6 @@ class StaticAnalyzer: private ASTConstVisitor /// when traversing. std::map, int> m_localVarUseCount; - /// Cache that holds information about whether a contract's constructor - /// uses inline assembly. - std::unique_ptr m_constructorUsesAssembly; - FunctionDefinition const* m_currentFunction = nullptr; /// Flag that indicates a constructor. diff --git a/compiler/libsolidity/analysis/SyntaxChecker.cpp b/compiler/libsolidity/analysis/SyntaxChecker.cpp index 99418d33..96f47219 100644 --- a/compiler/libsolidity/analysis/SyntaxChecker.cpp +++ b/compiler/libsolidity/analysis/SyntaxChecker.cpp @@ -29,7 +29,6 @@ #include -using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; @@ -52,17 +51,17 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit) { if (!m_versionPragma.has_value()) { - string errorString("Source file does not specify required compiler version!"); - SemVerVersion recommendedVersion{string(VersionString)}; + std::string errorString("Source file does not specify required compiler version!"); + SemVerVersion recommendedVersion{std::string(VersionString)}; if (!recommendedVersion.isPrerelease()) errorString += " Consider adding \"pragma ever-solidity ^" + - to_string(recommendedVersion.major()) + - string(".") + - to_string(recommendedVersion.minor()) + - string(".") + - to_string(recommendedVersion.patch()) + - string(";\""); + std::to_string(recommendedVersion.major()) + + std::string(".") + + std::to_string(recommendedVersion.minor()) + + std::string(".") + + std::to_string(recommendedVersion.patch()) + + std::string(";\""); // when reporting the warning, print the source name only m_errorReporter.warning(3420_error, {-1, -1, _sourceUnit.location().sourceName}, errorString); @@ -86,12 +85,8 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma) else if (_pragma.literals()[0] == "experimental") { solAssert(m_sourceUnit, ""); - - if (_pragma.literals().size() >= 2) { - m_errorReporter.warning(9089_error, _pragma.location(), "Has no effect. Delete this."); - } - - vector literals(_pragma.literals().begin() + 1, _pragma.literals().end()); + std::vector literals(_pragma.literals().begin() + 1, _pragma.literals().end()); + m_errorReporter.warning(9089_error, _pragma.location(), "\"pragma experimental\" is not supported. Delete this."); if (literals.empty()) m_errorReporter.syntaxError( 9679_error, @@ -106,7 +101,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma) ); else { - string const literal = literals[0]; + std::string const literal = literals[0]; if (literal.empty()) m_errorReporter.syntaxError(3250_error, _pragma.location(), "Empty experimental feature name is invalid."); else if (!ExperimentalFeatureNames.count(literal)) @@ -125,7 +120,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma) solAssert(m_sourceUnit, ""); if ( _pragma.literals().size() != 2 || - !set{"v1", "v2"}.count(_pragma.literals()[1]) + !std::set{"v1", "v2"}.count(_pragma.literals()[1]) ) m_errorReporter.syntaxError( 2745_error, @@ -143,16 +138,16 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma) } else if (_pragma.literals()[0] == "solidity") { - SemVerVersion recommendedVersion{string(VersionString)}; + SemVerVersion recommendedVersion{std::string(VersionString)}; std::string errorString = "It's deprecated." " Consider adding \"pragma ever-solidity ^" + - to_string(recommendedVersion.major()) + - string(".") + - to_string(recommendedVersion.minor()) + - string(".") + - to_string(recommendedVersion.patch()) + - string(";\"") + + std::to_string(recommendedVersion.major()) + + std::string(".") + + std::to_string(recommendedVersion.minor()) + + std::string(".") + + std::to_string(recommendedVersion.patch()) + + std::string(";\"") + " to set a version of the compiler."; m_errorReporter.warning(6413_error, _pragma.location(), errorString); } @@ -163,23 +158,23 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma) 8884_error, _pragma.location(), SecondarySourceLocation().append("Previous definition:", *m_versionPragma.value()), - "Compiler version is defined more than once."); + "Compiler version is defined more than once."); + } + try + { + std::vector tokens(_pragma.tokens().begin() + 3, _pragma.tokens().end()); + std::vector literals(_pragma.literals().begin() + 3, _pragma.literals().end()); + SemVerMatchExpressionParser parser(tokens, literals); + SemVerMatchExpression matchExpression = parser.parse(); + static SemVerVersion const currentVersion{std::string(VersionString)}; + solAssert(matchExpression.matches(currentVersion)); + m_versionPragma = &_pragma.location(); + } + catch (SemVerError const&) + { + // An unparsable version pragma is an unrecoverable fatal error in the parser. + solAssert(false); } - vector tokens(_pragma.tokens().begin() + 3, _pragma.tokens().end()); - vector literals(_pragma.literals().begin() + 3, _pragma.literals().end()); - SemVerMatchExpressionParser parser(tokens, literals); - auto matchExpression = parser.parse(); - // An unparsable version pragma is an unrecoverable fatal error in the parser. - solAssert(matchExpression.has_value(), ""); - static SemVerVersion const currentVersion{string(VersionString)}; - if (!matchExpression->matches(currentVersion)) - m_errorReporter.syntaxError( - 3997_error, - _pragma.location(), - "Source file requires different compiler version (current compiler is " + - string(VersionString) + ")" - ); - m_versionPragma = &_pragma.location(); } else if (_pragma.literals()[0] == "AbiHeader") { @@ -399,17 +394,10 @@ bool SyntaxChecker::visit(Literal const& _literal) bool SyntaxChecker::visit(UnaryOperation const& _operation) { - if (_operation.getOperator() == Token::Add) - m_errorReporter.syntaxError(9636_error, _operation.location(), "Use of unary + is disallowed."); - + solAssert(_operation.getOperator() != Token::Add); return true; } -bool SyntaxChecker::visit(InlineAssembly const& /*_inlineAssembly*/) -{ - return false; -} - bool SyntaxChecker::visit(PlaceholderStatement const& _placeholder) { if (m_uncheckedArithmetic) @@ -446,6 +434,12 @@ void SyntaxChecker::endVisit(ContractDefinition const&) bool SyntaxChecker::visit(UsingForDirective const& _usingFor) { + if (!_usingFor.usesBraces()) + solAssert( + _usingFor.functionsAndOperators().size() == 1 && + !std::get<1>(_usingFor.functionsAndOperators().front()) + ); + if (!m_currentContractKind && !_usingFor.typeName()) m_errorReporter.syntaxError( 8118_error, @@ -462,7 +456,7 @@ bool SyntaxChecker::visit(UsingForDirective const& _usingFor) m_errorReporter.syntaxError( 2854_error, _usingFor.location(), - "Can only globally bind functions to specific types." + "Can only globally attach functions to specific types." ); if (_usingFor.global() && m_currentContractKind) m_errorReporter.syntaxError( @@ -482,11 +476,13 @@ bool SyntaxChecker::visit(UsingForDirective const& _usingFor) bool SyntaxChecker::visit(FunctionDefinition const& _function) { - solAssert(_function.isFree() == (m_currentContractKind == std::nullopt), ""); + if (m_sourceUnit && m_sourceUnit->experimentalSolidity()) + // Handled in experimental::SyntaxRestrictor instead. + return true; if (!_function.isFree() && !_function.isConstructor() && _function.noVisibilitySpecified()) { - string suggestedVisibility = + std::string suggestedVisibility = _function.isFallback() || _function.isReceive() || m_currentContractKind == ContractKind::Interface @@ -537,3 +533,13 @@ bool SyntaxChecker::visit(StructDefinition const& _struct) return true; } + +bool SyntaxChecker::visitNode(ASTNode const& _node) +{ + if (_node.experimentalSolidityOnly()) + { + solAssert(m_sourceUnit); + solAssert(m_sourceUnit->experimentalSolidity()); + } + return ASTConstVisitor::visitNode(_node); +} diff --git a/compiler/libsolidity/analysis/SyntaxChecker.h b/compiler/libsolidity/analysis/SyntaxChecker.h index 557acf8c..967417a8 100644 --- a/compiler/libsolidity/analysis/SyntaxChecker.h +++ b/compiler/libsolidity/analysis/SyntaxChecker.h @@ -83,8 +83,6 @@ class SyntaxChecker: private ASTConstVisitor bool visit(UnaryOperation const& _operation) override; - bool visit(InlineAssembly const& _inlineAssembly) override; - bool visit(PlaceholderStatement const& _placeholderStatement) override; bool visit(ContractDefinition const& _contract) override; @@ -98,6 +96,8 @@ class SyntaxChecker: private ASTConstVisitor bool visit(StructDefinition const& _struct) override; bool visit(Literal const& _literal) override; + bool visitNode(ASTNode const&) override; + langutil::ErrorReporter& m_errorReporter; /// Flag that indicates whether a function modifier actually contains '_'. diff --git a/compiler/libsolidity/analysis/TypeChecker.cpp b/compiler/libsolidity/analysis/TypeChecker.cpp index b2a1383d..24d85434 100644 --- a/compiler/libsolidity/analysis/TypeChecker.cpp +++ b/compiler/libsolidity/analysis/TypeChecker.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -35,6 +36,9 @@ #include #include +#include + +#include #include #include @@ -44,11 +48,9 @@ #include #include -#include "../codegen/TVMCommons.hpp" -#include "../codegen/TVMConstants.hpp" - +#include +#include -using namespace std; using namespace solidity; using namespace solidity::util; using namespace solidity::langutil; @@ -129,7 +131,7 @@ void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment) TypePointers TypeChecker::getReturnTypesForTVMConfig(FunctionCall const& _functionCall) { - vector> arguments = _functionCall.arguments(); + std::vector> arguments = _functionCall.arguments(); if (arguments.size() != 1) m_errorReporter.typeError( 7750_error, @@ -139,7 +141,7 @@ TypePointers TypeChecker::getReturnTypesForTVMConfig(FunctionCall const& _functi " were provided." ); - set availableParams {"1","15","17","34"}; + std::set availableParams {"1","15","17","34"}; auto paramNumberLiteral = dynamic_cast(arguments[0].get()); if (!paramNumberLiteral) @@ -189,8 +191,8 @@ TypePointers TypeChecker::getReturnTypesForTVMConfig(FunctionCall const& _functi TypeProvider::integer(16, IntegerType::Modifier::Unsigned), TypeProvider::integer(16, IntegerType::Modifier::Unsigned), TypeProvider::integer(64, IntegerType::Modifier::Unsigned), - TypeProvider::mapping(TypeProvider::integer(16, IntegerType::Modifier::Unsigned), - TypeProvider::tvmslice()), + TypeProvider::mapping(TypeProvider::integer(16, IntegerType::Modifier::Unsigned), "", + TypeProvider::tvmslice(), ""), TypeProvider::boolean()}; return ret; } @@ -199,7 +201,7 @@ TypePointers TypeChecker::getReturnTypesForTVMConfig(FunctionCall const& _functi TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall const& _functionCall, bool _abiEncoderV2) { - vector> arguments = _functionCall.arguments(); + std::vector> arguments = _functionCall.arguments(); if (arguments.size() != 2) m_errorReporter.typeError( 5782_error, @@ -250,7 +252,6 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c // We force memory because the parser currently cannot handle // data locations. Furthermore, storage can be a little dangerous and // calldata is not really implemented anyway. - actualType = TypeProvider::withLocationIfReference(actualType); if (!actualType->fullEncodingType(false, _abiEncoderV2, false)) m_errorReporter.typeError( 9611_error, @@ -269,19 +270,19 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c return components; } -void TypeChecker::typeCheckTVMBuildStateInit( +void TypeChecker::typeCheckABIEncodeStateInit( FunctionCall const& _functionCall, const std::function& hasName, const std::function& findName ) { bool hasNames = !_functionCall.names().empty(); - const vector> &args = _functionCall.arguments(); + const std::vector> &args = _functionCall.arguments(); size_t argCnt = args.size(); if (!hasNames && argCnt != 3 && argCnt != 2) m_errorReporter.typeError( 6303_error, _functionCall.location(), - string("If parameters are set without names, only 2 or 3 arguments can be specified.") + std::string("If parameters are set without names, only 2 or 3 arguments can be specified.") ); if (hasNames) { @@ -291,98 +292,79 @@ void TypeChecker::typeCheckTVMBuildStateInit( bool hasPubkey = hasName("pubkey"); bool hasContr = hasName("contr"); - if (!hasCode) { + if (!hasCode) m_errorReporter.typeError( - 6128_error, - _functionCall.location(), - string("Parameter \"code\" must be set.") + 6128_error, + _functionCall.location(), + std::string("Parameter \"code\" must be set.") ); - } - if (hasData && (hasVarInit || hasPubkey)) { + if (hasData && (hasVarInit || hasPubkey)) m_errorReporter.typeError( - 6578_error, - _functionCall.location(), - string(R"(Parameter "data" can't be specified with "pubkey" or "varInit".)") + 6578_error, + _functionCall.location(), + std::string(R"(Parameter "data" can't be specified with "pubkey" or "varInit".)") ); - } - if (hasVarInit != hasContr) { + if (!hasContr && !hasData) m_errorReporter.typeError( - 1476_error, - _functionCall.location(), - string(R"(Parameter "varInit" requires parameter "contr" and there is no need in "contr" without "varInit".)") + 8123_error, + _functionCall.location(), + std::string(R"(Expected parameter "contr" or "data".)") ); - } - if (hasContr) { int contrInd = findName("contr"); const ASTPointer& contrArg = args.at(contrInd); ContractType const* ct = getContractType(contrArg.get()); if (ct == nullptr) { m_errorReporter.typeError( - 8286_error, - contrArg->location(), - "Expected contract type." + 8286_error, + contrArg->location(), + "Expected contract type." ); - return; + return ; } + InitializerList const * list{}; int varInitInd = findName("varInit"); - if (varInitInd == -1) { - return; - } - auto list = dynamic_cast(args.at(varInitInd).get()); - if (!list) { - // It's checked in typeCheckFunctionCall function - return; - } - checkInitList(list, ct); + if (varInitInd != -1) + list = dynamic_cast(args.at(varInitInd).get()); + checkInitList(list, *ct, _functionCall.location()); } } } -void TypeChecker::typeCheckTVMBuildDataInit( +void TypeChecker::typeCheckABIEncodeData( FunctionCall const& _functionCall, const std::function& hasName, const std::function& findName ) { - bool hasNames = !_functionCall.names().empty(); - const vector> &args = _functionCall.arguments(); + const std::vector> &args = _functionCall.arguments(); - if (hasNames) { - bool hasVarInit = hasName("varInit"); - bool hasContr = hasName("contr"); + if (!hasName("contr")) { + m_errorReporter.typeError( + 2957_error, + _functionCall.location(), + std::string(R"(Expected parameter "contr".)") + ); + return ; + } - if (hasVarInit != hasContr) { - m_errorReporter.typeError( - 2957_error, - _functionCall.location(), - string(R"(Parameter "varInit" requires parameter "contr" and there is no need in "contr" without "varInit".)") - ); - } + int contrInd = findName("contr"); + const ASTPointer& contrArg = args.at(contrInd); + ContractType const* ct = getContractType(contrArg.get()); + if (ct == nullptr) { + m_errorReporter.typeError( + 9417_error, + contrArg->location(), + "Expected contract type." + ); + return; + } - if (hasContr) { - int contrInd = findName("contr"); - const ASTPointer& contrArg = args.at(contrInd); - ContractType const* ct = getContractType(contrArg.get()); - if (ct == nullptr) { - m_errorReporter.typeError( - 9417_error, - contrArg->location(), - "Expected contract type." - ); - return; - } - int varInitInd = findName("varInit"); - if (varInitInd == -1) { - return; - } - auto list = dynamic_cast(args.at(varInitInd).get()); - if (!list) { - // It's checked in typeCheckFunctionCall function - return; - } - checkInitList(list, ct); - } + InitializerList const * list{}; + int varInitInd = findName("varInit"); + if (varInitInd != -1) { + list = dynamic_cast(args.at(varInitInd).get()); } + checkInitList(list, *ct, _functionCall.location()); } void TypeChecker::typeCheckCallBack(FunctionType const* remoteFunction, Expression const& option) { @@ -502,7 +484,7 @@ TypePointers TypeChecker::checkSliceDecodeQ(std::vector> arguments = _functionCall.arguments(); + std::vector> arguments = _functionCall.arguments(); if (arguments.size() != 1) m_errorReporter.fatalTypeError( 8885_error, @@ -573,7 +555,7 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) toString(arguments->size()) + " arguments given but expected " + toString(parameterTypes.size()) + - ". Remove parentheses if you do not want to provide arguments here." + (arguments->size() == 0 ? ". Remove parentheses if you do not want to provide arguments here." : "") ); } for (size_t i = 0; i < std::min(arguments->size(), parameterTypes.size()); ++i) @@ -776,12 +758,59 @@ bool TypeChecker::visit(FunctionDefinition const& _function) } } - vector internalParametersInConstructor; + std::vector internalParametersInConstructor; + + auto checkArgumentAndReturnParameter = [&](VariableDeclaration const& _var) { + bool functionIsExternallyVisible = + (!_function.isConstructor() && _function.isPublic()) || + (_function.isConstructor() && !m_currentContract->abstract()); + if (functionIsExternallyVisible) + { + auto iType = type(_var)->interfaceType(_function.libraryFunction()); - set modifiers; + if (!iType) + { + std::string message = iType.message(); + solAssert(!message.empty(), "Expected detailed error message!"); + if (_function.isConstructor()) + message += " You can make the contract abstract to avoid this problem."; + m_errorReporter.fatalTypeError(4103_error, _var.location(), message); + } + else if ( + !useABICoderV2() && + !typeSupportedByOldABIEncoder(*type(_var), _function.libraryFunction()) + ) + { + std::string message = + "This type is only supported in ABI coder v2. " + "Use \"pragma abicoder v2;\" to enable the feature."; + if (_function.isConstructor()) + message += + " Alternatively, make the contract abstract and supply the " + "constructor arguments from a derived contract."; + m_errorReporter.typeError( + 4957_error, + _var.location(), + message + ); + } + } + }; + for (ASTPointer const& var: _function.parameters()) + { + checkArgumentAndReturnParameter(*var); + var->accept(*this); + } + for (ASTPointer const& var: _function.returnParameters()) + { + checkArgumentAndReturnParameter(*var); + var->accept(*this); + } + + std::set modifiers; for (ASTPointer const& modifier: _function.modifiers()) { - vector baseContracts; + std::vector baseContracts; if (auto contract = dynamic_cast(_function.scope())) { baseContracts = contract->annotation().linearizedBaseContracts; @@ -791,7 +820,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function) visitManually( *modifier, - _function.isConstructor() ? baseContracts : vector() + _function.isConstructor() ? baseContracts : std::vector() ); Declaration const* decl = &dereference(modifier->name()); if (modifiers.count(decl)) @@ -854,7 +883,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function) for (ASTPointer const &var : params) { std::set usedStructs; isBadAbiType(var.get()->location(), var->type(), var.get()->location(), usedStructs, true); - var->accept(*this); +// var->accept(*this); } } } @@ -921,7 +950,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) FunctionType getter(_variable); if (!useABICoderV2()) { - vector unsupportedTypes; + std::vector unsupportedTypes; for (auto const& param: getter.parameterTypes() + getter.returnParameterTypes()) if (!typeSupportedByOldABIEncoder(*param, false /* isLibrary */)) unsupportedTypes.emplace_back(param->humanReadableName()); @@ -1017,7 +1046,7 @@ void TypeChecker::endVisit(StructDefinition const& _struct) void TypeChecker::visitManually( ModifierInvocation const& _modifier, - vector const& _bases + std::vector const& _bases ) { std::vector> const& arguments = @@ -1028,8 +1057,8 @@ void TypeChecker::visitManually( _modifier.name().accept(*this); auto const* declaration = &dereference(_modifier.name()); - vector> emptyParameterList; - vector> const* parameters = nullptr; + std::vector> emptyParameterList; + std::vector> const* parameters = nullptr; if (auto modifierDecl = dynamic_cast(declaration)) { parameters = &modifierDecl->parameters(); @@ -1138,11 +1167,6 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType) } } -bool TypeChecker::visit(InlineAssembly const& /*_inlineAssembly*/) -{ - return true; -} - bool TypeChecker::visit(IfStatement const& _ifStatement) { expectType(_ifStatement.condition(), *TypeProvider::boolean()); @@ -1185,7 +1209,7 @@ bool TypeChecker::visit(WhileStatement const& _whileStatement) expectType(_whileStatement.condition(), *TypeProvider::boolean()); break; case WhileStatement::LoopType::REPEAT: - expectType(_whileStatement.condition(), *TypeProvider::uint256()); + expectType(_whileStatement.condition(), *TypeProvider::integer(31, IntegerType::Modifier::Unsigned)); break; } @@ -1289,7 +1313,7 @@ void TypeChecker::endVisit(Return const& _return) for (size_t i = 0; i < _return.names().size(); ++i) { const std::string &name = *_return.names().at(i); const std::map nameToType = { - {"value", TypeProvider::uint(128)}, + {"value", TypeProvider::coins()}, {"currencies", TypeProvider::extraCurrencyCollection()}, {"bounce", TypeProvider::boolean()}, {"flag", TypeProvider::uint(16)}, @@ -1435,15 +1459,14 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) // the variable declaration(s). _statement.initialValue()->accept(*this); - vector> const& variables = _statement.declarations(); + std::vector> const& variables = _statement.declarations(); TypePointers valueTypes; if (auto tupleType = dynamic_cast(type(*_statement.initialValue()))) { valueTypes = tupleType->components(); if (valueTypes.size() != variables.size()) valueTypes = TypePointers{tupleType}; - } else { + } else valueTypes = TypePointers{type(*_statement.initialValue())}; - } if (variables.empty()) // We already have an error for this in the SyntaxChecker. @@ -1459,7 +1482,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) ")." ); - for (size_t i = 0; i < min(variables.size(), valueTypes.size()); ++i) + for (size_t i = 0; i < std::min(variables.size(), valueTypes.size()); ++i) { if (!variables[i]) continue; @@ -1616,14 +1639,14 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const& m_errorReporter.typeError(5547_error, _expression.location(), "Empty tuple on the left hand side."); auto const* tupleType = dynamic_cast(&_type); - auto const& types = tupleType && tupleExpression->components().size() != 1 ? tupleType->components() : vector { &_type }; + auto const& types = tupleType && tupleExpression->components().size() != 1 ? tupleType->components() : std::vector { &_type }; solAssert( tupleExpression->components().size() == types.size() || m_errorReporter.hasErrors(), "Array sizes don't match and no errors generated." ); - for (size_t i = 0; i < min(tupleExpression->components().size(), types.size()); i++) + for (size_t i = 0; i < std::min(tupleExpression->components().size(), types.size()); i++) if (types[i]) { solAssert(!!tupleExpression->components()[i], ""); @@ -1678,7 +1701,7 @@ bool TypeChecker::visit(Assignment const& _assignment) 7366_error, _assignment.location(), "Operator " + - string(TokenTraits::toString(_assignment.assignmentOperator())) + + std::string(TokenTraits::friendlyName(_assignment.assignmentOperator())) + " not compatible with types " + t->humanReadableName() + " and " + @@ -1692,7 +1715,7 @@ bool TypeChecker::visit(Assignment const& _assignment) bool TypeChecker::visit(TupleExpression const& _tuple) { _tuple.annotation().isConstant = false; - vector> const& components = _tuple.components(); + std::vector> const& components = _tuple.components(); TypePointers types; if (_tuple.annotation().willBeWrittenTo) @@ -1801,23 +1824,66 @@ bool TypeChecker::visit(UnaryOperation const& _operation) requireLValue(_operation.subExpression(), false); else _operation.subExpression().accept(*this); - Type const* subExprType = type(_operation.subExpression()); - TypeResult result = type(_operation.subExpression())->unaryOperatorResult(op); - if (!result) + Type const* operandType = type(_operation.subExpression()); + + // Check if the operator is built-in or user-defined. + TypeResult builtinResult = operandType->unaryOperatorResult(op); + std::set matchingDefinitions = operandType->operatorDefinitions( + op, + *currentDefinitionScope(), + true // _unary + ); + + // Operator can't be both user-defined and built-in at the same time. + solAssert(!builtinResult || matchingDefinitions.empty()); + + // By default use the type we'd expect from correct code. This way we can continue analysis + // of other expressions in a sensible way in case of a non-fatal error. + Type const* resultType = operandType; + + FunctionDefinition const* operatorDefinition = nullptr; + if (builtinResult) + resultType = builtinResult; + else if (!matchingDefinitions.empty()) + { + // This is checked along with `using for` directive but the error is not fatal. + if (matchingDefinitions.size() != 1) + solAssert(m_errorReporter.hasErrors()); + + operatorDefinition = *matchingDefinitions.begin(); + } + else { - string description = "Unary operator " + string(TokenTraits::toString(op)) + " cannot be applied to type " + subExprType->humanReadableName() + "." + (!result.message().empty() ? " " + result.message() : ""); + std::string description = fmt::format( + "Built-in unary operator {} cannot be applied to type {}.", + TokenTraits::friendlyName(op), + operandType->humanReadableName() + ); + if (!builtinResult.message().empty()) + description += " " + builtinResult.message(); + if (operandType->typeDefinition() && util::contains(userDefinableOperators, op)) + description += " No matching user-defined operator found."; + if (modifying) // Cannot just report the error, ignore the unary operator, and continue, // because the sub-expression was already processed with requireLValue() m_errorReporter.fatalTypeError(9767_error, _operation.location(), description); else m_errorReporter.typeError(4907_error, _operation.location(), description); - _operation.annotation().type = subExprType; } - else - _operation.annotation().type = result.get(); + + _operation.annotation().userDefinedFunction = operatorDefinition; + + if (operatorDefinition && !_operation.userDefinedFunctionType()->returnParameterTypes().empty()) + // Use the actual result type from operator definition. Ignore all values but the + // first one - in valid code there will be only one anyway. + resultType = _operation.userDefinedFunctionType()->returnParameterTypes()[0]; + _operation.annotation().type = resultType; _operation.annotation().isConstant = false; - _operation.annotation().isPure = !modifying && *_operation.subExpression().annotation().isPure; + _operation.annotation().isPure = + !modifying && + *_operation.subExpression().annotation().isPure && + (!_operation.userDefinedFunctionType() || _operation.userDefinedFunctionType()->isPure()); _operation.annotation().isLValue = false; return false; @@ -1827,37 +1893,122 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) { Type const* leftType = type(_operation.leftExpression()); Type const* rightType = type(_operation.rightExpression()); - TypeResult result = leftType->binaryOperatorResult(_operation.getOperator(), rightType); - Type const* commonType = result.get(); - if (!commonType) + + // Check if the operator is built-in or user-defined. + TypeResult builtinResult = leftType->binaryOperatorResult(_operation.getOperator(), rightType); + std::set matchingDefinitions = leftType->operatorDefinitions( + _operation.getOperator(), + *currentDefinitionScope(), + false // _unary + ); + + // Operator can't be both user-defined and built-in at the same time. + solAssert(!builtinResult || matchingDefinitions.empty()); + + Type const* commonType = nullptr; + FunctionDefinition const* operatorDefinition = nullptr; + if (builtinResult) + commonType = builtinResult.get(); + else if (!matchingDefinitions.empty()) { - m_errorReporter.typeError( - 2271_error, - _operation.location(), - "Operator " + - string(TokenTraits::toString(_operation.getOperator())) + - " not compatible with types " + - leftType->humanReadableName() + - " and " + - rightType->humanReadableName() + "." + - (!result.message().empty() ? " " + result.message() : "") + // This is checked along with `using for` directive but the error is not fatal. + if (matchingDefinitions.size() != 1) + solAssert(m_errorReporter.hasErrors()); + + operatorDefinition = *matchingDefinitions.begin(); + + // Set common type to the type used in the `using for` directive. + commonType = leftType; + } + else + { + std::string description = fmt::format( + "Built-in binary operator {} cannot be applied to types {} and {}.", + TokenTraits::friendlyName(_operation.getOperator()), + leftType->humanReadableName(), + rightType->humanReadableName() ); + if (!builtinResult.message().empty()) + description += " " + builtinResult.message(); + if (leftType->typeDefinition() && util::contains(userDefinableOperators, _operation.getOperator())) + description += " No matching user-defined operator found."; + + m_errorReporter.typeError(2271_error, _operation.location(), description); + + // Set common type to something we'd expect from correct code just so that we can continue analysis. commonType = leftType; } + _operation.annotation().commonType = commonType; - _operation.annotation().type = + _operation.annotation().userDefinedFunction = operatorDefinition; + FunctionType const* userDefinedFunctionType = _operation.userDefinedFunctionType(); + + // By default use the type we'd expect from correct code. This way we can continue analysis + // of other expressions in a sensible way in case of a non-fatal error. + Type const* resultType = TokenTraits::isCompareOp(_operation.getOperator()) ? TypeProvider::boolean() : commonType; + + if (operatorDefinition) + { + TypePointers const& parameterTypes = userDefinedFunctionType->parameterTypes(); + TypePointers const& returnParameterTypes = userDefinedFunctionType->returnParameterTypes(); + + // operatorDefinitions() filters out definitions with non-matching first argument. + solAssert(parameterTypes.size() == 2); + solAssert(parameterTypes[0] && *leftType == *parameterTypes[0]); + + if (*rightType != *parameterTypes[0]) + m_errorReporter.typeError( + 5653_error, + _operation.location(), + fmt::format( + "The type of the second operand of this user-defined binary operator {} " + "does not match the type of the first operand, which is {}.", + TokenTraits::friendlyName(_operation.getOperator()), + parameterTypes[0]->humanReadableName() + ) + ); + + if (!returnParameterTypes.empty()) + // Use the actual result type from operator definition. Ignore all values but the + // first one - in valid code there will be only one anyway. + resultType = returnParameterTypes[0]; + } + + _operation.annotation().type = resultType; _operation.annotation().isPure = *_operation.leftExpression().annotation().isPure && - *_operation.rightExpression().annotation().isPure; + *_operation.rightExpression().annotation().isPure && + (!userDefinedFunctionType || userDefinedFunctionType->isPure()); _operation.annotation().isLValue = false; _operation.annotation().isConstant = false; + if (_operation.getOperator() == Token::Equal || _operation.getOperator() == Token::NotEqual) + { + auto const* leftFunction = dynamic_cast(leftType); + auto const* rightFunction = dynamic_cast(rightType); + if ( + leftFunction && + rightFunction && + leftFunction->kind() == FunctionType::Kind::Internal && + rightFunction->kind() == FunctionType::Kind::Internal + ) + { + m_errorReporter.warning( + 3075_error, + _operation.location(), + "Comparison of internal function pointers can yield unexpected results " + "in the legacy pipeline with the optimizer enabled, and will be disallowed entirely " + "in the next breaking release." + ); + } + } + if (_operation.getOperator() == Token::Exp || _operation.getOperator() == Token::SHL) { - string operation = _operation.getOperator() == Token::Exp ? "exponentiation" : "shift"; + std::string operation = _operation.getOperator() == Token::Exp ? "exponentiation" : "shift"; if ( leftType->category() == Type::Category::RationalNumber && rightType->category() != Type::Category::RationalNumber @@ -1878,14 +2029,14 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) m_errorReporter.warning( 3149_error, _operation.location(), - "The result type of the " + - operation + - " operation is equal to the type of the first operand (" + - commonType->humanReadableName() + - ") ignoring the (larger) type of the second operand (" + - rightType->humanReadableName() + - ") which might be unexpected. Silence this warning by either converting " - "the first or the second operand to the type of the other." + fmt::format( + "The result type of the {} operation is equal to the type of the first operand ({}) " + "ignoring the (larger) type of the second operand ({}) which might be unexpected. " + "Silence this warning by either converting the first or the second operand to the type of the other.", + operation, + commonType->humanReadableName(), + rightType->humanReadableName() + ) ); } } @@ -1897,12 +2048,14 @@ Type const* TypeChecker::typeCheckTypeConversionAndRetrieveReturnType( solAssert(*_functionCall.annotation().kind == FunctionCallKind::TypeConversion, ""); Type const* expressionType = type(_functionCall.expression()); - vector> const& arguments = _functionCall.arguments(); + std::vector> const& arguments = _functionCall.arguments(); bool const isPositionalCall = _functionCall.names().empty(); Type const* resultType = dynamic_cast(*expressionType).actualType(); - if (arguments.empty() && resultType->category() == Type::Category::TvmCell) - { + if (arguments.empty() && + (resultType->category() == Type::Category::TvmCell || + resultType->category() == Type::Category::TvmBuilder) + ) { // all right } else if (arguments.size() != 1) @@ -1923,8 +2076,6 @@ Type const* TypeChecker::typeCheckTypeConversionAndRetrieveReturnType( // Resulting data location is memory unless we are converting from a reference // type with a different data location. // (data location cannot yet be specified for type conversions) - if (auto type = dynamic_cast(resultType)) - resultType = TypeProvider::withLocation(type, type->isPointer()); BoolResult result = argType->isExplicitlyConvertibleTo(*resultType); if (result) { @@ -2127,7 +2278,7 @@ void TypeChecker::checkNeedCallback(FunctionType const* callee, ASTNode const& n m_errorReporter.typeError( 9205_error, node.location(), - SecondarySourceLocation().append("Declaration is here:", funcDef->location()), + SecondarySourceLocation().append("The declaration is here:", funcDef->location()), R"("callback" option must be set because callee function is marked as responsible.)" ); } @@ -2175,7 +2326,7 @@ void TypeChecker::typeCheckTvmEncodeArg(Type const* type, Expression const& node } void TypeChecker::typeCheckTvmEncodeFunctions(FunctionCall const& _functionCall) { - vector> const &arguments = _functionCall.arguments(); + std::vector> const &arguments = _functionCall.arguments(); for (const auto & argument : arguments) { auto const &argType = type(*argument); typeCheckTvmEncodeArg(argType, *argument); @@ -2225,7 +2376,7 @@ void TypeChecker::typeCheckABIEncodeFunctions( } // Check additional arguments for variadic functions - vector> const& arguments = _functionCall.arguments(); + std::vector> const& arguments = _functionCall.arguments(); for (size_t i = 0; i < arguments.size(); ++i) { auto const& argType = type(*arguments[i]); @@ -2324,7 +2475,7 @@ ContractType const* TypeChecker::getContractType(Expression const* expr) { void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCall) { - vector> const& arguments = _functionCall.arguments(); + std::vector> const& arguments = _functionCall.arguments(); // Expecting first argument to be the function pointer and second to be a tuple. if (arguments.size() != 2) @@ -2361,7 +2512,7 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa externalFunctionType->kind() != FunctionType::Kind::Declaration ) { - string msg = "Expected regular external function type, or external view on public function."; + std::string msg = "Expected regular external function type, or external view on public function."; switch (externalFunctionType->kind()) { @@ -2410,7 +2561,7 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa } solAssert(!externalFunctionType->takesArbitraryParameters(), "Function must have fixed parameters."); // Tuples with only one component become that component - vector> callArguments; + std::vector> callArguments; auto const* tupleType = dynamic_cast(type(*arguments[1])); if (tupleType) @@ -2437,9 +2588,9 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa 7788_error, _functionCall.location(), "Expected " + - to_string(externalFunctionType->parameterTypes().size()) + + std::to_string(externalFunctionType->parameterTypes().size()) + " instead of " + - to_string(callArguments.size()) + + std::to_string(callArguments.size()) + " components for the tuple parameter." ); else @@ -2447,13 +2598,13 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa 7515_error, _functionCall.location(), "Expected a tuple with " + - to_string(externalFunctionType->parameterTypes().size()) + + std::to_string(externalFunctionType->parameterTypes().size()) + " components instead of a single non-tuple parameter." ); } // Use min() to check as much as we can before failing fatally - size_t const numParameters = min(callArguments.size(), externalFunctionType->parameterTypes().size()); + size_t const numParameters = std::min(callArguments.size(), externalFunctionType->parameterTypes().size()); for (size_t i = 0; i < numParameters; i++) { @@ -2464,7 +2615,7 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa 5407_error, callArguments[i]->location(), "Cannot implicitly convert component at position " + - to_string(i) + + std::to_string(i) + " from \"" + argType.humanReadableName() + "\" to \"" + @@ -2487,7 +2638,7 @@ void TypeChecker::typeCheckStringConcatFunction( typeCheckFunctionGeneralChecks(_functionCall, _functionType); - for (shared_ptr const& argument: _functionCall.arguments()) + for (std::shared_ptr const& argument: _functionCall.arguments()) { Type const* argumentType = type(*argument); bool notConvertibleToString = !argumentType->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()); @@ -2514,7 +2665,7 @@ void TypeChecker::typeCheckBytesConcatFunction( typeCheckFunctionGeneralChecks(_functionCall, _functionType); - for (shared_ptr const& argument: _functionCall.arguments()) + for (std::shared_ptr const& argument: _functionCall.arguments()) { Type const* argumentType = type(*argument); bool notConvertibleToBytes = @@ -2555,8 +2706,8 @@ void TypeChecker::typeCheckFunctionGeneralChecks( ); TypePointers const& parameterTypes = _functionType->parameterTypes(); - vector> const& arguments = _functionCall.arguments(); - vector> const& argumentNames = _functionCall.names(); + std::vector> const& arguments = _functionCall.arguments(); + std::vector> const& argumentNames = _functionCall.names(); bool isFunctionWithDefaultValues = false; { auto ma = dynamic_cast(&_functionCall.expression()); @@ -2564,9 +2715,14 @@ void TypeChecker::typeCheckFunctionGeneralChecks( isFunctionWithDefaultValues = true; } if (ma && dynamic_cast(ma->expression().annotation().type)) { - if (ma->memberName() == "buildStateInit" || + if (ma->memberName() == "encodeStateInit" || + ma->memberName() == "buildStateInit" || ma->memberName() == "buildDataInit" || + ma->memberName() == "encodeData" || + ma->memberName() == "encodeOldDataInit" || ma->memberName() == "buildExtMsg" || + ma->memberName() == "encodeExtMsg" || + ma->memberName() == "encodeIntMsg" || ma->memberName() == "buildIntMsg") isFunctionWithDefaultValues = true; } @@ -2580,22 +2736,22 @@ void TypeChecker::typeCheckFunctionGeneralChecks( bool const isStructConstructorCall = functionCallKind == FunctionCallKind::StructConstructorCall; - auto [errorId, description] = [&]() -> tuple { - string msg = isVariadic ? + auto [errorId, description] = [&]() -> std::tuple { + std::string msg = isVariadic ? "Need at least " + toString(parameterTypes.size()) + " arguments for " + - string(isStructConstructorCall ? "struct constructor" : "function call") + + std::string(isStructConstructorCall ? "struct constructor" : "function call") + ", but provided only " + toString(arguments.size()) + "." : "Wrong argument count for " + - string(isStructConstructorCall ? "struct constructor" : "function call") + + std::string(isStructConstructorCall ? "struct constructor" : "function call") + ": " + toString(arguments.size()) + " arguments given but " + - string(isVariadic ? "need at least " : "expected ") + + std::string(isVariadic ? "need at least " : "expected ") + toString(parameterTypes.size()) + "."; @@ -2701,8 +2857,8 @@ void TypeChecker::typeCheckFunctionGeneralChecks( { if (j < paramArgMap.size()) paramArgMap[j] = nullptr; - if ((_functionType->kind() == FunctionType::Kind::TVMBuildStateInit || - _functionType->kind() == FunctionType::Kind::TVMBuildDataInit) + if ((_functionType->kind() == FunctionType::Kind::ABIEncodeStateInit || + _functionType->kind() == FunctionType::Kind::ABIEncodeData) && *argumentNames.at(i) == "contr") { // Do nothing. @@ -2755,12 +2911,12 @@ void TypeChecker::typeCheckFunctionGeneralChecks( continue; } solAssert(!!paramArgMap[i], "unmapped parameter"); - vector const ¶meterNames = _functionType->parameterNames(); + std::vector const ¶meterNames = _functionType->parameterNames(); if (i >= parameterNames.size()) { m_errorReporter.fatalTypeError(3774_error, paramArgMap[i]->location(), "Too many arguments."); } if (!parameterNames.empty()) { - string const &argName = parameterNames.at(i); + std::string const &argName = parameterNames.at(i); if (_ignoreOptions.count(argName)) { continue; } @@ -2768,8 +2924,8 @@ void TypeChecker::typeCheckFunctionGeneralChecks( BoolResult result = type(*paramArgMap[i])->isImplicitlyConvertibleTo(*parameterTypes[i]); if (!result) { - auto [errorId, description] = [&]() -> tuple { - string msg = + auto [errorId, description] = [&]() -> std::tuple { + std::string msg = "Invalid type for argument in function call. " "Invalid implicit conversion from " + type(*paramArgMap[i])->humanReadableName() + @@ -2884,9 +3040,9 @@ void TypeChecker::typeCheckFunctionGeneralChecks( } auto names = functionCallOpt->names(); - const vector> &options = functionCallOpt->options(); + const std::vector> &options = functionCallOpt->options(); for (size_t i = 0; i < names.size(); ++i) { - string const &name = *(names[i]); + std::string const &name = *(names[i]); if (std::find(arr.begin(), arr.end(), name) == arr.end()) { m_errorReporter.typeError( 4187_error, @@ -2902,7 +3058,7 @@ void TypeChecker::typeCheckFunctionGeneralChecks( expectType(*options[i], *TypeProvider::uint256()); } } - auto getId = [&](string const & param) { + auto getId = [&](std::string const & param) { auto it = find_if(names.begin(), names.end(), [&](auto el){ return *el == param; }); @@ -2946,7 +3102,7 @@ TypeChecker::checkPubFunctionAndGetDefinition(Expression const& arg, bool printE m_errorReporter.fatalTypeError( 6273_error, arg.location(), - SecondarySourceLocation().append("Declaration is here:", funcDef->location()), + SecondarySourceLocation().append("The declaration is here:", funcDef->location()), "Public/external function or contract type required, but \"" + Declaration::visibilityToString(funcDef->visibility()) + "\" function is provided." @@ -2984,22 +3140,58 @@ TypeChecker::checkPubFunctionOrContractTypeAndGetDefinition(Expression const& ar return constructorDef; } -void TypeChecker::checkInitList(InitializerList const *list, ContractType const *ct) { - vector> vars = ct->stateVariables(); +void TypeChecker::checkInitList(InitializerList const* list, ContractType const& ct, + langutil::SourceLocation const& _functionCallLocation +) { + std::vector stateVariables; + for (auto const &[v, _, ___] : ct.stateVariables()) { + boost::ignore_unused(_); + boost::ignore_unused(___); + stateVariables.push_back(v); + } + + if (list == nullptr) { + for (VariableDeclaration const* v : stateVariables) { + if (v->isStatic()) { + m_errorReporter.typeError( + 8332_error, + _functionCallLocation, + SecondarySourceLocation() + .append("The declaration of state static variable is here:", v->location()), + "Expected \"varInit\" option. All static state variables should be defined." + ); + break; + } + } + return ; + } + + std::map usedNamedParams; for (size_t i = 0; i < list->names().size(); ++i) { const std::string name = *list->names().at(i); + if (usedNamedParams.count(name) != 0) { + size_t prevIndex = usedNamedParams.at(name); + m_errorReporter.typeError( + 4019_error, + list->nameLocations().at(i), + SecondarySourceLocation() + .append("Previous named argument is here:", list->nameLocations().at(prevIndex)), + "Duplicate named argument \"" + name + "\"." + ); + } + usedNamedParams[name] = i; Type const* exprType = list->options().at(i)->annotation().type; size_t j; - for (j = 0; j < vars.size(); ++j) { - VariableDeclaration const* v = std::get<0>(vars.at(j)); + for (j = 0; j < stateVariables.size(); ++j) { + VariableDeclaration const* v = stateVariables.at(j); if (name == v->name()) { if (!v->isStatic()) { m_errorReporter.typeError( - 6626_error, - list->options().at(i)->location(), - SecondarySourceLocation() - .append("Declaration is here:", v->location()), - "Initialization of a non-static variable." + 6626_error, + list->nameLocations().at(i), + SecondarySourceLocation() + .append("The declaration is here:", v->location()), + "Initialization of a non-static variable." ); } else if (!exprType->isImplicitlyConvertibleTo(*v->type())) { m_errorReporter.typeError( @@ -3012,20 +3204,32 @@ void TypeChecker::checkInitList(InitializerList const *list, ContractType const break; } } - if (j == vars.size()) { + if (j == stateVariables.size()) { m_errorReporter.typeError( 6711_error, - list->options().at(i)->location(), + list->nameLocations().at(i), SecondarySourceLocation() - .append("Contract is here:", ct->contractDefinition().location()), + .append("The contract is here:", ct.contractDefinition().location()), "Unknown state variable \"" + name + "\"." ); } } + + for (VariableDeclaration const* v : stateVariables) { + if (v->isStatic() && usedNamedParams.count(v->name()) == 0) { + m_errorReporter.typeError( + 6515_error, + list->location(), + SecondarySourceLocation() + .append("The declaration of state static variable is here:", v->location()), + "Expected named argument \"" + v->name() + "\" in the list. All static state variables should be defined." + ); + } + } } void TypeChecker::checkCallList( - vector const& arguments, + std::vector const& arguments, FunctionCall const& _functionCall, bool ignoreCallBack // for ext msg we set callbackFunctionId==0 ) { @@ -3050,7 +3254,7 @@ void TypeChecker::checkCallList( 5424_error, _functionCall.location(), SecondarySourceLocation() - .append("Declaration is here:", functionDeclaration->location()), + .append("The declaration is here:", functionDeclaration->location()), "Wrong arguments count: " + toString(arguments.size()) + " arguments given but expected " + @@ -3076,7 +3280,7 @@ void TypeChecker::checkCallList( m_errorReporter.fatalTypeError( 2182_error, _functionCall.location(), - SecondarySourceLocation().append("Declaration is here:", + SecondarySourceLocation().append("The declaration is here:", contractDefinition.location()), "Wrong arguments count: " + toString(arguments.size()) + @@ -3087,8 +3291,8 @@ void TypeChecker::checkCallList( } void TypeChecker::checkBuildExtMsg(FunctionCall const& _functionCall) { - vector> const& arguments = _functionCall.arguments(); - vector> const &argumentNames = _functionCall.names(); + std::vector> const& arguments = _functionCall.arguments(); + std::vector> const &argumentNames = _functionCall.names(); auto findName = [&](const ASTString& optName) { auto it = std::find_if(argumentNames.begin(), argumentNames.end(), [&](const ASTPointer &name) { @@ -3243,8 +3447,8 @@ void TypeChecker::checkOnErrorId(FunctionDefinition const* errorFunction, langut bool TypeChecker::visit(FunctionCall const& _functionCall) { - vector> const& arguments = _functionCall.arguments(); - vector> const &argumentNames = _functionCall.names(); + std::vector> const& arguments = _functionCall.arguments(); + std::vector> const &argumentNames = _functionCall.names(); bool argumentsArePure = true; // We need to check arguments' type first as they will be needed for overload resolution. @@ -3278,6 +3482,20 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) bool isLValue = false; + for (auto const& argument : arguments) + { + auto const& argType = type(*argument); + if (argType->category() == Type::Category::RationalNumber && !argType->mobileType()) + { + m_errorReporter.fatalTypeError( + 8009_error, + argument->location(), + "Invalid rational number (too large or division by zero)." + // See also TypeChecker::typeCheckABIEncodeFunctions + ); + } + } + // Determine and assign function call kind, lvalue, purity and function type for this FunctionCall node switch (expressionType->category()) { @@ -3338,7 +3556,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } default: - m_errorReporter.fatalTypeError(5704_error, _functionCall.location(), "Type is not callable"); + m_errorReporter.fatalTypeError(5704_error, _functionCall.location(), "This expression is not callable."); // Unreachable, because fatalTypeError throws. We don't set kind, but that's okay because the switch below // is never reached. And, even if it was, SetOnce would trigger an assertion violation and not UB. funcCallAnno.isPure = argumentsArePure; @@ -3348,7 +3566,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) funcCallAnno.isLValue = isLValue; auto checkArgNumAndIsInteger = [&]( - vector> const& arguments, + std::vector> const& arguments, size_t arguments_cnt, const std::function& cmpOperator, const std::string& errorMsg @@ -3362,7 +3580,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } for (const auto & arg : arguments) { - Type::Category cat = arg->annotation().type->mobileType()->category(); + auto t = arg->annotation().type; + Type::Category cat = t->mobileType()->category(); if (cat != Type::Category::Integer && cat != Type::Category::VarInteger) { m_errorReporter.fatalTypeError( 4283_error, @@ -3373,8 +3592,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } }; - auto checkArgNumAndIsIntegerOrFixedPoint = [&]( - vector> const& arguments, + auto checkArgQtyAndIsIntOrVarIntOrFixedPoint = [&]( + std::vector> const& arguments, size_t arguments_cnt, const std::function& cmpOperator, const std::string& errorMsg @@ -3391,7 +3610,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) Type::Category cat = arg->annotation().type->mobileType()->category(); if (cat != Type::Category::Integer && cat != Type::Category::FixedPoint && cat != Type::Category::VarInteger) { m_errorReporter.fatalTypeError( - 6033_error, + 5943_error, arg->location(), "Expected integer, variable integer or fixed point type." ); @@ -3399,7 +3618,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } }; - auto checkAllAreNotFractions = [&] (vector> const& arguments) { + auto checkAllAreNotFractions = [&] (std::vector> const& arguments) { bool areAllConstants = true; bool haveAnyFraction = false; SourceLocation loc = getSmallestCovering(arguments); @@ -3417,7 +3636,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } }; - auto getCommonType = [&](vector> const& arguments){ + auto getCommonType = [&](std::vector> const& arguments){ Type const* result = arguments.at(0)->annotation().type; for (std::size_t i = 1; i < arguments.size(); ++i) { Type const* rightType = arguments.at(i)->annotation().type; @@ -3445,12 +3664,12 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) return findName(optName) != -1; }; - auto checkHaveNamedParams = [&]() { + auto checkHasNamedParams = [&]() { if (argumentNames.empty()) m_errorReporter.fatalTypeError( - 8461_error, - _functionCall.location(), - string("Function parameters should be specified with names.") + 8461_error, + _functionCall.location(), + std::string("Function call arguments should be given by name.") ); }; @@ -3492,16 +3711,16 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } expectType(*arguments.at(0), *TypeProvider::tvmcell(), false); ASTPointer arg1 = arguments.at(1); - ASTPointer te = dynamic_pointer_cast(arg1); - std::vector> arguments; + ASTPointer te = std::dynamic_pointer_cast(arg1); + std::vector> args; if (te) { for (const ASTPointer& e : te->components()) { - arguments.emplace_back(e); + args.emplace_back(e); } } else { - arguments.emplace_back(arg1); + args.emplace_back(arg1); } - returnTypes = checkSliceDecode(arguments); + returnTypes = checkSliceDecode(args); break; } case FunctionType::Kind::TVMSliceLoad: @@ -3527,13 +3746,34 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) returnTypes = getReturnTypesForTVMConfig(_functionCall); break; } + case FunctionType::Kind::ABIDecodeFunctionParams: { + if (arguments.size() != 2) { + m_errorReporter.fatalTypeError( + 1782_error, + _functionCall.location(), + std::string("Expected two arguments.") + ); + } + FunctionDefinition const* functionDeclaration = checkPubFunctionOrContractTypeAndGetDefinition(*arguments.front().get()); + if (functionDeclaration != nullptr) { // if nullptr => default constructor + if (functionDeclaration->isResponsible()) { + returnTypes.push_back(TypeProvider::uint(32)); // callback function + } + for (const ASTPointer &vd : functionDeclaration->parameters()) { + returnTypes.push_back(vd->type()); + } + } + paramTypes.emplace_back(arguments.at(0)->annotation().type); + paramTypes.emplace_back(arguments.at(1)->annotation().type); + break; + } case FunctionType::Kind::TVMSliceLoadFunctionParams: { if (arguments.size() != 1) { m_errorReporter.fatalTypeError( 7016_error, _functionCall.location(), - string("Expected one argument.") + std::string("Expected one argument.") ); } FunctionDefinition const* functionDeclaration = checkPubFunctionOrContractTypeAndGetDefinition(*arguments.front().get()); @@ -3578,13 +3818,42 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) returnTypes = functionType->returnParameterTypes(); break; } + case FunctionType::Kind::ABIDecodeData: + { + if (arguments.size() != 2) { + m_errorReporter.fatalTypeError( + 3017_error, + _functionCall.location(), + std::string("Expected two arguments.") + ); + } + ContractType const* ct = getContractType(arguments.front().get()); + if (ct == nullptr) { + m_errorReporter.fatalTypeError( + 8880_error, + arguments.front()->location(), + "Expected contract type." + ); + } + + std::vector stateVars = ::stateVariables(&ct->contractDefinition(), false); + returnTypes.push_back(TypeProvider::uint256()); // pubkey + returnTypes.push_back(TypeProvider::uint(64)); // timestamp + returnTypes.push_back(TypeProvider::boolean()); // constructor flag + for (VariableDeclaration const * v : stateVars) { + returnTypes.push_back(v->type()); + } + paramTypes.emplace_back(arguments.at(0)->annotation().type); + paramTypes.emplace_back(arguments.at(1)->annotation().type); + break; + } case FunctionType::Kind::TVMSliceLoadStateVars: { if (arguments.size() != 1) { m_errorReporter.fatalTypeError( 5457_error, _functionCall.location(), - string("Expected one argument.") + std::string("Expected one argument.") ); } ContractType const* ct = getContractType(arguments.front().get()); @@ -3596,7 +3865,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) ); } - std::vector stateVars = ::notConstantStateVariables(&ct->contractDefinition()); + std::vector stateVars = ::stateVariables(&ct->contractDefinition(), false); returnTypes.push_back(TypeProvider::uint256()); // pubkey returnTypes.push_back(TypeProvider::uint(64)); // timestamp returnTypes.push_back(TypeProvider::boolean()); // constructor flag @@ -3620,7 +3889,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) case FunctionType::Kind::MathMin: case FunctionType::Kind::MathMax: { - checkArgNumAndIsIntegerOrFixedPoint(arguments, 2, std::greater_equal<>(), "Expected at least two arguments."); + checkArgQtyAndIsIntOrVarIntOrFixedPoint(arguments, 2, std::greater_equal<>(), "Expected at least two arguments."); Type const* result = getCommonType(arguments); paramTypes = TypePointers(arguments.size(), result); returnTypes.push_back(result); @@ -3628,7 +3897,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::MathMinMax: { - checkArgNumAndIsIntegerOrFixedPoint(arguments, 2, std::equal_to<>(), "Expected two arguments."); + checkArgQtyAndIsIntOrVarIntOrFixedPoint(arguments, 2, std::equal_to<>(), "Expected two arguments."); Type const* result = getCommonType(arguments); paramTypes = TypePointers(arguments.size(), result); returnTypes.push_back(result); @@ -3638,7 +3907,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) case FunctionType::Kind::MathDivR: case FunctionType::Kind::MathDivC: { - checkArgNumAndIsIntegerOrFixedPoint(arguments, 2, std::equal_to<>(), "Expected two arguments."); + checkArgQtyAndIsIntOrVarIntOrFixedPoint(arguments, 2, std::equal_to<>(), "Expected two arguments."); checkAllAreNotFractions(arguments); Type const* result = getCommonType(arguments); paramTypes = TypePointers(arguments.size(), result); @@ -3671,10 +3940,46 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::MathAbs: { - checkArgNumAndIsIntegerOrFixedPoint(arguments, 1, std::equal_to<>(), "Expected one argument."); - Type const* type = arguments[0]->annotation().type->mobileType(); - paramTypes.push_back(type); - returnTypes.push_back(type->mobileType()); + checkArgQtyAndIsIntOrVarIntOrFixedPoint(arguments, 1, std::equal_to<>(), "Expected one argument."); + Type const* argType = arguments.at(0)->annotation().type->mobileType(); + Type::Category cat = argType->category(); + paramTypes.push_back(argType); + + if (cat == Type::Category::Integer || cat == Type::Category::VarInteger) { + IntegerType const* intType; + if (cat == Type::Category::Integer) + intType = dynamic_cast(argType); + else if (cat == Type::Category::VarInteger) + intType = &dynamic_cast(argType)->asIntegerType(); + else + solUnimplemented(""); + if (!intType->isSigned()) + m_errorReporter.fatalTypeError( + 7695_error, + arguments.at(0)->location(), + "Expected signed integer type." + ); + if (intType->numBits() == 1) + m_errorReporter.fatalTypeError( + 7695_error, + arguments.at(0)->location(), + "Too low type." + ); + returnTypes.push_back(TypeProvider::integer(intType->numBits() - 1, IntegerType::Modifier::Unsigned)); + } else if (cat == Type::Category::FixedPoint) { + auto fixed = dynamic_cast(argType); + if (!fixed->isSigned()) + m_errorReporter.fatalTypeError( + 7030_error, + arguments.at(0)->location(), + "Expected signed fixed-point type." + ); + returnTypes.push_back(TypeProvider::fixedPoint( + std::max(8, fixed->numBits() - 1), + fixed->fractionalDigits(), + FixedPointType::Modifier::Unsigned + )); + } break; } case FunctionType::Kind::MathModpow2: @@ -3747,7 +4052,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) m_errorReporter.fatalTypeError( 1878_error, arguments[0]->location(), - string("Expected string literal.") + std::string("Expected string literal.") ); } auto lit = dynamic_cast(arguments[0].get()); @@ -3755,7 +4060,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) m_errorReporter.fatalTypeError( 4336_error, arguments[0]->location(), - string("Expected string literal.") + std::string("Expected string literal.") ); } std::string format = lit->value(); @@ -3771,15 +4076,15 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) m_errorReporter.fatalTypeError( 2689_error, _functionCall.location(), - string("Number of arguments is not equal to the number of placeholders!") + std::string("Number of arguments is not equal to the number of placeholders!") ); } typeCheckFunctionCall(_functionCall, functionType); returnTypes = functionType->returnParameterTypes(); break; } - case FunctionType::Kind::TVMBuildIntMsg: { - checkHaveNamedParams(); + case FunctionType::Kind::ABIBuildIntMsg: { + checkHasNamedParams(); for (const std::string name : {"dest", "call", "value"}) { int index = findName(name); @@ -3787,7 +4092,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) m_errorReporter.typeError( 3252_error, _functionCall.location(), - string("Parameter \"" + name + "\" must be set.") + std::string("Parameter \"" + name + "\" must be set.") ); } } @@ -3814,29 +4119,30 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) returnTypes = functionType->returnParameterTypes(); break; } - case FunctionType::Kind::TVMBuildExtMsg: { - checkHaveNamedParams(); + case FunctionType::Kind::ABIBuildExtMsg: { + checkHasNamedParams(); checkBuildExtMsg(_functionCall); typeCheckFunctionCall(_functionCall, functionType, {"callbackId", "onErrorId"}); returnTypes = functionType->returnParameterTypes(); break; } - case FunctionType::Kind::TVMBuildStateInit: { + case FunctionType::Kind::ABIEncodeStateInit: { typeCheckFunctionCall(_functionCall, functionType); returnTypes = m_evmVersion.supportsReturndata() ? functionType->returnParameterTypes() : functionType->returnParameterTypesWithoutDynamicTypes(); - typeCheckTVMBuildStateInit(_functionCall, hasName, findName); + typeCheckABIEncodeStateInit(_functionCall, hasName, findName); break; } - case FunctionType::Kind::TVMBuildDataInit: { + case FunctionType::Kind::ABIEncodeData: { + checkHasNamedParams(); typeCheckFunctionCall(_functionCall, functionType); returnTypes = m_evmVersion.supportsReturndata() ? functionType->returnParameterTypes() : functionType->returnParameterTypesWithoutDynamicTypes(); - typeCheckTVMBuildDataInit(_functionCall, hasName, findName); + typeCheckABIEncodeData(_functionCall, hasName, findName); break; } case FunctionType::Kind::AddressTransfer: { @@ -3853,7 +4159,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) m_errorReporter.fatalTypeError( 3221_error, _functionCall.location(), - string("Parameter \"value\" must be set.") + std::string("Parameter \"value\" must be set.") ); } // parameter names are checked in function below @@ -3863,7 +4169,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) functionType->returnParameterTypesWithoutDynamicTypes(); break; } - case FunctionType::Kind::TVMFunctionId: { + case FunctionType::Kind::ABIFunctionId: { if (arguments.size() != 1) { m_errorReporter.fatalTypeError( 7354_error, @@ -3876,7 +4182,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) returnTypes = functionType->returnParameterTypes(); break; } - case FunctionType::Kind::TVMEncodeBody: + case FunctionType::Kind::ABIEncodeBody: { std::vector params; for (const ASTPointer& p : arguments) { @@ -3995,6 +4301,26 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) returnTypes.push_back(TypeProvider::uint256()); break; } + case FunctionType::Kind::IntCast: { + if (arguments.size() != 1) + m_errorReporter.fatalTypeError(5461_error, _functionCall.location(), "Expected one argument."); + auto typeType = dynamic_cast(arguments.at(0)->annotation().type); + auto actualType = typeType == nullptr ? nullptr : typeType->actualType(); + if (actualType == nullptr || actualType->category() != Type::Category::Integer) + m_errorReporter.fatalTypeError(5974_error, arguments.at(0)->location(), "Expected integer type name."); + auto const targetType = dynamic_cast(actualType); + auto ma = dynamic_cast(&_functionCall.expression()); + auto const fromType = dynamic_cast(ma->expression().annotation().type); + if (fromType->isSigned() != targetType->isSigned() && fromType->numBits() != targetType->numBits()) { + m_errorReporter.typeError( + 7427_error, + _functionCall.location(), + "Type of integer and target type must have same sign or bit-size." + ); + } + returnTypes.push_back(actualType); + break; + } default: { typeCheckFunctionCall(_functionCall, functionType); @@ -4056,7 +4382,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions) { - const vector> &options = _functionCallOptions.options(); + const std::vector> &options = _functionCallOptions.options(); solAssert(options.size() == _functionCallOptions.names().size(), "Lengths of name & value arrays differ!"); _functionCallOptions.expression().annotation().arguments = _functionCallOptions.annotation().arguments; @@ -4121,7 +4447,7 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions) return false; } - auto setCheckOption = [&](int& _option, string const&& _name, int index) + auto setCheckOption = [&](int& _option, std::string const&& _name, int index) { if (_option != -1) m_errorReporter.typeError( @@ -4133,6 +4459,7 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions) _option = index; }; + const bool isNewExpression = dynamic_cast(&_functionCallOptions.expression()) != nullptr; auto names = _functionCallOptions.names(); std::vector arr; @@ -4152,7 +4479,7 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions) return s; }; for (size_t i = 0; i < names.size(); ++i) { - string const &name = *(names[i]); + std::string const &name = *(names[i]); if (std::find(arr.begin(), arr.end(), name) == arr.end()) { m_errorReporter.typeError( @@ -4242,7 +4569,7 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions) expectType(*options[i], *TypeProvider::initializerList()); setCheckOption(setVarInit, "varInit", i); } else if (name == "value") { - expectType(*options[i], *TypeProvider::uint(128)); + expectType(*options[i], *TypeProvider::coins()); setCheckOption(setValue, "value", i); } else if (name == "splitDepth") { expectType(*options[i], *TypeProvider::uint(8)); @@ -4276,15 +4603,16 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions) R"(Option "varInit" is not compatible with option "stateInit". Only with option "code".)" ); } - if (setValue == -1) { + if (setValue == -1) m_errorReporter.typeError(4337_error, _functionCallOptions.location(), R"(Option "value" must be set.)"); - } - if (setVarInit != -1) { + if (setStateInit == -1) { auto newExpr = to(&_functionCallOptions.expression()); Type const* type = newExpr->typeName().annotation().type; auto ct = to(type); - auto list = dynamic_cast(options.at(setVarInit).get()); - checkInitList(list, ct); + InitializerList const* list{}; + if (setVarInit != -1) + list = dynamic_cast(options.at(setVarInit).get()); + checkInitList(list, *ct, _functionCallOptions.location()); } } @@ -4322,7 +4650,6 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) _newExpression.typeName().location(), "Length has to be placed in parentheses after the array type for new expression." ); - type = TypeProvider::withLocationIfReference(type); _newExpression.annotation().type = TypeProvider::function( TypePointers{TypeProvider::uint256()}, TypePointers{type}, @@ -4374,16 +4701,14 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) m_errorReporter.fatalTypeError( 9836_error, _memberAccess.location(), - string("\".value()\" and \".flag()\" functionality is ") + + std::string("\".value()\" and \".flag()\" functionality is ") + "deprecated, use call options in {} instead." ); if (initialMemberCount == 0 && !dynamic_cast(exprType)) { // Try to see if the member was removed because it is only available for storage types. - auto storageType = TypeProvider::withLocationIfReference( - exprType - ); + auto storageType = exprType; if (!storageType->members(currentDefinitionScope()).membersByName(memberName).empty()) m_errorReporter.fatalTypeError( 4994_error, @@ -4394,8 +4719,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) ); } - auto [errorId, description] = [&]() -> tuple { - string errorMsg = "Member \"" + memberName + "\" not found or not visible " + auto [errorId, description] = [&]() -> std::tuple { + std::string errorMsg = "Member \"" + memberName + "\" not found or not visible " "after argument-dependent lookup in " + exprType->humanReadableName() + "."; if (auto const* funType = dynamic_cast(exprType)) @@ -4452,7 +4777,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) if (auto funType = dynamic_cast(annotation.type)) { solAssert( - !funType->bound() || exprType->isImplicitlyConvertibleTo(*funType->selfType()), + !funType->hasBoundFirstArgument() || exprType->isImplicitlyConvertibleTo(*funType->selfType()), "Function \"" + memberName + "\" cannot be called on an object of type " + exprType->humanReadableName() + " (expected " + funType->selfType()->humanReadableName() + ")." ); @@ -4468,7 +4793,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) "Using \"." + memberName + "(...)\" is deprecated. Use \"{" + memberName + ": ...}\" instead." ); - if (!funType->bound()) + if (!funType->hasBoundFirstArgument()) if (auto typeType = dynamic_cast(exprType)) { auto contractType = dynamic_cast(typeType->actualType()); @@ -4518,7 +4843,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) ) annotation.isPure = true; else if (tt->actualType()->category() == Type::Category::Address) { - if (memberName == "makeAddrStd" || memberName == "makeAddrNone") { + if (memberName == "makeAddrStd" || memberName == "addrNone" || memberName == "makeAddrExtern") { annotation.isPure = true; } } @@ -4577,18 +4902,39 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) (memberName == "min" || memberName == "max") ) annotation.isPure = true; - else if (magicType->kind() == MagicType::Kind::Block && memberName == "chainid" && !m_evmVersion.hasChainID()) - m_errorReporter.typeError( - 3081_error, - _memberAccess.location(), - "\"chainid\" is not supported by the VM version." - ); - else if (magicType->kind() == MagicType::Kind::Block && memberName == "basefee" && !m_evmVersion.hasBaseFee()) - m_errorReporter.typeError( - 5921_error, - _memberAccess.location(), - "\"basefee\" is not supported by the VM version." - ); + else if (magicType->kind() == MagicType::Kind::Block) + { + if (memberName == "chainid" && !m_evmVersion.hasChainID()) + m_errorReporter.typeError( + 3081_error, + _memberAccess.location(), + "\"chainid\" is not supported by the VM version." + ); + else if (memberName == "basefee" && !m_evmVersion.hasBaseFee()) + m_errorReporter.typeError( + 5921_error, + _memberAccess.location(), + "\"basefee\" is not supported by the VM version." + ); + else if (memberName == "blobbasefee" && !m_evmVersion.hasBlobBaseFee()) + m_errorReporter.typeError( + 1006_error, + _memberAccess.location(), + "\"blobbasefee\" is not supported by the VM version." + ); + else if (memberName == "prevrandao" && !m_evmVersion.hasPrevRandao()) + m_errorReporter.warning( + 9432_error, + _memberAccess.location(), + "\"prevrandao\" is not supported by the VM version and will be treated as \"difficulty\"." + ); + else if (memberName == "difficulty" && m_evmVersion.hasPrevRandao()) + m_errorReporter.warning( + 8417_error, + _memberAccess.location(), + "Since the VM version paris, \"difficulty\" was replaced by \"prevrandao\", which now returns a random number based on the beacon chain." + ); + } } if ( @@ -4781,13 +5127,13 @@ bool TypeChecker::visit(IndexRangeAccess const& _access) return false; } -vector TypeChecker::cleanOverloadedDeclarations( +std::vector TypeChecker::cleanOverloadedDeclarations( Identifier const& _identifier, - vector const& _candidates + std::vector const& _candidates ) { solAssert(_candidates.size() > 1, ""); - vector uniqueDeclarations; + std::vector uniqueDeclarations; for (Declaration const* declaration: _candidates) { @@ -4840,7 +5186,7 @@ bool TypeChecker::visit(Identifier const& _identifier) else if (!annotation.arguments) { // The identifier should be a public state variable shadowing other functions - vector candidates; + std::vector candidates; for (Declaration const* declaration: annotation.overloadedDeclarations) { @@ -4856,7 +5202,7 @@ bool TypeChecker::visit(Identifier const& _identifier) } else { - vector candidates; + std::vector candidates; for (Declaration const* declaration: annotation.overloadedDeclarations) { @@ -4875,7 +5221,7 @@ bool TypeChecker::visit(Identifier const& _identifier) if (!declaration->location().isValid()) { // Try to re-construct function definition - string description; + std::string description; for (auto const& param: declaration->functionType(true)->parameterTypes()) description += (description.empty() ? "" : ", ") + param->humanReadableName(); description = "function " + _identifier.name() + "(" + description + ")"; @@ -4973,9 +5319,11 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) void TypeChecker::endVisit(MappingNameExpression const& _expr) { _expr.annotation().type = TypeProvider::typeType(TypeProvider::mapping( - _expr.type().keyType().annotation().type, - _expr.type().valueType().annotation().type - )); + _expr.type().keyType().annotation().type, + "", + _expr.type().valueType().annotation().type, + "" + )); _expr.annotation().isPure = true; } @@ -5060,8 +5408,9 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) solAssert(m_errorReporter.hasErrors()); return; } - solAssert(_usingFor.typeName()->annotation().type); - if (Declaration const* typeDefinition = _usingFor.typeName()->annotation().type->typeDefinition()) + Type const* usingForType = _usingFor.typeName()->annotation().type; + solAssert(usingForType); + if (Declaration const* typeDefinition = usingForType->typeDefinition()) { if (typeDefinition->scope() != m_currentSourceUnit) m_errorReporter.typeError( @@ -5095,54 +5444,226 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) return; } - solAssert(_usingFor.typeName()->annotation().type); - Type const* normalizedType = TypeProvider::withLocationIfReference( - _usingFor.typeName()->annotation().type - ); + Type const* usingForType = _usingFor.typeName()->annotation().type; + solAssert(usingForType); + + Type const* normalizedType = usingForType; solAssert(normalizedType); - for (ASTPointer const& path: _usingFor.functionsOrLibrary()) + for (auto const& [path, operator_]: _usingFor.functionsAndOperators()) { solAssert(path->annotation().referencedDeclaration); FunctionDefinition const& functionDefinition = dynamic_cast(*path->annotation().referencedDeclaration); - solAssert(functionDefinition.type()); + FunctionType const* functionType = dynamic_cast( + functionDefinition.libraryFunction() ? + functionDefinition.typeViaContractName() : + functionDefinition.type() + ); + + solAssert(functionType); if (functionDefinition.parameters().empty()) m_errorReporter.fatalTypeError( 4731_error, path->location(), - "The function \"" + joinHumanReadable(path->path(), ".") + "\" " + - "does not have any parameters, and therefore cannot be bound to the type \"" + - (normalizedType ? normalizedType->humanReadableName() : "*") + "\"." + SecondarySourceLocation().append( + "Function defined here:", + functionDefinition.location() + ), + fmt::format( + "The function \"{}\" does not have any parameters, and therefore cannot be attached to the type \"{}\".", + joinHumanReadable(path->path(), "."), + normalizedType ? normalizedType->toString(true /* withoutDataLocation */) : "*" + ) + ); + + if ( + functionDefinition.visibility() == Visibility::Private && + functionDefinition.scope() != m_currentContract + ) + { + solAssert(functionDefinition.libraryFunction()); + m_errorReporter.typeError( + 6772_error, + path->location(), + SecondarySourceLocation().append( + "Function defined here:", + functionDefinition.location() + ), + fmt::format( + "Function \"{}\" is private and therefore cannot be attached" + " to a type outside of the library where it is defined.", + joinHumanReadable(path->path(), ".") + ) ); + } - FunctionType const* functionType = dynamic_cast(*functionDefinition.type()).asBoundFunction(); - solAssert(functionType && functionType->selfType(), ""); + FunctionType const* functionTypeWithBoundFirstArgument = functionType->withBoundFirstArgument(); + solAssert(functionTypeWithBoundFirstArgument && functionTypeWithBoundFirstArgument->selfType(), ""); BoolResult result = normalizedType->isImplicitlyConvertibleTo( - *TypeProvider::withLocationIfReference(functionType->selfType()) + *functionTypeWithBoundFirstArgument->selfType() ); - if (!result) + if (!result && !operator_) m_errorReporter.typeError( 3100_error, path->location(), - "The function \"" + joinHumanReadable(path->path(), ".") + "\" "+ - "cannot be bound to the type \"" + _usingFor.typeName()->annotation().type->humanReadableName() + - "\" because the type cannot be implicitly converted to the first argument" + - " of the function (\"" + functionType->selfType()->humanReadableName() + "\")" + - ( - result.message().empty() ? - "." : - ": " + result.message() + SecondarySourceLocation().append( + "Function defined here:", + functionDefinition.location() + ), + fmt::format( + "The function \"{}\" cannot be attached to the type \"{}\" because the type cannot " + "be implicitly converted to the first argument of the function (\"{}\"){}", + joinHumanReadable(path->path(), "."), + usingForType->toString(true /* withoutDataLocation */), + functionTypeWithBoundFirstArgument->selfType()->humanReadableName(), + result.message().empty() ? "." : ": " + result.message() ) ); + else if (operator_.has_value()) + { + if (!_usingFor.global()) + m_errorReporter.typeError( + 3320_error, + path->location(), + "Operators can only be defined in a global 'using for' directive." + ); + + if ( + functionType->stateMutability() != StateMutability::Pure || + !functionDefinition.isFree() + ) + m_errorReporter.typeError( + 7775_error, + path->location(), + SecondarySourceLocation().append( + "Function defined as non-pure here:", + functionDefinition.location() + ), + "Only pure free functions can be used to define operators." + ); + + solAssert(!functionType->hasBoundFirstArgument()); + TypePointers const& parameterTypes = functionType->parameterTypes(); + size_t const parameterCount = parameterTypes.size(); + if (usingForType->category() != Type::Category::UserDefinedValueType) + { + m_errorReporter.typeError( + 5332_error, + path->location(), + "Operators can only be implemented for user-defined value types." + ); + continue; + } + solAssert(usingForType->typeDefinition()); + + bool identicalFirstTwoParameters = (parameterCount < 2 || *parameterTypes.at(0) == *parameterTypes.at(1)); + bool isUnaryOnlyOperator = (!TokenTraits::isBinaryOp(operator_.value()) && TokenTraits::isUnaryOp(operator_.value())); + bool isBinaryOnlyOperator = (TokenTraits::isBinaryOp(operator_.value()) && !TokenTraits::isUnaryOp(operator_.value())); + bool firstParameterMatchesUsingFor = parameterCount == 0 || *usingForType == *parameterTypes.front(); + + std::optional wrongParametersMessage; + if (isBinaryOnlyOperator && (parameterCount != 2 || !identicalFirstTwoParameters)) + wrongParametersMessage = fmt::format("two parameters of type {} and the same data location", usingForType->canonicalName()); + else if (isUnaryOnlyOperator && (parameterCount != 1 || !firstParameterMatchesUsingFor)) + wrongParametersMessage = fmt::format("exactly one parameter of type {}", usingForType->canonicalName()); + else if (parameterCount >= 3 || !firstParameterMatchesUsingFor || !identicalFirstTwoParameters) + wrongParametersMessage = fmt::format("one or two parameters of type {} and the same data location", usingForType->canonicalName()); + + if (wrongParametersMessage.has_value()) + m_errorReporter.typeError( + 1884_error, + functionDefinition.parameterList().location(), + SecondarySourceLocation().append( + "Function was used to implement an operator here:", + path->location() + ), + fmt::format( + "Wrong parameters in operator definition. " + "The function \"{}\" needs to have {} to be used for the operator {}.", + joinHumanReadable(path->path(), "."), + wrongParametersMessage.value(), + TokenTraits::friendlyName(operator_.value()) + ) + ); + + // This case is separately validated for all attached functions and is a fatal error + solAssert(parameterCount != 0); + + TypePointers const& returnParameterTypes = functionType->returnParameterTypes(); + size_t const returnParameterCount = returnParameterTypes.size(); + + std::optional wrongReturnParametersMessage; + if (!TokenTraits::isCompareOp(operator_.value()) && operator_.value() != Token::Not) + { + if (returnParameterCount != 1 || *usingForType != *returnParameterTypes.front()) + wrongReturnParametersMessage = "exactly one value of type " + usingForType->canonicalName(); + else if (*returnParameterTypes.front() != *parameterTypes.front()) + wrongReturnParametersMessage = "a value of the same type and data location as its parameters"; + } + else if (returnParameterCount != 1 || *returnParameterTypes.front() != *TypeProvider::boolean()) + wrongReturnParametersMessage = "exactly one value of type bool"; + + solAssert(functionDefinition.returnParameterList()); + if (wrongReturnParametersMessage.has_value()) + m_errorReporter.typeError( + 7743_error, + functionDefinition.returnParameterList()->location(), + SecondarySourceLocation().append( + "Function was used to implement an operator here:", + path->location() + ), + fmt::format( + "Wrong return parameters in operator definition. " + "The function \"{}\" needs to return {} to be used for the operator {}.", + joinHumanReadable(path->path(), "."), + wrongReturnParametersMessage.value(), + TokenTraits::friendlyName(operator_.value()) + ) + ); + + if (parameterCount != 1 && parameterCount != 2) + solAssert(m_errorReporter.hasErrors()); + else + { + // TODO: This is pretty inefficient. For every operator binding we find, we're + // traversing all bindings in all `using for` directives in the current scope. + std::set matchingDefinitions = usingForType->operatorDefinitions( + operator_.value(), + *currentDefinitionScope(), + parameterCount == 1 // _unary + ); + + if (matchingDefinitions.size() >= 2) + { + // TODO: We should point at other places that bind the operator rather than at + // the definitions they bind. + SecondarySourceLocation secondaryLocation; + for (FunctionDefinition const* definition: matchingDefinitions) + if (functionDefinition != *definition) + secondaryLocation.append("Conflicting definition:", definition->location()); + + m_errorReporter.typeError( + 4705_error, + path->location(), + secondaryLocation, + fmt::format( + "User-defined {} operator {} has more than one definition matching the operand type visible in the current scope.", + parameterCount == 1 ? "unary" : "binary", + TokenTraits::friendlyName(operator_.value()) + ) + ); + } + } + } } } void TypeChecker::checkErrorAndEventParameters(CallableDeclaration const& _callable) { - string kind = dynamic_cast(&_callable) ? "event" : "error"; + std::string kind = dynamic_cast(&_callable) ? "event" : "error"; for (ASTPointer const& var: _callable.parameters()) { if (!type(*var)->interfaceType(false)) @@ -5227,7 +5748,7 @@ void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAss if (*_expression.annotation().isLValue) return; - auto [errorId, description] = [&]() -> tuple { + auto [errorId, description] = [&]() -> std::tuple { if (*_expression.annotation().isConstant) return { 6520_error, "Cannot assign to a constant variable." }; @@ -5247,11 +5768,6 @@ void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAss return { 7567_error, "Member \"length\" is read-only and cannot be used to resize arrays." }; } - if (auto identifier = dynamic_cast(&_expression)) - if (auto varDecl = dynamic_cast(identifier->annotation().referencedDeclaration)) - if (varDecl->isExternalCallableParameter() && dynamic_cast(identifier->annotation().type)) - return { 7128_error, "External function arguments of reference type are read-only." }; - return { 4247_error, "Expression has to be an lvalue." }; }(); diff --git a/compiler/libsolidity/analysis/TypeChecker.h b/compiler/libsolidity/analysis/TypeChecker.h index e224f632..b66063a5 100644 --- a/compiler/libsolidity/analysis/TypeChecker.h +++ b/compiler/libsolidity/analysis/TypeChecker.h @@ -87,12 +87,12 @@ class TypeChecker: private ASTConstVisitor bool _abiEncoderV2 ); - void typeCheckTVMBuildStateInit( + void typeCheckABIEncodeStateInit( FunctionCall const& _functionCall, const std::function& hasName, const std::function& findName ); - void typeCheckTVMBuildDataInit( + void typeCheckABIEncodeData( FunctionCall const& _functionCall, const std::function& hasName, const std::function& findName @@ -141,7 +141,8 @@ class TypeChecker: private ASTConstVisitor static ContractType const* getContractType(Expression const* expr); FunctionDefinition const* checkPubFunctionAndGetDefinition(Expression const& arg, bool printError = false); FunctionDefinition const* checkPubFunctionOrContractTypeAndGetDefinition(Expression const& arg); - void checkInitList(InitializerList const *list, ContractType const *ct); + void checkInitList(InitializerList const* list, ContractType const& ct, + langutil::SourceLocation const& _functionCallLocation); void checkCallList( std::vector const& arguments, FunctionCall const& _functionCall, @@ -195,7 +196,6 @@ class TypeChecker: private ASTConstVisitor bool visit(EventDefinition const& _eventDef) override; bool visit(ErrorDefinition const& _errorDef) override; void endVisit(FunctionTypeName const& _funType) override; - bool visit(InlineAssembly const& _inlineAssembly) override; bool visit(IfStatement const& _ifStatement) override; void endVisit(TryStatement const& _tryStatement) override; bool visit(WhileStatement const& _whileStatement) override; diff --git a/compiler/libsolidity/analysis/ViewPureChecker.cpp b/compiler/libsolidity/analysis/ViewPureChecker.cpp index bdd40fcf..d7918116 100644 --- a/compiler/libsolidity/analysis/ViewPureChecker.cpp +++ b/compiler/libsolidity/analysis/ViewPureChecker.cpp @@ -24,7 +24,6 @@ #include #include -using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; @@ -53,9 +52,9 @@ bool ViewPureChecker::visit(FunctionDefinition const& _funDef) "Library functions must have default mutability. Delete keyword view or pure."); } } - if (_funDef.isFree() && !_funDef.isInlineAssembly() && _funDef.stateMutability() != StateMutability::NonPayable) { + if (_funDef.isFree() && !_funDef.isInlineAssembly() && _funDef.stateMutability() != StateMutability::Pure) { m_errorReporter.warning(4029_error, _funDef.location(), - "Free functions must have default mutability. Delete keyword view or pure."); + "Free function must have pure mutability."); } return true; } @@ -144,10 +143,6 @@ void ViewPureChecker::endVisit(Identifier const& _identifier) reportMutability(mutability, _identifier.location()); } -void ViewPureChecker::endVisit(InlineAssembly const& /*_inlineAssembly*/) -{ -} - void ViewPureChecker::reportMutability( StateMutability _mutability, SourceLocation const& _location, @@ -214,17 +209,34 @@ ViewPureChecker::MutabilityAndLocation const& ViewPureChecker::modifierMutabilit { MutabilityAndLocation bestMutabilityAndLocation{}; FunctionDefinition const* currentFunction = nullptr; - swap(bestMutabilityAndLocation, m_bestMutabilityAndLocation); - swap(currentFunction, m_currentFunction); + std::swap(bestMutabilityAndLocation, m_bestMutabilityAndLocation); + std::swap(currentFunction, m_currentFunction); _modifier.accept(*this); - swap(bestMutabilityAndLocation, m_bestMutabilityAndLocation); - swap(currentFunction, m_currentFunction); + std::swap(bestMutabilityAndLocation, m_bestMutabilityAndLocation); + std::swap(currentFunction, m_currentFunction); } return m_inferredMutability.at(&_modifier); } +void ViewPureChecker::reportFunctionCallMutability(StateMutability _mutability, langutil::SourceLocation const& _location) +{ + reportMutability(_mutability, _location); +} + +void ViewPureChecker::endVisit(BinaryOperation const& _binaryOperation) +{ + if (*_binaryOperation.annotation().userDefinedFunction != nullptr) + reportFunctionCallMutability((*_binaryOperation.annotation().userDefinedFunction)->stateMutability(), _binaryOperation.location()); +} + +void ViewPureChecker::endVisit(UnaryOperation const& _unaryOperation) +{ + if (*_unaryOperation.annotation().userDefinedFunction != nullptr) + reportFunctionCallMutability((*_unaryOperation.annotation().userDefinedFunction)->stateMutability(), _unaryOperation.location()); +} + void ViewPureChecker::endVisit(FunctionCall const& _functionCall) { if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall) @@ -329,14 +341,25 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) break; case Type::Category::Magic: { - using MagicMember = pair; - set static const pureMembers{ + using MagicMember = std::pair; + std::set static const pureMembers{ + {MagicType::Kind::ABI, "codeSalt"}, {MagicType::Kind::ABI, "decode"}, + {MagicType::Kind::ABI, "decodeData"}, {MagicType::Kind::ABI, "encode"}, + {MagicType::Kind::ABI, "encodeBody"}, {MagicType::Kind::ABI, "encodeCall"}, + {MagicType::Kind::ABI, "encodeData"}, + {MagicType::Kind::ABI, "encodeExtMsg"}, + {MagicType::Kind::ABI, "encodeIntMsg"}, + {MagicType::Kind::ABI, "encodeOldDataInit"}, {MagicType::Kind::ABI, "encodePacked"}, + {MagicType::Kind::ABI, "encodeStateInit"}, {MagicType::Kind::ABI, "encodeWithSelector"}, {MagicType::Kind::ABI, "encodeWithSignature"}, + {MagicType::Kind::ABI, "functionId"}, + {MagicType::Kind::ABI, "setCodeSalt"}, + {MagicType::Kind::ABI, "stateInitHash"}, {MagicType::Kind::Block, "blockhash"}, {MagicType::Kind::Block, "logicaltime"}, {MagicType::Kind::Block, "timestamp"}, @@ -366,12 +389,12 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) {MagicType::Kind::Math, "muldivr"}, {MagicType::Kind::Math, "sign"}, {MagicType::Kind::Message, "body"}, - {MagicType::Kind::Message, "forwardFee"}, - {MagicType::Kind::Message, "importFee"}, {MagicType::Kind::Message, "createdAt"}, {MagicType::Kind::Message, "currencies"}, {MagicType::Kind::Message, "data"}, + {MagicType::Kind::Message, "forwardFee"}, {MagicType::Kind::Message, "hasStateInit"}, + {MagicType::Kind::Message, "importFee"}, {MagicType::Kind::Message, "isExternal"}, {MagicType::Kind::Message, "isInternal"}, {MagicType::Kind::Message, "isTickTock"}, @@ -407,7 +430,6 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) {MagicType::Kind::TVM, "hash"}, {MagicType::Kind::TVM, "hexdump"}, {MagicType::Kind::TVM, "initCodeHash"}, - {MagicType::Kind::TVM, "insertPubkey"}, {MagicType::Kind::TVM, "log"}, {MagicType::Kind::TVM, "rawConfigParam"}, {MagicType::Kind::TVM, "rawReserve"}, @@ -421,7 +443,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) {MagicType::Kind::Transaction, "storageFee"}, {MagicType::Kind::Transaction, "timestamp"}, }; - set static const nonpayableMembers{ + std::set static const nonpayableMembers{ {MagicType::Kind::TVM, "commit"}, {MagicType::Kind::TVM, "rawCommit"}, {MagicType::Kind::TVM, "setData"}, diff --git a/compiler/libsolidity/analysis/ViewPureChecker.h b/compiler/libsolidity/analysis/ViewPureChecker.h index 6698c104..24cc8ffc 100644 --- a/compiler/libsolidity/analysis/ViewPureChecker.h +++ b/compiler/libsolidity/analysis/ViewPureChecker.h @@ -54,6 +54,8 @@ class ViewPureChecker: private ASTConstVisitor bool visit(FunctionDefinition const& _funDef) override; void endVisit(FunctionDefinition const& _funDef) override; + void endVisit(BinaryOperation const& _binaryOperation) override; + void endVisit(UnaryOperation const& _unaryOperation) override; bool visit(ModifierDefinition const& _modifierDef) override; void endVisit(ModifierDefinition const& _modifierDef) override; void endVisit(Identifier const& _identifier) override; @@ -63,7 +65,6 @@ class ViewPureChecker: private ASTConstVisitor void endVisit(IndexRangeAccess const& _indexAccess) override; void endVisit(ModifierInvocation const& _modifier) override; void endVisit(FunctionCall const& _functionCall) override; - void endVisit(InlineAssembly const& _inlineAssembly) override; /// Called when an element of mutability @a _mutability is encountered. /// Creates appropriate warnings and errors and sets @a m_currentBestMutability. @@ -74,6 +75,7 @@ class ViewPureChecker: private ASTConstVisitor ); bool isStateVariable(Expression const& expression) const; + void reportFunctionCallMutability(StateMutability _mutability, langutil::SourceLocation const& _location); /// Determines the mutability of modifier if not already cached. MutabilityAndLocation const& modifierMutability(ModifierDefinition const& _modifier); diff --git a/compiler/libsolidity/ast/AST.cpp b/compiler/libsolidity/ast/AST.cpp index 1390ff0b..9e71380f 100644 --- a/compiler/libsolidity/ast/AST.cpp +++ b/compiler/libsolidity/ast/AST.cpp @@ -27,16 +27,18 @@ #include #include #include +#include #include +#include #include +#include #include #include #include -using namespace std; using namespace solidity; using namespace solidity::frontend; @@ -105,7 +107,7 @@ FunctionDefinition const* ASTNode::resolveFunctionCall(FunctionCall const& _func ASTAnnotation& ASTNode::annotation() const { if (!m_annotation) - m_annotation = make_unique(); + m_annotation = std::make_unique(); return *m_annotation; } @@ -114,9 +116,9 @@ SourceUnitAnnotation& SourceUnit::annotation() const return initAnnotation(); } -set SourceUnit::referencedSourceUnits(bool _recurse, set _skipList) const +std::set SourceUnit::referencedSourceUnits(bool _recurse, std::set _skipList) const { - set sourceUnits; + std::set sourceUnits; for (ImportDirective const* importDirective: filteredNodes(nodes())) { auto const& sourceUnit = importDirective->annotation().sourceUnit; @@ -147,11 +149,11 @@ bool ContractDefinition::derivesFrom(ContractDefinition const& _base) const return util::contains(annotation().linearizedBaseContracts, &_base); } -map, FunctionTypePointer> ContractDefinition::interfaceFunctions(bool _includeInheritedFunctions) const +std::map, FunctionTypePointer> ContractDefinition::interfaceFunctions(bool _includeInheritedFunctions) const { auto exportedFunctionList = interfaceFunctionList(_includeInheritedFunctions); - map, FunctionTypePointer> exportedFunctions; + std::map, FunctionTypePointer> exportedFunctions; for (auto const& it: exportedFunctionList) exportedFunctions.insert(it); @@ -204,11 +206,11 @@ FunctionDefinition const* ContractDefinition::onBounceFunction() const return nullptr; } -vector const& ContractDefinition::definedInterfaceEvents() const +std::vector const& ContractDefinition::definedInterfaceEvents() const { return m_interfaceEvents.init([&]{ - set eventsSeen; - vector interfaceEvents; + std::set eventsSeen; + std::vector interfaceEvents; for (ContractDefinition const* contract: annotation().linearizedBaseContracts) for (EventDefinition const* e: contract->events()) @@ -216,9 +218,9 @@ vector const& ContractDefinition::definedInterfaceEvents /// NOTE: this requires the "internal" version of an Event, /// though here internal strictly refers to visibility, /// and not to function encoding (jump vs. call) - auto const& function = e->functionType(true); - solAssert(function, ""); - string eventSignature = function->externalSignature(); + FunctionType const* functionType = e->functionType(true); + solAssert(functionType, ""); + std::string eventSignature = functionType->externalSignature(); if (eventsSeen.count(eventSignature) == 0) { eventsSeen.insert(eventSignature); @@ -229,7 +231,7 @@ vector const& ContractDefinition::definedInterfaceEvents }); } -vector const ContractDefinition::usedInterfaceEvents() const +std::vector const ContractDefinition::usedInterfaceEvents() const { solAssert(annotation().creationCallGraph.set(), ""); @@ -239,9 +241,24 @@ vector const ContractDefinition::usedInterfaceEvents() c ); } -vector ContractDefinition::interfaceErrors(bool _requireCallGraph) const +std::vector ContractDefinition::interfaceEvents(bool _requireCallGraph) const { - set result; + std::set result; + for (ContractDefinition const* contract: annotation().linearizedBaseContracts) + result += contract->events(); + solAssert(annotation().creationCallGraph.set() == annotation().deployedCallGraph.set()); + if (_requireCallGraph) + solAssert(annotation().creationCallGraph.set()); + if (annotation().creationCallGraph.set()) + result += usedInterfaceEvents(); + // We could filter out all events that do not have an external interface + // if _requireCallGraph is false. + return util::convertContainer>(std::move(result)); +} + +std::vector ContractDefinition::interfaceErrors(bool _requireCallGraph) const +{ + std::set result; for (ContractDefinition const* contract: annotation().linearizedBaseContracts) result += filteredNodes(contract->m_subNodes); solAssert(annotation().creationCallGraph.set() == annotation().deployedCallGraph.set(), ""); @@ -251,20 +268,20 @@ vector ContractDefinition::interfaceErrors(bool _require result += (*annotation().creationCallGraph)->usedErrors + (*annotation().deployedCallGraph)->usedErrors; - return util::convertContainer>(std::move(result)); + return util::convertContainer>(std::move(result)); } -vector, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const +std::vector, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const { return m_interfaceFunctionList[_includeInheritedFunctions].init([&]{ - set signaturesSeen; - vector, FunctionTypePointer>> interfaceFunctionList; + std::set signaturesSeen; + std::vector, FunctionTypePointer>> interfaceFunctionList; for (ContractDefinition const* contract: annotation().linearizedBaseContracts) { if (_includeInheritedFunctions == false && contract != this) continue; - vector functions; + std::vector functions; for (FunctionDefinition const* f: contract->definedFunctions()) if (f->isPartOfExternalInterface()) functions.push_back(TypeProvider::function(*f, FunctionType::Kind::External)); @@ -276,12 +293,11 @@ vector, FunctionTypePointer>> const& ContractDefinition: if (!fun->interfaceFunctionType()) // Fails hopefully because we already registered the error continue; - string functionSignature = fun->externalSignature(); + std::string functionSignature = fun->externalSignature(); if (signaturesSeen.count(functionSignature) == 0) { signaturesSeen.insert(functionSignature); - util::FixedHash<4> hash(util::keccak256(functionSignature)); - interfaceFunctionList.emplace_back(hash, fun); + interfaceFunctionList.emplace_back(util::selectorFromSignatureH32(functionSignature), fun); } } } @@ -339,7 +355,7 @@ FunctionDefinition const* ContractDefinition::nextConstructor(ContractDefinition return nullptr; } -multimap const& ContractDefinition::definedFunctionsByName() const +std::multimap const& ContractDefinition::definedFunctionsByName() const { return m_definedFunctionsByName.init([&]{ std::multimap result; @@ -366,6 +382,11 @@ TypeDeclarationAnnotation& UserDefinedValueTypeDefinition::annotation() const return initAnnotation(); } +std::vector, std::optional>> UsingForDirective::functionsAndOperators() const +{ + return ranges::zip_view(m_functionsOrLibrary, m_operators) | ranges::to; +} + Type const* StructDefinition::type() const { solAssert(annotation().recursive.has_value(), "Requested struct type before DeclarationTypeChecker."); @@ -461,12 +482,12 @@ Type const* FunctionDefinition::typeViaContractName() const return TypeProvider::function(*this, FunctionType::Kind::Declaration); } -string FunctionDefinition::externalSignature() const +std::string FunctionDefinition::externalSignature() const { return TypeProvider::function(*this)->externalSignature(); } -string FunctionDefinition::externalIdentifierHex() const +std::string FunctionDefinition::externalIdentifierHex() const { return TypeProvider::function(*this)->externalIdentifierHex(); } @@ -616,7 +637,7 @@ CallableDeclaration const* Scopable::functionOrModifierDefinition() const return nullptr; } -string Scopable::sourceUnitName() const +std::string Scopable::sourceUnitName() const { return *sourceUnit().annotation().path; } @@ -679,7 +700,7 @@ bool VariableDeclaration::isCallableOrCatchParameter() const if (isReturnParameter() || isTryCatchParameter()) return true; - vector> const* parameters = nullptr; + std::vector> const* parameters = nullptr; if (auto const* funTypeName = dynamic_cast(scope())) parameters = &funTypeName->parameterTypes(); @@ -700,7 +721,7 @@ bool VariableDeclaration::isLocalOrReturn() const bool VariableDeclaration::isReturnParameter() const { - vector> const* returnParameters = nullptr; + std::vector> const* returnParameters = nullptr; if (auto const* funTypeName = dynamic_cast(scope())) returnParameters = &funTypeName->returnParameterTypes(); @@ -778,7 +799,7 @@ bool VariableDeclaration::hasReferenceOrMappingType() const { solAssert(typeName().annotation().type, "Can only be called after reference resolution"); Type const* type = typeName().annotation().type; - return type->category() == Type::Category::Mapping || dynamic_cast(type); + return type->category() == Type::Category::Mapping; } bool VariableDeclaration::isStateVariable() const @@ -791,7 +812,7 @@ bool VariableDeclaration::isFileLevelVariable() const return dynamic_cast(scope()); } -string VariableDeclaration::externalIdentifierHex() const +std::string VariableDeclaration::externalIdentifierHex() const { solAssert(isStateVariable() && isPublic(), "Can only be called for public state variables"); return TypeProvider::function(*this)->externalIdentifierHex(); @@ -837,11 +858,6 @@ StatementAnnotation& FreeInlineAssembly::annotation() const return initAnnotation(); } -InlineAssemblyAnnotation& InlineAssembly::annotation() const -{ - return initAnnotation(); -} - BlockAnnotation& Block::annotation() const { return initAnnotation(); @@ -877,6 +893,37 @@ MemberAccessAnnotation& MemberAccess::annotation() const return initAnnotation(); } +OperationAnnotation& UnaryOperation::annotation() const +{ + return initAnnotation(); +} + +FunctionType const* UnaryOperation::userDefinedFunctionType() const +{ + if (*annotation().userDefinedFunction == nullptr) + return nullptr; + + FunctionDefinition const* userDefinedFunction = *annotation().userDefinedFunction; + return dynamic_cast( + userDefinedFunction->libraryFunction() ? + userDefinedFunction->typeViaContractName() : + userDefinedFunction->type() + ); +} + +FunctionType const* BinaryOperation::userDefinedFunctionType() const +{ + if (*annotation().userDefinedFunction == nullptr) + return nullptr; + + FunctionDefinition const* userDefinedFunction = *annotation().userDefinedFunction; + return dynamic_cast( + userDefinedFunction->libraryFunction() ? + userDefinedFunction->typeViaContractName() : + userDefinedFunction->type() + ); +} + BinaryOperationAnnotation& BinaryOperation::annotation() const { return initAnnotation(); @@ -887,7 +934,7 @@ FunctionCallAnnotation& FunctionCall::annotation() const return initAnnotation(); } -vector> FunctionCall::sortedArguments() const +std::vector> FunctionCall::sortedArguments() const { // normal arguments if (m_names.empty()) @@ -904,7 +951,7 @@ vector> FunctionCall::sortedArguments() const else functionType = dynamic_cast(m_expression->annotation().type); - vector> sorted; + std::vector> sorted; for (auto const& parameterName: functionType->parameterNames()) { bool found = false; @@ -959,13 +1006,25 @@ bool Literal::passesAddressChecksum() const return util::passesAddressChecksum(valueWithoutUnderscores(), true); } -string Literal::getChecksummedAddress() const +std::string Literal::getChecksummedAddress() const { solAssert(isHexNumber(), "Expected hex number"); /// Pad literal to be a proper hex address. - string address = valueWithoutUnderscores().substr(2); + std::string address = valueWithoutUnderscores().substr(2); if (address.length() > 40) - return string(); + return std::string(); address.insert(address.begin(), 40 - address.size(), '0'); return util::getChecksummedAddress(address); } + +/// Experimental Solidity nodes +/// @{ +TypeClassDefinitionAnnotation& TypeClassDefinition::annotation() const +{ + return initAnnotation(); +} +TypeDeclarationAnnotation& TypeDefinition::annotation() const +{ + return initAnnotation(); +} +/// @} diff --git a/compiler/libsolidity/ast/AST.h b/compiler/libsolidity/ast/AST.h index 8d727af7..6b490609 100644 --- a/compiler/libsolidity/ast/AST.h +++ b/compiler/libsolidity/ast/AST.h @@ -86,15 +86,19 @@ class ASTNode static void listAccept(std::vector const& _list, ASTVisitor& _visitor) { for (T const& element: _list) - if (element) - element->accept(_visitor); + { + solAssert(element); + element->accept(_visitor); + } } template static void listAccept(std::vector const& _list, ASTConstVisitor& _visitor) { for (T const& element: _list) - if (element) - element->accept(_visitor); + { + solAssert(element); + element->accept(_visitor); + } } /// @returns a copy of the vector containing only the nodes which derive from T. @@ -120,6 +124,8 @@ class ASTNode bool operator!=(ASTNode const& _other) const { return !operator==(_other); } ///@} + virtual bool experimentalSolidityOnly() const { return false; } + protected: size_t const m_id = 0; @@ -166,9 +172,14 @@ class SourceUnit: public ASTNode, public ScopeOpener int64_t _id, SourceLocation const& _location, std::optional _licenseString, - std::vector> _nodes + std::vector> _nodes, + bool _experimentalSolidity ): - ASTNode(_id, _location), m_licenseString(std::move(_licenseString)), m_nodes(std::move(_nodes)) {} + ASTNode(_id, _location), + m_licenseString(std::move(_licenseString)), + m_nodes(std::move(_nodes)), + m_experimentalSolidity(_experimentalSolidity) + {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -179,10 +190,12 @@ class SourceUnit: public ASTNode, public ScopeOpener /// @returns a set of referenced SourceUnits. Recursively if @a _recurse is true. std::set referencedSourceUnits(bool _recurse = false, std::set _skipList = std::set()) const; + bool experimentalSolidity() const { return m_experimentalSolidity; } private: std::optional m_licenseString; std::vector> m_nodes; + bool m_experimentalSolidity = false; }; /** @@ -526,6 +539,10 @@ class ContractDefinition: public Declaration, public StructurallyDocumented, pub std::vector events() const { return filteredNodes(m_subNodes); } std::vector const& definedInterfaceEvents() const; std::vector const usedInterfaceEvents() const; + /// @return all events defined in this contract and its base contracts and all events + /// that are emitted during the execution of the contract. + /// @param _requireCallGraph if false, do not fail if the call graph has not been computed yet. + std::vector interfaceEvents(bool _requireCallGraph = true) const; /// @returns all errors defined in this contract or any base contract /// and all errors referenced during execution. /// @param _requireCallGraph if false, do not fail if the call graph has not been computed yet. @@ -651,15 +668,20 @@ class InheritanceSpecifier: public ASTNode /** * Using for directive: * - * 1. `using LibraryName for T` attaches all functions from the library `LibraryName` to the type `T` + * 1. `using LibraryName for T` attaches all functions from the library `LibraryName` to the type `T`. * 2. `using LibraryName for *` attaches to all types. - * 3. `using {f1, f2, ..., fn} for T` attaches the functions `f1`, `f2`, ..., - * `fn`, respectively to `T`. + * 3. `using {f1, f2, ..., fn} for T` attaches the functions `f1`, `f2`, ..., `fn`, respectively to `T`. + * 4. `using {f1 as op1, f2 as op2, ..., fn as opn} for T` implements operator `opn` for type `T` with function `fn`. * * For version 3, T has to be implicitly convertible to the first parameter type of * all functions, and this is checked at the point of the using statement. For versions 1 and * 2, this check is only done when a function is called. * + * For version 4, T has to be user-defined value type and the function must be pure. + * All parameters and return value of all the functions have to be of type T. + * This version can be combined with version 3 - a single directive may attach functions to the + * type and define operators on it at the same time. + * * Finally, `using {f1, f2, ..., fn} for T global` is also valid at file level, as long as T is * a user-defined type defined in the same file at file level. In this case, the methods are * attached to all objects of that type regardless of scope. @@ -670,17 +692,20 @@ class UsingForDirective: public ASTNode UsingForDirective( int64_t _id, SourceLocation const& _location, - std::vector> _functions, + std::vector> _functionsOrLibrary, + std::vector> _operators, bool _usesBraces, ASTPointer _typeName, bool _global ): ASTNode(_id, _location), - m_functions(_functions), + m_functionsOrLibrary(std::move(_functionsOrLibrary)), + m_operators(std::move(_operators)), m_usesBraces(_usesBraces), m_typeName(std::move(_typeName)), m_global{_global} { + solAssert(m_functionsOrLibrary.size() == m_operators.size()); } void accept(ASTVisitor& _visitor) override; @@ -690,19 +715,25 @@ class UsingForDirective: public ASTNode TypeName const* typeName() const { return m_typeName.get(); } /// @returns a list of functions or the single library. - std::vector> const& functionsOrLibrary() const { return m_functions; } + std::vector> const& functionsOrLibrary() const { return m_functionsOrLibrary; } + std::vector, std::optional>> functionsAndOperators() const; bool usesBraces() const { return m_usesBraces; } bool global() const { return m_global; } private: /// Either the single library or a list of functions. - std::vector> m_functions; + std::vector> m_functionsOrLibrary; + /// Operators, the functions from @a m_functionsOrLibrary implement. + /// A token if the corresponding element in m_functionsOrLibrary + /// defines an operator, nullptr otherwise. + /// Note that this vector size must be equal to m_functionsOrLibrary size. + std::vector> m_operators; bool m_usesBraces; ASTPointer m_typeName; bool m_global = false; }; -class StructDefinition: public Declaration, public ScopeOpener +class StructDefinition: public Declaration, public StructurallyDocumented, public ScopeOpener { public: StructDefinition( @@ -710,9 +741,13 @@ class StructDefinition: public Declaration, public ScopeOpener SourceLocation const& _location, ASTPointer const& _name, SourceLocation _nameLocation, - std::vector> _members + std::vector> _members, + ASTPointer _documentation ): - Declaration(_id, _location, _name, std::move(_nameLocation)), m_members(std::move(_members)) {} + Declaration(_id, _location, _name, std::move(_nameLocation)), + StructurallyDocumented(std::move(_documentation)), + m_members(std::move(_members)) + {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -730,7 +765,7 @@ class StructDefinition: public Declaration, public ScopeOpener std::vector> m_members; }; -class EnumDefinition: public Declaration, public ScopeOpener +class EnumDefinition: public Declaration, public StructurallyDocumented, public ScopeOpener { public: EnumDefinition( @@ -738,9 +773,14 @@ class EnumDefinition: public Declaration, public ScopeOpener SourceLocation const& _location, ASTPointer const& _name, SourceLocation _nameLocation, - std::vector> _members + std::vector> _members, + ASTPointer _documentation ): - Declaration(_id, _location, _name, std::move(_nameLocation)), m_members(std::move(_members)) {} + Declaration(_id, _location, _name, std::move(_nameLocation)), + StructurallyDocumented(std::move(_documentation)), + m_members(std::move(_members)) + {} + void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -935,9 +975,10 @@ class FunctionDefinition: public CallableDeclaration, public StructurallyDocumen bool _responsible, bool _externalMsg, bool _internalMsg, - bool _freeInlineAssembly + bool _freeInlineAssembly, + ASTPointer const& _experimentalReturnExpression = {} ): - CallableDeclaration(_id, _location, _name, std::move(_nameLocation), _visibility, _parameters, _isVirtual, _overrides, _returnParameters), + CallableDeclaration(_id, _location, _name, _nameLocation, _visibility, _parameters, _isVirtual, _overrides, _returnParameters), StructurallyDocumented(_documentation), ImplementationOptional(_body != nullptr), m_stateMutability(_stateMutability), @@ -950,13 +991,15 @@ class FunctionDefinition: public CallableDeclaration, public StructurallyDocumen m_responsible{_responsible}, m_externalMsg{_externalMsg}, m_internalMsg{_internalMsg}, - m_inlineAssembly{_freeInlineAssembly} + m_inlineAssembly{_freeInlineAssembly}, + m_experimentalReturnExpression(_experimentalReturnExpression) { solAssert(_kind == Token::Constructor || _kind == Token::Function || _kind == Token::Fallback || _kind == Token::Receive || _kind == Token::onBounce || _kind == Token::onTickTock , ""); solAssert(isOrdinary() == !name().empty(), ""); + // TODO: assert _returnParameters implies non-experimental _experimentalReturnExpression implies experimental } void accept(ASTVisitor& _visitor) override; @@ -1027,6 +1070,8 @@ class FunctionDefinition: public CallableDeclaration, public StructurallyDocumen ContractDefinition const* _searchStart = nullptr ) const override; + Expression const* experimentalReturnExpression() const { return m_experimentalReturnExpression.get(); } + private: StateMutability m_stateMutability; bool m_free; @@ -1039,6 +1084,7 @@ class FunctionDefinition: public CallableDeclaration, public StructurallyDocumen bool m_externalMsg{}; bool m_internalMsg{}; bool m_inlineAssembly{}; + ASTPointer m_experimentalReturnExpression; }; /** @@ -1072,7 +1118,9 @@ class VariableDeclaration: public Declaration, public StructurallyDocumented bool _isIndexed = false, Mutability _mutability = Mutability::Mutable, ASTPointer _overrides = nullptr, - bool isStatic = false + bool isStatic = false, + bool _noStorage = false, + ASTPointer _typeExpression = {} ): Declaration(_id, _location, _name, std::move(_nameLocation), _visibility), StructurallyDocumented(std::move(_documentation)), @@ -1081,15 +1129,19 @@ class VariableDeclaration: public Declaration, public StructurallyDocumented m_isIndexed(_isIndexed), m_mutability(_mutability), m_overrides(std::move(_overrides)), - m_isStatic(isStatic) + m_isStatic(isStatic), + m_noStorage(_noStorage), + m_typeExpression(std::move(_typeExpression)) { - solAssert(m_typeName, ""); + // TODO: consider still asserting unless we are in experimental solidity. + // solAssert(m_typeName, ""); solAssert(!m_typeExpression, ""); } void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; + bool hasTypeName() const { return m_typeName != nullptr; } TypeName const& typeName() const { return *m_typeName; } ASTPointer const& value() const { return m_value; } @@ -1131,6 +1183,7 @@ class VariableDeclaration: public Declaration, public StructurallyDocumented bool isConstant() const { return m_mutability == Mutability::Constant; } bool immutable() const { return m_mutability == Mutability::Immutable; } bool isStatic() const { return m_isStatic; } + bool isNoStorage() const { return m_noStorage; } ASTPointer const& overrides() const { return m_overrides; } /// @returns the external identifier of this variable (the hash of the signature) as a hex string (works only for public state variables). @@ -1142,6 +1195,7 @@ class VariableDeclaration: public Declaration, public StructurallyDocumented /// @returns null when it is not accessible as a function. FunctionTypePointer functionType(bool /*_internal*/) const override; + ASTPointer const& typeExpression() const { return m_typeExpression; } VariableDeclarationAnnotation& annotation() const override; protected: @@ -1156,8 +1210,9 @@ class VariableDeclaration: public Declaration, public StructurallyDocumented /// Whether the variable is "constant", "immutable" or non-marked (mutable). Mutability m_mutability = Mutability::Mutable; ASTPointer m_overrides; ///< Contains the override specifier node - ASTPointer m_attribute; ///< Attribute for variable. bool m_isStatic = false; + bool m_noStorage = false; + ASTPointer m_typeExpression; }; /** @@ -1266,7 +1321,7 @@ class EventDefinition: public CallableDeclaration, public StructurallyDocumented FunctionTypePointer functionType(bool /*_internal*/) const override; bool isVisibleInDerivedContracts() const override { return true; } - bool isVisibleViaContractTypeAccess() const override { return false; /* TODO */ } + bool isVisibleViaContractTypeAccess() const override { return true; } EventDefinitionAnnotation& annotation() const override; @@ -1461,18 +1516,37 @@ class Mapping: public TypeName int64_t _id, SourceLocation const& _location, ASTPointer _keyType, - ASTPointer _valueType + ASTPointer _keyName, + SourceLocation _keyNameLocation, + ASTPointer _valueType, + ASTPointer _valueName, + SourceLocation _valueNameLocation ): - TypeName(_id, _location), m_keyType(std::move(_keyType)), m_valueType(std::move(_valueType)) {} + TypeName(_id, _location), + m_keyType(std::move(_keyType)), + m_keyName(std::move(_keyName)), + m_keyNameLocation(std::move(_keyNameLocation)), + m_valueType(std::move(_valueType)), + m_valueName(std::move(_valueName)), + m_valueNameLocation(std::move(_valueNameLocation)) + {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; TypeName const& keyType() const { return *m_keyType; } + ASTString keyName() const { return *m_keyName; } + SourceLocation keyNameLocation() const { return m_keyNameLocation; } TypeName const& valueType() const { return *m_valueType; } + ASTString valueName() const { return *m_valueName; } + SourceLocation valueNameLocation() const { return m_valueNameLocation; } private: ASTPointer m_keyType; + ASTPointer m_keyName; + SourceLocation m_keyNameLocation; ASTPointer m_valueType; + ASTPointer m_valueName; + SourceLocation m_valueNameLocation; }; /** @@ -1579,24 +1653,6 @@ class FreeInlineAssembly: public Statement std::vector> m_lines; }; -/** - * Inline assembly. - */ -class InlineAssembly: public Statement -{ -public: - InlineAssembly( - int64_t _id, - SourceLocation const& _location, - ASTPointer const& _docString - ): - Statement(_id, _location, _docString) {} - void accept(ASTVisitor& _visitor) override; - void accept(ASTConstVisitor& _visitor) const override; - - InlineAssemblyAnnotation& annotation() const override; -}; - /** * Brace-enclosed block containing zero or more statements. */ @@ -1910,16 +1966,17 @@ class Return: public Statement SourceLocation const& _location, ASTPointer const& _docString, ASTPointer _expression, - std::vector> options, - std::vector> names + std::vector> _options, + std::vector> _names, + std::vector _nameLocations ): Statement(_id, _location, _docString), m_expression(std::move(_expression)), - m_options(std::move(options)), m_names(std::move(names)) {} + m_options(std::move(_options)), m_names(std::move(_names)), m_nameLocations(std::move(_nameLocations)) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; - Expression const* expression() const { return m_expression.get(); } const std::vector>& options() const { return m_options; } const std::vector>& names() const { return m_names; } + const std::vector& nameLocations() const { return m_nameLocations; } ReturnAnnotation& annotation() const override; @@ -1927,6 +1984,7 @@ class Return: public Statement ASTPointer m_expression; ///< value to return, optional std::vector> m_options; std::vector> m_names; + std::vector m_nameLocations; }; /** @@ -2197,6 +2255,10 @@ class UnaryOperation: public Expression bool isPrefixOperation() const { return m_isPrefix; } Expression const& subExpression() const { return *m_subExpression; } + FunctionType const* userDefinedFunctionType() const; + + OperationAnnotation& annotation() const override; + private: Token m_operator; ASTPointer m_subExpression; @@ -2219,7 +2281,8 @@ class BinaryOperation: public Expression ): Expression(_id, _location), m_left(std::move(_left)), m_operator(_operator), m_right(std::move(_right)) { - solAssert(TokenTraits::isBinaryOp(_operator) || TokenTraits::isCompareOp(_operator), ""); + // TODO: assert against colon for non-experimental solidity + solAssert(TokenTraits::isBinaryOp(_operator) || TokenTraits::isCompareOp(_operator) || _operator == Token::Colon || _operator == Token::RightArrow, ""); } void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -2228,6 +2291,8 @@ class BinaryOperation: public Expression Expression const& rightExpression() const { return *m_right; } Token getOperator() const { return m_operator; } + FunctionType const* userDefinedFunctionType() const; + BinaryOperationAnnotation& annotation() const override; private: @@ -2298,19 +2363,23 @@ class InitializerList: public Expression { InitializerList( int64_t _id, SourceLocation const& _location, - std::vector> const& _options, - std::vector> const& _names + std::vector> _options, + std::vector> _names, + std::vector _nameLocations ): - Expression(_id, _location), m_options(_options), m_names(_names) {} + Expression(_id, _location), m_options(std::move(_options)), m_names(std::move(_names)), + m_nameLocations(std::move(_nameLocations)) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; std::vector> options() const { return {m_options.begin(), m_options.end()}; } std::vector> const& names() const { return m_names; } + std::vector const& nameLocations() const { return m_nameLocations; } private: std::vector> m_options; std::vector> m_names; + std::vector m_nameLocations; }; @@ -2665,4 +2734,167 @@ class Literal: public PrimaryExpression /// @} +/// Experimental Solidity nodes +/// @{ +class TypeClassDefinition: public Declaration, public StructurallyDocumented, public ScopeOpener +{ +public: + TypeClassDefinition( + int64_t _id, + SourceLocation const& _location, + ASTPointer _typeVariable, + ASTPointer const& _name, + SourceLocation _nameLocation, + ASTPointer const& _documentation, + std::vector> _subNodes + ): + Declaration(_id, _location, _name, std::move(_nameLocation)), + StructurallyDocumented(_documentation), + m_typeVariable(std::move(_typeVariable)), + m_subNodes(std::move(_subNodes)) + {} + + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + VariableDeclaration const& typeVariable() const { return *m_typeVariable; } + std::vector> const& subNodes() const { return m_subNodes; } + + TypeClassDefinitionAnnotation& annotation() const override; + + Type const* type() const override { solAssert(false, "Requested type of experimental solidity node."); } + + bool experimentalSolidityOnly() const override { return true; } + +private: + ASTPointer m_typeVariable; + std::vector> m_subNodes; +}; + +class TypeClassInstantiation: public ASTNode, public ScopeOpener +{ +public: + TypeClassInstantiation( + int64_t _id, + SourceLocation const& _location, + ASTPointer _typeConstructor, + ASTPointer _argumentSorts, + ASTPointer _class, + std::vector> _subNodes + ): + ASTNode(_id, _location), + m_typeConstructor(std::move(_typeConstructor)), + m_argumentSorts(std::move(_argumentSorts)), + m_class(std::move(_class)), + m_subNodes(std::move(_subNodes)) + {} + + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + TypeName const& typeConstructor() const { return *m_typeConstructor; } + ParameterList const* argumentSorts() const { return m_argumentSorts.get(); } + TypeClassName const& typeClass() const { return *m_class; } + std::vector> const& subNodes() const { return m_subNodes; } + + bool experimentalSolidityOnly() const override { return true; } + +private: + ASTPointer m_typeConstructor; + ASTPointer m_argumentSorts; + ASTPointer m_class; + std::vector> m_subNodes; +}; + +class TypeDefinition: public Declaration, public ScopeOpener +{ +public: + TypeDefinition( + int64_t _id, + SourceLocation const& _location, + ASTPointer _name, + SourceLocation _nameLocation, + ASTPointer _arguments, + ASTPointer _typeExpression + ): + Declaration(_id, _location, _name, std::move(_nameLocation), Visibility::Default), + m_arguments(std::move(_arguments)), + m_typeExpression(std::move(_typeExpression)) + { + } + + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + Type const* type() const override { return nullptr; } + + TypeDeclarationAnnotation& annotation() const override; + + ParameterList const* arguments() const { return m_arguments.get(); } + Expression const* typeExpression() const { return m_typeExpression.get(); } + + bool experimentalSolidityOnly() const override { return true; } + +private: + ASTPointer m_arguments; + ASTPointer m_typeExpression; +}; + +class TypeClassName: public ASTNode +{ +public: + TypeClassName( + int64_t _id, + SourceLocation const& _location, + std::variant> _name + ): + ASTNode(_id, _location), + m_name(std::move(_name)) + { + if (Token const* token = std::get_if(&_name)) + solAssert(TokenTraits::isBuiltinTypeClassName(*token)); + } + + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + bool experimentalSolidityOnly() const override { return true; } + + std::variant> name() const { return m_name; } + +private: + std::variant> m_name; +}; + +class Builtin: public Expression +{ +public: + Builtin( + int64_t _id, + SourceLocation _location, + ASTPointer _nameParameter, + SourceLocation _nameParameterLocation + ): + Expression(_id, std::move(_location)), + m_nameParameter(std::move(_nameParameter)), + m_nameParameterLocation(std::move(_nameParameterLocation)) + { + solAssert(m_nameParameter); + } + + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + bool experimentalSolidityOnly() const override { return true; } + + ASTString const& nameParameter() const { return *m_nameParameter; } + SourceLocation const& nameParameterLocation() const { return m_nameParameterLocation; } + +private: + ASTPointer m_nameParameter; + SourceLocation m_nameParameterLocation; +}; + +/// @} + } diff --git a/compiler/libsolidity/ast/ASTAnnotations.cpp b/compiler/libsolidity/ast/ASTAnnotations.cpp index 3556041d..84d49fa2 100644 --- a/compiler/libsolidity/ast/ASTAnnotations.cpp +++ b/compiler/libsolidity/ast/ASTAnnotations.cpp @@ -23,7 +23,6 @@ #include -using namespace std; using namespace solidity; using namespace solidity::frontend; diff --git a/compiler/libsolidity/ast/ASTAnnotations.h b/compiler/libsolidity/ast/ASTAnnotations.h index 00fff99e..102c1e23 100644 --- a/compiler/libsolidity/ast/ASTAnnotations.h +++ b/compiler/libsolidity/ast/ASTAnnotations.h @@ -162,6 +162,9 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocu /// List of contracts whose bytecode is referenced by this contract, e.g. through "new". /// The Value represents the ast node that referenced the contract. std::map> contractDependencies; + + // Per-contract map from function AST IDs to internal dispatch function IDs. + std::map internalFunctionIDs; }; struct CallableDeclarationAnnotation: DeclarationAnnotation @@ -225,6 +228,7 @@ struct TryCatchClauseAnnotation: ASTAnnotation, ScopableAnnotation struct ForStatementAnnotation: StatementAnnotation, ScopableAnnotation { + util::SetOnce isSimpleCounterLoop; }; @@ -236,6 +240,8 @@ struct ReturnAnnotation: StatementAnnotation { /// Reference to the return parameters of the function. ParameterList const* functionReturnParameters = nullptr; + /// Reference to the function containing the return statement. + FunctionDefinition const* function = nullptr; }; struct TypeNameAnnotation: ASTAnnotation @@ -307,7 +313,12 @@ struct MemberAccessAnnotation: ExpressionAnnotation util::SetOnce requiredLookup; }; -struct BinaryOperationAnnotation: ExpressionAnnotation +struct OperationAnnotation: ExpressionAnnotation +{ + util::SetOnce userDefinedFunction; +}; + +struct BinaryOperationAnnotation: OperationAnnotation { /// The common type that is used for the operation, not necessarily the result type (which /// e.g. for comparisons is bool). @@ -328,4 +339,12 @@ struct FunctionCallAnnotation: ExpressionAnnotation bool tryCall = false; }; +/// Experimental Solidity annotations. +/// Used to integrate with name and type resolution. +/// @{ +struct TypeClassDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation +{ +}; +/// @} + } diff --git a/compiler/libsolidity/ast/ASTForward.h b/compiler/libsolidity/ast/ASTForward.h index c9826307..3f8afea1 100644 --- a/compiler/libsolidity/ast/ASTForward.h +++ b/compiler/libsolidity/ast/ASTForward.h @@ -68,7 +68,6 @@ class Mapping; class Optional; class TvmVector; class ArrayTypeName; -class InlineAssembly; class Statement; class Block; class PlaceholderStatement; @@ -101,6 +100,15 @@ class ElementaryTypeNameExpression; class Literal; class StructuredDocumentation; +/// Experimental Solidity nodes +/// @{ +class TypeClassDefinition; +class TypeClassInstantiation; +class TypeClassName; +class TypeDefinition; +class Builtin; +/// @} + class VariableScope; template diff --git a/compiler/libsolidity/ast/ASTJsonExporter.cpp b/compiler/libsolidity/ast/ASTJsonExporter.cpp index 64ad29af..cf5e1364 100644 --- a/compiler/libsolidity/ast/ASTJsonExporter.cpp +++ b/compiler/libsolidity/ast/ASTJsonExporter.cpp @@ -40,21 +40,21 @@ #include #include -using namespace std; +using namespace std::string_literals; using namespace solidity::langutil; namespace { template typename C> -void addIfSet(std::vector>& _attributes, string const& _name, C const& _value) +void addIfSet(std::vector>& _attributes, std::string const& _name, C const& _value) { if constexpr (std::is_same_v, solidity::util::SetOnce>) { if (!_value.set()) return; } - else if constexpr (std::is_same_v, optional>) + else if constexpr (std::is_same_v, std::optional>) { if (!_value.has_value()) return; @@ -68,7 +68,7 @@ void addIfSet(std::vector>& _attributes, string const& namespace solidity::frontend { -ASTJsonExporter::ASTJsonExporter(CompilerStack::State _stackState, map _sourceIndices): +ASTJsonExporter::ASTJsonExporter(CompilerStack::State _stackState, std::map _sourceIndices): m_stackState(_stackState), m_sourceIndices(std::move(_sourceIndices)) { @@ -77,21 +77,21 @@ ASTJsonExporter::ASTJsonExporter(CompilerStack::State _stackState, map>&& _attributes + std::string const& _nodeName, + std::initializer_list>&& _attributes ) { ASTJsonExporter::setJsonNode( _node, _nodeName, - std::vector>(std::move(_attributes)) + std::vector>(std::move(_attributes)) ); } void ASTJsonExporter::setJsonNode( ASTNode const& _node, - string const& _nodeType, - std::vector>&& _attributes + std::string const& _nodeType, + std::vector>&& _attributes ) { m_currentValue = Json::objectValue; @@ -105,24 +105,24 @@ void ASTJsonExporter::setJsonNode( m_currentValue[e.first] = std::move(e.second); } -optional ASTJsonExporter::sourceIndexFromLocation(SourceLocation const& _location) const +std::optional ASTJsonExporter::sourceIndexFromLocation(SourceLocation const& _location) const { if (_location.sourceName && m_sourceIndices.count(*_location.sourceName)) return m_sourceIndices.at(*_location.sourceName); else - return nullopt; + return std::nullopt; } -string ASTJsonExporter::sourceLocationToString(SourceLocation const& _location) const +std::string ASTJsonExporter::sourceLocationToString(SourceLocation const& _location) const { - optional sourceIndexOpt = sourceIndexFromLocation(_location); + std::optional sourceIndexOpt = sourceIndexFromLocation(_location); int length = -1; if (_location.start >= 0 && _location.end >= 0) length = _location.end - _location.start; - return to_string(_location.start) + ":" + to_string(length) + ":" + (sourceIndexOpt.has_value() ? to_string(sourceIndexOpt.value()) : "-1"); + return std::to_string(_location.start) + ":" + std::to_string(length) + ":" + (sourceIndexOpt.has_value() ? std::to_string(sourceIndexOpt.value()) : "-1"); } -Json::Value ASTJsonExporter::sourceLocationsToJson(vector const& _sourceLocations) const +Json::Value ASTJsonExporter::sourceLocationsToJson(std::vector const& _sourceLocations) const { Json::Value locations = Json::arrayValue; @@ -132,9 +132,9 @@ Json::Value ASTJsonExporter::sourceLocationsToJson(vector const& return locations; } -string ASTJsonExporter::namePathToString(std::vector const& _namePath) +std::string ASTJsonExporter::namePathToString(std::vector const& _namePath) { - return boost::algorithm::join(_namePath, "."); + return boost::algorithm::join(_namePath, "."s); } Json::Value ASTJsonExporter::typePointerToJson(Type const* _tp, bool _withoutDataLocation) @@ -159,13 +159,13 @@ Json::Value ASTJsonExporter::typePointerToJson(std::optional } void ASTJsonExporter::appendExpressionAttributes( - std::vector>& _attributes, + std::vector>& _attributes, ExpressionAnnotation const& _annotation ) { - std::vector> exprAttributes = { - make_pair("typeDescriptions", typePointerToJson(_annotation.type)), - make_pair("argumentTypes", typePointerToJson(_annotation.arguments)) + std::vector> exprAttributes = { + std::make_pair("typeDescriptions", typePointerToJson(_annotation.type)), + std::make_pair("argumentTypes", typePointerToJson(_annotation.arguments)) }; addIfSet(exprAttributes, "isLValue", _annotation.isLValue); @@ -178,7 +178,7 @@ void ASTJsonExporter::appendExpressionAttributes( _attributes += exprAttributes; } -void ASTJsonExporter::print(ostream& _stream, ASTNode const& _node, util::JsonFormat const& _format) +void ASTJsonExporter::print(std::ostream& _stream, ASTNode const& _node, util::JsonFormat const& _format) { _stream << util::jsonPrint(toJson(_node), _format); } @@ -191,11 +191,14 @@ Json::Value ASTJsonExporter::toJson(ASTNode const& _node) bool ASTJsonExporter::visit(SourceUnit const& _node) { - std::vector> attributes = { - make_pair("license", _node.licenseString() ? Json::Value(*_node.licenseString()) : Json::nullValue), - make_pair("nodes", toJson(_node.nodes())) + std::vector> attributes = { + std::make_pair("license", _node.licenseString() ? Json::Value(*_node.licenseString()) : Json::nullValue), + std::make_pair("nodes", toJson(_node.nodes())), }; + if (_node.experimentalSolidity()) + attributes.emplace_back("experimentalSolidity", Json::Value(_node.experimentalSolidity())); + if (_node.annotation().exportedSymbols.set()) { Json::Value exportedSymbols = Json::objectValue; @@ -222,17 +225,17 @@ bool ASTJsonExporter::visit(PragmaDirective const& _node) for (auto const& literal: _node.literals()) literals.append(literal); setJsonNode(_node, "PragmaDirective", { - make_pair("literals", std::move(literals)) + std::make_pair("literals", std::move(literals)) }); return false; } bool ASTJsonExporter::visit(ImportDirective const& _node) { - std::vector> attributes = { - make_pair("file", _node.path()), - make_pair("sourceUnit", idOrNull(_node.annotation().sourceUnit)), - make_pair("scope", idOrNull(_node.scope())) + std::vector> attributes = { + std::make_pair("file", _node.path()), + std::make_pair("sourceUnit", idOrNull(_node.annotation().sourceUnit)), + std::make_pair("scope", idOrNull(_node.scope())) }; addIfSet(attributes, "absolutePath", _node.annotation().absolutePath); @@ -257,17 +260,19 @@ bool ASTJsonExporter::visit(ImportDirective const& _node) bool ASTJsonExporter::visit(ContractDefinition const& _node) { - std::vector> attributes = { - make_pair("name", _node.name()), - make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), - make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), - make_pair("contractKind", contractKind(_node.contractKind())), - make_pair("abstract", _node.abstract()), - make_pair("baseContracts", toJson(_node.baseContracts())), - make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies | ranges::views::keys)), - make_pair("usedErrors", getContainerIds(_node.interfaceErrors(false))), - make_pair("nodes", toJson(_node.subNodes())), - make_pair("scope", idOrNull(_node.scope())) + std::vector> attributes = { + std::make_pair("name", _node.name()), + std::make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), + std::make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), + std::make_pair("contractKind", contractKind(_node.contractKind())), + std::make_pair("abstract", _node.abstract()), + std::make_pair("baseContracts", toJson(_node.baseContracts())), + std::make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies | ranges::views::keys)), + // Do not require call graph because the AST is also created for incorrect sources. + std::make_pair("usedEvents", getContainerIds(_node.interfaceEvents(false))), + std::make_pair("usedErrors", getContainerIds(_node.interfaceErrors(false))), + std::make_pair("nodes", toJson(_node.subNodes())), + std::make_pair("scope", idOrNull(_node.scope())) }; addIfSet(attributes, "canonicalName", _node.annotation().canonicalName); @@ -276,6 +281,14 @@ bool ASTJsonExporter::visit(ContractDefinition const& _node) if (!_node.annotation().linearizedBaseContracts.empty()) attributes.emplace_back("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)); + if (!_node.annotation().internalFunctionIDs.empty()) + { + Json::Value internalFunctionIDs(Json::objectValue); + for (auto const& [functionDefinition, internalFunctionID]: _node.annotation().internalFunctionIDs) + internalFunctionIDs[std::to_string(functionDefinition->id())] = internalFunctionID; + attributes.emplace_back("internalFunctionIDs", std::move(internalFunctionIDs)); + } + setJsonNode(_node, "ContractDefinition", std::move(attributes)); return false; } @@ -288,9 +301,9 @@ bool ASTJsonExporter::visit(IdentifierPath const& _node) nameLocations.append(sourceLocationToString(location)); setJsonNode(_node, "IdentifierPath", { - make_pair("name", namePathToString(_node.path())), - make_pair("nameLocations", nameLocations), - make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)) + std::make_pair("name", namePathToString(_node.path())), + std::make_pair("nameLocations", nameLocations), + std::make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)) }); return false; } @@ -298,30 +311,42 @@ bool ASTJsonExporter::visit(IdentifierPath const& _node) bool ASTJsonExporter::visit(InheritanceSpecifier const& _node) { setJsonNode(_node, "InheritanceSpecifier", { - make_pair("baseName", toJson(_node.name())), - make_pair("arguments", _node.arguments() ? toJson(*_node.arguments()) : Json::nullValue) + std::make_pair("baseName", toJson(_node.name())), + std::make_pair("arguments", _node.arguments() ? toJson(*_node.arguments()) : Json::nullValue) }); return false; } bool ASTJsonExporter::visit(UsingForDirective const& _node) { - vector> attributes = { - make_pair("typeName", _node.typeName() ? toJson(*_node.typeName()) : Json::nullValue) + std::vector> attributes = { + std::make_pair("typeName", _node.typeName() ? toJson(*_node.typeName()) : Json::nullValue) }; + if (_node.usesBraces()) { Json::Value functionList; - for (auto const& function: _node.functionsOrLibrary()) + for (auto&& [function, op]: _node.functionsAndOperators()) { Json::Value functionNode; - functionNode["function"] = toJson(*function); + if (!op.has_value()) + functionNode["function"] = toJson(*function); + else + { + functionNode["definition"] = toJson(*function); + functionNode["operator"] = std::string(TokenTraits::toString(*op)); + } functionList.append(std::move(functionNode)); } attributes.emplace_back("functionList", std::move(functionList)); } else - attributes.emplace_back("libraryName", toJson(*_node.functionsOrLibrary().front())); + { + auto const& functionAndOperators = _node.functionsAndOperators(); + solAssert(_node.functionsAndOperators().size() == 1); + solAssert(!functionAndOperators.front().second.has_value()); + attributes.emplace_back("libraryName", toJson(*(functionAndOperators.front().first))); + } attributes.emplace_back("global", _node.global()); setJsonNode(_node, "UsingForDirective", std::move(attributes)); @@ -331,12 +356,13 @@ bool ASTJsonExporter::visit(UsingForDirective const& _node) bool ASTJsonExporter::visit(StructDefinition const& _node) { - std::vector> attributes = { - make_pair("name", _node.name()), - make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), - make_pair("visibility", Declaration::visibilityToString(_node.visibility())), - make_pair("members", toJson(_node.members())), - make_pair("scope", idOrNull(_node.scope())) + std::vector> attributes = { + std::make_pair("name", _node.name()), + std::make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), + std::make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), + std::make_pair("visibility", Declaration::visibilityToString(_node.visibility())), + std::make_pair("members", toJson(_node.members())), + std::make_pair("scope", idOrNull(_node.scope())) }; addIfSet(attributes,"canonicalName", _node.annotation().canonicalName); @@ -348,10 +374,11 @@ bool ASTJsonExporter::visit(StructDefinition const& _node) bool ASTJsonExporter::visit(EnumDefinition const& _node) { - std::vector> attributes = { - make_pair("name", _node.name()), - make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), - make_pair("members", toJson(_node.members())) + std::vector> attributes = { + std::make_pair("name", _node.name()), + std::make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), + std::make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), + std::make_pair("members", toJson(_node.members())) }; addIfSet(attributes,"canonicalName", _node.annotation().canonicalName); @@ -364,8 +391,8 @@ bool ASTJsonExporter::visit(EnumDefinition const& _node) bool ASTJsonExporter::visit(EnumValue const& _node) { setJsonNode(_node, "EnumValue", { - make_pair("name", _node.name()), - make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), + std::make_pair("name", _node.name()), + std::make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), }); return false; } @@ -373,10 +400,10 @@ bool ASTJsonExporter::visit(EnumValue const& _node) bool ASTJsonExporter::visit(UserDefinedValueTypeDefinition const& _node) { solAssert(_node.underlyingType(), ""); - std::vector> attributes = { - make_pair("name", _node.name()), - make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), - make_pair("underlyingType", toJson(*_node.underlyingType())) + std::vector> attributes = { + std::make_pair("name", _node.name()), + std::make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), + std::make_pair("underlyingType", toJson(*_node.underlyingType())) }; addIfSet(attributes, "canonicalName", _node.annotation().canonicalName); @@ -388,7 +415,7 @@ bool ASTJsonExporter::visit(UserDefinedValueTypeDefinition const& _node) bool ASTJsonExporter::visit(ParameterList const& _node) { setJsonNode(_node, "ParameterList", { - make_pair("parameters", toJson(_node.parameters())) + std::make_pair("parameters", toJson(_node.parameters())) }); return false; } @@ -396,30 +423,30 @@ bool ASTJsonExporter::visit(ParameterList const& _node) bool ASTJsonExporter::visit(OverrideSpecifier const& _node) { setJsonNode(_node, "OverrideSpecifier", { - make_pair("overrides", toJson(_node.overrides())) + std::make_pair("overrides", toJson(_node.overrides())) }); return false; } bool ASTJsonExporter::visit(FunctionDefinition const& _node) { - std::vector> attributes = { - make_pair("name", _node.name()), - make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), - make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), - make_pair("kind", _node.isFree() ? "freeFunction" : TokenTraits::toString(_node.kind())), - make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())), - make_pair("virtual", _node.markedVirtual()), - make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue), - make_pair("parameters", toJson(_node.parameterList())), - make_pair("returnParameters", toJson(*_node.returnParameterList())), - make_pair("modifiers", toJson(_node.modifiers())), - make_pair("body", _node.isImplemented() ? toJson(_node.body()) : Json::nullValue), - make_pair("implemented", _node.isImplemented()), - make_pair("scope", idOrNull(_node.scope())) + std::vector> attributes = { + std::make_pair("name", _node.name()), + std::make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), + std::make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), + std::make_pair("kind", _node.isFree() ? "freeFunction" : TokenTraits::toString(_node.kind())), + std::make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())), + std::make_pair("virtual", _node.markedVirtual()), + std::make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue), + std::make_pair("parameters", toJson(_node.parameterList())), + std::make_pair("returnParameters", toJson(*_node.returnParameterList())), + std::make_pair("modifiers", toJson(_node.modifiers())), + std::make_pair("body", _node.isImplemented() ? toJson(_node.body()) : Json::nullValue), + std::make_pair("implemented", _node.isImplemented()), + std::make_pair("scope", idOrNull(_node.scope())) }; - optional visibility; + std::optional visibility; if (_node.isConstructor()) { if (_node.annotation().contract) @@ -432,25 +459,26 @@ bool ASTJsonExporter::visit(FunctionDefinition const& _node) attributes.emplace_back("visibility", Declaration::visibilityToString(*visibility)); if (!_node.annotation().baseFunctions.empty()) - attributes.emplace_back(make_pair("baseFunctions", getContainerIds(_node.annotation().baseFunctions, true))); + attributes.emplace_back(std::make_pair("baseFunctions", getContainerIds(_node.annotation().baseFunctions, true))); + setJsonNode(_node, "FunctionDefinition", std::move(attributes)); return false; } bool ASTJsonExporter::visit(VariableDeclaration const& _node) { - std::vector> attributes = { - make_pair("name", _node.name()), - make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), - make_pair("typeName", toJson(_node.typeName())), - make_pair("constant", _node.isConstant()), - make_pair("mutability", VariableDeclaration::mutabilityToString(_node.mutability())), - make_pair("stateVariable", _node.isStateVariable()), - make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue), - make_pair("visibility", Declaration::visibilityToString(_node.visibility())), - make_pair("value", _node.value() ? toJson(*_node.value()) : Json::nullValue), - make_pair("scope", idOrNull(_node.scope())), - make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) + std::vector> attributes = { + std::make_pair("name", _node.name()), + std::make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), + std::make_pair("typeName", toJson(_node.typeName())), + std::make_pair("constant", _node.isConstant()), + std::make_pair("mutability", VariableDeclaration::mutabilityToString(_node.mutability())), + std::make_pair("stateVariable", _node.isStateVariable()), + std::make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue), + std::make_pair("visibility", Declaration::visibilityToString(_node.visibility())), + std::make_pair("value", _node.value() ? toJson(*_node.value()) : Json::nullValue), + std::make_pair("scope", idOrNull(_node.scope())), + std::make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) }; if (_node.isStateVariable() && _node.isPublic()) attributes.emplace_back("functionSelector", _node.externalIdentifierHex()); @@ -459,34 +487,34 @@ bool ASTJsonExporter::visit(VariableDeclaration const& _node) if (m_inEvent) attributes.emplace_back("indexed", _node.isIndexed()); if (!_node.annotation().baseFunctions.empty()) - attributes.emplace_back(make_pair("baseFunctions", getContainerIds(_node.annotation().baseFunctions, true))); + attributes.emplace_back(std::make_pair("baseFunctions", getContainerIds(_node.annotation().baseFunctions, true))); setJsonNode(_node, "VariableDeclaration", std::move(attributes)); return false; } bool ASTJsonExporter::visit(ModifierDefinition const& _node) { - std::vector> attributes = { - make_pair("name", _node.name()), - make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), - make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), - make_pair("visibility", Declaration::visibilityToString(_node.visibility())), - make_pair("parameters", toJson(_node.parameterList())), - make_pair("virtual", _node.markedVirtual()), - make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue), - make_pair("body", _node.isImplemented() ? toJson(_node.body()) : Json::nullValue) + std::vector> attributes = { + std::make_pair("name", _node.name()), + std::make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), + std::make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), + std::make_pair("visibility", Declaration::visibilityToString(_node.visibility())), + std::make_pair("parameters", toJson(_node.parameterList())), + std::make_pair("virtual", _node.markedVirtual()), + std::make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue), + std::make_pair("body", _node.isImplemented() ? toJson(_node.body()) : Json::nullValue) }; if (!_node.annotation().baseFunctions.empty()) - attributes.emplace_back(make_pair("baseModifiers", getContainerIds(_node.annotation().baseFunctions, true))); + attributes.emplace_back(std::make_pair("baseModifiers", getContainerIds(_node.annotation().baseFunctions, true))); setJsonNode(_node, "ModifierDefinition", std::move(attributes)); return false; } bool ASTJsonExporter::visit(ModifierInvocation const& _node) { - std::vector> attributes{ - make_pair("modifierName", toJson(_node.name())), - make_pair("arguments", _node.arguments() ? toJson(*_node.arguments()) : Json::nullValue) + std::vector> attributes{ + std::make_pair("modifierName", toJson(_node.name())), + std::make_pair("arguments", _node.arguments() ? toJson(*_node.arguments()) : Json::nullValue) }; if (Declaration const* declaration = _node.name().annotation().referencedDeclaration) { @@ -502,16 +530,16 @@ bool ASTJsonExporter::visit(ModifierInvocation const& _node) bool ASTJsonExporter::visit(EventDefinition const& _node) { m_inEvent = true; - std::vector> _attributes = { - make_pair("name", _node.name()), - make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), - make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), - make_pair("parameters", toJson(_node.parameterList())), - make_pair("anonymous", _node.isAnonymous()) + std::vector> _attributes = { + std::make_pair("name", _node.name()), + std::make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), + std::make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), + std::make_pair("parameters", toJson(_node.parameterList())), + std::make_pair("anonymous", _node.isAnonymous()) }; - if (m_stackState >= CompilerStack::State::AnalysisPerformed) + if (m_stackState >= CompilerStack::State::AnalysisSuccessful) _attributes.emplace_back( - make_pair( + std::make_pair( "eventSelector", toHex(u256(util::h256::Arith(util::keccak256(_node.functionType(true)->externalSignature())))) )); @@ -522,14 +550,14 @@ bool ASTJsonExporter::visit(EventDefinition const& _node) bool ASTJsonExporter::visit(ErrorDefinition const& _node) { - std::vector> _attributes = { - make_pair("name", _node.name()), - make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), - make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), - make_pair("parameters", toJson(_node.parameterList())) + std::vector> _attributes = { + std::make_pair("name", _node.name()), + std::make_pair("nameLocation", sourceLocationToString(_node.nameLocation())), + std::make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), + std::make_pair("parameters", toJson(_node.parameterList())) }; - if (m_stackState >= CompilerStack::State::AnalysisPerformed) - _attributes.emplace_back(make_pair("errorSelector", _node.functionType(true)->externalIdentifierHex())); + if (m_stackState >= CompilerStack::State::AnalysisSuccessful) + _attributes.emplace_back(std::make_pair("errorSelector", _node.functionType(true)->externalIdentifierHex())); setJsonNode(_node, "ErrorDefinition", std::move(_attributes)); return false; @@ -537,10 +565,11 @@ bool ASTJsonExporter::visit(ErrorDefinition const& _node) bool ASTJsonExporter::visit(ElementaryTypeName const& _node) { - std::vector> attributes = { - make_pair("name", _node.typeName().toString()), - make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) + std::vector> attributes = { + std::make_pair("name", _node.typeName().toString()), + std::make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) }; + setJsonNode(_node, "ElementaryTypeName", std::move(attributes)); return false; } @@ -548,9 +577,9 @@ bool ASTJsonExporter::visit(ElementaryTypeName const& _node) bool ASTJsonExporter::visit(UserDefinedTypeName const& _node) { setJsonNode(_node, "UserDefinedTypeName", { - make_pair("pathNode", toJson(_node.pathNode())), - make_pair("referencedDeclaration", idOrNull(_node.pathNode().annotation().referencedDeclaration)), - make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) + std::make_pair("pathNode", toJson(_node.pathNode())), + std::make_pair("referencedDeclaration", idOrNull(_node.pathNode().annotation().referencedDeclaration)), + std::make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) }); return false; } @@ -558,11 +587,11 @@ bool ASTJsonExporter::visit(UserDefinedTypeName const& _node) bool ASTJsonExporter::visit(FunctionTypeName const& _node) { setJsonNode(_node, "FunctionTypeName", { - make_pair("visibility", Declaration::visibilityToString(_node.visibility())), - make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())), - make_pair("parameterTypes", toJson(*_node.parameterTypeList())), - make_pair("returnParameterTypes", toJson(*_node.returnParameterTypeList())), - make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) + std::make_pair("visibility", Declaration::visibilityToString(_node.visibility())), + std::make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())), + std::make_pair("parameterTypes", toJson(*_node.parameterTypeList())), + std::make_pair("returnParameterTypes", toJson(*_node.returnParameterTypeList())), + std::make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) }); return false; } @@ -570,23 +599,27 @@ bool ASTJsonExporter::visit(FunctionTypeName const& _node) bool ASTJsonExporter::visit(Mapping const& _node) { setJsonNode(_node, "Mapping", { - make_pair("keyType", toJson(_node.keyType())), - make_pair("valueType", toJson(_node.valueType())), - make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) + std::make_pair("keyType", toJson(_node.keyType())), + std::make_pair("keyName", _node.keyName()), + std::make_pair("keyNameLocation", sourceLocationToString(_node.keyNameLocation())), + std::make_pair("valueType", toJson(_node.valueType())), + std::make_pair("valueName", _node.valueName()), + std::make_pair("valueNameLocation", sourceLocationToString(_node.valueNameLocation())), + std::make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) }); return false; } bool ASTJsonExporter::visit(Optional const& _node) { setJsonNode(_node, "Optional", { - make_pair("maybeTypes", toJson(_node.maybeTypes())), + std::make_pair("maybeTypes", toJson(_node.maybeTypes())), }); return false; } bool ASTJsonExporter::visit(TvmVector const& _node) { setJsonNode(_node, "vector", { - make_pair("type", toJson(_node.type())), + std::make_pair("type", toJson(_node.type())), }); return false; } @@ -594,9 +627,9 @@ bool ASTJsonExporter::visit(TvmVector const& _node) { bool ASTJsonExporter::visit(ArrayTypeName const& _node) { setJsonNode(_node, "ArrayTypeName", { - make_pair("baseType", toJson(_node.baseType())), - make_pair("length", toJsonOrNull(_node.length())), - make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) + std::make_pair("baseType", toJson(_node.baseType())), + std::make_pair("length", toJsonOrNull(_node.length())), + std::make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) }); return false; } @@ -604,20 +637,15 @@ bool ASTJsonExporter::visit(ArrayTypeName const& _node) bool ASTJsonExporter::visit(FreeInlineAssembly const& _node) { setJsonNode(_node, "FreeInlineAssembly", { - make_pair("body", toJson(_node.lines())) + std::make_pair("body", toJson(_node.lines())) }); return false; } -bool ASTJsonExporter::visit(InlineAssembly const& /*_node*/) -{ - return false; -} - bool ASTJsonExporter::visit(Block const& _node) { setJsonNode(_node, _node.unchecked() ? "UncheckedBlock" : "Block", { - make_pair("statements", toJson(_node.statements())) + std::make_pair("statements", toJson(_node.statements())) }); return false; } @@ -631,9 +659,9 @@ bool ASTJsonExporter::visit(PlaceholderStatement const& _node) bool ASTJsonExporter::visit(IfStatement const& _node) { setJsonNode(_node, "IfStatement", { - make_pair("condition", toJson(_node.condition())), - make_pair("trueBody", toJson(_node.trueStatement())), - make_pair("falseBody", toJsonOrNull(_node.falseStatement())) + std::make_pair("condition", toJson(_node.condition())), + std::make_pair("trueBody", toJson(_node.trueStatement())), + std::make_pair("falseBody", toJsonOrNull(_node.falseStatement())) }); return false; } @@ -641,9 +669,9 @@ bool ASTJsonExporter::visit(IfStatement const& _node) bool ASTJsonExporter::visit(TryCatchClause const& _node) { setJsonNode(_node, "TryCatchClause", { - make_pair("errorName", _node.errorName()), - make_pair("parameters", toJsonOrNull(_node.parameters())), - make_pair("block", toJson(_node.block())) + std::make_pair("errorName", _node.errorName()), + std::make_pair("parameters", toJsonOrNull(_node.parameters())), + std::make_pair("block", toJson(_node.block())) }); return false; } @@ -651,8 +679,8 @@ bool ASTJsonExporter::visit(TryCatchClause const& _node) bool ASTJsonExporter::visit(TryStatement const& _node) { setJsonNode(_node, "TryStatement", { - make_pair("body", toJson(_node.body())), - make_pair("clause", toJson(_node.clause())) + std::make_pair("body", toJson(_node.body())), + std::make_pair("clause", toJson(_node.clause())) }); return false; } @@ -675,8 +703,8 @@ bool ASTJsonExporter::visit(WhileStatement const& _node) _node, name, { - make_pair("condition", toJson(_node.condition())), - make_pair("body", toJson(_node.body())) + std::make_pair("condition", toJson(_node.condition())), + std::make_pair("body", toJson(_node.body())) } ); return false; @@ -684,20 +712,26 @@ bool ASTJsonExporter::visit(WhileStatement const& _node) bool ASTJsonExporter::visit(ForStatement const& _node) { - setJsonNode(_node, "ForStatement", { - make_pair("initializationExpression", toJsonOrNull(_node.initializationExpression())), - make_pair("condition", toJsonOrNull(_node.condition())), - make_pair("loopExpression", toJsonOrNull(_node.loopExpression())), - make_pair("body", toJson(_node.body())) - }); + + std::vector> attributes = { + std::make_pair("initializationExpression", toJsonOrNull(_node.initializationExpression())), + std::make_pair("condition", toJsonOrNull(_node.condition())), + std::make_pair("loopExpression", toJsonOrNull(_node.loopExpression())), + std::make_pair("body", toJson(_node.body())) + }; + + if (_node.annotation().isSimpleCounterLoop.set()) + attributes.emplace_back("isSimpleCounterLoop", *_node.annotation().isSimpleCounterLoop); + + setJsonNode(_node, "ForStatement", std::move(attributes)); return false; } bool ASTJsonExporter::visit(ForEachStatement const& _node) { setJsonNode(_node, "ForEachStatement", { - make_pair("rangeDeclaration", toJsonOrNull(_node.rangeDeclaration())), - make_pair("rangeExpression", toJsonOrNull(_node.rangeExpression())), - make_pair("body", toJson(_node.body())) + std::make_pair("rangeDeclaration", toJsonOrNull(_node.rangeDeclaration())), + std::make_pair("rangeExpression", toJsonOrNull(_node.rangeExpression())), + std::make_pair("body", toJson(_node.body())) }); return false; } @@ -717,9 +751,9 @@ bool ASTJsonExporter::visit(Break const& _node) bool ASTJsonExporter::visit(Return const& _node) { setJsonNode(_node, "Return", { - make_pair("expression", toJsonOrNull(_node.expression())), - make_pair("functionReturnParameters", idOrNull(_node.annotation().functionReturnParameters)), - make_pair("options", toJson(_node.options())), + std::make_pair("expression", toJsonOrNull(_node.expression())), + std::make_pair("functionReturnParameters", idOrNull(_node.annotation().functionReturnParameters)), + std::make_pair("options", toJson(_node.options())), }); return false; } @@ -736,10 +770,10 @@ bool ASTJsonExporter::visit(EmitStatement const& _node) for (auto const& name: _node.names()) names.append(Json::Value(*name)); - std::vector> attributes = { - make_pair("names", std::move(names)), - make_pair("options", toJson(_node.options())), - make_pair("eventCall", toJson(_node.eventCall())) + std::vector> attributes = { + std::make_pair("names", std::move(names)), + std::make_pair("options", toJson(_node.options())), + std::make_pair("eventCall", toJson(_node.eventCall())) }; setJsonNode(_node, "EmitStatement", std::move(attributes)); @@ -749,7 +783,7 @@ bool ASTJsonExporter::visit(EmitStatement const& _node) bool ASTJsonExporter::visit(RevertStatement const& _node) { setJsonNode(_node, "RevertStatement", { - make_pair("errorCall", toJson(_node.errorCall())) + std::make_pair("errorCall", toJson(_node.errorCall())) }); return false; } @@ -760,9 +794,9 @@ bool ASTJsonExporter::visit(VariableDeclarationStatement const& _node) for (auto const& v: _node.declarations()) appendMove(varDecs, idOrNull(v.get())); setJsonNode(_node, "VariableDeclarationStatement", { - make_pair("assignments", std::move(varDecs)), - make_pair("declarations", toJson(_node.declarations())), - make_pair("initialValue", toJsonOrNull(_node.initialValue())) + std::make_pair("assignments", std::move(varDecs)), + std::make_pair("declarations", toJson(_node.declarations())), + std::make_pair("initialValue", toJsonOrNull(_node.initialValue())) }); return false; } @@ -770,17 +804,17 @@ bool ASTJsonExporter::visit(VariableDeclarationStatement const& _node) bool ASTJsonExporter::visit(ExpressionStatement const& _node) { setJsonNode(_node, "ExpressionStatement", { - make_pair("expression", toJson(_node.expression())) + std::make_pair("expression", toJson(_node.expression())) }); return false; } bool ASTJsonExporter::visit(Conditional const& _node) { - std::vector> attributes = { - make_pair("condition", toJson(_node.condition())), - make_pair("trueExpression", toJson(_node.trueExpression())), - make_pair("falseExpression", toJson(_node.falseExpression())) + std::vector> attributes = { + std::make_pair("condition", toJson(_node.condition())), + std::make_pair("trueExpression", toJson(_node.trueExpression())), + std::make_pair("falseExpression", toJson(_node.falseExpression())) }; appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "Conditional", std::move(attributes)); @@ -789,10 +823,10 @@ bool ASTJsonExporter::visit(Conditional const& _node) bool ASTJsonExporter::visit(Assignment const& _node) { - std::vector> attributes = { - make_pair("operator", TokenTraits::toString(_node.assignmentOperator())), - make_pair("leftHandSide", toJson(_node.leftHandSide())), - make_pair("rightHandSide", toJson(_node.rightHandSide())) + std::vector> attributes = { + std::make_pair("operator", TokenTraits::toString(_node.assignmentOperator())), + std::make_pair("leftHandSide", toJson(_node.leftHandSide())), + std::make_pair("rightHandSide", toJson(_node.rightHandSide())) }; appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "Assignment", std::move(attributes)); @@ -801,9 +835,9 @@ bool ASTJsonExporter::visit(Assignment const& _node) bool ASTJsonExporter::visit(TupleExpression const& _node) { - std::vector> attributes = { - make_pair("isInlineArray", Json::Value(_node.isInlineArray())), - make_pair("components", toJson(_node.components())), + std::vector> attributes = { + std::make_pair("isInlineArray", Json::Value(_node.isInlineArray())), + std::make_pair("components", toJson(_node.components())), }; appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "TupleExpression", std::move(attributes)); @@ -812,11 +846,14 @@ bool ASTJsonExporter::visit(TupleExpression const& _node) bool ASTJsonExporter::visit(UnaryOperation const& _node) { - std::vector> attributes = { - make_pair("prefix", _node.isPrefixOperation()), - make_pair("operator", TokenTraits::toString(_node.getOperator())), - make_pair("subExpression", toJson(_node.subExpression())) + std::vector> attributes = { + std::make_pair("prefix", _node.isPrefixOperation()), + std::make_pair("operator", TokenTraits::toString(_node.getOperator())), + std::make_pair("subExpression", toJson(_node.subExpression())) }; + // NOTE: This annotation is guaranteed to be set but only if we didn't stop at the parsing stage. + if (_node.annotation().userDefinedFunction.set() && *_node.annotation().userDefinedFunction != nullptr) + attributes.emplace_back("function", nodeId(**_node.annotation().userDefinedFunction)); appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "UnaryOperation", std::move(attributes)); return false; @@ -824,12 +861,15 @@ bool ASTJsonExporter::visit(UnaryOperation const& _node) bool ASTJsonExporter::visit(BinaryOperation const& _node) { - std::vector> attributes = { - make_pair("operator", TokenTraits::toString(_node.getOperator())), - make_pair("leftExpression", toJson(_node.leftExpression())), - make_pair("rightExpression", toJson(_node.rightExpression())), - make_pair("commonType", typePointerToJson(_node.annotation().commonType)), + std::vector> attributes = { + std::make_pair("operator", TokenTraits::toString(_node.getOperator())), + std::make_pair("leftExpression", toJson(_node.leftExpression())), + std::make_pair("rightExpression", toJson(_node.rightExpression())), + std::make_pair("commonType", typePointerToJson(_node.annotation().commonType)), }; + // NOTE: This annotation is guaranteed to be set but only if we didn't stop at the parsing stage. + if (_node.annotation().userDefinedFunction.set() && *_node.annotation().userDefinedFunction != nullptr) + attributes.emplace_back("function", nodeId(**_node.annotation().userDefinedFunction)); appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "BinaryOperation", std::move(attributes)); return false; @@ -840,12 +880,12 @@ bool ASTJsonExporter::visit(FunctionCall const& _node) Json::Value names(Json::arrayValue); for (auto const& name: _node.names()) names.append(Json::Value(*name)); - std::vector> attributes = { - make_pair("expression", toJson(_node.expression())), - make_pair("names", std::move(names)), - make_pair("nameLocations", sourceLocationsToJson(_node.nameLocations())), - make_pair("arguments", toJson(_node.arguments())), - make_pair("tryCall", _node.annotation().tryCall) + std::vector> attributes = { + std::make_pair("expression", toJson(_node.expression())), + std::make_pair("names", std::move(names)), + std::make_pair("nameLocations", sourceLocationsToJson(_node.nameLocations())), + std::make_pair("arguments", toJson(_node.arguments())), + std::make_pair("tryCall", _node.annotation().tryCall) }; if (_node.annotation().kind.set()) @@ -865,10 +905,10 @@ bool ASTJsonExporter::visit(FunctionCallOptions const& _node) for (auto const& name: _node.names()) names.append(Json::Value(*name)); - std::vector> attributes = { - make_pair("expression", toJson(_node.expression())), - make_pair("names", std::move(names)), - make_pair("options", toJson(_node.options())), + std::vector> attributes = { + std::make_pair("expression", toJson(_node.expression())), + std::make_pair("names", std::move(names)), + std::make_pair("options", toJson(_node.options())), }; appendExpressionAttributes(attributes, _node.annotation()); @@ -882,9 +922,9 @@ bool ASTJsonExporter::visit(InitializerList const& _node) for (auto const& name: _node.names()) names.append(Json::Value(*name)); - std::vector> attributes = { - make_pair("names", std::move(names)), - make_pair("options", toJson(_node.options())), + std::vector> attributes = { + std::make_pair("names", std::move(names)), + std::make_pair("options", toJson(_node.options())), }; appendExpressionAttributes(attributes, _node.annotation()); @@ -894,9 +934,9 @@ bool ASTJsonExporter::visit(InitializerList const& _node) bool ASTJsonExporter::visit(CallList const& _node) { - std::vector> attributes = { - make_pair("expression", toJson(*_node.function())), - make_pair("arguments", toJson(_node.arguments())), + std::vector> attributes = { + std::make_pair("expression", toJson(*_node.function())), + std::make_pair("arguments", toJson(_node.arguments())), }; appendExpressionAttributes(attributes, _node.annotation()); @@ -906,8 +946,8 @@ bool ASTJsonExporter::visit(CallList const& _node) bool ASTJsonExporter::visit(NewExpression const& _node) { - std::vector> attributes = { - make_pair("typeName", toJson(_node.typeName())) + std::vector> attributes = { + std::make_pair("typeName", toJson(_node.typeName())) }; appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "NewExpression", std::move(attributes)); @@ -916,11 +956,11 @@ bool ASTJsonExporter::visit(NewExpression const& _node) bool ASTJsonExporter::visit(MemberAccess const& _node) { - std::vector> attributes = { - make_pair("memberName", _node.memberName()), - make_pair("memberLocation", Json::Value(sourceLocationToString(_node.memberLocation()))), - make_pair("expression", toJson(_node.expression())), - make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)), + std::vector> attributes = { + std::make_pair("memberName", _node.memberName()), + std::make_pair("memberLocation", Json::Value(sourceLocationToString(_node.memberLocation()))), + std::make_pair("expression", toJson(_node.expression())), + std::make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)), }; appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "MemberAccess", std::move(attributes)); @@ -929,9 +969,9 @@ bool ASTJsonExporter::visit(MemberAccess const& _node) bool ASTJsonExporter::visit(IndexAccess const& _node) { - std::vector> attributes = { - make_pair("baseExpression", toJson(_node.baseExpression())), - make_pair("indexExpression", toJsonOrNull(_node.indexExpression())), + std::vector> attributes = { + std::make_pair("baseExpression", toJson(_node.baseExpression())), + std::make_pair("indexExpression", toJsonOrNull(_node.indexExpression())), }; appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "IndexAccess", std::move(attributes)); @@ -940,10 +980,10 @@ bool ASTJsonExporter::visit(IndexAccess const& _node) bool ASTJsonExporter::visit(IndexRangeAccess const& _node) { - std::vector> attributes = { - make_pair("baseExpression", toJson(_node.baseExpression())), - make_pair("startExpression", toJsonOrNull(_node.startExpression())), - make_pair("endExpression", toJsonOrNull(_node.endExpression())), + std::vector> attributes = { + std::make_pair("baseExpression", toJson(_node.baseExpression())), + std::make_pair("startExpression", toJsonOrNull(_node.startExpression())), + std::make_pair("endExpression", toJsonOrNull(_node.endExpression())), }; appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "IndexRangeAccess", std::move(attributes)); @@ -956,19 +996,19 @@ bool ASTJsonExporter::visit(Identifier const& _node) for (auto const& dec: _node.annotation().overloadedDeclarations) overloads.append(nodeId(*dec)); setJsonNode(_node, "Identifier", { - make_pair("name", _node.name()), - make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)), - make_pair("overloadedDeclarations", overloads), - make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)), - make_pair("argumentTypes", typePointerToJson(_node.annotation().arguments)) + std::make_pair("name", _node.name()), + std::make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)), + std::make_pair("overloadedDeclarations", overloads), + std::make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)), + std::make_pair("argumentTypes", typePointerToJson(_node.annotation().arguments)) }); return false; } bool ASTJsonExporter::visit(ElementaryTypeNameExpression const& _node) { - std::vector> attributes = { - make_pair("typeName", toJson(_node.type())) + std::vector> attributes = { + std::make_pair("typeName", toJson(_node.type())) }; appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "ElementaryTypeNameExpression", std::move(attributes)); @@ -977,8 +1017,8 @@ bool ASTJsonExporter::visit(ElementaryTypeNameExpression const& _node) bool ASTJsonExporter::visit(MappingNameExpression const& _node) { - std::vector> attributes = { - make_pair("typeName", toJson(_node.type())) + std::vector> attributes = { + std::make_pair("typeName", toJson(_node.type())) }; appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "MappingNameExpression", std::move(attributes)); @@ -987,8 +1027,8 @@ bool ASTJsonExporter::visit(MappingNameExpression const& _node) bool ASTJsonExporter::visit(OptionalNameExpression const& _node) { - std::vector> attributes = { - make_pair("typeName", toJson(_node.type())) + std::vector> attributes = { + std::make_pair("typeName", toJson(_node.type())) }; appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "OptionalNameExpression", std::move(attributes)); @@ -1001,11 +1041,11 @@ bool ASTJsonExporter::visit(Literal const& _node) if (!util::validateUTF8(_node.value())) value = Json::nullValue; Token subdenomination = Token(_node.subDenomination()); - std::vector> attributes = { - make_pair("kind", literalTokenKind(_node.token())), - make_pair("value", value), - make_pair("hexValue", util::toHex(util::asBytes(_node.value()))), - make_pair( + std::vector> attributes = { + std::make_pair("kind", literalTokenKind(_node.token())), + std::make_pair("value", value), + std::make_pair("hexValue", util::toHex(util::asBytes(_node.value()))), + std::make_pair( "subdenomination", subdenomination == Token::Illegal ? Json::nullValue : @@ -1020,8 +1060,8 @@ bool ASTJsonExporter::visit(Literal const& _node) bool ASTJsonExporter::visit(StructuredDocumentation const& _node) { Json::Value text{*_node.text()}; - std::vector> attributes = { - make_pair("text", text) + std::vector> attributes = { + std::make_pair("text", text) }; setJsonNode(_node, "StructuredDocumentation", std::move(attributes)); return false; @@ -1032,7 +1072,16 @@ void ASTJsonExporter::endVisit(EventDefinition const&) m_inEvent = false; } -string ASTJsonExporter::contractKind(ContractKind _kind) +bool ASTJsonExporter::visitNode(ASTNode const& _node) +{ + solAssert(false, _node.experimentalSolidityOnly() ? + "Attempt to export an AST of experimental solidity." : + "Attempt to export an AST that contains unexpected nodes." + ); + return false; +} + +std::string ASTJsonExporter::contractKind(ContractKind _kind) { switch (_kind) { @@ -1048,7 +1097,7 @@ string ASTJsonExporter::contractKind(ContractKind _kind) return {}; } -string ASTJsonExporter::functionCallKind(FunctionCallKind _kind) +std::string ASTJsonExporter::functionCallKind(FunctionCallKind _kind) { switch (_kind) { @@ -1063,7 +1112,7 @@ string ASTJsonExporter::functionCallKind(FunctionCallKind _kind) } } -string ASTJsonExporter::literalTokenKind(Token _token) +std::string ASTJsonExporter::literalTokenKind(Token _token) { switch (_token) { @@ -1087,12 +1136,12 @@ string ASTJsonExporter::literalTokenKind(Token _token) } } -string ASTJsonExporter::type(Expression const& _expression) +std::string ASTJsonExporter::type(Expression const& _expression) { return _expression.annotation().type ? _expression.annotation().type->toString() : "Unknown"; } -string ASTJsonExporter::type(VariableDeclaration const& _varDecl) +std::string ASTJsonExporter::type(VariableDeclaration const& _varDecl) { return _varDecl.annotation().type ? _varDecl.annotation().type->toString() : "Unknown"; } diff --git a/compiler/libsolidity/ast/ASTJsonExporter.h b/compiler/libsolidity/ast/ASTJsonExporter.h index 9b0f7085..151b2e12 100644 --- a/compiler/libsolidity/ast/ASTJsonExporter.h +++ b/compiler/libsolidity/ast/ASTJsonExporter.h @@ -99,7 +99,6 @@ class ASTJsonExporter: public ASTConstVisitor bool visit(TvmVector const& _node) override; bool visit(ArrayTypeName const& _node) override; bool visit(FreeInlineAssembly const& _node) override; - bool visit(InlineAssembly const& _node) override; bool visit(Block const& _node) override; bool visit(PlaceholderStatement const& _node) override; bool visit(IfStatement const& _node) override; @@ -139,7 +138,7 @@ class ASTJsonExporter: public ASTConstVisitor void endVisit(EventDefinition const&) override; protected: - bool visitNode(ASTNode const&) override { solUnimplemented(""); } + bool visitNode(ASTNode const&) override; private: void setJsonNode( diff --git a/compiler/libsolidity/ast/ASTJsonImporter.cpp b/compiler/libsolidity/ast/ASTJsonImporter.cpp index f618005f..8a56e357 100644 --- a/compiler/libsolidity/ast/ASTJsonImporter.cpp +++ b/compiler/libsolidity/ast/ASTJsonImporter.cpp @@ -22,6 +22,9 @@ */ #include + +#include + #include #include #include @@ -30,8 +33,6 @@ #include #include -using namespace std; - namespace solidity::frontend { @@ -43,16 +44,16 @@ ASTPointer ASTJsonImporter::nullOrCast(Json::Value const& _json) if (_json.isNull()) return nullptr; else - return dynamic_pointer_cast(convertJsonToASTNode(_json)); + return std::dynamic_pointer_cast(convertJsonToASTNode(_json)); } // ============ public =========================== -map> ASTJsonImporter::jsonToSourceUnit(map const& _sourceList) +std::map> ASTJsonImporter::jsonToSourceUnit(std::map const& _sourceList) { for (auto const& src: _sourceList) - m_sourceNames.emplace_back(make_shared(src.first)); + m_sourceNames.emplace_back(std::make_shared(src.first)); for (auto const& srcPair: _sourceList) { astAssert(!srcPair.second.isNull()); @@ -74,7 +75,7 @@ ASTPointer ASTJsonImporter::createASTNode(Json::Value const& _node, Args&&... astAssert(m_usedIDs.insert(id).second, "Found duplicate node ID!"); - auto n = make_shared( + auto n = std::make_shared( id, createSourceLocation(_node), std::forward(_args)... @@ -89,9 +90,9 @@ SourceLocation const ASTJsonImporter::createSourceLocation(Json::Value const& _n return solidity::langutil::parseSourceLocation(_node["src"].asString(), m_sourceNames); } -optional> ASTJsonImporter::createSourceLocations(Json::Value const& _node) const +std::optional> ASTJsonImporter::createSourceLocations(Json::Value const& _node) const { - vector locations; + std::vector locations; if (_node.isMember("nameLocations") && _node["nameLocations"].isArray()) { @@ -100,7 +101,7 @@ optional> ASTJsonImporter::createSourceLocations(Json::Va return locations; } - return nullopt; + return std::nullopt; } SourceLocation ASTJsonImporter::createNameSourceLocation(Json::Value const& _node) @@ -110,10 +111,24 @@ SourceLocation ASTJsonImporter::createNameSourceLocation(Json::Value const& _nod return solidity::langutil::parseSourceLocation(_node["nameLocation"].asString(), m_sourceNames); } +SourceLocation ASTJsonImporter::createKeyNameSourceLocation(Json::Value const& _node) +{ + astAssert(member(_node, "keyNameLocation").isString(), "'keyNameLocation' must be a string"); + + return solidity::langutil::parseSourceLocation(_node["keyNameLocation"].asString(), m_sourceNames); +} + +SourceLocation ASTJsonImporter::createValueNameSourceLocation(Json::Value const& _node) +{ + astAssert(member(_node, "valueNameLocation").isString(), "'valueNameLocation' must be a string"); + + return solidity::langutil::parseSourceLocation(_node["valueNameLocation"].asString(), m_sourceNames); +} + template ASTPointer ASTJsonImporter::convertJsonToASTNode(Json::Value const& _node) { - ASTPointer ret = dynamic_pointer_cast(convertJsonToASTNode(_node)); + ASTPointer ret = std::dynamic_pointer_cast(convertJsonToASTNode(_node)); astAssert(ret, "cast of converted json-node must not be nullptr"); return ret; } @@ -122,7 +137,7 @@ ASTPointer ASTJsonImporter::convertJsonToASTNode(Json::Value const& _node) ASTPointer ASTJsonImporter::convertJsonToASTNode(Json::Value const& _json) { astAssert(_json["nodeType"].isString() && _json.isMember("id"), "JSON-Node needs to have 'nodeType' and 'id' fields."); - string nodeType = _json["nodeType"].asString(); + std::string nodeType = _json["nodeType"].asString(); if (nodeType == "PragmaDirective") return createPragmaDirective(_json); if (nodeType == "ImportDirective") @@ -169,8 +184,6 @@ ASTPointer ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js return createMapping(_json); if (nodeType == "ArrayTypeName") return createArrayTypeName(_json); - if (nodeType == "InlineAssembly") - return createInlineAssembly(_json); if (nodeType == "Block") return createBlock(_json, false); if (nodeType == "UncheckedBlock") @@ -246,28 +259,32 @@ ASTPointer ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js // ============ functions to instantiate the AST-Nodes from Json-Nodes ============== -ASTPointer ASTJsonImporter::createSourceUnit(Json::Value const& _node, string const& _srcName) +ASTPointer ASTJsonImporter::createSourceUnit(Json::Value const& _node, std::string const& _srcName) { - optional license; + std::optional license; if (_node.isMember("license") && !_node["license"].isNull()) license = _node["license"].asString(); - vector> nodes; + bool experimentalSolidity = false; + if (_node.isMember("experimentalSolidity") && !_node["experimentalSolidity"].isNull()) + experimentalSolidity = _node["experimentalSolidity"].asBool(); + + std::vector> nodes; for (auto& child: member(_node, "nodes")) nodes.emplace_back(convertJsonToASTNode(child)); - ASTPointer tmp = createASTNode(_node, license, nodes); + ASTPointer tmp = createASTNode(_node, license, nodes, experimentalSolidity); tmp->annotation().path = _srcName; return tmp; } ASTPointer ASTJsonImporter::createPragmaDirective(Json::Value const& _node) { - vector tokens; - vector literals; + std::vector tokens; + std::vector literals; for (auto const& lit: member(_node, "literals")) { - string l = lit.asString(); + std::string l = lit.asString(); literals.push_back(l); tokens.push_back(scanSingleToken(l)); } @@ -286,7 +303,7 @@ ASTPointer ASTJsonImporter::createImportDirective(Json::Value c symbolAliases.push_back({ createIdentifier(tuple["foreign"]), - tuple["local"].isNull() ? nullptr : make_shared(tuple["local"].asString()), + tuple["local"].isNull() ? nullptr : std::make_shared(tuple["local"].asString()), createSourceLocation(tuple["foreign"])} ); } @@ -320,7 +337,7 @@ ASTPointer ASTJsonImporter::createContractDefinition(Json::V return createASTNode( _node, - make_shared(_node["name"].asString()), + std::make_shared(_node["name"].asString()), createNameSourceLocation(_node), _node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")), baseContracts, @@ -334,13 +351,13 @@ ASTPointer ASTJsonImporter::createIdentifierPath(Json::Value con { astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); - vector namePath; - vector namePathLocations; - vector strs; - string nameString = member(_node, "name").asString(); + std::vector namePath; + std::vector namePathLocations; + std::vector strs; + std::string nameString = member(_node, "name").asString(); boost::algorithm::split(strs, nameString, boost::is_any_of(".")); astAssert(!strs.empty(), "Expected at least one element in IdentifierPath."); - for (string s: strs) + for (std::string s: strs) { astAssert(!s.empty(), "Expected non-empty string for IdentifierPath element."); namePath.emplace_back(s); @@ -372,22 +389,49 @@ ASTPointer ASTJsonImporter::createInheritanceSpecifier(Jso return createASTNode( _node, createIdentifierPath(member(_node, "baseName")), - member(_node, "arguments").isNull() ? nullptr : make_unique>>(arguments) + member(_node, "arguments").isNull() ? nullptr : std::make_unique>>(arguments) ); } ASTPointer ASTJsonImporter::createUsingForDirective(Json::Value const& _node) { - vector> functions; + std::vector> functions; + std::vector> operators; if (_node.isMember("libraryName")) + { + astAssert(!_node["libraryName"].isArray()); + astAssert(!_node["libraryName"]["operator"]); functions.emplace_back(createIdentifierPath(_node["libraryName"])); + operators.emplace_back(std::nullopt); + } else if (_node.isMember("functionList")) for (Json::Value const& function: _node["functionList"]) - functions.emplace_back(createIdentifierPath(function["function"])); + { + if (function.isMember("function")) + { + astAssert(!function.isMember("operator")); + astAssert(!function.isMember("definition")); + + functions.emplace_back(createIdentifierPath(function["function"])); + operators.emplace_back(std::nullopt); + } + else + { + astAssert(function.isMember("operator")); + astAssert(function.isMember("definition")); + + Token const operatorName = scanSingleToken(function["operator"]); + astAssert(util::contains(frontend::userDefinableOperators, operatorName)); + + functions.emplace_back(createIdentifierPath(function["definition"])); + operators.emplace_back(operatorName); + } + } return createASTNode( _node, std::move(functions), + std::move(operators), !_node.isMember("libraryName"), _node["typeName"].isNull() ? nullptr : convertJsonToASTNode(_node["typeName"]), memberAsBool(_node, "global") @@ -403,7 +447,8 @@ ASTPointer ASTJsonImporter::createStructDefinition(Json::Value const& _ _node, memberAsASTString(_node, "name"), createNameSourceLocation(_node), - members + members, + _node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")) ); } @@ -416,7 +461,8 @@ ASTPointer ASTJsonImporter::createEnumDefinition(Json::Value con _node, memberAsASTString(_node, "name"), createNameSourceLocation(_node), - members + members, + _node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")) ); } @@ -468,7 +514,7 @@ ASTPointer ASTJsonImporter::createFunctionDefinition(Json::V Token kind; bool freeFunction = false; - string kindStr = member(_node, "kind").asString(); + std::string kindStr = member(_node, "kind").asString(); if (kindStr == "constructor") kind = Token::Constructor; @@ -511,7 +557,7 @@ ASTPointer ASTJsonImporter::createFunctionDefinition(Json::V modifiers, createParameterList(member(_node, "returnParameters")), memberAsBool(_node, "implemented") ? createBlock(member(_node, "body"), false) : nullptr, - nullopt, + std::nullopt, false, false, false, @@ -526,7 +572,7 @@ ASTPointer ASTJsonImporter::createVariableDeclaration(Json: VariableDeclaration::Mutability mutability{}; astAssert(member(_node, "mutability").isString(), "'mutability' expected to be string."); - string const mutabilityStr = member(_node, "mutability").asString(); + std::string const mutabilityStr = member(_node, "mutability").asString(); if (mutabilityStr == "constant") { mutability = VariableDeclaration::Mutability::Constant; @@ -546,7 +592,7 @@ ASTPointer ASTJsonImporter::createVariableDeclaration(Json: return createASTNode( _node, nullOrCast(member(_node, "typeName")), - make_shared(member(_node, "name").asString()), + std::make_shared(member(_node, "name").asString()), createNameSourceLocation(_node), nullOrCast(member(_node, "value")), visibility(_node), @@ -579,7 +625,7 @@ ASTPointer ASTJsonImporter::createModifierInvocation(Json::V return createASTNode( _node, createIdentifierPath(member(_node, "modifierName")), - member(_node, "arguments").isNull() ? nullptr : make_unique>>(arguments) + member(_node, "arguments").isNull() ? nullptr : std::make_unique>>(arguments) ); } @@ -613,9 +659,9 @@ ASTPointer ASTJsonImporter::createElementaryTypeName(Json::V astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); - string name = member(_node, "name").asString(); + std::string name = member(_node, "name").asString(); Token token; - tie(token, firstNum, secondNum) = TokenTraits::fromIdentifierOrKeyword(name); + std::tie(token, firstNum, secondNum) = TokenTraits::fromIdentifierOrKeyword(name); ElementaryTypeNameToken elem(token, firstNum, secondNum); std::optional mutability = {}; @@ -649,7 +695,11 @@ ASTPointer ASTJsonImporter::createMapping(Json::Value const& _node) return createASTNode( _node, convertJsonToASTNode(member(_node, "keyType")), - convertJsonToASTNode(member(_node, "valueType")) + memberAsASTString(_node, "keyName"), + createKeyNameSourceLocation(_node), + convertJsonToASTNode(member(_node, "valueType")), + memberAsASTString(_node, "valueName"), + createValueNameSourceLocation(_node) ); } @@ -662,19 +712,6 @@ ASTPointer ASTJsonImporter::createArrayTypeName(Json::Value const ); } -ASTPointer ASTJsonImporter::createInlineAssembly(Json::Value const& _node) -{ - astAssert(_node["evmVersion"].isString(), "Expected evmVersion to be a string!"); - auto evmVersion = langutil::EVMVersion::fromString(_node["evmVersion"].asString()); - astAssert(evmVersion.has_value(), "Invalid EVM version!"); - astAssert(m_evmVersion == evmVersion, "Imported tree evm version differs from configured evm version!"); - - return createASTNode( - _node, - nullOrASTString(_node, "documentation") - ); -} - ASTPointer ASTJsonImporter::createBlock(Json::Value const& _node, bool _unchecked) { std::vector> statements; @@ -721,7 +758,6 @@ ASTPointer ASTJsonImporter::createTryStatement(Json::Value const& { ASTPointer body = createBlock(_node["block"], false); ASTPointer clause = createTryCatchClause(_node["clause"]); - return createASTNode( _node, nullOrASTString(_node, "documentation"), @@ -774,6 +810,7 @@ ASTPointer ASTJsonImporter::createBreak(Json::Value const& _node) ASTPointer ASTJsonImporter::createReturn(Json::Value const& _node) { + solUnimplemented(""); std::vector> options; for (auto& option: member(_node, "options")) options.push_back(convertJsonToASTNode(option)); @@ -781,15 +818,17 @@ ASTPointer ASTJsonImporter::createReturn(Json::Value const& _node) for (auto& name: member(_node, "names")) { astAssert(name.isString(), "Expected 'names' members to be strings!"); - names.push_back(make_shared(name.asString())); + names.push_back(std::make_shared(name.asString())); } + std::optional> sourceLocations = createSourceLocations(_node); return createASTNode( _node, nullOrASTString(_node, "documentation"), nullOrCast(member(_node, "expression")), options, - names + names, + sourceLocations.value() ); } @@ -895,33 +934,9 @@ ASTPointer ASTJsonImporter::createBinaryOperation(Json::Value c ); } -ASTPointer ASTJsonImporter::createFunctionCall(Json::Value const& _node) +ASTPointer ASTJsonImporter::createFunctionCall(Json::Value const& /*_node*/) { - // TODO DELETE solUnimplemented("TODO"); - - std::vector> arguments; - for (auto& arg: member(_node, "arguments")) - arguments.push_back(convertJsonToASTNode(arg)); - std::vector> names; - for (auto& name: member(_node, "names")) - { - astAssert(name.isString(), "Expected 'names' members to be strings!"); - names.push_back(make_shared(name.asString())); - } - - optional> sourceLocations = createSourceLocations(_node); - - return createASTNode( - _node, - convertJsonToASTNode(member(_node, "expression")), - arguments, - names, - sourceLocations ? - *sourceLocations : - vector(names.size()), - FunctionCall::Kind::Usual - ); } ASTPointer ASTJsonImporter::createFunctionCallOptions(Json::Value const& _node) @@ -933,7 +948,7 @@ ASTPointer ASTJsonImporter::createFunctionCallOptions(Json: for (auto& name: member(_node, "names")) { astAssert(name.isString(), "Expected 'names' members to be strings!"); - names.push_back(make_shared(name.asString())); + names.push_back(std::make_shared(name.asString())); } return createASTNode( @@ -1000,14 +1015,14 @@ ASTPointer ASTJsonImporter::createElementaryTypeNa ASTPointer ASTJsonImporter::createLiteral(Json::Value const& _node) { - static string const valStr = "value"; - static string const hexValStr = "hexValue"; + static std::string const valStr = "value"; + static std::string const hexValStr = "hexValue"; astAssert(member(_node, valStr).isString() || member(_node, hexValStr).isString(), "Literal-value is unset."); ASTPointer value = _node.isMember(hexValStr) ? - make_shared(util::asString(util::fromHex(_node[hexValStr].asString()))) : - make_shared(_node[valStr].asString()); + std::make_shared(util::asString(util::fromHex(_node[hexValStr].asString()))) : + std::make_shared(_node[valStr].asString()); return createASTNode( _node, @@ -1019,19 +1034,19 @@ ASTPointer ASTJsonImporter::createLiteral(Json::Value const& _node) ASTPointer ASTJsonImporter::createDocumentation(Json::Value const& _node) { - static string const textString = "text"; + static std::string const textString = "text"; astAssert(member(_node, textString).isString(), "'text' must be a string"); return createASTNode( _node, - make_shared(_node[textString].asString()) + std::make_shared(_node[textString].asString()) ); } // ===== helper functions ========== -Json::Value ASTJsonImporter::member(Json::Value const& _node, string const& _name) +Json::Value ASTJsonImporter::member(Json::Value const& _node, std::string const& _name) { if (!_node.isMember(_name)) return Json::nullValue; @@ -1046,19 +1061,19 @@ Token ASTJsonImporter::scanSingleToken(Json::Value const& _node) return scanner.currentToken(); } -ASTPointer ASTJsonImporter::nullOrASTString(Json::Value const& _json, string const& _name) +ASTPointer ASTJsonImporter::nullOrASTString(Json::Value const& _json, std::string const& _name) { return _json[_name].isString() ? memberAsASTString(_json, _name) : nullptr; } -ASTPointer ASTJsonImporter::memberAsASTString(Json::Value const& _node, string const& _name) +ASTPointer ASTJsonImporter::memberAsASTString(Json::Value const& _node, std::string const& _name) { Json::Value value = member(_node, _name); astAssert(value.isString(), "field " + _name + " must be of type string."); - return make_shared(_node[_name].asString()); + return std::make_shared(_node[_name].asString()); } -bool ASTJsonImporter::memberAsBool(Json::Value const& _node, string const& _name) +bool ASTJsonImporter::memberAsBool(Json::Value const& _node, std::string const& _name) { Json::Value value = member(_node, _name); astAssert(value.isBool(), "field " + _name + " must be of type boolean."); @@ -1107,7 +1122,7 @@ Visibility ASTJsonImporter::visibility(Json::Value const& _node) Json::Value visibility = member(_node, "visibility"); astAssert(visibility.isString(), "'visibility' expected to be a string."); - string const visibilityStr = visibility.asString(); + std::string const visibilityStr = visibility.asString(); if (visibilityStr == "default") return Visibility::Default; @@ -1135,7 +1150,7 @@ Literal::SubDenomination ASTJsonImporter::subdenomination(Json::Value const& _no astAssert(subDen.isString(), "'subDenomination' expected to be string."); - string const subDenStr = subDen.asString(); + std::string const subDenStr = subDen.asString(); if (subDenStr == "seconds") return Literal::SubDenomination::Second; @@ -1159,7 +1174,7 @@ Literal::SubDenomination ASTJsonImporter::subdenomination(Json::Value const& _no StateMutability ASTJsonImporter::stateMutability(Json::Value const& _node) { astAssert(member(_node, "stateMutability").isString(), "StateMutability' expected to be string."); - string const mutabilityStr = member(_node, "stateMutability").asString(); + std::string const mutabilityStr = member(_node, "stateMutability").asString(); if (mutabilityStr == "pure") return StateMutability::Pure; diff --git a/compiler/libsolidity/ast/ASTJsonImporter.h b/compiler/libsolidity/ast/ASTJsonImporter.h index d54adc7d..628f280a 100644 --- a/compiler/libsolidity/ast/ASTJsonImporter.h +++ b/compiler/libsolidity/ast/ASTJsonImporter.h @@ -69,6 +69,10 @@ class ASTJsonImporter ASTPointer convertJsonToASTNode(Json::Value const& _node); langutil::SourceLocation createNameSourceLocation(Json::Value const& _node); + /// @returns source location of a mapping key name + langutil::SourceLocation createKeyNameSourceLocation(Json::Value const& _node); + /// @returns source location of a mapping value name + langutil::SourceLocation createValueNameSourceLocation(Json::Value const& _node); /// \defgroup nodeCreators JSON to AST-Nodes ///@{ @@ -96,7 +100,6 @@ class ASTJsonImporter ASTPointer createFunctionTypeName(Json::Value const& _node); ASTPointer createMapping(Json::Value const& _node); ASTPointer createArrayTypeName(Json::Value const& _node); - ASTPointer createInlineAssembly(Json::Value const& _node); ASTPointer createBlock(Json::Value const& _node, bool _unchecked); ASTPointer createPlaceholderStatement(Json::Value const& _node); ASTPointer createIfStatement(Json::Value const& _node); diff --git a/compiler/libsolidity/ast/ASTVisitor.h b/compiler/libsolidity/ast/ASTVisitor.h index 8de5358c..d841ac2a 100644 --- a/compiler/libsolidity/ast/ASTVisitor.h +++ b/compiler/libsolidity/ast/ASTVisitor.h @@ -81,7 +81,6 @@ class ASTVisitor virtual bool visit(TvmVector& _node) { return visitNode(_node); } virtual bool visit(ArrayTypeName& _node) { return visitNode(_node); } virtual bool visit(FreeInlineAssembly& _node) { return visitNode(_node); } - virtual bool visit(InlineAssembly& _node) { return visitNode(_node); } virtual bool visit(Block& _node) { return visitNode(_node); } virtual bool visit(PlaceholderStatement& _node) { return visitNode(_node); } virtual bool visit(IfStatement& _node) { return visitNode(_node); } @@ -117,6 +116,14 @@ class ASTVisitor virtual bool visit(StructuredDocumentation& _node) { return visitNode(_node); } virtual bool visit(MappingNameExpression& _node) { return visitNode(_node); } virtual bool visit(OptionalNameExpression& _node) { return visitNode(_node); } + /// Experimental Solidity nodes + /// @{ + virtual bool visit(TypeClassDefinition& _node) { return visitNode(_node); } + virtual bool visit(TypeClassInstantiation& _node) { return visitNode(_node); } + virtual bool visit(TypeDefinition& _node) { return visitNode(_node); } + virtual bool visit(TypeClassName& _node) { return visitNode(_node); } + virtual bool visit(Builtin& _node) { return visitNode(_node); } + /// @} virtual void endVisit(SourceUnit& _node) { endVisitNode(_node); } virtual void endVisit(PragmaDirective& _node) { endVisitNode(_node); } @@ -145,7 +152,6 @@ class ASTVisitor virtual void endVisit(TvmVector& _node) { endVisitNode(_node); } virtual void endVisit(ArrayTypeName& _node) { endVisitNode(_node); } virtual void endVisit(FreeInlineAssembly& _node) { endVisitNode(_node); } - virtual void endVisit(InlineAssembly& _node) { endVisitNode(_node); } virtual void endVisit(Block& _node) { endVisitNode(_node); } virtual void endVisit(PlaceholderStatement& _node) { endVisitNode(_node); } virtual void endVisit(IfStatement& _node) { endVisitNode(_node); } @@ -181,6 +187,14 @@ class ASTVisitor virtual void endVisit(StructuredDocumentation& _node) { endVisitNode(_node); } virtual void endVisit(MappingNameExpression& _node) { endVisitNode(_node); } virtual void endVisit(OptionalNameExpression& _node) { endVisitNode(_node); } + /// Experimental Solidity nodes + /// @{ + virtual void endVisit(TypeClassDefinition& _node) { endVisitNode(_node); } + virtual void endVisit(TypeClassInstantiation& _node) { endVisitNode(_node); } + virtual void endVisit(TypeDefinition& _node) { endVisitNode(_node); } + virtual void endVisit(TypeClassName& _node) { endVisitNode(_node); } + virtual void endVisit(Builtin& _node) { endVisitNode(_node); } + /// @} protected: /// Generic function called by default for each node, to be overridden by derived classes @@ -240,7 +254,6 @@ class ASTConstVisitor virtual bool visit(ForEachStatement const& _node) { return visitNode(_node); } virtual bool visit(Continue const& _node) { return visitNode(_node); } virtual bool visit(FreeInlineAssembly const& _node) { return visitNode(_node); } - virtual bool visit(InlineAssembly const& _node) { return visitNode(_node); } virtual bool visit(Break const& _node) { return visitNode(_node); } virtual bool visit(Return const& _node) { return visitNode(_node); } virtual bool visit(Throw const& _node) { return visitNode(_node); } @@ -267,6 +280,14 @@ class ASTConstVisitor virtual bool visit(StructuredDocumentation const& _node) { return visitNode(_node); } virtual bool visit(MappingNameExpression const& _node) { return visitNode(_node); } virtual bool visit(OptionalNameExpression const& _node) { return visitNode(_node); } + /// Experimental Solidity nodes + /// @{ + virtual bool visit(TypeClassDefinition const& _node) { return visitNode(_node); } + virtual bool visit(TypeClassInstantiation const& _node) { return visitNode(_node); } + virtual bool visit(TypeDefinition const& _node) { return visitNode(_node); } + virtual bool visit(TypeClassName const& _node) { return visitNode(_node); } + virtual bool visit(Builtin const& _node) { return visitNode(_node); } + /// @} virtual void endVisit(SourceUnit const& _node) { endVisitNode(_node); } virtual void endVisit(PragmaDirective const& _node) { endVisitNode(_node); } @@ -304,7 +325,6 @@ class ASTConstVisitor virtual void endVisit(ForEachStatement const& _node) { endVisitNode(_node); } virtual void endVisit(Continue const& _node) { endVisitNode(_node); } virtual void endVisit(FreeInlineAssembly const& _node) { endVisitNode(_node); } - virtual void endVisit(InlineAssembly const& _node) { endVisitNode(_node); } virtual void endVisit(Break const& _node) { endVisitNode(_node); } virtual void endVisit(Return const& _node) { endVisitNode(_node); } virtual void endVisit(Throw const& _node) { endVisitNode(_node); } @@ -331,6 +351,14 @@ class ASTConstVisitor virtual void endVisit(StructuredDocumentation const& _node) { endVisitNode(_node); } virtual void endVisit(MappingNameExpression const& _node) { endVisitNode(_node); } virtual void endVisit(OptionalNameExpression const& _node) { endVisitNode(_node); } + /// Experimental Solidity nodes + /// @{ + virtual void endVisit(TypeClassDefinition const& _node) { endVisitNode(_node); } + virtual void endVisit(TypeClassInstantiation const& _node) { endVisitNode(_node); } + virtual void endVisit(TypeDefinition const& _node) { endVisitNode(_node); } + virtual void endVisit(TypeClassName const& _node) { endVisitNode(_node); } + virtual void endVisit(Builtin const& _node) { endVisitNode(_node); } + /// @} protected: /// Generic function called by default for each node, to be overridden by derived classes diff --git a/compiler/libsolidity/ast/AST_accept.h b/compiler/libsolidity/ast/AST_accept.h index ec0fd7e6..aad41ed3 100644 --- a/compiler/libsolidity/ast/AST_accept.h +++ b/compiler/libsolidity/ast/AST_accept.h @@ -196,7 +196,7 @@ void UsingForDirective::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) { - listAccept(functionsOrLibrary(), _visitor); + listAccept(m_functionsOrLibrary, _visitor); if (m_typeName) m_typeName->accept(_visitor); } @@ -207,7 +207,7 @@ void UsingForDirective::accept(ASTConstVisitor& _visitor) const { if (_visitor.visit(*this)) { - listAccept(functionsOrLibrary(), _visitor); + listAccept(m_functionsOrLibrary, _visitor); if (m_typeName) m_typeName->accept(_visitor); } @@ -267,6 +267,8 @@ void FunctionDefinition::accept(ASTVisitor& _visitor) m_parameters->accept(_visitor); if (m_returnParameters) m_returnParameters->accept(_visitor); + if (m_experimentalReturnExpression) + m_experimentalReturnExpression->accept(_visitor); listAccept(m_functionModifiers, _visitor); if (m_body) m_body->accept(_visitor); @@ -285,6 +287,8 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const m_parameters->accept(_visitor); if (m_returnParameters) m_returnParameters->accept(_visitor); + if (m_experimentalReturnExpression) + m_experimentalReturnExpression->accept(_visitor); listAccept(m_functionModifiers, _visitor); if (m_body) m_body->accept(_visitor); @@ -298,6 +302,8 @@ void VariableDeclaration::accept(ASTVisitor& _visitor) { if (m_typeName) m_typeName->accept(_visitor); + if (m_typeExpression) + m_typeExpression->accept(_visitor); if (m_overrides) m_overrides->accept(_visitor); if (m_value) @@ -310,9 +316,10 @@ void VariableDeclaration::accept(ASTConstVisitor& _visitor) const { if (_visitor.visit(*this)) { - if (m_typeName) { + if (m_typeName) m_typeName->accept(_visitor); - } + if (m_typeExpression) + m_typeExpression->accept(_visitor); if (m_overrides) m_overrides->accept(_visitor); if (m_value) @@ -555,18 +562,6 @@ void FreeInlineAssembly::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } -void InlineAssembly::accept(ASTVisitor& _visitor) -{ - _visitor.visit(*this); - _visitor.endVisit(*this); -} - -void InlineAssembly::accept(ASTConstVisitor& _visitor) const -{ - _visitor.visit(*this); - _visitor.endVisit(*this); -} - void Block::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) @@ -1183,4 +1178,114 @@ void Literal::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +/// Experimental Solidity nodes +/// @{ +void TypeClassDefinition::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_typeVariable->accept(_visitor); + if (m_documentation) + m_documentation->accept(_visitor); + listAccept(m_subNodes, _visitor); + } + _visitor.endVisit(*this); +} + +void TypeClassDefinition::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_typeVariable->accept(_visitor); + if (m_documentation) + m_documentation->accept(_visitor); + listAccept(m_subNodes, _visitor); + } + _visitor.endVisit(*this); +} + +void TypeClassInstantiation::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_typeConstructor->accept(_visitor); + if (m_argumentSorts) + m_argumentSorts->accept(_visitor); + m_class->accept(_visitor); + listAccept(m_subNodes, _visitor); + } + _visitor.endVisit(*this); +} + +void TypeClassInstantiation::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_typeConstructor->accept(_visitor); + if (m_argumentSorts) + m_argumentSorts->accept(_visitor); + m_class->accept(_visitor); + listAccept(m_subNodes, _visitor); + } + _visitor.endVisit(*this); +} + +void TypeDefinition::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + if (m_arguments) + m_arguments->accept(_visitor); + if (m_typeExpression) + m_typeExpression->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void TypeDefinition::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + if (m_arguments) + m_arguments->accept(_visitor); + if (m_typeExpression) + m_typeExpression->accept(_visitor); + } + _visitor.endVisit(*this); +} + + +void TypeClassName::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + if (auto* path = std::get_if>(&m_name)) + (*path)->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void TypeClassName::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + if (auto* path = std::get_if>(&m_name)) + (*path)->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void Builtin::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void Builtin::accept(ASTConstVisitor& _visitor) const +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} +/// @} + } diff --git a/compiler/libsolidity/ast/CallGraph.cpp b/compiler/libsolidity/ast/CallGraph.cpp index bf37b226..04275c37 100644 --- a/compiler/libsolidity/ast/CallGraph.cpp +++ b/compiler/libsolidity/ast/CallGraph.cpp @@ -18,7 +18,6 @@ #include -using namespace std; using namespace solidity::frontend; bool CallGraph::CompareByID::operator()(Node const& _lhs, Node const& _rhs) const @@ -26,21 +25,21 @@ bool CallGraph::CompareByID::operator()(Node const& _lhs, Node const& _rhs) cons if (_lhs.index() != _rhs.index()) return _lhs.index() < _rhs.index(); - if (holds_alternative(_lhs)) - return get(_lhs) < get(_rhs); - return get(_lhs)->id() < get(_rhs)->id(); + if (std::holds_alternative(_lhs)) + return std::get(_lhs) < std::get(_rhs); + return std::get(_lhs)->id() < std::get(_rhs)->id(); } bool CallGraph::CompareByID::operator()(Node const& _lhs, int64_t _rhs) const { - solAssert(!holds_alternative(_lhs), ""); + solAssert(!std::holds_alternative(_lhs), ""); - return get(_lhs)->id() < _rhs; + return std::get(_lhs)->id() < _rhs; } bool CallGraph::CompareByID::operator()(int64_t _lhs, Node const& _rhs) const { - solAssert(!holds_alternative(_rhs), ""); + solAssert(!std::holds_alternative(_rhs), ""); - return _lhs < get(_rhs)->id(); + return _lhs < std::get(_rhs)->id(); } diff --git a/compiler/libsolidity/ast/ExperimentalFeatures.h b/compiler/libsolidity/ast/ExperimentalFeatures.h index 772bc4e0..ae2cb14c 100644 --- a/compiler/libsolidity/ast/ExperimentalFeatures.h +++ b/compiler/libsolidity/ast/ExperimentalFeatures.h @@ -32,7 +32,8 @@ enum class ExperimentalFeature ABIEncoderV2, // new ABI encoder that makes use of Yul SMTChecker, Test, - TestOnlyAnalysis + TestOnlyAnalysis, + Solidity }; static std::set const ExperimentalFeatureWithoutWarning = @@ -48,6 +49,7 @@ static std::map const ExperimentalFeatureNames { "SMTChecker", ExperimentalFeature::SMTChecker }, { "__test", ExperimentalFeature::Test }, { "__testOnlyAnalysis", ExperimentalFeature::TestOnlyAnalysis }, + { "solidity", ExperimentalFeature::Solidity } }; } diff --git a/compiler/libsolidity/ast/TypeProvider.cpp b/compiler/libsolidity/ast/TypeProvider.cpp index b7c1ad06..da760c8b 100644 --- a/compiler/libsolidity/ast/TypeProvider.cpp +++ b/compiler/libsolidity/ast/TypeProvider.cpp @@ -21,7 +21,6 @@ #include #include -using namespace std; using namespace solidity; using namespace solidity::frontend; using namespace solidity::util; @@ -37,586 +36,585 @@ InaccessibleDynamicType const TypeProvider::m_inaccessibleDynamic{}; /// The string and bytes unique_ptrs are initialized when they are first used because /// they rely on `byte` being available which we cannot guarantee in the static init context. -unique_ptr TypeProvider::m_bytesStorage; -unique_ptr TypeProvider::m_bytesMemory; -unique_ptr TypeProvider::m_bytesCalldata; -unique_ptr TypeProvider::m_stringStorage; -unique_ptr TypeProvider::m_variant; -unique_ptr TypeProvider::m_stringMemory; +std::unique_ptr TypeProvider::m_bytesStorage; +std::unique_ptr TypeProvider::m_bytesMemory; +std::unique_ptr TypeProvider::m_bytesCalldata; +std::unique_ptr TypeProvider::m_stringStorage; +std::unique_ptr TypeProvider::m_stringMemory; +std::unique_ptr TypeProvider::m_variant; TupleType const TypeProvider::m_emptyTuple{}; AddressType const TypeProvider::m_address{}; InitializerListType const TypeProvider::m_initializerList{}; CallListType const TypeProvider::m_callList{}; -std::unique_ptr const TypeProvider::m_int257 = make_unique(257, IntegerType::Modifier::Signed); - -//for i in range(1, 257): -// print("{{make_unique({}, IntegerType::Modifier::Signed)}},".format(i)) -array, 256> const TypeProvider::m_intM{{ - {make_unique(1, IntegerType::Modifier::Signed)}, - {make_unique(2, IntegerType::Modifier::Signed)}, - {make_unique(3, IntegerType::Modifier::Signed)}, - {make_unique(4, IntegerType::Modifier::Signed)}, - {make_unique(5, IntegerType::Modifier::Signed)}, - {make_unique(6, IntegerType::Modifier::Signed)}, - {make_unique(7, IntegerType::Modifier::Signed)}, - {make_unique(8, IntegerType::Modifier::Signed)}, - {make_unique(9, IntegerType::Modifier::Signed)}, - {make_unique(10, IntegerType::Modifier::Signed)}, - {make_unique(11, IntegerType::Modifier::Signed)}, - {make_unique(12, IntegerType::Modifier::Signed)}, - {make_unique(13, IntegerType::Modifier::Signed)}, - {make_unique(14, IntegerType::Modifier::Signed)}, - {make_unique(15, IntegerType::Modifier::Signed)}, - {make_unique(16, IntegerType::Modifier::Signed)}, - {make_unique(17, IntegerType::Modifier::Signed)}, - {make_unique(18, IntegerType::Modifier::Signed)}, - {make_unique(19, IntegerType::Modifier::Signed)}, - {make_unique(20, IntegerType::Modifier::Signed)}, - {make_unique(21, IntegerType::Modifier::Signed)}, - {make_unique(22, IntegerType::Modifier::Signed)}, - {make_unique(23, IntegerType::Modifier::Signed)}, - {make_unique(24, IntegerType::Modifier::Signed)}, - {make_unique(25, IntegerType::Modifier::Signed)}, - {make_unique(26, IntegerType::Modifier::Signed)}, - {make_unique(27, IntegerType::Modifier::Signed)}, - {make_unique(28, IntegerType::Modifier::Signed)}, - {make_unique(29, IntegerType::Modifier::Signed)}, - {make_unique(30, IntegerType::Modifier::Signed)}, - {make_unique(31, IntegerType::Modifier::Signed)}, - {make_unique(32, IntegerType::Modifier::Signed)}, - {make_unique(33, IntegerType::Modifier::Signed)}, - {make_unique(34, IntegerType::Modifier::Signed)}, - {make_unique(35, IntegerType::Modifier::Signed)}, - {make_unique(36, IntegerType::Modifier::Signed)}, - {make_unique(37, IntegerType::Modifier::Signed)}, - {make_unique(38, IntegerType::Modifier::Signed)}, - {make_unique(39, IntegerType::Modifier::Signed)}, - {make_unique(40, IntegerType::Modifier::Signed)}, - {make_unique(41, IntegerType::Modifier::Signed)}, - {make_unique(42, IntegerType::Modifier::Signed)}, - {make_unique(43, IntegerType::Modifier::Signed)}, - {make_unique(44, IntegerType::Modifier::Signed)}, - {make_unique(45, IntegerType::Modifier::Signed)}, - {make_unique(46, IntegerType::Modifier::Signed)}, - {make_unique(47, IntegerType::Modifier::Signed)}, - {make_unique(48, IntegerType::Modifier::Signed)}, - {make_unique(49, IntegerType::Modifier::Signed)}, - {make_unique(50, IntegerType::Modifier::Signed)}, - {make_unique(51, IntegerType::Modifier::Signed)}, - {make_unique(52, IntegerType::Modifier::Signed)}, - {make_unique(53, IntegerType::Modifier::Signed)}, - {make_unique(54, IntegerType::Modifier::Signed)}, - {make_unique(55, IntegerType::Modifier::Signed)}, - {make_unique(56, IntegerType::Modifier::Signed)}, - {make_unique(57, IntegerType::Modifier::Signed)}, - {make_unique(58, IntegerType::Modifier::Signed)}, - {make_unique(59, IntegerType::Modifier::Signed)}, - {make_unique(60, IntegerType::Modifier::Signed)}, - {make_unique(61, IntegerType::Modifier::Signed)}, - {make_unique(62, IntegerType::Modifier::Signed)}, - {make_unique(63, IntegerType::Modifier::Signed)}, - {make_unique(64, IntegerType::Modifier::Signed)}, - {make_unique(65, IntegerType::Modifier::Signed)}, - {make_unique(66, IntegerType::Modifier::Signed)}, - {make_unique(67, IntegerType::Modifier::Signed)}, - {make_unique(68, IntegerType::Modifier::Signed)}, - {make_unique(69, IntegerType::Modifier::Signed)}, - {make_unique(70, IntegerType::Modifier::Signed)}, - {make_unique(71, IntegerType::Modifier::Signed)}, - {make_unique(72, IntegerType::Modifier::Signed)}, - {make_unique(73, IntegerType::Modifier::Signed)}, - {make_unique(74, IntegerType::Modifier::Signed)}, - {make_unique(75, IntegerType::Modifier::Signed)}, - {make_unique(76, IntegerType::Modifier::Signed)}, - {make_unique(77, IntegerType::Modifier::Signed)}, - {make_unique(78, IntegerType::Modifier::Signed)}, - {make_unique(79, IntegerType::Modifier::Signed)}, - {make_unique(80, IntegerType::Modifier::Signed)}, - {make_unique(81, IntegerType::Modifier::Signed)}, - {make_unique(82, IntegerType::Modifier::Signed)}, - {make_unique(83, IntegerType::Modifier::Signed)}, - {make_unique(84, IntegerType::Modifier::Signed)}, - {make_unique(85, IntegerType::Modifier::Signed)}, - {make_unique(86, IntegerType::Modifier::Signed)}, - {make_unique(87, IntegerType::Modifier::Signed)}, - {make_unique(88, IntegerType::Modifier::Signed)}, - {make_unique(89, IntegerType::Modifier::Signed)}, - {make_unique(90, IntegerType::Modifier::Signed)}, - {make_unique(91, IntegerType::Modifier::Signed)}, - {make_unique(92, IntegerType::Modifier::Signed)}, - {make_unique(93, IntegerType::Modifier::Signed)}, - {make_unique(94, IntegerType::Modifier::Signed)}, - {make_unique(95, IntegerType::Modifier::Signed)}, - {make_unique(96, IntegerType::Modifier::Signed)}, - {make_unique(97, IntegerType::Modifier::Signed)}, - {make_unique(98, IntegerType::Modifier::Signed)}, - {make_unique(99, IntegerType::Modifier::Signed)}, - {make_unique(100, IntegerType::Modifier::Signed)}, - {make_unique(101, IntegerType::Modifier::Signed)}, - {make_unique(102, IntegerType::Modifier::Signed)}, - {make_unique(103, IntegerType::Modifier::Signed)}, - {make_unique(104, IntegerType::Modifier::Signed)}, - {make_unique(105, IntegerType::Modifier::Signed)}, - {make_unique(106, IntegerType::Modifier::Signed)}, - {make_unique(107, IntegerType::Modifier::Signed)}, - {make_unique(108, IntegerType::Modifier::Signed)}, - {make_unique(109, IntegerType::Modifier::Signed)}, - {make_unique(110, IntegerType::Modifier::Signed)}, - {make_unique(111, IntegerType::Modifier::Signed)}, - {make_unique(112, IntegerType::Modifier::Signed)}, - {make_unique(113, IntegerType::Modifier::Signed)}, - {make_unique(114, IntegerType::Modifier::Signed)}, - {make_unique(115, IntegerType::Modifier::Signed)}, - {make_unique(116, IntegerType::Modifier::Signed)}, - {make_unique(117, IntegerType::Modifier::Signed)}, - {make_unique(118, IntegerType::Modifier::Signed)}, - {make_unique(119, IntegerType::Modifier::Signed)}, - {make_unique(120, IntegerType::Modifier::Signed)}, - {make_unique(121, IntegerType::Modifier::Signed)}, - {make_unique(122, IntegerType::Modifier::Signed)}, - {make_unique(123, IntegerType::Modifier::Signed)}, - {make_unique(124, IntegerType::Modifier::Signed)}, - {make_unique(125, IntegerType::Modifier::Signed)}, - {make_unique(126, IntegerType::Modifier::Signed)}, - {make_unique(127, IntegerType::Modifier::Signed)}, - {make_unique(128, IntegerType::Modifier::Signed)}, - {make_unique(129, IntegerType::Modifier::Signed)}, - {make_unique(130, IntegerType::Modifier::Signed)}, - {make_unique(131, IntegerType::Modifier::Signed)}, - {make_unique(132, IntegerType::Modifier::Signed)}, - {make_unique(133, IntegerType::Modifier::Signed)}, - {make_unique(134, IntegerType::Modifier::Signed)}, - {make_unique(135, IntegerType::Modifier::Signed)}, - {make_unique(136, IntegerType::Modifier::Signed)}, - {make_unique(137, IntegerType::Modifier::Signed)}, - {make_unique(138, IntegerType::Modifier::Signed)}, - {make_unique(139, IntegerType::Modifier::Signed)}, - {make_unique(140, IntegerType::Modifier::Signed)}, - {make_unique(141, IntegerType::Modifier::Signed)}, - {make_unique(142, IntegerType::Modifier::Signed)}, - {make_unique(143, IntegerType::Modifier::Signed)}, - {make_unique(144, IntegerType::Modifier::Signed)}, - {make_unique(145, IntegerType::Modifier::Signed)}, - {make_unique(146, IntegerType::Modifier::Signed)}, - {make_unique(147, IntegerType::Modifier::Signed)}, - {make_unique(148, IntegerType::Modifier::Signed)}, - {make_unique(149, IntegerType::Modifier::Signed)}, - {make_unique(150, IntegerType::Modifier::Signed)}, - {make_unique(151, IntegerType::Modifier::Signed)}, - {make_unique(152, IntegerType::Modifier::Signed)}, - {make_unique(153, IntegerType::Modifier::Signed)}, - {make_unique(154, IntegerType::Modifier::Signed)}, - {make_unique(155, IntegerType::Modifier::Signed)}, - {make_unique(156, IntegerType::Modifier::Signed)}, - {make_unique(157, IntegerType::Modifier::Signed)}, - {make_unique(158, IntegerType::Modifier::Signed)}, - {make_unique(159, IntegerType::Modifier::Signed)}, - {make_unique(160, IntegerType::Modifier::Signed)}, - {make_unique(161, IntegerType::Modifier::Signed)}, - {make_unique(162, IntegerType::Modifier::Signed)}, - {make_unique(163, IntegerType::Modifier::Signed)}, - {make_unique(164, IntegerType::Modifier::Signed)}, - {make_unique(165, IntegerType::Modifier::Signed)}, - {make_unique(166, IntegerType::Modifier::Signed)}, - {make_unique(167, IntegerType::Modifier::Signed)}, - {make_unique(168, IntegerType::Modifier::Signed)}, - {make_unique(169, IntegerType::Modifier::Signed)}, - {make_unique(170, IntegerType::Modifier::Signed)}, - {make_unique(171, IntegerType::Modifier::Signed)}, - {make_unique(172, IntegerType::Modifier::Signed)}, - {make_unique(173, IntegerType::Modifier::Signed)}, - {make_unique(174, IntegerType::Modifier::Signed)}, - {make_unique(175, IntegerType::Modifier::Signed)}, - {make_unique(176, IntegerType::Modifier::Signed)}, - {make_unique(177, IntegerType::Modifier::Signed)}, - {make_unique(178, IntegerType::Modifier::Signed)}, - {make_unique(179, IntegerType::Modifier::Signed)}, - {make_unique(180, IntegerType::Modifier::Signed)}, - {make_unique(181, IntegerType::Modifier::Signed)}, - {make_unique(182, IntegerType::Modifier::Signed)}, - {make_unique(183, IntegerType::Modifier::Signed)}, - {make_unique(184, IntegerType::Modifier::Signed)}, - {make_unique(185, IntegerType::Modifier::Signed)}, - {make_unique(186, IntegerType::Modifier::Signed)}, - {make_unique(187, IntegerType::Modifier::Signed)}, - {make_unique(188, IntegerType::Modifier::Signed)}, - {make_unique(189, IntegerType::Modifier::Signed)}, - {make_unique(190, IntegerType::Modifier::Signed)}, - {make_unique(191, IntegerType::Modifier::Signed)}, - {make_unique(192, IntegerType::Modifier::Signed)}, - {make_unique(193, IntegerType::Modifier::Signed)}, - {make_unique(194, IntegerType::Modifier::Signed)}, - {make_unique(195, IntegerType::Modifier::Signed)}, - {make_unique(196, IntegerType::Modifier::Signed)}, - {make_unique(197, IntegerType::Modifier::Signed)}, - {make_unique(198, IntegerType::Modifier::Signed)}, - {make_unique(199, IntegerType::Modifier::Signed)}, - {make_unique(200, IntegerType::Modifier::Signed)}, - {make_unique(201, IntegerType::Modifier::Signed)}, - {make_unique(202, IntegerType::Modifier::Signed)}, - {make_unique(203, IntegerType::Modifier::Signed)}, - {make_unique(204, IntegerType::Modifier::Signed)}, - {make_unique(205, IntegerType::Modifier::Signed)}, - {make_unique(206, IntegerType::Modifier::Signed)}, - {make_unique(207, IntegerType::Modifier::Signed)}, - {make_unique(208, IntegerType::Modifier::Signed)}, - {make_unique(209, IntegerType::Modifier::Signed)}, - {make_unique(210, IntegerType::Modifier::Signed)}, - {make_unique(211, IntegerType::Modifier::Signed)}, - {make_unique(212, IntegerType::Modifier::Signed)}, - {make_unique(213, IntegerType::Modifier::Signed)}, - {make_unique(214, IntegerType::Modifier::Signed)}, - {make_unique(215, IntegerType::Modifier::Signed)}, - {make_unique(216, IntegerType::Modifier::Signed)}, - {make_unique(217, IntegerType::Modifier::Signed)}, - {make_unique(218, IntegerType::Modifier::Signed)}, - {make_unique(219, IntegerType::Modifier::Signed)}, - {make_unique(220, IntegerType::Modifier::Signed)}, - {make_unique(221, IntegerType::Modifier::Signed)}, - {make_unique(222, IntegerType::Modifier::Signed)}, - {make_unique(223, IntegerType::Modifier::Signed)}, - {make_unique(224, IntegerType::Modifier::Signed)}, - {make_unique(225, IntegerType::Modifier::Signed)}, - {make_unique(226, IntegerType::Modifier::Signed)}, - {make_unique(227, IntegerType::Modifier::Signed)}, - {make_unique(228, IntegerType::Modifier::Signed)}, - {make_unique(229, IntegerType::Modifier::Signed)}, - {make_unique(230, IntegerType::Modifier::Signed)}, - {make_unique(231, IntegerType::Modifier::Signed)}, - {make_unique(232, IntegerType::Modifier::Signed)}, - {make_unique(233, IntegerType::Modifier::Signed)}, - {make_unique(234, IntegerType::Modifier::Signed)}, - {make_unique(235, IntegerType::Modifier::Signed)}, - {make_unique(236, IntegerType::Modifier::Signed)}, - {make_unique(237, IntegerType::Modifier::Signed)}, - {make_unique(238, IntegerType::Modifier::Signed)}, - {make_unique(239, IntegerType::Modifier::Signed)}, - {make_unique(240, IntegerType::Modifier::Signed)}, - {make_unique(241, IntegerType::Modifier::Signed)}, - {make_unique(242, IntegerType::Modifier::Signed)}, - {make_unique(243, IntegerType::Modifier::Signed)}, - {make_unique(244, IntegerType::Modifier::Signed)}, - {make_unique(245, IntegerType::Modifier::Signed)}, - {make_unique(246, IntegerType::Modifier::Signed)}, - {make_unique(247, IntegerType::Modifier::Signed)}, - {make_unique(248, IntegerType::Modifier::Signed)}, - {make_unique(249, IntegerType::Modifier::Signed)}, - {make_unique(250, IntegerType::Modifier::Signed)}, - {make_unique(251, IntegerType::Modifier::Signed)}, - {make_unique(252, IntegerType::Modifier::Signed)}, - {make_unique(253, IntegerType::Modifier::Signed)}, - {make_unique(254, IntegerType::Modifier::Signed)}, - {make_unique(255, IntegerType::Modifier::Signed)}, - {make_unique(256, IntegerType::Modifier::Signed)}, +//for i in range(1, 258): +// print("{{std::make_unique({}, IntegerType::Modifier::Signed)}},".format(i)) +std::array, 257> const TypeProvider::m_intM{{ + {std::make_unique(1, IntegerType::Modifier::Signed)}, + {std::make_unique(2, IntegerType::Modifier::Signed)}, + {std::make_unique(3, IntegerType::Modifier::Signed)}, + {std::make_unique(4, IntegerType::Modifier::Signed)}, + {std::make_unique(5, IntegerType::Modifier::Signed)}, + {std::make_unique(6, IntegerType::Modifier::Signed)}, + {std::make_unique(7, IntegerType::Modifier::Signed)}, + {std::make_unique(8, IntegerType::Modifier::Signed)}, + {std::make_unique(9, IntegerType::Modifier::Signed)}, + {std::make_unique(10, IntegerType::Modifier::Signed)}, + {std::make_unique(11, IntegerType::Modifier::Signed)}, + {std::make_unique(12, IntegerType::Modifier::Signed)}, + {std::make_unique(13, IntegerType::Modifier::Signed)}, + {std::make_unique(14, IntegerType::Modifier::Signed)}, + {std::make_unique(15, IntegerType::Modifier::Signed)}, + {std::make_unique(16, IntegerType::Modifier::Signed)}, + {std::make_unique(17, IntegerType::Modifier::Signed)}, + {std::make_unique(18, IntegerType::Modifier::Signed)}, + {std::make_unique(19, IntegerType::Modifier::Signed)}, + {std::make_unique(20, IntegerType::Modifier::Signed)}, + {std::make_unique(21, IntegerType::Modifier::Signed)}, + {std::make_unique(22, IntegerType::Modifier::Signed)}, + {std::make_unique(23, IntegerType::Modifier::Signed)}, + {std::make_unique(24, IntegerType::Modifier::Signed)}, + {std::make_unique(25, IntegerType::Modifier::Signed)}, + {std::make_unique(26, IntegerType::Modifier::Signed)}, + {std::make_unique(27, IntegerType::Modifier::Signed)}, + {std::make_unique(28, IntegerType::Modifier::Signed)}, + {std::make_unique(29, IntegerType::Modifier::Signed)}, + {std::make_unique(30, IntegerType::Modifier::Signed)}, + {std::make_unique(31, IntegerType::Modifier::Signed)}, + {std::make_unique(32, IntegerType::Modifier::Signed)}, + {std::make_unique(33, IntegerType::Modifier::Signed)}, + {std::make_unique(34, IntegerType::Modifier::Signed)}, + {std::make_unique(35, IntegerType::Modifier::Signed)}, + {std::make_unique(36, IntegerType::Modifier::Signed)}, + {std::make_unique(37, IntegerType::Modifier::Signed)}, + {std::make_unique(38, IntegerType::Modifier::Signed)}, + {std::make_unique(39, IntegerType::Modifier::Signed)}, + {std::make_unique(40, IntegerType::Modifier::Signed)}, + {std::make_unique(41, IntegerType::Modifier::Signed)}, + {std::make_unique(42, IntegerType::Modifier::Signed)}, + {std::make_unique(43, IntegerType::Modifier::Signed)}, + {std::make_unique(44, IntegerType::Modifier::Signed)}, + {std::make_unique(45, IntegerType::Modifier::Signed)}, + {std::make_unique(46, IntegerType::Modifier::Signed)}, + {std::make_unique(47, IntegerType::Modifier::Signed)}, + {std::make_unique(48, IntegerType::Modifier::Signed)}, + {std::make_unique(49, IntegerType::Modifier::Signed)}, + {std::make_unique(50, IntegerType::Modifier::Signed)}, + {std::make_unique(51, IntegerType::Modifier::Signed)}, + {std::make_unique(52, IntegerType::Modifier::Signed)}, + {std::make_unique(53, IntegerType::Modifier::Signed)}, + {std::make_unique(54, IntegerType::Modifier::Signed)}, + {std::make_unique(55, IntegerType::Modifier::Signed)}, + {std::make_unique(56, IntegerType::Modifier::Signed)}, + {std::make_unique(57, IntegerType::Modifier::Signed)}, + {std::make_unique(58, IntegerType::Modifier::Signed)}, + {std::make_unique(59, IntegerType::Modifier::Signed)}, + {std::make_unique(60, IntegerType::Modifier::Signed)}, + {std::make_unique(61, IntegerType::Modifier::Signed)}, + {std::make_unique(62, IntegerType::Modifier::Signed)}, + {std::make_unique(63, IntegerType::Modifier::Signed)}, + {std::make_unique(64, IntegerType::Modifier::Signed)}, + {std::make_unique(65, IntegerType::Modifier::Signed)}, + {std::make_unique(66, IntegerType::Modifier::Signed)}, + {std::make_unique(67, IntegerType::Modifier::Signed)}, + {std::make_unique(68, IntegerType::Modifier::Signed)}, + {std::make_unique(69, IntegerType::Modifier::Signed)}, + {std::make_unique(70, IntegerType::Modifier::Signed)}, + {std::make_unique(71, IntegerType::Modifier::Signed)}, + {std::make_unique(72, IntegerType::Modifier::Signed)}, + {std::make_unique(73, IntegerType::Modifier::Signed)}, + {std::make_unique(74, IntegerType::Modifier::Signed)}, + {std::make_unique(75, IntegerType::Modifier::Signed)}, + {std::make_unique(76, IntegerType::Modifier::Signed)}, + {std::make_unique(77, IntegerType::Modifier::Signed)}, + {std::make_unique(78, IntegerType::Modifier::Signed)}, + {std::make_unique(79, IntegerType::Modifier::Signed)}, + {std::make_unique(80, IntegerType::Modifier::Signed)}, + {std::make_unique(81, IntegerType::Modifier::Signed)}, + {std::make_unique(82, IntegerType::Modifier::Signed)}, + {std::make_unique(83, IntegerType::Modifier::Signed)}, + {std::make_unique(84, IntegerType::Modifier::Signed)}, + {std::make_unique(85, IntegerType::Modifier::Signed)}, + {std::make_unique(86, IntegerType::Modifier::Signed)}, + {std::make_unique(87, IntegerType::Modifier::Signed)}, + {std::make_unique(88, IntegerType::Modifier::Signed)}, + {std::make_unique(89, IntegerType::Modifier::Signed)}, + {std::make_unique(90, IntegerType::Modifier::Signed)}, + {std::make_unique(91, IntegerType::Modifier::Signed)}, + {std::make_unique(92, IntegerType::Modifier::Signed)}, + {std::make_unique(93, IntegerType::Modifier::Signed)}, + {std::make_unique(94, IntegerType::Modifier::Signed)}, + {std::make_unique(95, IntegerType::Modifier::Signed)}, + {std::make_unique(96, IntegerType::Modifier::Signed)}, + {std::make_unique(97, IntegerType::Modifier::Signed)}, + {std::make_unique(98, IntegerType::Modifier::Signed)}, + {std::make_unique(99, IntegerType::Modifier::Signed)}, + {std::make_unique(100, IntegerType::Modifier::Signed)}, + {std::make_unique(101, IntegerType::Modifier::Signed)}, + {std::make_unique(102, IntegerType::Modifier::Signed)}, + {std::make_unique(103, IntegerType::Modifier::Signed)}, + {std::make_unique(104, IntegerType::Modifier::Signed)}, + {std::make_unique(105, IntegerType::Modifier::Signed)}, + {std::make_unique(106, IntegerType::Modifier::Signed)}, + {std::make_unique(107, IntegerType::Modifier::Signed)}, + {std::make_unique(108, IntegerType::Modifier::Signed)}, + {std::make_unique(109, IntegerType::Modifier::Signed)}, + {std::make_unique(110, IntegerType::Modifier::Signed)}, + {std::make_unique(111, IntegerType::Modifier::Signed)}, + {std::make_unique(112, IntegerType::Modifier::Signed)}, + {std::make_unique(113, IntegerType::Modifier::Signed)}, + {std::make_unique(114, IntegerType::Modifier::Signed)}, + {std::make_unique(115, IntegerType::Modifier::Signed)}, + {std::make_unique(116, IntegerType::Modifier::Signed)}, + {std::make_unique(117, IntegerType::Modifier::Signed)}, + {std::make_unique(118, IntegerType::Modifier::Signed)}, + {std::make_unique(119, IntegerType::Modifier::Signed)}, + {std::make_unique(120, IntegerType::Modifier::Signed)}, + {std::make_unique(121, IntegerType::Modifier::Signed)}, + {std::make_unique(122, IntegerType::Modifier::Signed)}, + {std::make_unique(123, IntegerType::Modifier::Signed)}, + {std::make_unique(124, IntegerType::Modifier::Signed)}, + {std::make_unique(125, IntegerType::Modifier::Signed)}, + {std::make_unique(126, IntegerType::Modifier::Signed)}, + {std::make_unique(127, IntegerType::Modifier::Signed)}, + {std::make_unique(128, IntegerType::Modifier::Signed)}, + {std::make_unique(129, IntegerType::Modifier::Signed)}, + {std::make_unique(130, IntegerType::Modifier::Signed)}, + {std::make_unique(131, IntegerType::Modifier::Signed)}, + {std::make_unique(132, IntegerType::Modifier::Signed)}, + {std::make_unique(133, IntegerType::Modifier::Signed)}, + {std::make_unique(134, IntegerType::Modifier::Signed)}, + {std::make_unique(135, IntegerType::Modifier::Signed)}, + {std::make_unique(136, IntegerType::Modifier::Signed)}, + {std::make_unique(137, IntegerType::Modifier::Signed)}, + {std::make_unique(138, IntegerType::Modifier::Signed)}, + {std::make_unique(139, IntegerType::Modifier::Signed)}, + {std::make_unique(140, IntegerType::Modifier::Signed)}, + {std::make_unique(141, IntegerType::Modifier::Signed)}, + {std::make_unique(142, IntegerType::Modifier::Signed)}, + {std::make_unique(143, IntegerType::Modifier::Signed)}, + {std::make_unique(144, IntegerType::Modifier::Signed)}, + {std::make_unique(145, IntegerType::Modifier::Signed)}, + {std::make_unique(146, IntegerType::Modifier::Signed)}, + {std::make_unique(147, IntegerType::Modifier::Signed)}, + {std::make_unique(148, IntegerType::Modifier::Signed)}, + {std::make_unique(149, IntegerType::Modifier::Signed)}, + {std::make_unique(150, IntegerType::Modifier::Signed)}, + {std::make_unique(151, IntegerType::Modifier::Signed)}, + {std::make_unique(152, IntegerType::Modifier::Signed)}, + {std::make_unique(153, IntegerType::Modifier::Signed)}, + {std::make_unique(154, IntegerType::Modifier::Signed)}, + {std::make_unique(155, IntegerType::Modifier::Signed)}, + {std::make_unique(156, IntegerType::Modifier::Signed)}, + {std::make_unique(157, IntegerType::Modifier::Signed)}, + {std::make_unique(158, IntegerType::Modifier::Signed)}, + {std::make_unique(159, IntegerType::Modifier::Signed)}, + {std::make_unique(160, IntegerType::Modifier::Signed)}, + {std::make_unique(161, IntegerType::Modifier::Signed)}, + {std::make_unique(162, IntegerType::Modifier::Signed)}, + {std::make_unique(163, IntegerType::Modifier::Signed)}, + {std::make_unique(164, IntegerType::Modifier::Signed)}, + {std::make_unique(165, IntegerType::Modifier::Signed)}, + {std::make_unique(166, IntegerType::Modifier::Signed)}, + {std::make_unique(167, IntegerType::Modifier::Signed)}, + {std::make_unique(168, IntegerType::Modifier::Signed)}, + {std::make_unique(169, IntegerType::Modifier::Signed)}, + {std::make_unique(170, IntegerType::Modifier::Signed)}, + {std::make_unique(171, IntegerType::Modifier::Signed)}, + {std::make_unique(172, IntegerType::Modifier::Signed)}, + {std::make_unique(173, IntegerType::Modifier::Signed)}, + {std::make_unique(174, IntegerType::Modifier::Signed)}, + {std::make_unique(175, IntegerType::Modifier::Signed)}, + {std::make_unique(176, IntegerType::Modifier::Signed)}, + {std::make_unique(177, IntegerType::Modifier::Signed)}, + {std::make_unique(178, IntegerType::Modifier::Signed)}, + {std::make_unique(179, IntegerType::Modifier::Signed)}, + {std::make_unique(180, IntegerType::Modifier::Signed)}, + {std::make_unique(181, IntegerType::Modifier::Signed)}, + {std::make_unique(182, IntegerType::Modifier::Signed)}, + {std::make_unique(183, IntegerType::Modifier::Signed)}, + {std::make_unique(184, IntegerType::Modifier::Signed)}, + {std::make_unique(185, IntegerType::Modifier::Signed)}, + {std::make_unique(186, IntegerType::Modifier::Signed)}, + {std::make_unique(187, IntegerType::Modifier::Signed)}, + {std::make_unique(188, IntegerType::Modifier::Signed)}, + {std::make_unique(189, IntegerType::Modifier::Signed)}, + {std::make_unique(190, IntegerType::Modifier::Signed)}, + {std::make_unique(191, IntegerType::Modifier::Signed)}, + {std::make_unique(192, IntegerType::Modifier::Signed)}, + {std::make_unique(193, IntegerType::Modifier::Signed)}, + {std::make_unique(194, IntegerType::Modifier::Signed)}, + {std::make_unique(195, IntegerType::Modifier::Signed)}, + {std::make_unique(196, IntegerType::Modifier::Signed)}, + {std::make_unique(197, IntegerType::Modifier::Signed)}, + {std::make_unique(198, IntegerType::Modifier::Signed)}, + {std::make_unique(199, IntegerType::Modifier::Signed)}, + {std::make_unique(200, IntegerType::Modifier::Signed)}, + {std::make_unique(201, IntegerType::Modifier::Signed)}, + {std::make_unique(202, IntegerType::Modifier::Signed)}, + {std::make_unique(203, IntegerType::Modifier::Signed)}, + {std::make_unique(204, IntegerType::Modifier::Signed)}, + {std::make_unique(205, IntegerType::Modifier::Signed)}, + {std::make_unique(206, IntegerType::Modifier::Signed)}, + {std::make_unique(207, IntegerType::Modifier::Signed)}, + {std::make_unique(208, IntegerType::Modifier::Signed)}, + {std::make_unique(209, IntegerType::Modifier::Signed)}, + {std::make_unique(210, IntegerType::Modifier::Signed)}, + {std::make_unique(211, IntegerType::Modifier::Signed)}, + {std::make_unique(212, IntegerType::Modifier::Signed)}, + {std::make_unique(213, IntegerType::Modifier::Signed)}, + {std::make_unique(214, IntegerType::Modifier::Signed)}, + {std::make_unique(215, IntegerType::Modifier::Signed)}, + {std::make_unique(216, IntegerType::Modifier::Signed)}, + {std::make_unique(217, IntegerType::Modifier::Signed)}, + {std::make_unique(218, IntegerType::Modifier::Signed)}, + {std::make_unique(219, IntegerType::Modifier::Signed)}, + {std::make_unique(220, IntegerType::Modifier::Signed)}, + {std::make_unique(221, IntegerType::Modifier::Signed)}, + {std::make_unique(222, IntegerType::Modifier::Signed)}, + {std::make_unique(223, IntegerType::Modifier::Signed)}, + {std::make_unique(224, IntegerType::Modifier::Signed)}, + {std::make_unique(225, IntegerType::Modifier::Signed)}, + {std::make_unique(226, IntegerType::Modifier::Signed)}, + {std::make_unique(227, IntegerType::Modifier::Signed)}, + {std::make_unique(228, IntegerType::Modifier::Signed)}, + {std::make_unique(229, IntegerType::Modifier::Signed)}, + {std::make_unique(230, IntegerType::Modifier::Signed)}, + {std::make_unique(231, IntegerType::Modifier::Signed)}, + {std::make_unique(232, IntegerType::Modifier::Signed)}, + {std::make_unique(233, IntegerType::Modifier::Signed)}, + {std::make_unique(234, IntegerType::Modifier::Signed)}, + {std::make_unique(235, IntegerType::Modifier::Signed)}, + {std::make_unique(236, IntegerType::Modifier::Signed)}, + {std::make_unique(237, IntegerType::Modifier::Signed)}, + {std::make_unique(238, IntegerType::Modifier::Signed)}, + {std::make_unique(239, IntegerType::Modifier::Signed)}, + {std::make_unique(240, IntegerType::Modifier::Signed)}, + {std::make_unique(241, IntegerType::Modifier::Signed)}, + {std::make_unique(242, IntegerType::Modifier::Signed)}, + {std::make_unique(243, IntegerType::Modifier::Signed)}, + {std::make_unique(244, IntegerType::Modifier::Signed)}, + {std::make_unique(245, IntegerType::Modifier::Signed)}, + {std::make_unique(246, IntegerType::Modifier::Signed)}, + {std::make_unique(247, IntegerType::Modifier::Signed)}, + {std::make_unique(248, IntegerType::Modifier::Signed)}, + {std::make_unique(249, IntegerType::Modifier::Signed)}, + {std::make_unique(250, IntegerType::Modifier::Signed)}, + {std::make_unique(251, IntegerType::Modifier::Signed)}, + {std::make_unique(252, IntegerType::Modifier::Signed)}, + {std::make_unique(253, IntegerType::Modifier::Signed)}, + {std::make_unique(254, IntegerType::Modifier::Signed)}, + {std::make_unique(255, IntegerType::Modifier::Signed)}, + {std::make_unique(256, IntegerType::Modifier::Signed)}, + {std::make_unique(257, IntegerType::Modifier::Signed)}, }}; //for i in range(1, 257): -// print("{{make_unique({}, IntegerType::Modifier::Unsigned)}},".format(i)) -array, 256> const TypeProvider::m_uintM{{ - {make_unique(1, IntegerType::Modifier::Unsigned)}, - {make_unique(2, IntegerType::Modifier::Unsigned)}, - {make_unique(3, IntegerType::Modifier::Unsigned)}, - {make_unique(4, IntegerType::Modifier::Unsigned)}, - {make_unique(5, IntegerType::Modifier::Unsigned)}, - {make_unique(6, IntegerType::Modifier::Unsigned)}, - {make_unique(7, IntegerType::Modifier::Unsigned)}, - {make_unique(8, IntegerType::Modifier::Unsigned)}, - {make_unique(9, IntegerType::Modifier::Unsigned)}, - {make_unique(10, IntegerType::Modifier::Unsigned)}, - {make_unique(11, IntegerType::Modifier::Unsigned)}, - {make_unique(12, IntegerType::Modifier::Unsigned)}, - {make_unique(13, IntegerType::Modifier::Unsigned)}, - {make_unique(14, IntegerType::Modifier::Unsigned)}, - {make_unique(15, IntegerType::Modifier::Unsigned)}, - {make_unique(16, IntegerType::Modifier::Unsigned)}, - {make_unique(17, IntegerType::Modifier::Unsigned)}, - {make_unique(18, IntegerType::Modifier::Unsigned)}, - {make_unique(19, IntegerType::Modifier::Unsigned)}, - {make_unique(20, IntegerType::Modifier::Unsigned)}, - {make_unique(21, IntegerType::Modifier::Unsigned)}, - {make_unique(22, IntegerType::Modifier::Unsigned)}, - {make_unique(23, IntegerType::Modifier::Unsigned)}, - {make_unique(24, IntegerType::Modifier::Unsigned)}, - {make_unique(25, IntegerType::Modifier::Unsigned)}, - {make_unique(26, IntegerType::Modifier::Unsigned)}, - {make_unique(27, IntegerType::Modifier::Unsigned)}, - {make_unique(28, IntegerType::Modifier::Unsigned)}, - {make_unique(29, IntegerType::Modifier::Unsigned)}, - {make_unique(30, IntegerType::Modifier::Unsigned)}, - {make_unique(31, IntegerType::Modifier::Unsigned)}, - {make_unique(32, IntegerType::Modifier::Unsigned)}, - {make_unique(33, IntegerType::Modifier::Unsigned)}, - {make_unique(34, IntegerType::Modifier::Unsigned)}, - {make_unique(35, IntegerType::Modifier::Unsigned)}, - {make_unique(36, IntegerType::Modifier::Unsigned)}, - {make_unique(37, IntegerType::Modifier::Unsigned)}, - {make_unique(38, IntegerType::Modifier::Unsigned)}, - {make_unique(39, IntegerType::Modifier::Unsigned)}, - {make_unique(40, IntegerType::Modifier::Unsigned)}, - {make_unique(41, IntegerType::Modifier::Unsigned)}, - {make_unique(42, IntegerType::Modifier::Unsigned)}, - {make_unique(43, IntegerType::Modifier::Unsigned)}, - {make_unique(44, IntegerType::Modifier::Unsigned)}, - {make_unique(45, IntegerType::Modifier::Unsigned)}, - {make_unique(46, IntegerType::Modifier::Unsigned)}, - {make_unique(47, IntegerType::Modifier::Unsigned)}, - {make_unique(48, IntegerType::Modifier::Unsigned)}, - {make_unique(49, IntegerType::Modifier::Unsigned)}, - {make_unique(50, IntegerType::Modifier::Unsigned)}, - {make_unique(51, IntegerType::Modifier::Unsigned)}, - {make_unique(52, IntegerType::Modifier::Unsigned)}, - {make_unique(53, IntegerType::Modifier::Unsigned)}, - {make_unique(54, IntegerType::Modifier::Unsigned)}, - {make_unique(55, IntegerType::Modifier::Unsigned)}, - {make_unique(56, IntegerType::Modifier::Unsigned)}, - {make_unique(57, IntegerType::Modifier::Unsigned)}, - {make_unique(58, IntegerType::Modifier::Unsigned)}, - {make_unique(59, IntegerType::Modifier::Unsigned)}, - {make_unique(60, IntegerType::Modifier::Unsigned)}, - {make_unique(61, IntegerType::Modifier::Unsigned)}, - {make_unique(62, IntegerType::Modifier::Unsigned)}, - {make_unique(63, IntegerType::Modifier::Unsigned)}, - {make_unique(64, IntegerType::Modifier::Unsigned)}, - {make_unique(65, IntegerType::Modifier::Unsigned)}, - {make_unique(66, IntegerType::Modifier::Unsigned)}, - {make_unique(67, IntegerType::Modifier::Unsigned)}, - {make_unique(68, IntegerType::Modifier::Unsigned)}, - {make_unique(69, IntegerType::Modifier::Unsigned)}, - {make_unique(70, IntegerType::Modifier::Unsigned)}, - {make_unique(71, IntegerType::Modifier::Unsigned)}, - {make_unique(72, IntegerType::Modifier::Unsigned)}, - {make_unique(73, IntegerType::Modifier::Unsigned)}, - {make_unique(74, IntegerType::Modifier::Unsigned)}, - {make_unique(75, IntegerType::Modifier::Unsigned)}, - {make_unique(76, IntegerType::Modifier::Unsigned)}, - {make_unique(77, IntegerType::Modifier::Unsigned)}, - {make_unique(78, IntegerType::Modifier::Unsigned)}, - {make_unique(79, IntegerType::Modifier::Unsigned)}, - {make_unique(80, IntegerType::Modifier::Unsigned)}, - {make_unique(81, IntegerType::Modifier::Unsigned)}, - {make_unique(82, IntegerType::Modifier::Unsigned)}, - {make_unique(83, IntegerType::Modifier::Unsigned)}, - {make_unique(84, IntegerType::Modifier::Unsigned)}, - {make_unique(85, IntegerType::Modifier::Unsigned)}, - {make_unique(86, IntegerType::Modifier::Unsigned)}, - {make_unique(87, IntegerType::Modifier::Unsigned)}, - {make_unique(88, IntegerType::Modifier::Unsigned)}, - {make_unique(89, IntegerType::Modifier::Unsigned)}, - {make_unique(90, IntegerType::Modifier::Unsigned)}, - {make_unique(91, IntegerType::Modifier::Unsigned)}, - {make_unique(92, IntegerType::Modifier::Unsigned)}, - {make_unique(93, IntegerType::Modifier::Unsigned)}, - {make_unique(94, IntegerType::Modifier::Unsigned)}, - {make_unique(95, IntegerType::Modifier::Unsigned)}, - {make_unique(96, IntegerType::Modifier::Unsigned)}, - {make_unique(97, IntegerType::Modifier::Unsigned)}, - {make_unique(98, IntegerType::Modifier::Unsigned)}, - {make_unique(99, IntegerType::Modifier::Unsigned)}, - {make_unique(100, IntegerType::Modifier::Unsigned)}, - {make_unique(101, IntegerType::Modifier::Unsigned)}, - {make_unique(102, IntegerType::Modifier::Unsigned)}, - {make_unique(103, IntegerType::Modifier::Unsigned)}, - {make_unique(104, IntegerType::Modifier::Unsigned)}, - {make_unique(105, IntegerType::Modifier::Unsigned)}, - {make_unique(106, IntegerType::Modifier::Unsigned)}, - {make_unique(107, IntegerType::Modifier::Unsigned)}, - {make_unique(108, IntegerType::Modifier::Unsigned)}, - {make_unique(109, IntegerType::Modifier::Unsigned)}, - {make_unique(110, IntegerType::Modifier::Unsigned)}, - {make_unique(111, IntegerType::Modifier::Unsigned)}, - {make_unique(112, IntegerType::Modifier::Unsigned)}, - {make_unique(113, IntegerType::Modifier::Unsigned)}, - {make_unique(114, IntegerType::Modifier::Unsigned)}, - {make_unique(115, IntegerType::Modifier::Unsigned)}, - {make_unique(116, IntegerType::Modifier::Unsigned)}, - {make_unique(117, IntegerType::Modifier::Unsigned)}, - {make_unique(118, IntegerType::Modifier::Unsigned)}, - {make_unique(119, IntegerType::Modifier::Unsigned)}, - {make_unique(120, IntegerType::Modifier::Unsigned)}, - {make_unique(121, IntegerType::Modifier::Unsigned)}, - {make_unique(122, IntegerType::Modifier::Unsigned)}, - {make_unique(123, IntegerType::Modifier::Unsigned)}, - {make_unique(124, IntegerType::Modifier::Unsigned)}, - {make_unique(125, IntegerType::Modifier::Unsigned)}, - {make_unique(126, IntegerType::Modifier::Unsigned)}, - {make_unique(127, IntegerType::Modifier::Unsigned)}, - {make_unique(128, IntegerType::Modifier::Unsigned)}, - {make_unique(129, IntegerType::Modifier::Unsigned)}, - {make_unique(130, IntegerType::Modifier::Unsigned)}, - {make_unique(131, IntegerType::Modifier::Unsigned)}, - {make_unique(132, IntegerType::Modifier::Unsigned)}, - {make_unique(133, IntegerType::Modifier::Unsigned)}, - {make_unique(134, IntegerType::Modifier::Unsigned)}, - {make_unique(135, IntegerType::Modifier::Unsigned)}, - {make_unique(136, IntegerType::Modifier::Unsigned)}, - {make_unique(137, IntegerType::Modifier::Unsigned)}, - {make_unique(138, IntegerType::Modifier::Unsigned)}, - {make_unique(139, IntegerType::Modifier::Unsigned)}, - {make_unique(140, IntegerType::Modifier::Unsigned)}, - {make_unique(141, IntegerType::Modifier::Unsigned)}, - {make_unique(142, IntegerType::Modifier::Unsigned)}, - {make_unique(143, IntegerType::Modifier::Unsigned)}, - {make_unique(144, IntegerType::Modifier::Unsigned)}, - {make_unique(145, IntegerType::Modifier::Unsigned)}, - {make_unique(146, IntegerType::Modifier::Unsigned)}, - {make_unique(147, IntegerType::Modifier::Unsigned)}, - {make_unique(148, IntegerType::Modifier::Unsigned)}, - {make_unique(149, IntegerType::Modifier::Unsigned)}, - {make_unique(150, IntegerType::Modifier::Unsigned)}, - {make_unique(151, IntegerType::Modifier::Unsigned)}, - {make_unique(152, IntegerType::Modifier::Unsigned)}, - {make_unique(153, IntegerType::Modifier::Unsigned)}, - {make_unique(154, IntegerType::Modifier::Unsigned)}, - {make_unique(155, IntegerType::Modifier::Unsigned)}, - {make_unique(156, IntegerType::Modifier::Unsigned)}, - {make_unique(157, IntegerType::Modifier::Unsigned)}, - {make_unique(158, IntegerType::Modifier::Unsigned)}, - {make_unique(159, IntegerType::Modifier::Unsigned)}, - {make_unique(160, IntegerType::Modifier::Unsigned)}, - {make_unique(161, IntegerType::Modifier::Unsigned)}, - {make_unique(162, IntegerType::Modifier::Unsigned)}, - {make_unique(163, IntegerType::Modifier::Unsigned)}, - {make_unique(164, IntegerType::Modifier::Unsigned)}, - {make_unique(165, IntegerType::Modifier::Unsigned)}, - {make_unique(166, IntegerType::Modifier::Unsigned)}, - {make_unique(167, IntegerType::Modifier::Unsigned)}, - {make_unique(168, IntegerType::Modifier::Unsigned)}, - {make_unique(169, IntegerType::Modifier::Unsigned)}, - {make_unique(170, IntegerType::Modifier::Unsigned)}, - {make_unique(171, IntegerType::Modifier::Unsigned)}, - {make_unique(172, IntegerType::Modifier::Unsigned)}, - {make_unique(173, IntegerType::Modifier::Unsigned)}, - {make_unique(174, IntegerType::Modifier::Unsigned)}, - {make_unique(175, IntegerType::Modifier::Unsigned)}, - {make_unique(176, IntegerType::Modifier::Unsigned)}, - {make_unique(177, IntegerType::Modifier::Unsigned)}, - {make_unique(178, IntegerType::Modifier::Unsigned)}, - {make_unique(179, IntegerType::Modifier::Unsigned)}, - {make_unique(180, IntegerType::Modifier::Unsigned)}, - {make_unique(181, IntegerType::Modifier::Unsigned)}, - {make_unique(182, IntegerType::Modifier::Unsigned)}, - {make_unique(183, IntegerType::Modifier::Unsigned)}, - {make_unique(184, IntegerType::Modifier::Unsigned)}, - {make_unique(185, IntegerType::Modifier::Unsigned)}, - {make_unique(186, IntegerType::Modifier::Unsigned)}, - {make_unique(187, IntegerType::Modifier::Unsigned)}, - {make_unique(188, IntegerType::Modifier::Unsigned)}, - {make_unique(189, IntegerType::Modifier::Unsigned)}, - {make_unique(190, IntegerType::Modifier::Unsigned)}, - {make_unique(191, IntegerType::Modifier::Unsigned)}, - {make_unique(192, IntegerType::Modifier::Unsigned)}, - {make_unique(193, IntegerType::Modifier::Unsigned)}, - {make_unique(194, IntegerType::Modifier::Unsigned)}, - {make_unique(195, IntegerType::Modifier::Unsigned)}, - {make_unique(196, IntegerType::Modifier::Unsigned)}, - {make_unique(197, IntegerType::Modifier::Unsigned)}, - {make_unique(198, IntegerType::Modifier::Unsigned)}, - {make_unique(199, IntegerType::Modifier::Unsigned)}, - {make_unique(200, IntegerType::Modifier::Unsigned)}, - {make_unique(201, IntegerType::Modifier::Unsigned)}, - {make_unique(202, IntegerType::Modifier::Unsigned)}, - {make_unique(203, IntegerType::Modifier::Unsigned)}, - {make_unique(204, IntegerType::Modifier::Unsigned)}, - {make_unique(205, IntegerType::Modifier::Unsigned)}, - {make_unique(206, IntegerType::Modifier::Unsigned)}, - {make_unique(207, IntegerType::Modifier::Unsigned)}, - {make_unique(208, IntegerType::Modifier::Unsigned)}, - {make_unique(209, IntegerType::Modifier::Unsigned)}, - {make_unique(210, IntegerType::Modifier::Unsigned)}, - {make_unique(211, IntegerType::Modifier::Unsigned)}, - {make_unique(212, IntegerType::Modifier::Unsigned)}, - {make_unique(213, IntegerType::Modifier::Unsigned)}, - {make_unique(214, IntegerType::Modifier::Unsigned)}, - {make_unique(215, IntegerType::Modifier::Unsigned)}, - {make_unique(216, IntegerType::Modifier::Unsigned)}, - {make_unique(217, IntegerType::Modifier::Unsigned)}, - {make_unique(218, IntegerType::Modifier::Unsigned)}, - {make_unique(219, IntegerType::Modifier::Unsigned)}, - {make_unique(220, IntegerType::Modifier::Unsigned)}, - {make_unique(221, IntegerType::Modifier::Unsigned)}, - {make_unique(222, IntegerType::Modifier::Unsigned)}, - {make_unique(223, IntegerType::Modifier::Unsigned)}, - {make_unique(224, IntegerType::Modifier::Unsigned)}, - {make_unique(225, IntegerType::Modifier::Unsigned)}, - {make_unique(226, IntegerType::Modifier::Unsigned)}, - {make_unique(227, IntegerType::Modifier::Unsigned)}, - {make_unique(228, IntegerType::Modifier::Unsigned)}, - {make_unique(229, IntegerType::Modifier::Unsigned)}, - {make_unique(230, IntegerType::Modifier::Unsigned)}, - {make_unique(231, IntegerType::Modifier::Unsigned)}, - {make_unique(232, IntegerType::Modifier::Unsigned)}, - {make_unique(233, IntegerType::Modifier::Unsigned)}, - {make_unique(234, IntegerType::Modifier::Unsigned)}, - {make_unique(235, IntegerType::Modifier::Unsigned)}, - {make_unique(236, IntegerType::Modifier::Unsigned)}, - {make_unique(237, IntegerType::Modifier::Unsigned)}, - {make_unique(238, IntegerType::Modifier::Unsigned)}, - {make_unique(239, IntegerType::Modifier::Unsigned)}, - {make_unique(240, IntegerType::Modifier::Unsigned)}, - {make_unique(241, IntegerType::Modifier::Unsigned)}, - {make_unique(242, IntegerType::Modifier::Unsigned)}, - {make_unique(243, IntegerType::Modifier::Unsigned)}, - {make_unique(244, IntegerType::Modifier::Unsigned)}, - {make_unique(245, IntegerType::Modifier::Unsigned)}, - {make_unique(246, IntegerType::Modifier::Unsigned)}, - {make_unique(247, IntegerType::Modifier::Unsigned)}, - {make_unique(248, IntegerType::Modifier::Unsigned)}, - {make_unique(249, IntegerType::Modifier::Unsigned)}, - {make_unique(250, IntegerType::Modifier::Unsigned)}, - {make_unique(251, IntegerType::Modifier::Unsigned)}, - {make_unique(252, IntegerType::Modifier::Unsigned)}, - {make_unique(253, IntegerType::Modifier::Unsigned)}, - {make_unique(254, IntegerType::Modifier::Unsigned)}, - {make_unique(255, IntegerType::Modifier::Unsigned)}, - {make_unique(256, IntegerType::Modifier::Unsigned)}, +// print("{{std::make_unique({}, IntegerType::Modifier::Unsigned)}},".format(i)) +std::array, 256> const TypeProvider::m_uintM{{ + {std::make_unique(1, IntegerType::Modifier::Unsigned)}, + {std::make_unique(2, IntegerType::Modifier::Unsigned)}, + {std::make_unique(3, IntegerType::Modifier::Unsigned)}, + {std::make_unique(4, IntegerType::Modifier::Unsigned)}, + {std::make_unique(5, IntegerType::Modifier::Unsigned)}, + {std::make_unique(6, IntegerType::Modifier::Unsigned)}, + {std::make_unique(7, IntegerType::Modifier::Unsigned)}, + {std::make_unique(8, IntegerType::Modifier::Unsigned)}, + {std::make_unique(9, IntegerType::Modifier::Unsigned)}, + {std::make_unique(10, IntegerType::Modifier::Unsigned)}, + {std::make_unique(11, IntegerType::Modifier::Unsigned)}, + {std::make_unique(12, IntegerType::Modifier::Unsigned)}, + {std::make_unique(13, IntegerType::Modifier::Unsigned)}, + {std::make_unique(14, IntegerType::Modifier::Unsigned)}, + {std::make_unique(15, IntegerType::Modifier::Unsigned)}, + {std::make_unique(16, IntegerType::Modifier::Unsigned)}, + {std::make_unique(17, IntegerType::Modifier::Unsigned)}, + {std::make_unique(18, IntegerType::Modifier::Unsigned)}, + {std::make_unique(19, IntegerType::Modifier::Unsigned)}, + {std::make_unique(20, IntegerType::Modifier::Unsigned)}, + {std::make_unique(21, IntegerType::Modifier::Unsigned)}, + {std::make_unique(22, IntegerType::Modifier::Unsigned)}, + {std::make_unique(23, IntegerType::Modifier::Unsigned)}, + {std::make_unique(24, IntegerType::Modifier::Unsigned)}, + {std::make_unique(25, IntegerType::Modifier::Unsigned)}, + {std::make_unique(26, IntegerType::Modifier::Unsigned)}, + {std::make_unique(27, IntegerType::Modifier::Unsigned)}, + {std::make_unique(28, IntegerType::Modifier::Unsigned)}, + {std::make_unique(29, IntegerType::Modifier::Unsigned)}, + {std::make_unique(30, IntegerType::Modifier::Unsigned)}, + {std::make_unique(31, IntegerType::Modifier::Unsigned)}, + {std::make_unique(32, IntegerType::Modifier::Unsigned)}, + {std::make_unique(33, IntegerType::Modifier::Unsigned)}, + {std::make_unique(34, IntegerType::Modifier::Unsigned)}, + {std::make_unique(35, IntegerType::Modifier::Unsigned)}, + {std::make_unique(36, IntegerType::Modifier::Unsigned)}, + {std::make_unique(37, IntegerType::Modifier::Unsigned)}, + {std::make_unique(38, IntegerType::Modifier::Unsigned)}, + {std::make_unique(39, IntegerType::Modifier::Unsigned)}, + {std::make_unique(40, IntegerType::Modifier::Unsigned)}, + {std::make_unique(41, IntegerType::Modifier::Unsigned)}, + {std::make_unique(42, IntegerType::Modifier::Unsigned)}, + {std::make_unique(43, IntegerType::Modifier::Unsigned)}, + {std::make_unique(44, IntegerType::Modifier::Unsigned)}, + {std::make_unique(45, IntegerType::Modifier::Unsigned)}, + {std::make_unique(46, IntegerType::Modifier::Unsigned)}, + {std::make_unique(47, IntegerType::Modifier::Unsigned)}, + {std::make_unique(48, IntegerType::Modifier::Unsigned)}, + {std::make_unique(49, IntegerType::Modifier::Unsigned)}, + {std::make_unique(50, IntegerType::Modifier::Unsigned)}, + {std::make_unique(51, IntegerType::Modifier::Unsigned)}, + {std::make_unique(52, IntegerType::Modifier::Unsigned)}, + {std::make_unique(53, IntegerType::Modifier::Unsigned)}, + {std::make_unique(54, IntegerType::Modifier::Unsigned)}, + {std::make_unique(55, IntegerType::Modifier::Unsigned)}, + {std::make_unique(56, IntegerType::Modifier::Unsigned)}, + {std::make_unique(57, IntegerType::Modifier::Unsigned)}, + {std::make_unique(58, IntegerType::Modifier::Unsigned)}, + {std::make_unique(59, IntegerType::Modifier::Unsigned)}, + {std::make_unique(60, IntegerType::Modifier::Unsigned)}, + {std::make_unique(61, IntegerType::Modifier::Unsigned)}, + {std::make_unique(62, IntegerType::Modifier::Unsigned)}, + {std::make_unique(63, IntegerType::Modifier::Unsigned)}, + {std::make_unique(64, IntegerType::Modifier::Unsigned)}, + {std::make_unique(65, IntegerType::Modifier::Unsigned)}, + {std::make_unique(66, IntegerType::Modifier::Unsigned)}, + {std::make_unique(67, IntegerType::Modifier::Unsigned)}, + {std::make_unique(68, IntegerType::Modifier::Unsigned)}, + {std::make_unique(69, IntegerType::Modifier::Unsigned)}, + {std::make_unique(70, IntegerType::Modifier::Unsigned)}, + {std::make_unique(71, IntegerType::Modifier::Unsigned)}, + {std::make_unique(72, IntegerType::Modifier::Unsigned)}, + {std::make_unique(73, IntegerType::Modifier::Unsigned)}, + {std::make_unique(74, IntegerType::Modifier::Unsigned)}, + {std::make_unique(75, IntegerType::Modifier::Unsigned)}, + {std::make_unique(76, IntegerType::Modifier::Unsigned)}, + {std::make_unique(77, IntegerType::Modifier::Unsigned)}, + {std::make_unique(78, IntegerType::Modifier::Unsigned)}, + {std::make_unique(79, IntegerType::Modifier::Unsigned)}, + {std::make_unique(80, IntegerType::Modifier::Unsigned)}, + {std::make_unique(81, IntegerType::Modifier::Unsigned)}, + {std::make_unique(82, IntegerType::Modifier::Unsigned)}, + {std::make_unique(83, IntegerType::Modifier::Unsigned)}, + {std::make_unique(84, IntegerType::Modifier::Unsigned)}, + {std::make_unique(85, IntegerType::Modifier::Unsigned)}, + {std::make_unique(86, IntegerType::Modifier::Unsigned)}, + {std::make_unique(87, IntegerType::Modifier::Unsigned)}, + {std::make_unique(88, IntegerType::Modifier::Unsigned)}, + {std::make_unique(89, IntegerType::Modifier::Unsigned)}, + {std::make_unique(90, IntegerType::Modifier::Unsigned)}, + {std::make_unique(91, IntegerType::Modifier::Unsigned)}, + {std::make_unique(92, IntegerType::Modifier::Unsigned)}, + {std::make_unique(93, IntegerType::Modifier::Unsigned)}, + {std::make_unique(94, IntegerType::Modifier::Unsigned)}, + {std::make_unique(95, IntegerType::Modifier::Unsigned)}, + {std::make_unique(96, IntegerType::Modifier::Unsigned)}, + {std::make_unique(97, IntegerType::Modifier::Unsigned)}, + {std::make_unique(98, IntegerType::Modifier::Unsigned)}, + {std::make_unique(99, IntegerType::Modifier::Unsigned)}, + {std::make_unique(100, IntegerType::Modifier::Unsigned)}, + {std::make_unique(101, IntegerType::Modifier::Unsigned)}, + {std::make_unique(102, IntegerType::Modifier::Unsigned)}, + {std::make_unique(103, IntegerType::Modifier::Unsigned)}, + {std::make_unique(104, IntegerType::Modifier::Unsigned)}, + {std::make_unique(105, IntegerType::Modifier::Unsigned)}, + {std::make_unique(106, IntegerType::Modifier::Unsigned)}, + {std::make_unique(107, IntegerType::Modifier::Unsigned)}, + {std::make_unique(108, IntegerType::Modifier::Unsigned)}, + {std::make_unique(109, IntegerType::Modifier::Unsigned)}, + {std::make_unique(110, IntegerType::Modifier::Unsigned)}, + {std::make_unique(111, IntegerType::Modifier::Unsigned)}, + {std::make_unique(112, IntegerType::Modifier::Unsigned)}, + {std::make_unique(113, IntegerType::Modifier::Unsigned)}, + {std::make_unique(114, IntegerType::Modifier::Unsigned)}, + {std::make_unique(115, IntegerType::Modifier::Unsigned)}, + {std::make_unique(116, IntegerType::Modifier::Unsigned)}, + {std::make_unique(117, IntegerType::Modifier::Unsigned)}, + {std::make_unique(118, IntegerType::Modifier::Unsigned)}, + {std::make_unique(119, IntegerType::Modifier::Unsigned)}, + {std::make_unique(120, IntegerType::Modifier::Unsigned)}, + {std::make_unique(121, IntegerType::Modifier::Unsigned)}, + {std::make_unique(122, IntegerType::Modifier::Unsigned)}, + {std::make_unique(123, IntegerType::Modifier::Unsigned)}, + {std::make_unique(124, IntegerType::Modifier::Unsigned)}, + {std::make_unique(125, IntegerType::Modifier::Unsigned)}, + {std::make_unique(126, IntegerType::Modifier::Unsigned)}, + {std::make_unique(127, IntegerType::Modifier::Unsigned)}, + {std::make_unique(128, IntegerType::Modifier::Unsigned)}, + {std::make_unique(129, IntegerType::Modifier::Unsigned)}, + {std::make_unique(130, IntegerType::Modifier::Unsigned)}, + {std::make_unique(131, IntegerType::Modifier::Unsigned)}, + {std::make_unique(132, IntegerType::Modifier::Unsigned)}, + {std::make_unique(133, IntegerType::Modifier::Unsigned)}, + {std::make_unique(134, IntegerType::Modifier::Unsigned)}, + {std::make_unique(135, IntegerType::Modifier::Unsigned)}, + {std::make_unique(136, IntegerType::Modifier::Unsigned)}, + {std::make_unique(137, IntegerType::Modifier::Unsigned)}, + {std::make_unique(138, IntegerType::Modifier::Unsigned)}, + {std::make_unique(139, IntegerType::Modifier::Unsigned)}, + {std::make_unique(140, IntegerType::Modifier::Unsigned)}, + {std::make_unique(141, IntegerType::Modifier::Unsigned)}, + {std::make_unique(142, IntegerType::Modifier::Unsigned)}, + {std::make_unique(143, IntegerType::Modifier::Unsigned)}, + {std::make_unique(144, IntegerType::Modifier::Unsigned)}, + {std::make_unique(145, IntegerType::Modifier::Unsigned)}, + {std::make_unique(146, IntegerType::Modifier::Unsigned)}, + {std::make_unique(147, IntegerType::Modifier::Unsigned)}, + {std::make_unique(148, IntegerType::Modifier::Unsigned)}, + {std::make_unique(149, IntegerType::Modifier::Unsigned)}, + {std::make_unique(150, IntegerType::Modifier::Unsigned)}, + {std::make_unique(151, IntegerType::Modifier::Unsigned)}, + {std::make_unique(152, IntegerType::Modifier::Unsigned)}, + {std::make_unique(153, IntegerType::Modifier::Unsigned)}, + {std::make_unique(154, IntegerType::Modifier::Unsigned)}, + {std::make_unique(155, IntegerType::Modifier::Unsigned)}, + {std::make_unique(156, IntegerType::Modifier::Unsigned)}, + {std::make_unique(157, IntegerType::Modifier::Unsigned)}, + {std::make_unique(158, IntegerType::Modifier::Unsigned)}, + {std::make_unique(159, IntegerType::Modifier::Unsigned)}, + {std::make_unique(160, IntegerType::Modifier::Unsigned)}, + {std::make_unique(161, IntegerType::Modifier::Unsigned)}, + {std::make_unique(162, IntegerType::Modifier::Unsigned)}, + {std::make_unique(163, IntegerType::Modifier::Unsigned)}, + {std::make_unique(164, IntegerType::Modifier::Unsigned)}, + {std::make_unique(165, IntegerType::Modifier::Unsigned)}, + {std::make_unique(166, IntegerType::Modifier::Unsigned)}, + {std::make_unique(167, IntegerType::Modifier::Unsigned)}, + {std::make_unique(168, IntegerType::Modifier::Unsigned)}, + {std::make_unique(169, IntegerType::Modifier::Unsigned)}, + {std::make_unique(170, IntegerType::Modifier::Unsigned)}, + {std::make_unique(171, IntegerType::Modifier::Unsigned)}, + {std::make_unique(172, IntegerType::Modifier::Unsigned)}, + {std::make_unique(173, IntegerType::Modifier::Unsigned)}, + {std::make_unique(174, IntegerType::Modifier::Unsigned)}, + {std::make_unique(175, IntegerType::Modifier::Unsigned)}, + {std::make_unique(176, IntegerType::Modifier::Unsigned)}, + {std::make_unique(177, IntegerType::Modifier::Unsigned)}, + {std::make_unique(178, IntegerType::Modifier::Unsigned)}, + {std::make_unique(179, IntegerType::Modifier::Unsigned)}, + {std::make_unique(180, IntegerType::Modifier::Unsigned)}, + {std::make_unique(181, IntegerType::Modifier::Unsigned)}, + {std::make_unique(182, IntegerType::Modifier::Unsigned)}, + {std::make_unique(183, IntegerType::Modifier::Unsigned)}, + {std::make_unique(184, IntegerType::Modifier::Unsigned)}, + {std::make_unique(185, IntegerType::Modifier::Unsigned)}, + {std::make_unique(186, IntegerType::Modifier::Unsigned)}, + {std::make_unique(187, IntegerType::Modifier::Unsigned)}, + {std::make_unique(188, IntegerType::Modifier::Unsigned)}, + {std::make_unique(189, IntegerType::Modifier::Unsigned)}, + {std::make_unique(190, IntegerType::Modifier::Unsigned)}, + {std::make_unique(191, IntegerType::Modifier::Unsigned)}, + {std::make_unique(192, IntegerType::Modifier::Unsigned)}, + {std::make_unique(193, IntegerType::Modifier::Unsigned)}, + {std::make_unique(194, IntegerType::Modifier::Unsigned)}, + {std::make_unique(195, IntegerType::Modifier::Unsigned)}, + {std::make_unique(196, IntegerType::Modifier::Unsigned)}, + {std::make_unique(197, IntegerType::Modifier::Unsigned)}, + {std::make_unique(198, IntegerType::Modifier::Unsigned)}, + {std::make_unique(199, IntegerType::Modifier::Unsigned)}, + {std::make_unique(200, IntegerType::Modifier::Unsigned)}, + {std::make_unique(201, IntegerType::Modifier::Unsigned)}, + {std::make_unique(202, IntegerType::Modifier::Unsigned)}, + {std::make_unique(203, IntegerType::Modifier::Unsigned)}, + {std::make_unique(204, IntegerType::Modifier::Unsigned)}, + {std::make_unique(205, IntegerType::Modifier::Unsigned)}, + {std::make_unique(206, IntegerType::Modifier::Unsigned)}, + {std::make_unique(207, IntegerType::Modifier::Unsigned)}, + {std::make_unique(208, IntegerType::Modifier::Unsigned)}, + {std::make_unique(209, IntegerType::Modifier::Unsigned)}, + {std::make_unique(210, IntegerType::Modifier::Unsigned)}, + {std::make_unique(211, IntegerType::Modifier::Unsigned)}, + {std::make_unique(212, IntegerType::Modifier::Unsigned)}, + {std::make_unique(213, IntegerType::Modifier::Unsigned)}, + {std::make_unique(214, IntegerType::Modifier::Unsigned)}, + {std::make_unique(215, IntegerType::Modifier::Unsigned)}, + {std::make_unique(216, IntegerType::Modifier::Unsigned)}, + {std::make_unique(217, IntegerType::Modifier::Unsigned)}, + {std::make_unique(218, IntegerType::Modifier::Unsigned)}, + {std::make_unique(219, IntegerType::Modifier::Unsigned)}, + {std::make_unique(220, IntegerType::Modifier::Unsigned)}, + {std::make_unique(221, IntegerType::Modifier::Unsigned)}, + {std::make_unique(222, IntegerType::Modifier::Unsigned)}, + {std::make_unique(223, IntegerType::Modifier::Unsigned)}, + {std::make_unique(224, IntegerType::Modifier::Unsigned)}, + {std::make_unique(225, IntegerType::Modifier::Unsigned)}, + {std::make_unique(226, IntegerType::Modifier::Unsigned)}, + {std::make_unique(227, IntegerType::Modifier::Unsigned)}, + {std::make_unique(228, IntegerType::Modifier::Unsigned)}, + {std::make_unique(229, IntegerType::Modifier::Unsigned)}, + {std::make_unique(230, IntegerType::Modifier::Unsigned)}, + {std::make_unique(231, IntegerType::Modifier::Unsigned)}, + {std::make_unique(232, IntegerType::Modifier::Unsigned)}, + {std::make_unique(233, IntegerType::Modifier::Unsigned)}, + {std::make_unique(234, IntegerType::Modifier::Unsigned)}, + {std::make_unique(235, IntegerType::Modifier::Unsigned)}, + {std::make_unique(236, IntegerType::Modifier::Unsigned)}, + {std::make_unique(237, IntegerType::Modifier::Unsigned)}, + {std::make_unique(238, IntegerType::Modifier::Unsigned)}, + {std::make_unique(239, IntegerType::Modifier::Unsigned)}, + {std::make_unique(240, IntegerType::Modifier::Unsigned)}, + {std::make_unique(241, IntegerType::Modifier::Unsigned)}, + {std::make_unique(242, IntegerType::Modifier::Unsigned)}, + {std::make_unique(243, IntegerType::Modifier::Unsigned)}, + {std::make_unique(244, IntegerType::Modifier::Unsigned)}, + {std::make_unique(245, IntegerType::Modifier::Unsigned)}, + {std::make_unique(246, IntegerType::Modifier::Unsigned)}, + {std::make_unique(247, IntegerType::Modifier::Unsigned)}, + {std::make_unique(248, IntegerType::Modifier::Unsigned)}, + {std::make_unique(249, IntegerType::Modifier::Unsigned)}, + {std::make_unique(250, IntegerType::Modifier::Unsigned)}, + {std::make_unique(251, IntegerType::Modifier::Unsigned)}, + {std::make_unique(252, IntegerType::Modifier::Unsigned)}, + {std::make_unique(253, IntegerType::Modifier::Unsigned)}, + {std::make_unique(254, IntegerType::Modifier::Unsigned)}, + {std::make_unique(255, IntegerType::Modifier::Unsigned)}, + {std::make_unique(256, IntegerType::Modifier::Unsigned)}, }}; -array, 32> const TypeProvider::m_bytesM{{ - {make_unique(1)}, - {make_unique(2)}, - {make_unique(3)}, - {make_unique(4)}, - {make_unique(5)}, - {make_unique(6)}, - {make_unique(7)}, - {make_unique(8)}, - {make_unique(9)}, - {make_unique(10)}, - {make_unique(11)}, - {make_unique(12)}, - {make_unique(13)}, - {make_unique(14)}, - {make_unique(15)}, - {make_unique(16)}, - {make_unique(17)}, - {make_unique(18)}, - {make_unique(19)}, - {make_unique(20)}, - {make_unique(21)}, - {make_unique(22)}, - {make_unique(23)}, - {make_unique(24)}, - {make_unique(25)}, - {make_unique(26)}, - {make_unique(27)}, - {make_unique(28)}, - {make_unique(29)}, - {make_unique(30)}, - {make_unique(31)}, - {make_unique(32)} +std::array, 32> const TypeProvider::m_bytesM{{ + {std::make_unique(1)}, + {std::make_unique(2)}, + {std::make_unique(3)}, + {std::make_unique(4)}, + {std::make_unique(5)}, + {std::make_unique(6)}, + {std::make_unique(7)}, + {std::make_unique(8)}, + {std::make_unique(9)}, + {std::make_unique(10)}, + {std::make_unique(11)}, + {std::make_unique(12)}, + {std::make_unique(13)}, + {std::make_unique(14)}, + {std::make_unique(15)}, + {std::make_unique(16)}, + {std::make_unique(17)}, + {std::make_unique(18)}, + {std::make_unique(19)}, + {std::make_unique(20)}, + {std::make_unique(21)}, + {std::make_unique(22)}, + {std::make_unique(23)}, + {std::make_unique(24)}, + {std::make_unique(25)}, + {std::make_unique(26)}, + {std::make_unique(27)}, + {std::make_unique(28)}, + {std::make_unique(29)}, + {std::make_unique(30)}, + {std::make_unique(31)}, + {std::make_unique(32)} }}; -array, 8> const TypeProvider::m_magics{{ - {make_unique(MagicType::Kind::Block)}, - {make_unique(MagicType::Kind::Message)}, - {make_unique(MagicType::Kind::Transaction)}, - {make_unique(MagicType::Kind::ABI)}, - {make_unique(MagicType::Kind::TVM)}, - {make_unique(MagicType::Kind::Math)}, - {make_unique(MagicType::Kind::Rnd)}, - {make_unique(MagicType::Kind::Gosh)} +std::array, 8> const TypeProvider::m_magics{{ + {std::make_unique(MagicType::Kind::Block)}, + {std::make_unique(MagicType::Kind::Message)}, + {std::make_unique(MagicType::Kind::Transaction)}, + {std::make_unique(MagicType::Kind::ABI)}, + {std::make_unique(MagicType::Kind::TVM)}, + {std::make_unique(MagicType::Kind::Math)}, + {std::make_unique(MagicType::Kind::Rnd)}, + {std::make_unique(MagicType::Kind::Gosh)} // MetaType is stored separately }}; @@ -626,7 +624,7 @@ inline void clearCache(Type const& type) } template -inline void clearCache(unique_ptr const& type) +inline void clearCache(std::unique_ptr const& type) { // Some lazy-initialized types might not exist yet. if (type) @@ -667,7 +665,7 @@ void TypeProvider::reset() template inline T const* TypeProvider::createAndGet(Args&& ... _args) { - instance().m_generalTypes.emplace_back(make_unique(std::forward(_args)...)); + instance().m_generalTypes.emplace_back(std::make_unique(std::forward(_args)...)); return static_cast(instance().m_generalTypes.back().get()); } @@ -687,6 +685,8 @@ Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const& return varInteger(32, IntegerType::Modifier::Unsigned); case Token::VarUintM: return varInteger(m, IntegerType::Modifier::Unsigned); + case Token::coins: + return coins(); case Token::VarInt: return varInteger(32, IntegerType::Modifier::Signed); case Token::VarIntM: @@ -735,20 +735,20 @@ Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const& } } -Type const* TypeProvider::fromElementaryTypeName(string const& _name) +Type const* TypeProvider::fromElementaryTypeName(std::string const& _name) { - vector nameParts; + std::vector nameParts; boost::split(nameParts, _name, boost::is_any_of(" ")); solAssert(nameParts.size() == 1 || nameParts.size() == 2, "Cannot parse elementary type: " + _name); Token token; unsigned short firstNum, secondNum; - tie(token, firstNum, secondNum) = TokenTraits::fromIdentifierOrKeyword(nameParts[0]); + std::tie(token, firstNum, secondNum) = TokenTraits::fromIdentifierOrKeyword(nameParts[0]); auto t = fromElementaryTypeName(ElementaryTypeNameToken(token, firstNum, secondNum)); - if (auto* ref = dynamic_cast(t)) + if (auto* ref = dynamic_cast(t)) { - return withLocation(ref, true); + return ref; } else if (t->category() == Type::Category::Address) { @@ -771,42 +771,42 @@ Type const* TypeProvider::fromElementaryTypeName(string const& _name) ArrayType const* TypeProvider::bytesStorage() { if (!m_bytesStorage) - m_bytesStorage = make_unique(false); + m_bytesStorage = std::make_unique(false); return m_bytesStorage.get(); } ArrayType const* TypeProvider::bytesMemory() { if (!m_bytesMemory) - m_bytesMemory = make_unique(false); + m_bytesMemory = std::make_unique(false); return m_bytesMemory.get(); } ArrayType const* TypeProvider::bytesCalldata() { if (!m_bytesCalldata) - m_bytesCalldata = make_unique(false); + m_bytesCalldata = std::make_unique(false); return m_bytesCalldata.get(); } ArrayType const* TypeProvider::stringStorage() { if (!m_stringStorage) - m_stringStorage = make_unique(true); + m_stringStorage = std::make_unique(true); return m_stringStorage.get(); } Variant const* TypeProvider::variant() { if (!m_variant) - m_variant = make_unique(); + m_variant = std::make_unique(); return m_variant.get(); } ArrayType const* TypeProvider::stringMemory() { if (!m_stringMemory) - m_stringMemory = make_unique(true); + m_stringMemory = std::make_unique(true); return m_stringMemory.get(); } @@ -851,43 +851,47 @@ RationalNumberType const* TypeProvider::rationalNumber(Literal const& _literal) return nullptr; } -StringLiteralType const* TypeProvider::stringLiteral(string const& literal) +StringLiteralType const* TypeProvider::stringLiteral(std::string const& literal) { auto i = instance().m_stringLiteralTypes.find(literal); if (i != instance().m_stringLiteralTypes.end()) return i->second.get(); else - return instance().m_stringLiteralTypes.emplace(literal, make_unique(literal)).first->second.get(); + return instance().m_stringLiteralTypes.emplace(literal, std::make_unique(literal)).first->second.get(); } VarIntegerType const* TypeProvider::varInteger(unsigned m, IntegerType::Modifier _modifier) { auto& map = instance().m_varInterger; - auto i = map.find(make_pair(m, _modifier)); + auto i = map.find(std::make_pair(m, _modifier)); if (i != map.end()) return i->second.get(); return map.emplace( - make_pair(m, _modifier), - make_unique(m, _modifier) + std::make_pair(m, _modifier), + std::make_unique(m, _modifier) ).first->second.get(); } +VarIntegerType const* TypeProvider::coins() { + return TypeProvider::varInteger(16, IntegerType::Modifier::Unsigned); +} + FixedPointType const* TypeProvider::fixedPoint(unsigned m, unsigned n, FixedPointType::Modifier _modifier) { auto& map = _modifier == FixedPointType::Modifier::Unsigned ? instance().m_ufixedMxN : instance().m_fixedMxN; - auto i = map.find(make_pair(m, n)); + auto i = map.find(std::make_pair(m, n)); if (i != map.end()) return i->second.get(); return map.emplace( - make_pair(m, n), - make_unique(m, n, _modifier) + std::make_pair(m, n), + std::make_unique(m, n, _modifier) ).first->second.get(); } -TupleType const* TypeProvider::tuple(vector members) +TupleType const* TypeProvider::tuple(std::vector members) { if (members.empty()) return &m_emptyTuple; @@ -895,15 +899,6 @@ TupleType const* TypeProvider::tuple(vector members) return createAndGet(std::move(members)); } -ReferenceType const* TypeProvider::withLocation(ReferenceType const* _type, bool _isPointer) -{ - if (_type->isPointer() == _isPointer) - return _type; - - instance().m_generalTypes.emplace_back(_type->copyForLocation(_isPointer)); - return static_cast(instance().m_generalTypes.back().get()); -} - FunctionType const* TypeProvider::function(FunctionDefinition const& _function, FunctionType::Kind _kind) { return createAndGet(_function, _kind); @@ -938,7 +933,7 @@ FunctionType const* TypeProvider::function( ) { // Can only use this constructor for "arbitraryParameters". - solAssert(!_options.valueSet && !_options.gasSet && !_options.saltSet && !_options.bound); + solAssert(!_options.valueSet && !_options.gasSet && !_options.saltSet && !_options.hasBoundFirstArgument); return createAndGet( _parameterTypes, _returnParameterTypes, @@ -1046,16 +1041,16 @@ MagicType const* TypeProvider::meta(Type const* _type) return createAndGet(_type); } -MappingType const* TypeProvider::mapping(Type const* _keyType, Type const* _valueType) +MappingType const* TypeProvider::mapping(Type const* _keyType, ASTString _keyName, Type const* _valueType, ASTString _valueName) { - return createAndGet(_keyType, _valueType); + return createAndGet(_keyType, _keyName, _valueType, _valueName); } MappingType const *TypeProvider::extraCurrencyCollection() { auto keyType = TypeProvider::uint(32); auto valueType = TypeProvider::varInteger(32, IntegerType::Modifier::Unsigned); - return createAndGet(keyType, valueType); + return createAndGet(keyType, "", valueType, ""); } OptionalType const* TypeProvider::optional(Type const* _type) diff --git a/compiler/libsolidity/ast/TypeProvider.h b/compiler/libsolidity/ast/TypeProvider.h index f39e73a5..20e6bf62 100644 --- a/compiler/libsolidity/ast/TypeProvider.h +++ b/compiler/libsolidity/ast/TypeProvider.h @@ -93,10 +93,6 @@ class TypeProvider static IntegerType const* integer(unsigned _bits, IntegerType::Modifier _modifier) { - if (_bits == 257 && _modifier == IntegerType::Modifier::Signed) { - return m_int257.get(); - } - if (_modifier == IntegerType::Modifier::Unsigned) return m_uintM.at(_bits - 1).get(); else @@ -109,7 +105,9 @@ class TypeProvider static IntegerType const* uint256() { return uint(256); } static IntegerType const* int256() { return integer(256, IntegerType::Modifier::Signed); } + static VarIntegerType const* coins(); static VarIntegerType const* varInteger(unsigned m, IntegerType::Modifier _modifier); + static FixedPointType const* fixedPoint(unsigned m, unsigned n, FixedPointType::Modifier _modifier); static StringLiteralType const* stringLiteral(std::string const& literal); @@ -120,19 +118,6 @@ class TypeProvider static TupleType const* emptyTuple() noexcept { return &m_emptyTuple; } - static ReferenceType const* withLocation(ReferenceType const* _type, bool _isPointer); - - /// @returns a copy of @a _type having the same location as this (and is not a pointer type) - /// if _type is a reference type and an unmodified copy of _type otherwise. - /// This function is mostly useful to modify inner types appropriately. - static Type const* withLocationIfReference(Type const* _type, bool _isPointer = false) - { - if (auto refType = dynamic_cast(_type)) - return withLocation(refType, _isPointer); - - return _type; - } - /// @returns the internally-facing or externally-facing type of a function or the type of a function declaration. static FunctionType const* function(FunctionDefinition const& _function, FunctionType::Kind _kind = FunctionType::Kind::Declaration); @@ -197,7 +182,7 @@ class TypeProvider static MagicType const* meta(Type const* _type); - static MappingType const* mapping(Type const* _keyType, Type const* _valueType); + static MappingType const* mapping(Type const* _keyType, ASTString _keyName, Type const* _valueType, ASTString _valueName); static MappingType const* extraCurrencyCollection(); @@ -239,8 +224,7 @@ class TypeProvider static AddressType const m_address; static InitializerListType const m_initializerList; static CallListType const m_callList; - static std::unique_ptr const m_int257; - static std::array, 256> const m_intM; + static std::array, 257> const m_intM; static std::array, 256> const m_uintM; static std::array, 32> const m_bytesM; static std::array, 8> const m_magics; ///< MagicType's except MetaType diff --git a/compiler/libsolidity/ast/Types.cpp b/compiler/libsolidity/ast/Types.cpp index 8fe60ed6..dbc43bd3 100644 --- a/compiler/libsolidity/ast/Types.cpp +++ b/compiler/libsolidity/ast/Types.cpp @@ -48,12 +48,12 @@ #include #include #include +#include #include #include #include -using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; @@ -117,7 +117,7 @@ util::Result transformParametersToExternal(TypePointers const& _pa return transformed; } -string toStringInParentheses(TypePointers const& _types, bool _withoutDataLocation) +std::string toStringInParentheses(TypePointers const& _types, bool _withoutDataLocation) { return '(' + util::joinHumanReadable( _types | ranges::views::transform([&](auto const* _type) { return _type->toString(_withoutDataLocation); }), @@ -131,7 +131,7 @@ MemberList::Member::Member(Declaration const* _declaration, Type const* _type): Member(_declaration, _type, _declaration->name()) {} -MemberList::Member::Member(Declaration const* _declaration, Type const* _type, string _name): +MemberList::Member::Member(Declaration const* _declaration, Type const* _type, std::string _name): name(std::move(_name)), type(_type), declaration(_declaration) @@ -159,7 +159,7 @@ void StorageOffsets::computeOffsets(TypePointers const& _types) { bigint slotOffset = 0; unsigned byteOffset = 0; - map> offsets; + std::map> offsets; for (size_t i = 0; i < _types.size(); ++i) { Type const* type = _types[i]; @@ -172,7 +172,7 @@ void StorageOffsets::computeOffsets(TypePointers const& _types) byteOffset = 0; } solAssert(slotOffset < bigint(1) << 256 ,"Object too large for storage."); - offsets[i] = make_pair(u256(slotOffset), byteOffset); + offsets[i] = std::make_pair(u256(slotOffset), byteOffset); solAssert(type->storageSize() >= 1, "Invalid storage size."); if (type->storageSize() == 1 && byteOffset + type->storageBytes() <= 32) byteOffset += type->storageBytes(); @@ -189,7 +189,7 @@ void StorageOffsets::computeOffsets(TypePointers const& _types) swap(m_offsets, offsets); } -pair const* StorageOffsets::offset(size_t _index) const +std::pair const* StorageOffsets::offset(size_t _index) const { if (m_offsets.count(_index)) return &m_offsets.at(_index); @@ -202,7 +202,7 @@ void MemberList::combine(MemberList const & _other) m_memberTypes += _other.m_memberTypes; } -pair const* MemberList::memberStorageOffset(string const& _name) const +std::pair const* MemberList::memberStorageOffset(std::string const& _name) const { StorageOffsets const& offsets = storageOffsets(); @@ -235,33 +235,33 @@ StorageOffsets const& MemberList::storageOffsets() const { namespace { -string parenthesizeIdentifier(string const& _internal) +std::string parenthesizeIdentifier(std::string const& _internal) { return "(" + _internal + ")"; } template -string identifierList(Range const&& _list) +std::string identifierList(Range const&& _list) { return parenthesizeIdentifier(boost::algorithm::join(_list, ",")); } -string richIdentifier(Type const* _type) +std::string richIdentifier(Type const* _type) { return _type ? _type->richIdentifier() : ""; } -string identifierList(vector const& _list) +std::string identifierList(std::vector const& _list) { return identifierList(_list | ranges::views::transform(richIdentifier)); } -string identifierList(Type const* _type) +std::string identifierList(Type const* _type) { return parenthesizeIdentifier(richIdentifier(_type)); } -string identifierList(Type const* _type1, Type const* _type2) +std::string identifierList(Type const* _type1, Type const* _type2) { TypePointers list; list.push_back(_type1); @@ -269,16 +269,16 @@ string identifierList(Type const* _type1, Type const* _type2) return identifierList(list); } -string parenthesizeUserIdentifier(string const& _internal) +std::string parenthesizeUserIdentifier(std::string const& _internal) { return parenthesizeIdentifier(_internal); } } -string Type::escapeIdentifier(string const& _identifier) +std::string Type::escapeIdentifier(std::string const& _identifier) { - string ret = _identifier; + std::string ret = _identifier; // FIXME: should be _$$$_ boost::algorithm::replace_all(ret, "$", "$$$"); boost::algorithm::replace_all(ret, ",", "_$_"); @@ -287,12 +287,12 @@ string Type::escapeIdentifier(string const& _identifier) return ret; } -string Type::identifier() const +std::string Type::identifier() const { - string ret = escapeIdentifier(richIdentifier()); + std::string ret = escapeIdentifier(richIdentifier()); solAssert(ret.find_first_of("0123456789") != 0, "Identifier cannot start with a number."); solAssert( - ret.find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMONPQRSTUVWXYZ_$") == string::npos, + ret.find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMONPQRSTUVWXYZ_$") == std::string::npos, "Identifier contains invalid characters." ); return ret; @@ -321,8 +321,8 @@ MemberList const& Type::members(ASTNode const* _currentScope) const ""); MemberList::MemberMap members = nativeMembers(_currentScope); if (_currentScope) - members += boundFunctions(*this, *_currentScope); - m_members[_currentScope] = make_unique(std::move(members)); + members += attachedFunctions(*this, *_currentScope); + m_members[_currentScope] = std::make_unique(std::move(members)); } return *m_members[_currentScope]; } @@ -352,9 +352,12 @@ Type const* Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c return encodingType; } -MemberList::MemberMap Type::boundFunctions(Type const& _type, ASTNode const& _scope) +namespace +{ + +std::vector usingForDirectivesForType(Type const& _type, ASTNode const& _scope) { - vector usingForDirectives; + std::vector usingForDirectives; SourceUnit const* sourceUnit = dynamic_cast(&_scope); if (auto const* contract = dynamic_cast(&_scope)) { @@ -373,45 +376,82 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ASTNode const& _sc usingForDirectives.emplace_back(usingFor); // Normalise data location of type. + + return usingForDirectives | ranges::views::filter([&](UsingForDirective const* _directive) -> bool { + // Convert both types to pointers for comparison to see if the `using for` directive applies. + // Note that at this point we don't yet know if the functions are actually usable with the type. + // `_type` may not be convertible to the function parameter type. + return + !_directive->typeName() || + _type == *_directive->typeName()->annotation().type; + }) | ranges::to>; +} + +} + +std::set Type::operatorDefinitions( + Token _token, + ASTNode const& _scope, + bool _unary +) const +{ + if (!typeDefinition()) + return {}; + + std::set matchingDefinitions; + for (UsingForDirective const* directive: usingForDirectivesForType(*this, _scope)) + for (auto const& [identifierPath, operator_]: directive->functionsAndOperators()) + { + if (operator_ != _token) + continue; + + auto const& functionDefinition = dynamic_cast( + *identifierPath->annotation().referencedDeclaration + ); + auto const* functionType = dynamic_cast( + functionDefinition.libraryFunction() ? functionDefinition.typeViaContractName() : functionDefinition.type() + ); + solAssert(functionType && !functionType->parameterTypes().empty()); + + size_t parameterCount = functionDefinition.parameterList().parameters().size(); + if (*this == *functionType->parameterTypes().front() && (_unary ? parameterCount == 1 : parameterCount == 2)) + matchingDefinitions.insert(&functionDefinition); + } + + return matchingDefinitions; +} + +MemberList::MemberMap Type::attachedFunctions(Type const& _type, ASTNode const& _scope) +{ MemberList::MemberMap members; - set> seenFunctions; - auto addFunction = [&](FunctionDefinition const& _function, optional _name = {}) + std::set> seenFunctions; + auto addFunction = [&](FunctionDefinition const& _function, std::optional _name = {}) { if (!_name) _name = _function.name(); Type const* functionType = _function.libraryFunction() ? _function.typeViaContractName() : _function.type(); solAssert(functionType, ""); - FunctionType const* asBoundFunction = - dynamic_cast(*functionType).asBoundFunction(); - solAssert(asBoundFunction, ""); + FunctionType const* withBoundFirstArgument = + dynamic_cast(*functionType).withBoundFirstArgument(); + solAssert(withBoundFirstArgument, ""); - if (_type.isImplicitlyConvertibleTo(*asBoundFunction->selfType())) - if (seenFunctions.insert(make_pair(*_name, &_function)).second) - members.emplace_back(&_function, asBoundFunction, *_name); + if (_type.isImplicitlyConvertibleTo(*withBoundFirstArgument->selfType())) + if (seenFunctions.insert(std::make_pair(*_name, &_function)).second) + members.emplace_back(&_function, withBoundFirstArgument, *_name); }; - for (UsingForDirective const* ufd: usingForDirectives) - { - // Convert both types to pointers for comparison to see if the `using for` - // directive applies. - // Further down, we check more detailed for each function if `_type` is - // convertible to the function parameter type. - if ( - ufd->typeName() && - *TypeProvider::withLocationIfReference(&_type, true) != - *TypeProvider::withLocationIfReference( - ufd->typeName()->annotation().type, - true - ) - ) - continue; - - for (auto const& pathPointer: ufd->functionsOrLibrary()) + for (UsingForDirective const* ufd: usingForDirectivesForType(_type, _scope)) + for (auto const& [identifierPath, operator_]: ufd->functionsAndOperators()) { - solAssert(pathPointer); - Declaration const* declaration = pathPointer->annotation().referencedDeclaration; + if (operator_.has_value()) + // Functions used to define operators are not automatically attached to the type. + // I.e. `using {f, f as +} for T` allows `T x; x.f()` but `using {f as +} for T` does not. + continue; + + solAssert(identifierPath); + Declaration const* declaration = identifierPath->annotation().referencedDeclaration; solAssert(declaration); if (ContractDefinition const* library = dynamic_cast(declaration)) @@ -427,15 +467,14 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ASTNode const& _sc else addFunction( dynamic_cast(*declaration), - pathPointer->path().back() + identifierPath->path().back() ); } - } return members; } -string AddressType::richIdentifier() const +std::string AddressType::richIdentifier() const { return "t_address"; } @@ -459,12 +498,12 @@ BoolResult AddressType::isExplicitlyConvertibleTo(Type const& _convertTo) const return false; } -string AddressType::toString(bool) const +std::string AddressType::toString(bool) const { return "address"; } -string AddressType::canonicalName() const +std::string AddressType::canonicalName() const { return "address"; } @@ -498,7 +537,7 @@ bool AddressType::operator==(Type const& _other) const MemberList::MemberMap AddressType::nativeMembers(ASTNode const*) const { MemberList::MemberMap members = { - {"balance", TypeProvider::uint(128)}, + {"balance", TypeProvider::coins()}, {"currencies", TypeProvider::extraCurrencyCollection()}, {"wid", TypeProvider::integer(8, IntegerType::Modifier::Signed)}, {"value", TypeProvider::uint256()}, @@ -510,7 +549,7 @@ MemberList::MemberMap AddressType::nativeMembers(ASTNode const*) const TypePointers{}, TypePointers{TypeProvider::integer(32, IntegerType::Modifier::Signed), TypeProvider::uint256()}, strings{}, - strings{string(), string()}, + strings{std::string(), std::string()}, FunctionType::Kind::AddressUnpack, StateMutability::Pure )); @@ -518,7 +557,7 @@ MemberList::MemberMap AddressType::nativeMembers(ASTNode const*) const TypePointers{}, TypePointers{TypeProvider::uint(8)}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::AddressType, StateMutability::Pure )); @@ -526,13 +565,13 @@ MemberList::MemberMap AddressType::nativeMembers(ASTNode const*) const TypePointers{}, TypePointers{TypeProvider::boolean()}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::AddressIsStdAddrWithoutAnyCast, StateMutability::Pure )); members.emplace_back("transfer", TypeProvider::function( { - TypeProvider::uint(128), + TypeProvider::coins(), TypeProvider::boolean(), TypeProvider::uint(16), TypeProvider::tvmcell(), @@ -581,38 +620,25 @@ IntegerType::IntegerType(unsigned _bits, IntegerType::Modifier _modifier): } } -string IntegerType::richIdentifier() const +std::string IntegerType::richIdentifier() const { - return "t_" + string(isSigned() ? "" : "u") + "int" + to_string(numBits()); + return "t_" + std::string(isSigned() ? "" : "u") + "int" + std::to_string(numBits()); } BoolResult IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const { - if (Type::isImplicitlyConvertibleTo(_convertTo)) { + if (Type::isImplicitlyConvertibleTo(_convertTo)) return true; - } if (_convertTo.category() == Category::VarInteger) { IntegerType const& convertTo = dynamic_cast(_convertTo).asIntegerType(); - // disallowing unsigned to signed conversion of different bits - if (isSigned() != convertTo.isSigned()) - return false; - else if (convertTo.m_bits < m_bits) - return false; - else - return true; + return maxValue() <= convertTo.maxValue() && minValue() >= convertTo.minValue(); } else if (_convertTo.category() == category()) { IntegerType const& convertTo = dynamic_cast(_convertTo); - // disallowing unsigned to signed conversion of different bits - if (isSigned() != convertTo.isSigned()) - return false; - else if (convertTo.m_bits < m_bits) - return false; - else - return true; + return maxValue() <= convertTo.maxValue() && minValue() >= convertTo.minValue(); } else if (_convertTo.category() == Category::FixedPoint) { @@ -635,18 +661,18 @@ BoolResult IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const { if (isImplicitlyConvertibleTo(_convertTo)) return true; - else if (auto integerType = dynamic_cast(&_convertTo)) - return (numBits() == integerType->numBits()) || (isSigned() == integerType->isSigned()); - else if (auto varIntegerType = dynamic_cast(&_convertTo)) - return (numBits() == varIntegerType->asIntegerType().numBits()) || (isSigned() == varIntegerType->asIntegerType().isSigned()); + + Type::Category const category = _convertTo.category(); + if (category == Type::Category::Integer || + category == Type::Category::VarInteger || + category == Type::Category::Enum || + category == Type::Category::FixedPoint + ) + return true; else if (dynamic_cast(&_convertTo) || dynamic_cast(&_convertTo)) return !isSigned(); else if (auto fixedBytesType = dynamic_cast(&_convertTo)) return (!isSigned() && (numBits() == fixedBytesType->numBytes() * 8)); - else if (dynamic_cast(&_convertTo)) - return true; - else if (auto fixedPointType = dynamic_cast(&_convertTo)) - return (isSigned() == fixedPointType->isSigned()) && (numBits() == fixedPointType->numBits()); return false; } @@ -673,12 +699,27 @@ bool IntegerType::operator==(Type const& _other) const return other.m_bits == m_bits && other.m_modifier == m_modifier; } -string IntegerType::toString(bool) const +std::string IntegerType::toString(bool) const { - string prefix = isSigned() ? "int" : "uint"; + std::string prefix = isSigned() ? "int" : "uint"; return prefix + util::toString(m_bits); } +MemberList::MemberMap IntegerType::nativeMembers(ASTNode const*) const { + MemberList::MemberMap members = { + {"cast", TypeProvider::function( + TypePointers{}, + TypePointers{}, + strings{}, + strings{}, + FunctionType::Kind::IntCast, + StateMutability::Pure, + nullptr, FunctionType::Options::withArbitraryParameters() + )} + }; + return members; +} + u256 IntegerType::min() const { if (isSigned()) @@ -765,15 +806,15 @@ FixedPointType::FixedPointType(unsigned _totalBits, unsigned _fractionalDigits, m_totalBits(_totalBits), m_fractionalDigits(_fractionalDigits), m_modifier(_modifier) { solAssert( - 8 <= m_totalBits && m_totalBits <= 256 && m_totalBits % 8 == 0 && m_fractionalDigits <= 80, + 8 <= m_totalBits && m_totalBits <= 256 && m_fractionalDigits <= 80, "Invalid bit number(s) for fixed type: " + util::toString(_totalBits) + "x" + util::toString(_fractionalDigits) ); } -string FixedPointType::richIdentifier() const +std::string FixedPointType::richIdentifier() const { - return "t_" + string(isSigned() ? "" : "u") + "fixed" + to_string(m_totalBits) + "x" + to_string(m_fractionalDigits); + return "t_" + std::string(isSigned() ? "" : "u") + "fixed" + std::to_string(m_totalBits) + "x" + std::to_string(m_fractionalDigits); } BoolResult FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const @@ -804,16 +845,17 @@ BoolResult FixedPointType::isExplicitlyConvertibleTo(Type const& _convertTo) con TypeResult FixedPointType::unaryOperatorResult(Token _operator) const { + solAssert(_operator != Token::Add); + switch (_operator) { case Token::Delete: // "delete" is ok for all fixed types return TypeResult{TypeProvider::emptyTuple()}; - case Token::Add: case Token::Sub: case Token::Inc: case Token::Dec: - // for fixed, we allow +, -, ++ and -- + // for fixed, we allow -, ++ and -- return this; default: return nullptr; @@ -828,9 +870,9 @@ bool FixedPointType::operator==(Type const& _other) const return other.m_totalBits == m_totalBits && other.m_fractionalDigits == m_fractionalDigits && other.m_modifier == m_modifier; } -string FixedPointType::toString(bool) const +std::string FixedPointType::toString(bool) const { - string prefix = isSigned() ? "fixed" : "ufixed"; + std::string prefix = isSigned() ? "fixed" : "ufixed"; return prefix + util::toString(m_totalBits) + "x" + util::toString(m_fractionalDigits); } @@ -871,7 +913,7 @@ IntegerType const* FixedPointType::asIntegerType() const return TypeProvider::integer(numBits(), isSigned() ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned); } -tuple RationalNumberType::parseRational(string const& _value) +std::tuple RationalNumberType::parseRational(std::string const& _value) { rational value; try @@ -884,7 +926,7 @@ tuple RationalNumberType::parseRational(string const& _value) !all_of(radixPoint + 1, _value.end(), util::isDigit) || !all_of(_value.begin(), radixPoint, util::isDigit) ) - return make_tuple(false, rational(0)); + return std::make_tuple(false, rational(0)); // Only decimal notation allowed here, leading zeros would switch to octal. auto fractionalBegin = find_if_not( @@ -896,25 +938,25 @@ tuple RationalNumberType::parseRational(string const& _value) rational numerator; rational denominator(1); - denominator = bigint(string(fractionalBegin, _value.end())); + denominator = bigint(std::string(fractionalBegin, _value.end())); denominator /= boost::multiprecision::pow( bigint(10), static_cast(distance(radixPoint + 1, _value.end())) ); - numerator = bigint(string(_value.begin(), radixPoint)); + numerator = bigint(std::string(_value.begin(), radixPoint)); value = numerator + denominator; } else value = bigint(_value); - return make_tuple(true, value); + return std::make_tuple(true, value); } catch (...) { - return make_tuple(false, rational(0)); + return std::make_tuple(false, rational(0)); } } -tuple RationalNumberType::isValidLiteral(Literal const& _literal) +std::tuple RationalNumberType::isValidLiteral(Literal const& _literal) { rational value; try @@ -933,27 +975,27 @@ tuple RationalNumberType::isValidLiteral(Literal const& _literal else if (expPoint != valueString.end()) { // Parse mantissa and exponent. Checks numeric limit. - tuple mantissa = parseRational(string(valueString.begin(), expPoint)); + std::tuple mantissa = parseRational(std::string(valueString.begin(), expPoint)); - if (!get<0>(mantissa)) - return make_tuple(false, rational(0)); - value = get<1>(mantissa); + if (!std::get<0>(mantissa)) + return std::make_tuple(false, rational(0)); + value = std::get<1>(mantissa); // 0E... is always zero. if (value == 0) - return make_tuple(true, rational(0)); + return std::make_tuple(true, rational(0)); - bigint exp = bigint(string(expPoint + 1, valueString.end())); + bigint exp = bigint(std::string(expPoint + 1, valueString.end())); - if (exp > numeric_limits::max() || exp < numeric_limits::min()) - return make_tuple(false, rational(0)); + if (exp > std::numeric_limits::max() || exp < std::numeric_limits::min()) + return std::make_tuple(false, rational(0)); uint32_t expAbs = bigint(abs(exp)).convert_to(); if (exp < 0) { if (!fitsPrecisionBase10(abs(value.denominator()), expAbs)) - return make_tuple(false, rational(0)); + return std::make_tuple(false, rational(0)); value /= boost::multiprecision::pow( bigint(10), expAbs @@ -962,7 +1004,7 @@ tuple RationalNumberType::isValidLiteral(Literal const& _literal else if (exp > 0) { if (!fitsPrecisionBase10(abs(value.numerator()), expAbs)) - return make_tuple(false, rational(0)); + return std::make_tuple(false, rational(0)); value *= boost::multiprecision::pow( bigint(10), expAbs @@ -972,15 +1014,15 @@ tuple RationalNumberType::isValidLiteral(Literal const& _literal else { // parse as rational number - tuple tmp = parseRational(valueString); - if (!get<0>(tmp)) + std::tuple tmp = parseRational(valueString); + if (!std::get<0>(tmp)) return tmp; - value = get<1>(tmp); + value = std::get<1>(tmp); } } catch (...) { - return make_tuple(false, rational(0)); + return std::make_tuple(false, rational(0)); } switch (_literal.subDenomination()) { @@ -1043,7 +1085,7 @@ tuple RationalNumberType::isValidLiteral(Literal const& _literal } - return make_tuple(true, value); + return std::make_tuple(true, value); } BoolResult RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const @@ -1115,7 +1157,7 @@ BoolResult RationalNumberType::isExplicitlyConvertibleTo(Type const& _convertTo) TypeResult RationalNumberType::unaryOperatorResult(Token _operator) const { - if (optional value = ConstantEvaluator::evaluateUnaryOperator(_operator, m_value)) + if (std::optional value = ConstantEvaluator::evaluateUnaryOperator(_operator, m_value)) return TypeResult{TypeProvider::rationalNumber(*value)}; else return nullptr; @@ -1173,10 +1215,10 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, Type const* return nullptr; return thisMobile->binaryOperatorResult(_operator, otherMobile); } - else if (optional value = ConstantEvaluator::evaluateBinaryOperator(_operator, m_value, other.m_value)) + else if (std::optional value = ConstantEvaluator::evaluateBinaryOperator(_operator, m_value, other.m_value)) { // verify that numerator and denominator fit into 4096 bit after every operation - if (value->numerator() != 0 && max(boost::multiprecision::msb(abs(value->numerator())), boost::multiprecision::msb(abs(value->denominator()))) > 4096) + if (value->numerator() != 0 && std::max(boost::multiprecision::msb(abs(value->numerator())), boost::multiprecision::msb(abs(value->denominator()))) > 4096) return TypeResult::err("Precision of rational constants is limited to 4096 bits."); return TypeResult{TypeProvider::rationalNumber(*value)}; @@ -1185,7 +1227,7 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, Type const* return nullptr; } -string RationalNumberType::richIdentifier() const +std::string RationalNumberType::richIdentifier() const { // rational seemingly will put the sign always on the numerator, // but let just make it deterministic here. @@ -1205,24 +1247,24 @@ bool RationalNumberType::operator==(Type const& _other) const return m_value == other.m_value; } -string RationalNumberType::bigintToReadableString(bigint const& _num) +std::string RationalNumberType::bigintToReadableString(bigint const& _num) { - string str = _num.str(); + std::string str = _num.str(); if (str.size() > 32) { size_t omitted = str.size() - 8; - str = str.substr(0, 4) + "...(" + to_string(omitted) + " digits omitted)..." + str.substr(str.size() - 4, 4); + str = str.substr(0, 4) + "...(" + std::to_string(omitted) + " digits omitted)..." + str.substr(str.size() - 4, 4); } return str; } -string RationalNumberType::toString(bool) const +std::string RationalNumberType::toString(bool) const { if (!isFractional()) return "int_const " + bigintToReadableString(m_value.numerator()); - string numerator = bigintToReadableString(m_value.numerator()); - string denominator = bigintToReadableString(m_value.denominator()); + std::string numerator = bigintToReadableString(m_value.numerator()); + std::string denominator = bigintToReadableString(m_value.denominator()); return "rational_const " + numerator + " / " + denominator; } @@ -1290,7 +1332,7 @@ IntegerType const* RationalNumberType::integerType() const return nullptr; else return TypeProvider::integer( - max(numberEncodingSize(value), 1u) * 8, + std::max(numberEncodingSize(value), 1u) * 8, negative ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned ); } @@ -1324,7 +1366,7 @@ FixedPointType const* RationalNumberType::fixedPointType() const if (v > u256(-1)) return nullptr; - unsigned totalBits = max(numberEncodingSize(v), 1u) * 8; + unsigned totalBits = std::max(numberEncodingSize(v), 1u) * 8; solAssert(totalBits <= 256, ""); return TypeProvider::fixedPoint( @@ -1338,7 +1380,7 @@ StringLiteralType::StringLiteralType(Literal const& _literal): { } -StringLiteralType::StringLiteralType(string _value): +StringLiteralType::StringLiteralType(std::string _value): m_value{std::move(_value)} { } @@ -1371,9 +1413,9 @@ BoolResult StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) return false; } -string StringLiteralType::richIdentifier() const +std::string StringLiteralType::richIdentifier() const { - // Since we have to return a valid identifier and the string itself may contain + // Since we have to return a valid identifier and the std::string itself may contain // anything, we hash it. return "t_stringliteral_" + util::toHex(util::keccak256(m_value).asBytes()); } @@ -1387,7 +1429,7 @@ bool StringLiteralType::operator==(Type const& _other) const std::string StringLiteralType::toString(bool) const { - auto isPrintableASCII = [](string const& s) + auto isPrintableASCII = [](std::string const& s) { for (auto c: s) { @@ -1451,8 +1493,6 @@ BoolResult FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) con return (!integerType->isSigned() && integerType->numBits() == numBytes() * 8); else if (dynamic_cast(&_convertTo)) return numBytes() == 32; - else if (auto fixedPointType = dynamic_cast(&_convertTo)) - return fixedPointType->numBits() == numBytes() * 8; return false; } @@ -1499,9 +1539,9 @@ MemberList::MemberMap FixedBytesType::nativeMembers(ASTNode const*) const return MemberList::MemberMap{MemberList::Member{"length", TypeProvider::uint(8)}}; } -string FixedBytesType::richIdentifier() const +std::string FixedBytesType::richIdentifier() const { - return "t_bytes" + to_string(m_bytes); + return "t_bytes" + std::to_string(m_bytes); } bool FixedBytesType::operator==(Type const& _other) const @@ -1601,10 +1641,10 @@ TypeResult ContractType::unaryOperatorResult(Token _operator) const return nullptr; } -vector CompositeType::fullDecomposition() const +std::vector CompositeType::fullDecomposition() const { - vector res = {this}; - unordered_set seen = {richIdentifier()}; + std::vector res = {this}; + std::unordered_set seen = {richIdentifier()}; for (size_t k = 0; k < res.size(); ++k) if (auto composite = dynamic_cast(res[k])) for (Type const* next: composite->decomposition()) @@ -1616,38 +1656,15 @@ vector CompositeType::fullDecomposition() const return res; } -Type const* ReferenceType::withLocation(bool _isPointer) const -{ - return TypeProvider::withLocation(this, _isPointer); -} - -TypeResult ReferenceType::unaryOperatorResult(Token _operator) const -{ +TypeResult CompositeType::unaryOperatorResult(Token _operator) const { if (_operator != Token::Delete) return nullptr; return TypeProvider::emptyTuple(); -} -bool ReferenceType::isPointer() const -{ - return m_isPointer; -} - -Type const* ReferenceType::copyForLocationIfReference(Type const* _type) const -{ - return TypeProvider::withLocationIfReference(_type); -} - -string ReferenceType::identifierLocationSuffix() const -{ - string id; - if (isPointer()) - id += "_ptr"; - return id; } ArrayType::ArrayType(bool _isString): - ReferenceType(), + CompositeType(), m_arrayKind(_isString ? ArrayKind::String : ArrayKind::Bytes), m_baseType{TypeProvider::byte()} { @@ -1711,7 +1728,7 @@ BoolResult ArrayType::isExplicitlyConvertibleTo(Type const& _convertTo) const if (isByteArray() && _convertTo.category() == Category::FixedBytes) return true; - // allow conversion bytes <-> string and bytes -> bytesNN + // allow conversion bytes <-> std::string and bytes -> bytesNN if (_convertTo.category() != category()) return isByteArrayOrString() && _convertTo.category() == Type::Category::FixedBytes; auto& convertTo = dynamic_cast(_convertTo); @@ -1720,9 +1737,9 @@ BoolResult ArrayType::isExplicitlyConvertibleTo(Type const& _convertTo) const return true; } -string ArrayType::richIdentifier() const +std::string ArrayType::richIdentifier() const { - string id; + std::string id; if (isString()) id = "t_string"; else if (isByteArrayOrString()) @@ -1736,7 +1753,6 @@ string ArrayType::richIdentifier() const else id += length().str(); } - id += identifierLocationSuffix(); return id; } @@ -1747,7 +1763,6 @@ bool ArrayType::operator==(Type const& _other) const return false; ArrayType const& other = dynamic_cast(_other); if ( - !ReferenceType::operator==(other) || other.isByteArray() != isByteArray() || other.isString() != isString() || other.isDynamicallySized() != isDynamicallySized() @@ -1771,7 +1786,7 @@ unsigned ArrayType::calldataEncodedSize(bool _padded) const { solAssert(!isDynamicallyEncoded(), ""); bigint size = unlimitedStaticCalldataSize(_padded); - solAssert(size <= numeric_limits::max(), "Array size does not fit unsigned."); + solAssert(size <= std::numeric_limits::max(), "Array size does not fit unsigned."); return unsigned(size); } @@ -1783,7 +1798,7 @@ unsigned ArrayType::calldataEncodedTailSize() const // length must still be present. return 32; bigint size = unlimitedStaticCalldataSize(false); - solAssert(size <= numeric_limits::max(), "Array size does not fit unsigned."); + solAssert(size <= std::numeric_limits::max(), "Array size does not fit unsigned."); return unsigned(size); } @@ -1817,17 +1832,17 @@ u256 ArrayType::storageSize() const else size = bigint(length()) * baseType()->storageSize(); solAssert(size < bigint(1) << 256, "Array too large for storage."); - return max(1, u256(size)); + return std::max(1, u256(size)); } -vector> ArrayType::makeStackItems() const +std::vector> ArrayType::makeStackItems() const { return {std::make_tuple("slot", TypeProvider::uint256())}; } -string ArrayType::toString(bool _withoutDataLocation) const +std::string ArrayType::toString(bool _withoutDataLocation) const { - string ret; + std::string ret; if (isString()) ret = "string"; else if (isByteArrayOrString()) @@ -1842,9 +1857,9 @@ string ArrayType::toString(bool _withoutDataLocation) const return ret; } -string ArrayType::humanReadableName() const +std::string ArrayType::humanReadableName() const { - string ret; + std::string ret; if (isString()) ret = "string"; else if (isByteArrayOrString()) @@ -1859,9 +1874,9 @@ string ArrayType::humanReadableName() const return ret; } -string ArrayType::canonicalName() const +std::string ArrayType::canonicalName() const { - string ret; + std::string ret; if (isString()) ret = "string"; else if (isByteArrayOrString()) @@ -1876,7 +1891,7 @@ string ArrayType::canonicalName() const return ret; } -string ArrayType::signatureInExternalFunction(bool _structsByName) const +std::string ArrayType::signatureInExternalFunction(bool _structsByName) const { if (isByteArrayOrString()) return canonicalName(); @@ -1943,7 +1958,7 @@ MemberList::MemberMap ArrayType::nativeMembers(ASTNode const*) const members.emplace_back("append", TypeProvider::function( TypePointers{isString() ? TypeProvider::stringMemory() : TypeProvider::bytesMemory()}, TypePointers{}, - strings{string("tail")}, + strings{std::string("tail")}, strings{}, FunctionType::Kind::StringMethod, StateMutability::Pure @@ -1964,7 +1979,7 @@ MemberList::MemberMap ArrayType::nativeMembers(ASTNode const*) const members.emplace_back("push", TypeProvider::function( TypePointers{baseType()}, TypePointers{}, - strings{string()}, + strings{std::string()}, strings{}, isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush, StateMutability::Pure @@ -1977,20 +1992,42 @@ MemberList::MemberMap ArrayType::nativeMembers(ASTNode const*) const FunctionType::Kind::ArrayPop, StateMutability::Pure )); + // TODO DELETE UNCOMMENT? +// members.emplace_back("push", TypeProvider::function( +// TypePointers{thisAsPointer}, +// TypePointers{baseType()}, +// strings{std::string()}, +// strings{std::string()}, +// FunctionType::Kind::ArrayPush +// )->withBoundFirstArgument()); +// members.emplace_back("push", TypeProvider::function( +// TypePointers{thisAsPointer, baseType()}, +// TypePointers{}, +// strings{std::string(),std::string()}, +// strings{}, +// FunctionType::Kind::ArrayPush +// )->withBoundFirstArgument()); +// members.emplace_back("pop", TypeProvider::function( +// TypePointers{thisAsPointer}, +// TypePointers{}, +// strings{std::string()}, +// strings{}, +// FunctionType::Kind::ArrayPop +// )->withBoundFirstArgument()); } else { members.emplace_back("substr", TypeProvider::function( TypePointers{TypeProvider::uint256()}, TypePointers{TypeProvider::stringMemory()}, - strings{string("from")}, - strings{string("substr")}, + strings{std::string("from")}, + strings{std::string("substr")}, FunctionType::Kind::StringSubstr, StateMutability::Pure )); members.emplace_back("substr", TypeProvider::function( TypePointers{TypeProvider::uint256(), TypeProvider::uint256()}, TypePointers{TypeProvider::stringMemory()}, - strings{string("from"), "to"}, - strings{string("substr")}, + strings{std::string("from"), "to"}, + strings{std::string("substr")}, FunctionType::Kind::StringSubstr, StateMutability::Pure )); @@ -1998,7 +2035,7 @@ MemberList::MemberMap ArrayType::nativeMembers(ASTNode const*) const TypePointers{}, TypePointers{TypeProvider::uint(32)}, strings{}, - strings{string("byteLength")}, + strings{std::string("byteLength")}, FunctionType::Kind::StringMethod, StateMutability::Pure )); @@ -2006,42 +2043,17 @@ MemberList::MemberMap ArrayType::nativeMembers(ASTNode const*) const members.emplace_back(name.c_str(), TypeProvider::function( TypePointers{TypeProvider::fixedBytes(1)}, TypePointers{TypeProvider::optional(TypeProvider::uint(32))}, - strings{string("symbol")}, - strings{string("pos")}, + strings{std::string("symbol")}, + strings{std::string("pos")}, FunctionType::Kind::StringMethod, StateMutability::Pure )); - // TODO DELETE OR SUPPORT -// if (isDynamicallySized() && location() == DataLocation::Storage) -// { -// Type const* thisAsPointer = TypeProvider::withLocation(this, location(), true); -// members.emplace_back("push", TypeProvider::function( -// TypePointers{thisAsPointer}, -// TypePointers{baseType()}, -// strings{string()}, -// strings{string()}, -// FunctionType::Kind::ArrayPush -// )->asBoundFunction()); -// members.emplace_back("push", TypeProvider::function( -// TypePointers{thisAsPointer, baseType()}, -// TypePointers{}, -// strings{string(),string()}, -// strings{}, -// FunctionType::Kind::ArrayPush -// )->asBoundFunction()); -// members.emplace_back("pop", TypeProvider::function( -// TypePointers{thisAsPointer}, -// TypePointers{}, -// strings{string()}, -// strings{}, -// FunctionType::Kind::ArrayPop -// )->asBoundFunction()); } members.emplace_back("find", TypeProvider::function( TypePointers{TypeProvider::stringMemory()}, TypePointers{TypeProvider::optional(TypeProvider::uint(32))}, - strings{string("substr")}, - strings{string("pos")}, + strings{std::string("substr")}, + strings{std::string("pos")}, FunctionType::Kind::StringMethod, StateMutability::Pure )); @@ -2126,8 +2138,8 @@ static void appendMapMethods(MemberList::MemberMap& members, Type const* keyType TypeProvider::function( TypePointers{keyType}, TypePointers{TypeProvider::optional(valueType)}, - strings{string{}}, - strings{string{}}, + strings{std::string{}}, + strings{std::string{}}, FunctionType::Kind::MappingFetch, StateMutability::Pure ) @@ -2136,8 +2148,8 @@ static void appendMapMethods(MemberList::MemberMap& members, Type const* keyType members.emplace_back("exists", TypeProvider::function( TypePointers{keyType}, TypePointers{TypeProvider::boolean()}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::MappingExists, StateMutability::Pure )); @@ -2145,7 +2157,7 @@ static void appendMapMethods(MemberList::MemberMap& members, Type const* keyType TypePointers{}, TypePointers{TypeProvider::boolean()}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::MappingEmpty, StateMutability::Pure )); @@ -2153,8 +2165,8 @@ static void appendMapMethods(MemberList::MemberMap& members, Type const* keyType members.emplace_back(name.c_str(), TypeProvider::function( TypePointers{keyType, valueType}, TypePointers{TypeProvider::boolean()}, - strings{string(), string()}, - strings{string()}, + strings{std::string(), std::string()}, + strings{std::string()}, FunctionType::Kind::MappingReplaceOrAdd, StateMutability::Pure )); @@ -2163,8 +2175,8 @@ static void appendMapMethods(MemberList::MemberMap& members, Type const* keyType members.emplace_back(name.c_str(), TypeProvider::function( TypePointers{keyType, valueType}, TypePointers{TypeProvider::optional(valueType)}, - strings{string(), string()}, - strings{string()}, + strings{std::string(), std::string()}, + strings{std::string()}, FunctionType::Kind::MappingGetSet, StateMutability::Pure )); @@ -2180,7 +2192,7 @@ MemberList::MemberMap MappingType::nativeMembers(ASTNode const*) const Type const* ArrayType::encodingType() const { - return TypeProvider::withLocation(this, true); + return this; } Type const* ArrayType::decodingType() const @@ -2199,7 +2211,7 @@ TypeResult ArrayType::interfaceType(bool) const result = baseInterfaceType; } else if (m_arrayKind != ArrayKind::Ordinary) - result = TypeProvider::withLocation(this, true); + result = this; else if (isDynamicallySized()) result = TypeProvider::array(baseInterfaceType); else @@ -2229,21 +2241,10 @@ u256 ArrayType::memoryDataSize() const solAssert(!isDynamicallySized(), ""); solAssert(!isByteArrayOrString(), ""); bigint size = bigint(m_length) * m_baseType->memoryHeadSize(); - solAssert(size <= numeric_limits::max(), "Array size does not fit u256."); + solAssert(size <= std::numeric_limits::max(), "Array size does not fit u256."); return u256(size); } -std::unique_ptr ArrayType::copyForLocation(bool _isPointer) const -{ - auto copy = make_unique(); - copy->m_isPointer = _isPointer; - copy->m_arrayKind = m_arrayKind; - copy->m_baseType = copy->copyForLocationIfReference(m_baseType); - copy->m_hasDynamicLength = m_hasDynamicLength; - copy->m_length = m_length; - return copy; -} - BoolResult ArraySliceType::isImplicitlyConvertibleTo(Type const& _other) const { if (Type::isImplicitlyConvertibleTo(_other)) { @@ -2262,7 +2263,7 @@ BoolResult ArraySliceType::isExplicitlyConvertibleTo(Type const& _convertTo) con m_arrayType.isExplicitlyConvertibleTo(_convertTo); } -string ArraySliceType::richIdentifier() const +std::string ArraySliceType::richIdentifier() const { return m_arrayType.richIdentifier() + "_slice"; } @@ -2274,12 +2275,12 @@ bool ArraySliceType::operator==(Type const& _other) const return false; } -string ArraySliceType::toString(bool _withoutDataLocation) const +std::string ArraySliceType::toString(bool _withoutDataLocation) const { return m_arrayType.toString(_withoutDataLocation) + " slice"; } -string ArraySliceType::humanReadableName() const +std::string ArraySliceType::humanReadableName() const { return m_arrayType.humanReadableName() + " slice"; } @@ -2301,9 +2302,9 @@ std::vector> ArraySliceType::makeStackItems return {{"offset", TypeProvider::uint256()}, {"length", TypeProvider::uint256()}}; } -string ContractType::richIdentifier() const +std::string ContractType::richIdentifier() const { - return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + to_string(m_contract.id()); + return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + std::to_string(m_contract.id()); } bool ContractType::operator==(Type const& _other) const @@ -2314,15 +2315,15 @@ bool ContractType::operator==(Type const& _other) const return other.m_contract == m_contract && other.m_super == m_super; } -string ContractType::toString(bool) const +std::string ContractType::toString(bool) const { return - string(m_contract.isLibrary() ? "library " : "contract ") + - string(m_super ? "super " : "") + + std::string(m_contract.isLibrary() ? "library " : "contract ") + + std::string(m_super ? "super " : "") + m_contract.name(); } -string ContractType::canonicalName() const +std::string ContractType::canonicalName() const { return *m_contract.annotation().canonicalName; } @@ -2348,9 +2349,9 @@ FunctionType const* ContractType::newExpressionType() const return m_constructorType; } -vector> ContractType::stateVariables() const +std::vector> ContractType::stateVariables() const { - vector variables; + std::vector variables; for (ContractDefinition const* contract: m_contract.annotation().linearizedBaseContracts | ranges::views::reverse) for (VariableDeclaration const* variable: contract->stateVariables()) if (!(variable->isConstant() || variable->immutable())) @@ -2361,16 +2362,16 @@ vector> ContractType::stateVar StorageOffsets offsets; offsets.computeOffsets(types); - vector> variablesAndOffsets; + std::vector> variablesAndOffsets; for (size_t index = 0; index < variables.size(); ++index) if (auto const* offset = offsets.offset(index)) variablesAndOffsets.emplace_back(variables[index], offset->first, offset->second); return variablesAndOffsets; } -vector ContractType::immutableVariables() const +std::vector ContractType::immutableVariables() const { - vector variables; + std::vector variables; for (ContractDefinition const* contract: m_contract.annotation().linearizedBaseContracts | ranges::views::reverse) for (VariableDeclaration const* variable: contract->stateVariables()) if (variable->immutable()) @@ -2378,12 +2379,12 @@ vector ContractType::immutableVariables() const return variables; } -vector> ContractType::makeStackItems() const +std::vector> ContractType::makeStackItems() const { if (m_super) return {}; else - return {make_tuple("address", TypeProvider::address())}; + return {std::make_tuple("address", TypeProvider::address())}; } void StructType::clearCache() const @@ -2411,9 +2412,9 @@ BoolResult StructType::isImplicitlyConvertibleTo(Type const& _convertTo) const return this->m_struct == convertTo.m_struct; } -string StructType::richIdentifier() const +std::string StructType::richIdentifier() const { - return "t_struct" + parenthesizeUserIdentifier(m_struct.name()) + to_string(m_struct.id()) + identifierLocationSuffix(); + return "t_struct" + parenthesizeUserIdentifier(m_struct.name()) + std::to_string(m_struct.id()); } bool StructType::operator==(Type const& _other) const @@ -2501,7 +2502,7 @@ bigint StructType::storageSizeUpperBound() const u256 StructType::storageSize() const { - return max(1, members(nullptr).storageSize()); + return std::max(1, members(nullptr).storageSize()); } bool StructType::containsNestedMapping() const @@ -2540,9 +2541,9 @@ bool StructType::containsNestedMapping() const return m_struct.annotation().containsNestedMapping.value(); } -string StructType::toString(bool /*_withoutDataLocation*/) const +std::string StructType::toString(bool /*_withoutDataLocation*/) const { - string ret = "struct " + *m_struct.annotation().canonicalName; + std::string ret = "struct " + *m_struct.annotation().canonicalName; return ret; } @@ -2557,7 +2558,7 @@ MemberList::MemberMap StructType::onlyMembers() const types.push_back(type); members.emplace_back( variable.get(), - copyForLocationIfReference(type) + type ); } return members; @@ -2575,7 +2576,7 @@ MemberList::MemberMap StructType::nativeMembers(ASTNode const*) const types.push_back(type); members.emplace_back( variable.get(), - copyForLocationIfReference(type) + type ); } members.emplace_back("unpack", TypeProvider::function( @@ -2600,21 +2601,14 @@ bool StructType::recursive() const return *m_struct.annotation().recursive; } -std::unique_ptr StructType::copyForLocation(bool _isPointer) const -{ - auto copy = make_unique(m_struct); - copy->m_isPointer = _isPointer; - return copy; -} - -string StructType::signatureInExternalFunction(bool _structsByName) const +std::string StructType::signatureInExternalFunction(bool _structsByName) const { if (_structsByName) return canonicalName(); else { TypePointers memberTypes = memoryMemberTypes(); - auto memberTypeStrings = memberTypes | ranges::views::transform([&](Type const* _t) -> string + auto memberTypeStrings = memberTypes | ranges::views::transform([&](Type const* _t) -> std::string { solAssert(_t, "Parameter should have external type."); auto t = _t->interfaceType(_structsByName); @@ -2625,7 +2619,7 @@ string StructType::signatureInExternalFunction(bool _structsByName) const } } -string StructType::canonicalName() const +std::string StructType::canonicalName() const { return *m_struct.annotation().canonicalName; } @@ -2637,25 +2631,25 @@ FunctionTypePointer StructType::constructorType() const for (auto const& member: onlyMembers()) { paramNames.push_back(member.name); - paramTypes.push_back(TypeProvider::withLocationIfReference(member.type)); + paramTypes.push_back(member.type); } return TypeProvider::function( paramTypes, - TypePointers{TypeProvider::withLocation(this, false)}, + TypePointers{this}, paramNames, strings(1, ""), FunctionType::Kind::Internal ); } -pair const& StructType::storageOffsetsOfMember(string const& _name) const +std::pair const& StructType::storageOffsetsOfMember(std::string const& _name) const { auto const* offsets = members(nullptr).memberStorageOffset(_name); solAssert(offsets, "Storage offset of non-existing member requested."); return *offsets; } -u256 StructType::memoryOffsetOfMember(string const& _name) const +u256 StructType::memoryOffsetOfMember(std::string const& _name) const { u256 offset; for (auto const& member: members(nullptr)) @@ -2672,19 +2666,19 @@ TypePointers StructType::memoryMemberTypes() const solAssert(!containsNestedMapping(), ""); TypePointers types; for (ASTPointer const& variable: m_struct.members()) - types.push_back(TypeProvider::withLocationIfReference(variable->annotation().type)); + types.push_back(variable->annotation().type); return types; } -vector> StructType::makeStackItems() const +std::vector> StructType::makeStackItems() const { return {std::make_tuple("slot", TypeProvider::uint256())}; } -vector StructType::decomposition() const +std::vector StructType::decomposition() const { - vector res; + std::vector res; for (MemberList::Member const& member: members(nullptr)) res.push_back(member.type); return res; @@ -2706,9 +2700,9 @@ TypeResult EnumType::unaryOperatorResult(Token _operator) const return _operator == Token::Delete ? TypeProvider::emptyTuple() : nullptr; } -string EnumType::richIdentifier() const +std::string EnumType::richIdentifier() const { - return "t_enum" + parenthesizeUserIdentifier(m_enum.name()) + to_string(m_enum.id()); + return "t_enum" + parenthesizeUserIdentifier(m_enum.name()) + std::to_string(m_enum.id()); } bool EnumType::operator==(Type const& _other) const @@ -2725,12 +2719,12 @@ unsigned EnumType::storageBytes() const return 1; } -string EnumType::toString(bool) const +std::string EnumType::toString(bool) const { - return string("enum ") + *m_enum.annotation().canonicalName; + return std::string("enum ") + *m_enum.annotation().canonicalName; } -string EnumType::canonicalName() const +std::string EnumType::canonicalName() const { return *m_enum.annotation().canonicalName; } @@ -2774,9 +2768,9 @@ Declaration const* UserDefinedValueType::typeDefinition() const return &m_definition; } -string UserDefinedValueType::richIdentifier() const +std::string UserDefinedValueType::richIdentifier() const { - return "t_userDefinedValueType" + parenthesizeIdentifier(m_definition.name()) + to_string(m_definition.id()); + return "t_userDefinedValueType" + parenthesizeIdentifier(m_definition.name()) + std::to_string(m_definition.id()); } bool UserDefinedValueType::operator==(Type const& _other) const @@ -2787,17 +2781,17 @@ bool UserDefinedValueType::operator==(Type const& _other) const return other.definition() == definition(); } -string UserDefinedValueType::toString(bool /* _withoutDataLocation */) const +std::string UserDefinedValueType::toString(bool /* _withoutDataLocation */) const { return *definition().annotation().canonicalName; } -string UserDefinedValueType::canonicalName() const +std::string UserDefinedValueType::canonicalName() const { return *definition().annotation().canonicalName; } -vector> UserDefinedValueType::makeStackItems() const +std::vector> UserDefinedValueType::makeStackItems() const { return underlyingType().stackItems(); } @@ -2826,7 +2820,7 @@ BoolResult TupleType::isImplicitlyConvertibleTo(Type const& _other) const return false; } -string TupleType::richIdentifier() const +std::string TupleType::richIdentifier() const { return "t_tuple" + identifierList(components()); } @@ -2846,22 +2840,22 @@ bool TupleType::operator==(Type const& _other) const return false; } -string TupleType::toString(bool _withoutDataLocation) const +std::string TupleType::toString(bool _withoutDataLocation) const { if (components().empty()) return "tuple()"; - string str = "tuple("; + std::string str = "tuple("; for (auto const& t: components()) str += (t ? t->toString(_withoutDataLocation) : "") + ","; str.pop_back(); return str + ")"; } -string TupleType::humanReadableName() const +std::string TupleType::humanReadableName() const { if (components().empty()) return "tuple()"; - string str = "tuple("; + std::string str = "tuple("; for (auto const& t: components()) str += (t ? t->humanReadableName() : "") + ","; str.pop_back(); @@ -2873,9 +2867,9 @@ u256 TupleType::storageSize() const solAssert(false, "Storage size of non-storable tuple type requested."); } -vector> TupleType::makeStackItems() const +std::vector> TupleType::makeStackItems() const { - vector> slots; + std::vector> slots; unsigned i = 1; for (auto const& t: components()) { @@ -2946,14 +2940,16 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): m_declaration(&_varDecl) { auto returnType = _varDecl.annotation().type; + ASTString returnName; while (true) { if (auto mappingType = dynamic_cast(returnType)) { m_parameterTypes.push_back(mappingType->keyType()); - m_parameterNames.emplace_back(""); + m_parameterNames.push_back(mappingType->keyName()); returnType = mappingType->valueType(); + returnName = mappingType->valueName(); } else if (auto arrayType = dynamic_cast(returnType)) { @@ -2978,19 +2974,15 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): if (auto arrayType = dynamic_cast(member.type)) if (!arrayType->isByteArrayOrString()) continue; - m_returnParameterTypes.push_back(TypeProvider::withLocationIfReference( - member.type - )); + m_returnParameterTypes.push_back(member.type); m_returnParameterNames.push_back(member.name); } } } else { - m_returnParameterTypes.push_back(TypeProvider::withLocationIfReference( - returnType - )); - m_returnParameterNames.emplace_back(""); + m_returnParameterTypes.push_back(returnType); + m_returnParameterNames.emplace_back(returnName); } solAssert( @@ -3102,11 +3094,11 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c ); } -vector FunctionType::parameterNames() const +std::vector FunctionType::parameterNames() const { - if (!bound()) + if (!hasBoundFirstArgument()) return m_parameterNames; - return vector(m_parameterNames.cbegin() + 1, m_parameterNames.cend()); + return std::vector(m_parameterNames.cbegin() + 1, m_parameterNames.cend()); } TypePointers FunctionType::returnParameterTypesWithoutDynamicTypes() const @@ -3133,7 +3125,7 @@ TypePointers FunctionType::returnParameterTypesWithoutDynamicTypes() const TypePointers FunctionType::parameterTypes() const { - if (!bound()) + if (!hasBoundFirstArgument()) return m_parameterTypes; return TypePointers(m_parameterTypes.cbegin() + 1, m_parameterTypes.cend()); } @@ -3143,11 +3135,13 @@ TypePointers const& FunctionType::parameterTypesIncludingSelf() const return m_parameterTypes; } -string FunctionType::richIdentifier() const +std::string FunctionType::richIdentifier() const { - string id = "t_function_"; + std::string id = "t_function_"; switch (m_kind) { + case Kind::IntCast: id += "integercast"; break; + case Kind::StructUnpack: id += "structunpack"; break; case Kind::OptionalGet: id += "optionalmethod"; break; @@ -3173,7 +3167,10 @@ string FunctionType::richIdentifier() const case Kind::TVMSliceLoadIntQ: id += "tvmsliceloadintq"; break; case Kind::TVMSliceLoadLE: id += "tvmsliceloadle"; break; case Kind::TVMSliceLoadQ: id += "tvmsliceloadq"; break; + case Kind::TVMSliceLoadRef: id += "tvmloadref"; break; + case Kind::TVMSliceLoadSlice: id += "tvmloadslice"; break; case Kind::TVMSliceLoadStateVars: id += "tvmslicedecodestatevars"; break; + case Kind::TVMSliceLoadTons: id += "tvmsliceloadtons"; break; case Kind::TVMSliceLoadUint: id += "tvmsliceloaduint"; break; case Kind::TVMSliceLoadUintQ: id += "tvmsliceloaduintq"; break; case Kind::TVMSlicePreLoadInt: id += "tvmslicepreloadint"; break; @@ -3196,14 +3193,23 @@ string FunctionType::richIdentifier() const case Kind::Stoi: id += "stoi"; break; case Kind::LogTVM: id += "logtvm"; break; case Kind::TVMAccept: id += "tvmaccept"; break; - case Kind::TVMBuildExtMsg: id += "tvmbuildextmsg"; break; - case Kind::TVMBuildIntMsg: id += "tvmbuildintmsg"; break; - case Kind::TVMBuildStateInit: id += "tvmbuildstateinit"; break; - case Kind::TVMBuildDataInit: id += "tvmbuilddatainit"; break; + + case Kind::ABIBuildExtMsg: id += "abibuildextmsg"; break; + case Kind::ABIBuildIntMsg: id += "abibuildintmsg"; break; + case Kind::ABICodeSalt: id += "abicodesalt"; break; + case Kind::ABIDecodeFunctionParams: id += "abidecodefunctionparams"; break; + case Kind::ABIDecodeData: id += "abidecodestatevars"; break; + case Kind::ABIEncodeBody: id += "abiencodebody"; break; + case Kind::ABIEncodeData: id += "abibuilddatainit"; break; + case Kind::ABIEncodeStateInit: id += "abiencodestateinit"; break; + case Kind::ABIFunctionId: id += "abifunctionid"; break; + case Kind::ABISetCodeSalt: id += "abisetcodesalt"; break; + case Kind::ABIStateInitHash: id += "abistateinithash"; break; case Kind::TVMBuilderMethods: id += "tvmbuildermethods"; break; case Kind::TVMBuilderStore: id += "tvmbuilderstore"; break; case Kind::TVMBuilderStoreInt: id += "tvmbuilderstoreint"; break; + case Kind::TVMBuilderStoreTons: id += "tvmbuilderstoretons"; break; case Kind::TVMBuilderStoreUint: id += "tvmbuilderstoreuint"; break; case Kind::TVMTuplePush: id += "tvmtuplepush"; break; @@ -3214,32 +3220,25 @@ string FunctionType::richIdentifier() const case Kind::TVMBuyGas: id += "tvmbuygas"; break; case Kind::TVMChecksign: id += "tvmchecksign"; break; case Kind::TVMCode: id += "tvmcode"; break; - case Kind::TVMCodeSalt: id += "tvmcodesalt"; break; case Kind::TVMCommit: id += "tvmcommit"; break; case Kind::TVMConfigParam: id += "tvmconfigparam"; break; case Kind::TVMDeploy: id += "tvmdeploy"; break; case Kind::TVMDump: id += "tvmxxxdump"; break; - case Kind::TVMEncodeBody: id += "tvmencodebody"; break; case Kind::TVMExit1: id += "tvmexit1"; break; case Kind::TVMExit: id += "tvmexit"; break; - case Kind::TVMFunctionId: id += "tvmfunctionid"; break; case Kind::TVMHash: id += "tvmhash"; break; case Kind::TVMInitCodeHash: id += "tvminitcodehash"; break; - case Kind::TVMSliceLoadRef: id += "tvmloadref"; break; - case Kind::TVMSliceLoadSlice: id += "tvmloadslice"; break; case Kind::TVMPubkey: id += "tvmpubkey"; break; case Kind::TVMRawConfigParam: id += "tvmrawconfigparam"; break; case Kind::TVMReplayProtInterval: id += "tvmreplayprotinterval"; break; case Kind::TVMReplayProtTime: id += "tvmreplayprottime"; break; case Kind::TVMResetStorage: id += "tvmresetstorage"; break; case Kind::TVMSendMsg: id += "tvmsendmsg"; break; - case Kind::TVMSetCodeSalt: id += "tvmsetcodesalt"; break; case Kind::TVMSetGasLimit: id += "tvmsetgaslimit"; break; case Kind::TVMSetPubkey: id += "tvmsetpubkey"; break; case Kind::TVMSetReplayProtTime: id += "tvmsetreplayprottime"; break; case Kind::TVMSetcode: id += "tvmsetcode"; break; - case Kind::AddressTransfer: id += "tvmtransfer"; break; case Kind::TXtimestamp: id += "txtimestamp"; break; @@ -3254,7 +3253,6 @@ string FunctionType::richIdentifier() const case Kind::AddressType: id += "addresstype"; break; case Kind::AddressIsStdAddrWithoutAnyCast: id += "addressisstdaddrwithoutanycast"; break; case Kind::AddressMakeAddrExtern: id += "addressmakeaddrextern"; break; - case Kind::AddressMakeAddrNone: id += "addressmakeaddrnone"; break; case Kind::AddressMakeAddrStd: id += "addressmakeaddrstd"; break; case Kind::MathAbs: id += "mathabs"; break; @@ -3335,6 +3333,7 @@ string FunctionType::richIdentifier() const case Kind::ABIEncodeCall: id += "abiencodecall"; break; case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break; case Kind::ABIDecode: id += "abidecode"; break; + case Kind::BlobHash: id += "blobhash"; break; case Kind::MetaType: id += "metatype"; break; case Kind::RndGetSeed: id += "rndgetseed"; break; @@ -3357,9 +3356,14 @@ string FunctionType::richIdentifier() const } id += "_" + stateMutabilityToString(m_stateMutability); id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes); - - if (bound()) - id += "bound_to" + identifierList(selfType()); + if (gasSet()) + id += "gas"; + if (valueSet()) + id += "value"; + if (saltSet()) + id += "salt"; + if (hasBoundFirstArgument()) + id += "attached_to" + identifierList(selfType()); return id; } @@ -3397,11 +3401,17 @@ BoolResult FunctionType::isImplicitlyConvertibleTo(Type const& _convertTo) const FunctionType const& convertTo = dynamic_cast(_convertTo); // These two checks are duplicated in equalExcludingStateMutability, but are added here for error reporting. - if (convertTo.bound() != bound()) - return BoolResult::err("Bound functions can not be converted to non-bound functions."); + if (convertTo.hasBoundFirstArgument() != hasBoundFirstArgument()) + return BoolResult::err("Attached functions cannot be converted into unattached functions."); if (convertTo.kind() != kind()) - return BoolResult::err("Special functions can not be converted to function types."); + return BoolResult::err("Special functions cannot be converted to function types."); + + if ( + kind() == FunctionType::Kind::Declaration && + m_declaration != convertTo.m_declaration + ) + return BoolResult::err("Function declaration types referring to different functions cannot be converted to each other."); if (!equalExcludingStateMutability(convertTo)) return false; @@ -3430,22 +3440,22 @@ TypeResult FunctionType::binaryOperatorResult(Token _operator, Type const* _othe else if ( kind() == Kind::External && sizeOnStack() == 2 && - !bound() && + !hasBoundFirstArgument() && other.kind() == Kind::External && other.sizeOnStack() == 2 && - !other.bound() + !other.hasBoundFirstArgument() ) return commonType(this, _other); return nullptr; } -string FunctionType::canonicalName() const +std::string FunctionType::canonicalName() const { return "function"; } -string FunctionType::humanReadableName() const +std::string FunctionType::humanReadableName() const { switch (m_kind) { @@ -3458,9 +3468,9 @@ string FunctionType::humanReadableName() const } } -string FunctionType::toString(bool _withoutDataLocation) const +std::string FunctionType::toString(bool _withoutDataLocation) const { - string name = "function "; + std::string name = "function "; if (m_kind == Kind::Declaration) { auto const* functionDefinition = dynamic_cast(m_declaration); @@ -3517,16 +3527,16 @@ bool FunctionType::nameable() const { return (m_kind == Kind::Internal || m_kind == Kind::External) && - !bound() && + !hasBoundFirstArgument() && !takesArbitraryParameters() && !gasSet() && !valueSet() && !saltSet(); } -vector> FunctionType::makeStackItems() const +std::vector> FunctionType::makeStackItems() const { - vector> slots; + std::vector> slots; Kind kind = m_kind; if (m_kind == Kind::SetGas || m_kind == Kind::SetValue) { @@ -3539,8 +3549,8 @@ vector> FunctionType::makeStackItems() const case Kind::External: case Kind::DelegateCall: slots = { - make_tuple("address", TypeProvider::address()), - make_tuple("functionSelector", TypeProvider::uint(32)) + std::make_tuple("address", TypeProvider::address()), + std::make_tuple("functionSelector", TypeProvider::uint(32)) }; break; case Kind::BareCall: @@ -3549,21 +3559,27 @@ vector> FunctionType::makeStackItems() const case Kind::BareStaticCall: case Kind::Transfer: case Kind::Send: - slots = {make_tuple("address", TypeProvider::address())}; + slots = {std::make_tuple("address", TypeProvider::address())}; break; case Kind::Internal: - slots = {make_tuple("functionIdentifier", TypeProvider::uint256())}; + slots = {std::make_tuple("functionIdentifier", TypeProvider::uint256())}; break; case Kind::ArrayPush: case Kind::ArrayPop: - solAssert(bound(), ""); + solAssert(hasBoundFirstArgument(), ""); slots = {}; break; default: break; } - if (bound()) + if (gasSet()) + slots.emplace_back("gas", TypeProvider::uint256()); + if (valueSet()) + slots.emplace_back("value", TypeProvider::uint256()); + if (saltSet()) + slots.emplace_back("salt", TypeProvider::fixedBytes(32)); + if (hasBoundFirstArgument()) slots.emplace_back("self", m_parameterTypes.front()); return slots; } @@ -3647,12 +3663,10 @@ MemberList::MemberMap FunctionType::nativeMembers(ASTNode const* _scope) const } case Kind::DelegateCall: { - auto const* functionDefinition = dynamic_cast(m_declaration); - solAssert(functionDefinition, ""); - solAssert(functionDefinition->visibility() != Visibility::Private, ""); - if (functionDefinition->visibility() != Visibility::Internal) + if (auto const* functionDefinition = dynamic_cast(m_declaration)) { - auto const* contract = dynamic_cast(m_declaration->scope()); + solAssert(functionDefinition->visibility() > Visibility::Internal, ""); + auto const *contract = dynamic_cast(m_declaration->scope()); solAssert(contract, ""); solAssert(contract->isLibrary(), ""); return {{"selector", TypeProvider::fixedBytes(4)}}; @@ -3690,10 +3704,14 @@ TypeResult FunctionType::interfaceType(bool /*_inLibrary*/) const Type const* FunctionType::mobileType() const { - if (valueSet() || gasSet() || saltSet() || bound()) + if (valueSet() || gasSet() || saltSet() || hasBoundFirstArgument()) return nullptr; - // return function without parameter names + // Special function types do not get a mobile type, such that they cannot be used in complex expressions. + if (m_kind != FunctionType::Kind::Internal && m_kind != FunctionType::Kind::External && m_kind != FunctionType::Kind::DelegateCall) + return nullptr; + + // return function without parameter names and without declaration return TypeProvider::function( m_parameterTypes, m_returnParameterTypes, @@ -3701,7 +3719,7 @@ Type const* FunctionType::mobileType() const strings(m_returnParameterNames.size()), m_kind, m_stateMutability, - m_declaration, + nullptr, Options::fromFunctionType(*this) ); } @@ -3711,8 +3729,8 @@ bool FunctionType::canTakeArguments( Type const* _selfType ) const { - solAssert(!bound() || _selfType, ""); - if (bound() && !_selfType->isImplicitlyConvertibleTo(*selfType())) + solAssert(!hasBoundFirstArgument() || _selfType, ""); + if (hasBoundFirstArgument() && !_selfType->isImplicitlyConvertibleTo(*selfType())) return false; TypePointers paramTypes = parameterTypes(); std::vector const paramNames = parameterNames(); @@ -3787,10 +3805,14 @@ bool FunctionType::equalExcludingStateMutability(FunctionType const& _other) con if (!hasEqualParameterTypes(_other) || !hasEqualReturnTypes(_other)) return false; - if (bound() != _other.bound()) + //@todo this is ugly, but cannot be prevented right now + if (gasSet() != _other.gasSet() || valueSet() != _other.valueSet() || saltSet() != _other.saltSet()) + return false; + + if (hasBoundFirstArgument() != _other.hasBoundFirstArgument()) return false; - solAssert(!bound() || *selfType() == *_other.selfType(), ""); + solAssert(!hasBoundFirstArgument() || *selfType() == *_other.selfType(), ""); return true; } @@ -3812,7 +3834,7 @@ bool FunctionType::isBareCall() const } } -string FunctionType::externalSignature() const +std::string FunctionType::externalSignature() const { solAssert(m_declaration != nullptr, "External signature of function needs declaration"); solAssert(!m_declaration->name().empty(), "Fallback function has no signature."); @@ -3839,9 +3861,9 @@ string FunctionType::externalSignature() const solAssert(extParams.message().empty(), extParams.message()); - auto typeStrings = extParams.get() | ranges::views::transform([&](Type const* _t) -> string + auto typeStrings = extParams.get() | ranges::views::transform([&](Type const* _t) -> std::string { - string typeName = _t->signatureInExternalFunction(true); + std::string typeName = _t->signatureInExternalFunction(true); return typeName; }); return m_declaration->name() + "(" + boost::algorithm::join(typeStrings, ",") + ")"; @@ -3849,12 +3871,12 @@ string FunctionType::externalSignature() const u256 FunctionType::externalIdentifier() const { - return util::selectorFromSignature32(externalSignature()); + return util::selectorFromSignatureU32(externalSignature()); } -string FunctionType::externalIdentifierHex() const +std::string FunctionType::externalIdentifierHex() const { - return util::FixedHash<4>(util::keccak256(externalSignature())).hex(); + return util::selectorFromSignatureH32(externalSignature()).hex(); } bool FunctionType::isPure() const @@ -3877,8 +3899,8 @@ bool FunctionType::isPure() const m_kind == Kind::ABIDecode || m_kind == Kind::MetaType || + m_kind == Kind::AddressMakeAddrExtern || m_kind == Kind::AddressMakeAddrStd || - m_kind == Kind::AddressMakeAddrNone || m_kind == Kind::Wrap || m_kind == Kind::Unwrap; @@ -3888,7 +3910,7 @@ TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) { TypePointers pointers; pointers.reserve(_types.size()); - for (string const& type: _types) + for (std::string const& type: _types) pointers.push_back(TypeProvider::fromElementaryTypeName(type)); return pointers; } @@ -3912,14 +3934,14 @@ Type const* FunctionType::copyAndSetCallOptions(bool _setGas, bool _setValue, bo ); } -FunctionTypePointer FunctionType::asBoundFunction() const +FunctionTypePointer FunctionType::withBoundFirstArgument() const { solAssert(!m_parameterTypes.empty(), ""); solAssert(!gasSet(), ""); solAssert(!valueSet(), ""); solAssert(!saltSet(), ""); Options options = Options::fromFunctionType(*this); - options.bound = true; + options.hasBoundFirstArgument = true; return TypeProvider::function( m_parameterTypes, m_returnParameterTypes, @@ -3936,11 +3958,7 @@ FunctionTypePointer FunctionType::asExternallyCallableFunction(bool _inLibrary) { TypePointers parameterTypes; for (auto const& t: m_parameterTypes) { - auto refType = dynamic_cast(t); - if (refType) - parameterTypes.push_back(TypeProvider::withLocation(refType, true)); - else - parameterTypes.push_back(t); + parameterTypes.push_back(t); } TypePointers returnParameterTypes; @@ -3969,7 +3987,7 @@ FunctionTypePointer FunctionType::asExternallyCallableFunction(bool _inLibrary) Type const* FunctionType::selfType() const { - solAssert(bound(), "Function is not bound."); + solAssert(hasBoundFirstArgument(), "Function is not attached to a type."); solAssert(m_parameterTypes.size() > 0, "Function has no self type."); return m_parameterTypes.at(0); } @@ -4047,12 +4065,12 @@ BoolResult OptionalType::isImplicitlyConvertibleTo(Type const& _other) const return r; } -string MappingType::richIdentifier() const +std::string MappingType::richIdentifier() const { return "t_mapping" + identifierList(m_keyType, m_valueType); } -string OptionalType::richIdentifier() const +std::string OptionalType::richIdentifier() const { return "t_optional_" + m_type->richIdentifier(); } @@ -4074,22 +4092,22 @@ bool OptionalType::operator==(Type const& _other) const return *other.m_type == *m_type; } -string MappingType::toString(bool _withoutDataLocation) const +std::string MappingType::toString(bool _withoutDataLocation) const { return "mapping(" + keyType()->toString(_withoutDataLocation) + " => " + valueType()->toString(_withoutDataLocation) + ")"; } -string OptionalType::toString(bool _short) const +std::string OptionalType::toString(bool _short) const { return "optional(" + valueType()->toString(_short) + ")"; } -string MappingType::canonicalName() const +std::string MappingType::canonicalName() const { return "mapping(" + keyType()->canonicalName() + " => " + valueType()->canonicalName() + ")"; } -string OptionalType::canonicalName() const +std::string OptionalType::canonicalName() const { return "optional(" + valueType()->canonicalName() + ")"; } @@ -4149,7 +4167,7 @@ std::vector> MappingType::makeStackItems() return {std::make_tuple("slot", TypeProvider::uint256())}; } -string TypeType::richIdentifier() const +std::string TypeType::richIdentifier() const { return "t_type" + identifierList(actualType()); } @@ -4167,13 +4185,13 @@ u256 TypeType::storageSize() const solAssert(false, "Storage size of non-storable type type requested."); } -vector> TypeType::makeStackItems() const +std::vector> TypeType::makeStackItems() const { if (auto contractType = dynamic_cast(m_actualType)) if (contractType->contractDefinition().isLibrary()) { solAssert(!contractType->isSuper(), ""); - return {make_tuple("address", TypeProvider::address())}; + return {std::make_tuple("address", TypeProvider::address())}; } return {}; @@ -4256,24 +4274,17 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons members.emplace_back("makeAddrExtern", TypeProvider::function( TypePointers{TypeProvider::uint256(), TypeProvider::uint256()}, TypePointers{TypeProvider::address()}, - strings{string(), string()}, - strings{string()}, + strings{std::string(), std::string()}, + strings{std::string()}, FunctionType::Kind::AddressMakeAddrExtern, StateMutability::Pure )); - members.emplace_back("makeAddrNone", TypeProvider::function( - TypePointers{}, - TypePointers{TypeProvider::address()}, - strings{}, - strings{string()}, - FunctionType::Kind::AddressMakeAddrNone, - StateMutability::Pure - )); + members.emplace_back("addrNone", TypeProvider::address()); members.emplace_back("makeAddrStd", TypeProvider::function( TypePointers{TypeProvider::integer(8, IntegerType::Modifier::Signed), TypeProvider::uint256()}, TypePointers{TypeProvider::address()}, - strings{string(), string()}, - strings{string()}, + strings{std::string(), std::string()}, + strings{std::string()}, FunctionType::Kind::AddressMakeAddrStd, StateMutability::Pure )); @@ -4286,8 +4297,8 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons TypeProvider::function( TypePointers{&userDefined.underlyingType()}, TypePointers{&userDefined}, - strings{string{}}, - strings{string{}}, + strings{std::string{}}, + strings{std::string{}}, FunctionType::Kind::Wrap, StateMutability::Pure ) @@ -4297,8 +4308,8 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons TypeProvider::function( TypePointers{&userDefined}, TypePointers{&userDefined.underlyingType()}, - strings{string{}}, - strings{string{}}, + strings{std::string{}}, + strings{std::string{}}, FunctionType::Kind::Unwrap, StateMutability::Pure ) @@ -4312,7 +4323,7 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons TypePointers{}, TypePointers{arrayType->isString() ? TypeProvider::stringMemory() : TypeProvider::bytesMemory()}, strings{}, - strings{string{}}, + strings{std::string{}}, arrayType->isString() ? FunctionType::Kind::StringConcat : FunctionType::Kind::BytesConcat, StateMutability::Pure, nullptr, @@ -4343,7 +4354,7 @@ u256 ModifierType::storageSize() const solAssert(false, "Storage size of non-storable type type requested."); } -string ModifierType::richIdentifier() const +std::string ModifierType::richIdentifier() const { return "t_modifier" + identifierList(m_parameterTypes); } @@ -4368,17 +4379,17 @@ bool ModifierType::operator==(Type const& _other) const return true; } -string ModifierType::toString(bool _withoutDataLocation) const +std::string ModifierType::toString(bool _withoutDataLocation) const { - string name = "modifier ("; + std::string name = "modifier ("; for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it) name += (*it)->toString(_withoutDataLocation) + (it + 1 == m_parameterTypes.end() ? "" : ","); return name + ")"; } -string ModuleType::richIdentifier() const +std::string ModuleType::richIdentifier() const { - return "t_module_" + to_string(m_sourceUnit.id()); + return "t_module_" + std::to_string(m_sourceUnit.id()); } bool ModuleType::operator==(Type const& _other) const @@ -4397,12 +4408,12 @@ MemberList::MemberMap ModuleType::nativeMembers(ASTNode const*) const return symbols; } -string ModuleType::toString(bool) const +std::string ModuleType::toString(bool) const { - return string("module \"") + *m_sourceUnit.annotation().path + string("\""); + return std::string("module \"") + *m_sourceUnit.annotation().path + std::string("\""); } -string MagicType::richIdentifier() const +std::string MagicType::richIdentifier() const { switch (m_kind) { @@ -4446,10 +4457,12 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const {"logicaltime", TypeProvider::uint(64)}, {"timestamp", TypeProvider::uint(32)}, {"difficulty", TypeProvider::uint256()}, + {"prevrandao", TypeProvider::uint256()}, {"number", TypeProvider::uint256()}, {"gaslimit", TypeProvider::uint256()}, {"chainid", TypeProvider::uint256()}, - {"basefee", TypeProvider::uint256()} + {"basefee", TypeProvider::uint256()}, + {"blobbasefee", TypeProvider::uint256()} }); case Kind::Message: return MemberList::MemberMap({ @@ -4458,7 +4471,7 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const {"createdAt", TypeProvider::uint(32)}, {"hasStateInit", TypeProvider::boolean()}, {"gas", TypeProvider::uint256()}, - {"value", TypeProvider::uint(128)}, + {"value", TypeProvider::coins()}, {"data", TypeProvider::tvmcell()}, {"sig", TypeProvider::fixedBytes(4)}, {"currencies", TypeProvider::extraCurrencyCollection()}, @@ -4466,14 +4479,14 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const {"isInternal", TypeProvider::boolean()}, {"isTickTock", TypeProvider::boolean()}, {"body", TypeProvider::tvmslice()}, - {"forwardFee", TypeProvider::varInteger(16, IntegerType::Modifier::Unsigned)}, - {"importFee", TypeProvider::varInteger(16, IntegerType::Modifier::Unsigned)}, + {"forwardFee", TypeProvider::coins()}, + {"importFee", TypeProvider::coins()}, }); case Kind::TVM: { MemberList::MemberMap members = { {"code", TypeProvider::function({}, {TypeProvider::tvmcell()}, {}, {{}}, FunctionType::Kind::TVMCode, StateMutability::Pure)}, - {"codeSalt", TypeProvider::function({TypeProvider::tvmcell()}, {TypeProvider::optional(TypeProvider::tvmcell())}, {{}}, {{}}, FunctionType::Kind::TVMCodeSalt, StateMutability::Pure)}, - {"setCodeSalt", TypeProvider::function({TypeProvider::tvmcell(), TypeProvider::tvmcell()}, {TypeProvider::tvmcell()}, {{}, {}}, {{}}, FunctionType::Kind::TVMSetCodeSalt, StateMutability::Pure)}, + {"codeSalt", TypeProvider::function({TypeProvider::tvmcell()}, {TypeProvider::optional(TypeProvider::tvmcell())}, {{}}, {{}}, FunctionType::Kind::ABICodeSalt, StateMutability::Pure)}, + {"setCodeSalt", TypeProvider::function({TypeProvider::tvmcell(), TypeProvider::tvmcell()}, {TypeProvider::tvmcell()}, {{}, {}}, {{}}, FunctionType::Kind::ABISetCodeSalt, StateMutability::Pure)}, {"pubkey", TypeProvider::function(strings(), strings{"uint"}, FunctionType::Kind::TVMPubkey, StateMutability::Pure)}, {"setPubkey", TypeProvider::function({"uint"}, {}, FunctionType::Kind::TVMSetPubkey, StateMutability::NonPayable)}, {"accept", TypeProvider::function(strings(), strings(), FunctionType::Kind::TVMAccept, StateMutability::Pure)}, @@ -4493,48 +4506,48 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const {"replayProtTime", TypeProvider::function({}, {"uint64"}, FunctionType::Kind::TVMReplayProtTime, StateMutability::Pure)}, {"setReplayProtTime", TypeProvider::function({"uint64"}, {}, FunctionType::Kind::TVMSetReplayProtTime, StateMutability::Pure)}, {"replayProtInterval", TypeProvider::function({}, {"uint64"}, FunctionType::Kind::TVMReplayProtInterval, StateMutability::Pure)}, - }; - members.emplace_back("rawReserve", TypeProvider::function( + + {"rawReserve", TypeProvider::function( TypePointers{TypeProvider::uint256(), TypeProvider::extraCurrencyCollection(), TypeProvider::uint256()}, TypePointers{}, - strings{string{}, string{}, string{}}, + strings{std::string{}, std::string{}, std::string{}}, strings{}, FunctionType::Kind::TVMSetcode, StateMutability::Pure - )); - members.emplace_back("rawReserve", TypeProvider::function( + )}, + {"rawReserve", TypeProvider::function( TypePointers{TypeProvider::uint256(), TypeProvider::uint256()}, TypePointers{}, - strings{string{}, string{}}, + strings{std::string{}, std::string{}}, strings{}, FunctionType::Kind::TVMSetcode, StateMutability::Pure - )); - members.emplace_back("setcode", TypeProvider::function( + )}, + {"setcode", TypeProvider::function( TypePointers{TypeProvider::tvmcell()}, TypePointers{}, - strings{string()}, + strings{std::string()}, strings{}, FunctionType::Kind::TVMSetcode, StateMutability::Pure - )); - members.emplace_back("setCurrentCode", TypeProvider::function( + )}, + {"setCurrentCode", TypeProvider::function( TypePointers{TypeProvider::tvmcell()}, TypePointers{}, - strings{string()}, + strings{std::string()}, strings{}, FunctionType::Kind::TVMSetcode, StateMutability::Pure - )); - members.emplace_back("bindump", TypeProvider::function( + )}, + {"bindump", TypeProvider::function( TypePointers{}, TypePointers{}, strings{}, strings{}, FunctionType::Kind::TVMDump, StateMutability::Pure - )); - members.emplace_back("hexdump", TypeProvider::function( + )}, + {"hexdump", TypeProvider::function( TypePointers{}, TypePointers{}, strings{}, @@ -4543,8 +4556,8 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const StateMutability::Pure, nullptr, FunctionType::Options::withArbitraryParameters() - )); - members.emplace_back("hash", TypeProvider::function( + )}, + {"hash", TypeProvider::function( TypePointers{}, TypePointers{}, strings{}, @@ -4553,41 +4566,40 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const StateMutability::Pure, nullptr, FunctionType::Options::withArbitraryParameters() - )); - members.emplace_back("checkSign", TypeProvider::function( + )}, + {"checkSign", TypeProvider::function( TypePointers{TypeProvider::uint256(), TypeProvider::uint256(), TypeProvider::uint256(), TypeProvider::uint256()}, TypePointers{TypeProvider::boolean()}, - strings{string(), string(), string(), string()}, - strings{string()}, + strings{std::string(), std::string(), std::string(), std::string()}, + strings{std::string()}, FunctionType::Kind::TVMChecksign, StateMutability::Pure - )); - members.emplace_back("checkSign", TypeProvider::function( + )}, + {"checkSign", TypeProvider::function( TypePointers{TypeProvider::uint256(), TypeProvider::tvmslice(), TypeProvider::uint256()}, TypePointers{TypeProvider::boolean()}, - strings{string(), string(), string()}, - strings{string()}, + strings{std::string(), std::string(), std::string()}, + strings{std::string()}, FunctionType::Kind::TVMChecksign, StateMutability::Pure - )); - members.emplace_back("checkSign", TypeProvider::function( + )}, + {"checkSign", TypeProvider::function( TypePointers{TypeProvider::tvmslice(), TypeProvider::tvmslice(), TypeProvider::uint256()}, TypePointers{TypeProvider::boolean()}, - strings{string(), string(), string()}, - strings{string()}, + strings{std::string(), std::string(), std::string()}, + strings{std::string()}, FunctionType::Kind::TVMChecksign, StateMutability::Pure - )); - members.emplace_back("sendrawmsg", TypeProvider::function( + )}, + {"sendrawmsg", TypeProvider::function( TypePointers{TypeProvider::tvmcell(), TypeProvider::uint(8)}, TypePointers{}, - strings{string(), string()}, + strings{std::string(), std::string()}, strings{}, FunctionType::Kind::TVMSendMsg, StateMutability::Pure - )); - - members.emplace_back("configParam", TypeProvider::function( + )}, + {"configParam", TypeProvider::function( TypePointers{}, TypePointers{}, strings{}, @@ -4596,18 +4608,16 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const StateMutability::Pure, nullptr, FunctionType::Options::withArbitraryParameters() - )); - - members.emplace_back("rawConfigParam", TypeProvider::function( - {TypeProvider::integer(32, IntegerType::Modifier::Signed)}, - {TypeProvider::optional(TypeProvider::tvmcell())}, - {{}}, - {{}}, - FunctionType::Kind::TVMRawConfigParam, - StateMutability::Pure - )); - - members.emplace_back("buildExtMsg", TypeProvider::function( + )}, + {"rawConfigParam", TypeProvider::function( + {TypeProvider::integer(32, IntegerType::Modifier::Signed)}, + {TypeProvider::optional(TypeProvider::tvmcell())}, + {{}}, + {{}}, + FunctionType::Kind::TVMRawConfigParam, + StateMutability::Pure + )}, + {"buildExtMsg", TypeProvider::function( TypePointers{TypeProvider::address(), TypeProvider::callList(), TypeProvider::uint(32), @@ -4621,52 +4631,49 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const TypeProvider::tvmcell(), TypeProvider::uint(8)}, TypePointers{TypeProvider::tvmcell()}, - strings{string("dest"), // mandatory - string("call"), // mandatory - string("callbackId"), // mandatory - string("abiVer"), // can be omitted - string("onErrorId"), // mandatory - string("signBoxHandle"), // can be omitted - string("time"), // can be omitted - string("expire"), // can be omitted - string("pubkey"), // can be omitted - string("sign"), // can be omitted - string("stateInit"), // can be omitted - string("flags")}, // can be omitted - strings{string()}, - FunctionType::Kind::TVMBuildExtMsg, + strings{std::string("dest"), // mandatory + std::string("call"), // mandatory + std::string("callbackId"), // mandatory + std::string("abiVer"), // can be omitted + std::string("onErrorId"), // mandatory + std::string("signBoxHandle"), // can be omitted + std::string("time"), // can be omitted + std::string("expire"), // can be omitted + std::string("pubkey"), // can be omitted + std::string("sign"), // can be omitted + std::string("stateInit"), // can be omitted + std::string("flags")}, // can be omitted + strings{std::string()}, + FunctionType::Kind::ABIBuildExtMsg, StateMutability::Pure, nullptr, FunctionType::Options::withArbitraryParameters() - )); - - - members.emplace_back("buildIntMsg", TypeProvider::function( - { - TypeProvider::address(), - TypeProvider::uint(128), - TypeProvider::extraCurrencyCollection(), - TypeProvider::boolean(), - TypeProvider::callList(), - TypeProvider::tvmcell(), - }, - {TypeProvider::tvmcell()}, - { - "dest", // mandatory - "value", // mandatory - "currencies", // can be omitted - "bounce", // can be omitted - "call", // mandatory - "stateInit", // can be omitted - }, - {{}}, - FunctionType::Kind::TVMBuildIntMsg, - StateMutability::Pure, - nullptr, - FunctionType::Options::withArbitraryParameters() - )); - - members.emplace_back("buildStateInit", TypeProvider::function( + )}, + {"buildIntMsg", TypeProvider::function( + { + TypeProvider::address(), + TypeProvider::coins(), + TypeProvider::extraCurrencyCollection(), + TypeProvider::boolean(), + TypeProvider::callList(), + TypeProvider::tvmcell(), + }, + {TypeProvider::tvmcell()}, + { + "dest", // mandatory + "value", // mandatory + "currencies", // can be omitted + "bounce", // can be omitted + "call", // mandatory + "stateInit", // can be omitted + }, + {{}}, + FunctionType::Kind::ABIBuildIntMsg, + StateMutability::Pure, + nullptr, + FunctionType::Options::withArbitraryParameters() + )}, + {"buildStateInit", TypeProvider::function( TypePointers{TypeProvider::tvmcell(), TypeProvider::tvmcell(), TypeProvider::uint(8), @@ -4676,72 +4683,59 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const // but it can be any contract }, TypePointers{TypeProvider::tvmcell()}, - strings{string("code"), // mandatory - string("data"), // conflicts with pubkey and varInit - string("splitDepth"), // can be omitted - string("varInit"), // conflicts with data - string("pubkey"), // conflicts with data + strings{std::string("code"), // mandatory + std::string("data"), // conflicts with pubkey and varInit + std::string("splitDepth"), // can be omitted + std::string("varInit"), // conflicts with data + std::string("pubkey"), // conflicts with data //string("contr") }, - strings{string()}, - FunctionType::Kind::TVMBuildStateInit, + strings{std::string()}, + FunctionType::Kind::ABIEncodeStateInit, StateMutability::Pure, nullptr, FunctionType::Options::withArbitraryParameters() - )); - - members.emplace_back("buildDataInit", TypeProvider::function( - { - TypeProvider::uint256(), - TypeProvider::initializerList(), - //TypeProvider::contract(...) it's commented because we should set the concrete contract - }, - {TypeProvider::tvmcell()}, - {"pubkey", "varInit"}, - {{}}, - FunctionType::Kind::TVMBuildDataInit, - StateMutability::Pure, - nullptr, FunctionType::Options::withArbitraryParameters() - )); - - members.emplace_back("insertPubkey", TypeProvider::function( - TypePointers{TypeProvider::tvmcell(), TypeProvider::uint256()}, - TypePointers{TypeProvider::tvmcell()}, - strings{string(), string()}, - strings{string()}, - FunctionType::Kind::TVMDeploy, - StateMutability::Pure - )); - - members.emplace_back("stateInitHash", TypeProvider::function( + )}, + {"buildDataInit", TypeProvider::function( + { + TypeProvider::uint256(), + TypeProvider::initializerList(), + //TypeProvider::contract(...) it's commented because we should set the concrete contract + }, + {TypeProvider::tvmcell()}, + {"pubkey", "varInit"}, + {{}}, + FunctionType::Kind::ABIEncodeData, + StateMutability::Pure, + nullptr, FunctionType::Options::withArbitraryParameters() + )}, + {"stateInitHash", TypeProvider::function( TypePointers{TypeProvider::uint256(), TypeProvider::uint256(), TypeProvider::uint(16), TypeProvider::uint(16)}, TypePointers{TypeProvider::uint256()}, - strings{string(), string(), string(), string()}, - strings{string()}, - FunctionType::Kind::TVMDeploy, + strings{std::string(), std::string(), std::string(), std::string()}, + strings{std::string()}, + FunctionType::Kind::ABIStateInitHash, StateMutability::Pure - )); - - members.emplace_back("functionId", TypeProvider::function( + )}, + {"functionId", TypeProvider::function( TypePointers{}, TypePointers{TypeProvider::uint(32)}, strings{}, - strings{string()}, - FunctionType::Kind::TVMFunctionId, + strings{std::string()}, + FunctionType::Kind::ABIFunctionId, StateMutability::Pure, nullptr, FunctionType::Options::withArbitraryParameters() - )); - - members.emplace_back("encodeBody", TypeProvider::function( + )}, + {"encodeBody", TypeProvider::function( TypePointers{}, TypePointers{TypeProvider::tvmcell()}, strings{}, - strings{string()}, - FunctionType::Kind::TVMEncodeBody, + strings{std::string()}, + FunctionType::Kind::ABIEncodeBody, StateMutability::Pure, nullptr, FunctionType::Options::withArbitraryParameters() - )); - + )} + }; return members; } case Kind::Rnd: { @@ -4859,10 +4853,10 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const nullptr, FunctionType::Options::withArbitraryParameters() )); members.emplace_back("sign", TypeProvider::function( - TypePointers{TypeProvider::integer(256, IntegerType::Modifier::Signed)}, + TypePointers{TypeProvider::integer(257, IntegerType::Modifier::Signed)}, TypePointers{TypeProvider::integer(2, IntegerType::Modifier::Signed)}, - strings{string("value")}, - strings{string("sign")}, + strings{std::string("value")}, + strings{std::string("sign")}, FunctionType::Kind::MathSign, StateMutability::Pure )); @@ -4873,7 +4867,7 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const {"gasprice", TypeProvider::uint256()}, {"logicaltime", TypeProvider::uint(64)}, {"origin", TypeProvider::address()}, - {"storageFee", TypeProvider::uint(120)}, + {"storageFee", TypeProvider::coins()}, {"timestamp", TypeProvider::uint(64)}, }); case Kind::ABI: @@ -4937,11 +4931,176 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const StateMutability::Pure, nullptr, FunctionType::Options::withArbitraryParameters() + )}, + {"encodeStateInit", TypeProvider::function( + TypePointers{TypeProvider::tvmcell(), + TypeProvider::tvmcell(), + TypeProvider::uint(8), + TypeProvider::initializerList(), + TypeProvider::uint256(), + //TypeProvider::contract(...) it's commented because we should set the concrete contract + // but it can be any contract + }, + TypePointers{TypeProvider::tvmcell()}, + strings{std::string("code"), // mandatory + std::string("data"), // conflicts with pubkey and varInit + std::string("splitDepth"), // can be omitted + std::string("varInit"), // conflicts with data + std::string("pubkey"), // conflicts with data + //string("contr") + }, + strings{std::string()}, + FunctionType::Kind::ABIEncodeStateInit, + StateMutability::Pure, + nullptr, + FunctionType::Options::withArbitraryParameters() + )}, + {"stateInitHash", TypeProvider::function( + TypePointers{TypeProvider::uint256(), TypeProvider::uint256(), TypeProvider::uint(16), TypeProvider::uint(16)}, + TypePointers{TypeProvider::uint256()}, + strings{std::string(), std::string(), std::string(), std::string()}, + strings{std::string()}, + FunctionType::Kind::ABIStateInitHash, + StateMutability::Pure + )}, + {"encodeData", TypeProvider::function( + { + TypeProvider::uint256(), + TypeProvider::initializerList(), + //TypeProvider::contract(...) it's commented because we should set the concrete contract + }, + {TypeProvider::tvmcell()}, + {"pubkey", "varInit"}, + {{}}, + FunctionType::Kind::ABIEncodeData, + StateMutability::Pure, + nullptr, FunctionType::Options::withArbitraryParameters() + )}, + {"encodeOldDataInit", TypeProvider::function( + { + TypeProvider::uint256(), + TypeProvider::initializerList(), + //TypeProvider::contract(...) it's commented because we should set the concrete contract + }, + {TypeProvider::tvmcell()}, + {"pubkey", "varInit"}, + {{}}, + FunctionType::Kind::ABIEncodeData, + StateMutability::Pure, + nullptr, FunctionType::Options::withArbitraryParameters() + )}, + {"codeSalt", TypeProvider::function( + {TypeProvider::tvmcell()}, + {TypeProvider::optional(TypeProvider::tvmcell())}, + {{}}, + {{}}, + FunctionType::Kind::ABICodeSalt, + StateMutability::Pure + )}, + {"setCodeSalt", TypeProvider::function( + {TypeProvider::tvmcell(), TypeProvider::tvmcell()}, + {TypeProvider::tvmcell()}, + {{}, {}}, + {{}}, + FunctionType::Kind::ABISetCodeSalt, + StateMutability::Pure + )}, + {"functionId", TypeProvider::function( + TypePointers{}, + TypePointers{TypeProvider::uint(32)}, + strings{}, + strings{std::string()}, + FunctionType::Kind::ABIFunctionId, + StateMutability::Pure, + nullptr, FunctionType::Options::withArbitraryParameters() + )}, + {"encodeExtMsg", TypeProvider::function( + TypePointers{TypeProvider::address(), + TypeProvider::callList(), + TypeProvider::uint(32), + TypeProvider::uint(8), + TypeProvider::uint(32), + TypeProvider::optional(TypeProvider::uint(32)), + TypeProvider::uint(64), + TypeProvider::uint(32), + TypeProvider::optional(TypeProvider::uint256()), + TypeProvider::boolean(), + TypeProvider::tvmcell(), + TypeProvider::uint(8)}, + TypePointers{TypeProvider::tvmcell()}, + strings{std::string("dest"), // mandatory + std::string("call"), // mandatory + std::string("callbackId"), // mandatory + std::string("abiVer"), // can be omitted + std::string("onErrorId"), // mandatory + std::string("signBoxHandle"), // can be omitted + std::string("time"), // can be omitted + std::string("expire"), // can be omitted + std::string("pubkey"), // can be omitted + std::string("sign"), // can be omitted + std::string("stateInit"), // can be omitted + std::string("flags")}, // can be omitted + strings{std::string()}, + FunctionType::Kind::ABIBuildExtMsg, + StateMutability::Pure, + nullptr, + FunctionType::Options::withArbitraryParameters() + )}, + {"encodeIntMsg", TypeProvider::function( + { + TypeProvider::address(), + TypeProvider::coins(), + TypeProvider::extraCurrencyCollection(), + TypeProvider::boolean(), + TypeProvider::callList(), + TypeProvider::tvmcell(), + }, + {TypeProvider::tvmcell()}, + { + "dest", // mandatory + "value", // mandatory + "currencies", // can be omitted + "bounce", // can be omitted + "call", // mandatory + "stateInit", // can be omitted + }, + {{}}, + FunctionType::Kind::ABIBuildIntMsg, + StateMutability::Pure, + nullptr, + FunctionType::Options::withArbitraryParameters() + )}, + {"decodeData", TypeProvider::function( + TypePointers{}, + TypePointers{}, + strings{}, + strings{}, + FunctionType::Kind::ABIDecodeData, + StateMutability::Pure, + nullptr, FunctionType::Options::withArbitraryParameters() + )}, + {"encodeBody", TypeProvider::function( + TypePointers{}, + TypePointers{TypeProvider::tvmcell()}, + strings{}, + strings{std::string()}, + FunctionType::Kind::ABIEncodeBody, + StateMutability::Pure, + nullptr, FunctionType::Options::withArbitraryParameters() + )}, + {"decodeFunctionParams", TypeProvider::function( + TypePointers{}, + TypePointers{}, + strings{}, + strings{}, + FunctionType::Kind::ABIDecodeFunctionParams, + StateMutability::Pure, + nullptr, FunctionType::Options::withArbitraryParameters() )} }); case Kind::Gosh: { MemberList::MemberMap members; - for (auto const&[name, type] : std::vector>{ + for (auto const&[name, type] : std::vector>{ {"diff", FunctionType::Kind::GoshDiff}, {"applyPatch", FunctionType::Kind::GoshApplyPatch}, }) { @@ -4957,7 +5116,7 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const )}); } - for (auto const&[name, type] : std::vector>{ + for (auto const&[name, type] : std::vector>{ {"applyBinPatch", FunctionType::Kind::GoshApplyBinPatch}, {"applyZipBinPatch", FunctionType::Kind::GoshApplyZipBinPatch}, {"applyZipPatch", FunctionType::Kind::GoshApplyZipPatch}, @@ -4986,7 +5145,7 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const nullptr, FunctionType::Options::withArbitraryParameters() )}); - for (auto const&[name, type] : std::vector>{ + for (auto const&[name, type] : std::vector>{ {"applyZipPatchQ", FunctionType::Kind::GoshApplyZipPatchQ}, {"applyBinPatchQ", FunctionType::Kind::GoshApplyBinPatchQ}, {"applyZipBinPatchQ", FunctionType::Kind::GoshApplyZipBinPatchQ}, @@ -5084,7 +5243,7 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const return {}; } -string MagicType::toString(bool _withoutDataLocation) const +std::string MagicType::toString(bool _withoutDataLocation) const { switch (m_kind) { @@ -5397,8 +5556,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { "loadUnsigned", TypeProvider::function( TypePointers{TypeProvider::uint(9)}, TypePointers{TypeProvider::uint256()}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceLoadUint, StateMutability::Pure ) @@ -5407,8 +5566,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { "loadUint", TypeProvider::function( TypePointers{TypeProvider::uint(9)}, TypePointers{TypeProvider::uint256()}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceLoadUint, StateMutability::Pure ) @@ -5417,8 +5576,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { "loadUintQ", TypeProvider::function( TypePointers{TypeProvider::uint(9)}, TypePointers{TypeProvider::optional(TypeProvider::uint256())}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceLoadUintQ, StateMutability::Pure ) @@ -5607,8 +5766,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { "preloadUint", TypeProvider::function( TypePointers{TypeProvider::uint(9)}, TypePointers{TypeProvider::uint256()}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::TVMSlicePreLoadUint, StateMutability::Pure ) @@ -5617,8 +5776,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { "preloadUintQ", TypeProvider::function( TypePointers{TypeProvider::uint(9)}, TypePointers{TypeProvider::optional(TypeProvider::uint256())}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::TVMSlicePreLoadUintQ, StateMutability::Pure ) @@ -5627,8 +5786,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { "loadSigned", TypeProvider::function( TypePointers{TypeProvider::uint(9)}, TypePointers{TypeProvider::int256()}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceLoadInt, StateMutability::Pure ) @@ -5637,8 +5796,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { "loadInt", TypeProvider::function( TypePointers{TypeProvider::uint(9)}, TypePointers{TypeProvider::int256()}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceLoadInt, StateMutability::Pure ) @@ -5647,8 +5806,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { "loadIntQ", TypeProvider::function( TypePointers{TypeProvider::uint(9)}, TypePointers{TypeProvider::optional(TypeProvider::int256())}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceLoadIntQ, StateMutability::Pure ) @@ -5657,8 +5816,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { "preloadInt", TypeProvider::function( TypePointers{TypeProvider::uint(9)}, TypePointers{TypeProvider::int256()}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::TVMSlicePreLoadInt, StateMutability::Pure ) @@ -5667,8 +5826,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { "preloadIntQ", TypeProvider::function( TypePointers{TypeProvider::uint(9)}, TypePointers{TypeProvider::optional(TypeProvider::int256())}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::TVMSlicePreLoadIntQ, StateMutability::Pure ) @@ -5677,8 +5836,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { "hasNBits", TypeProvider::function( TypePointers{TypeProvider::uint(10)}, TypePointers{TypeProvider::boolean()}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceHas, StateMutability::Pure ) @@ -5687,8 +5846,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { "hasNRefs", TypeProvider::function( TypePointers{TypeProvider::uint(2)}, TypePointers{TypeProvider::boolean()}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceHas, StateMutability::Pure ) @@ -5697,8 +5856,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { "hasNBitsAndRefs", TypeProvider::function( TypePointers{TypeProvider::uint(10), TypeProvider::uint(2)}, TypePointers{TypeProvider::boolean()}, - strings{string(), string()}, - strings{string()}, + strings{std::string(), std::string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceHas, StateMutability::Pure ) @@ -5708,8 +5867,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { TypePointers{}, TypePointers{TypeProvider::uint(128)}, strings{}, - strings{string()}, - FunctionType::Kind::TVMSliceLoadRef, + strings{std::string()}, + FunctionType::Kind::TVMSliceLoadTons, StateMutability::Pure ) }, @@ -5814,7 +5973,7 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { TypePointers{}, TypePointers{TypeProvider::uint(10), TypeProvider::uint(2)}, strings{}, - strings{string(), string()}, + strings{std::string(), std::string()}, FunctionType::Kind::TVMSliceSize, StateMutability::Pure ) @@ -5834,7 +5993,7 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { TypePointers{}, TypePointers{TypeProvider::uint(10)}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceSize, StateMutability::Pure ) @@ -5844,7 +6003,7 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { TypePointers{}, TypePointers{TypeProvider::uint(2)}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceSize, StateMutability::Pure ) @@ -5854,7 +6013,7 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { TypePointers{}, TypePointers{TypeProvider::uint(16)}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceSize, StateMutability::Pure ) @@ -5864,7 +6023,7 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { TypePointers{}, TypePointers{TypeProvider::tvmcell()}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceLoadRef, StateMutability::Pure ) @@ -5894,7 +6053,7 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { TypePointers{}, TypePointers{TypeProvider::tvmslice()}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceLoadRef, StateMutability::Pure ) @@ -5903,8 +6062,8 @@ MemberList::MemberMap TvmSliceType::nativeMembers(ASTNode const *) const { "compare", TypeProvider::function( TypePointers{TypeProvider::tvmslice()}, TypePointers{TypeProvider::integer(2, IntegerType::Modifier::Signed)}, - strings{string()}, - strings{string()}, + strings{std::string()}, + strings{std::string()}, FunctionType::Kind::TVMSliceCompare, StateMutability::Pure ) @@ -5928,7 +6087,7 @@ MemberList::MemberMap TvmCellType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::uint(16)}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMCellDepth, StateMutability::Pure ) @@ -5939,7 +6098,7 @@ MemberList::MemberMap TvmCellType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::tvmslice()}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMCellToSlice, StateMutability::Pure ) @@ -5950,7 +6109,7 @@ MemberList::MemberMap TvmCellType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::tvmslice(), TypeProvider::boolean()}, strings{}, - strings{string(), string()}, + strings{std::string(), std::string()}, FunctionType::Kind::TVMCellToSlice, StateMutability::Pure ) @@ -5961,7 +6120,7 @@ MemberList::MemberMap TvmCellType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::tvmcell()}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMCellToSlice, StateMutability::Pure ) @@ -5972,7 +6131,7 @@ MemberList::MemberMap TvmCellType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::tvmcell(), TypeProvider::boolean()}, strings{}, - strings{string(), string()}, + strings{std::string(), std::string()}, FunctionType::Kind::TVMCellToSlice, StateMutability::Pure ) @@ -6037,7 +6196,7 @@ MemberList::MemberMap TvmVectorType::nativeMembers(const ASTNode *) const members.emplace_back("push", TypeProvider::function( TypePointers{valueType()}, TypePointers{}, - strings{string()}, + strings{std::string()}, strings{}, FunctionType::Kind::TVMTuplePush, StateMutability::Pure @@ -6047,7 +6206,7 @@ MemberList::MemberMap TvmVectorType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::uint(8)}, strings{}, - strings{string("length")}, + strings{std::string("length")}, FunctionType::Kind::TVMTupleLength, StateMutability::Pure )); @@ -6056,7 +6215,7 @@ MemberList::MemberMap TvmVectorType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{valueType()}, strings{}, - strings{string("last")}, + strings{std::string("last")}, FunctionType::Kind::TVMTuplePop, StateMutability::Pure )); @@ -6065,7 +6224,7 @@ MemberList::MemberMap TvmVectorType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::boolean()}, strings{}, - strings{string("is_empty")}, + strings{std::string("is_empty")}, FunctionType::Kind::TVMTupleLength, StateMutability::Pure )); @@ -6112,7 +6271,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::uint(16)}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMBuilderMethods, StateMutability::Pure ) @@ -6122,7 +6281,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::uint(10)}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMBuilderMethods, StateMutability::Pure ) @@ -6132,7 +6291,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::uint(2)}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMBuilderMethods, StateMutability::Pure ) @@ -6142,7 +6301,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::uint(10), TypeProvider::uint(2)}, strings{}, - strings{string(), string()}, + strings{std::string(), std::string()}, FunctionType::Kind::TVMBuilderMethods, StateMutability::Pure ) @@ -6152,7 +6311,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::uint(10)}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMBuilderMethods, StateMutability::Pure ) @@ -6162,7 +6321,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::uint(2)}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMBuilderMethods, StateMutability::Pure ) @@ -6172,7 +6331,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::uint(10), TypeProvider::uint(2)}, strings{}, - strings{string(), string()}, + strings{std::string(), std::string()}, FunctionType::Kind::TVMBuilderMethods, StateMutability::Pure ) @@ -6182,7 +6341,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::tvmcell()}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMBuilderMethods, StateMutability::Pure ) @@ -6192,7 +6351,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::tvmcell()}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMBuilderMethods, StateMutability::Pure ) @@ -6202,7 +6361,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const TypePointers{}, TypePointers{TypeProvider::tvmslice()}, strings{}, - strings{string()}, + strings{std::string()}, FunctionType::Kind::TVMBuilderMethods, StateMutability::Pure ) @@ -6211,7 +6370,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const "storeRef", TypeProvider::function( TypePointers{TypeProvider::tvmbuilder()}, TypePointers{}, - strings{string()}, + strings{std::string()}, strings{}, FunctionType::Kind::TVMBuilderMethods, StateMutability::Pure @@ -6272,7 +6431,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const "storeSigned", TypeProvider::function( TypePointers{TypeProvider::int256(), TypeProvider::uint(9)}, TypePointers{}, - strings{string(), string()}, + strings{std::string(), std::string()}, strings{}, FunctionType::Kind::TVMBuilderStoreInt, StateMutability::Pure @@ -6282,7 +6441,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const "storeInt", TypeProvider::function( TypePointers{TypeProvider::int256(), TypeProvider::uint(9)}, TypePointers{}, - strings{string(), string()}, + strings{std::string(), std::string()}, strings{}, FunctionType::Kind::TVMBuilderStoreInt, StateMutability::Pure @@ -6292,7 +6451,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const "storeUnsigned", TypeProvider::function( TypePointers{TypeProvider::uint256(), TypeProvider::uint(9)}, TypePointers{}, - strings{string(), string()}, + strings{std::string(), std::string()}, strings{}, FunctionType::Kind::TVMBuilderStoreUint, StateMutability::Pure @@ -6302,7 +6461,7 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const "storeUint", TypeProvider::function( TypePointers{TypeProvider::uint256(), TypeProvider::uint(9)}, TypePointers{}, - strings{string(), string()}, + strings{std::string(), std::string()}, strings{}, FunctionType::Kind::TVMBuilderStoreUint, StateMutability::Pure @@ -6312,9 +6471,9 @@ MemberList::MemberMap TvmBuilderType::nativeMembers(const ASTNode *) const "storeTons", TypeProvider::function( TypePointers{TypeProvider::uint(128)}, TypePointers{}, - strings{string()}, + strings{std::string()}, strings{}, - FunctionType::Kind::TVMBuilderMethods, + FunctionType::Kind::TVMBuilderStoreTons, StateMutability::Pure ) }, diff --git a/compiler/libsolidity/ast/Types.h b/compiler/libsolidity/ast/Types.h index 88ad151e..679fd1ea 100644 --- a/compiler/libsolidity/ast/Types.h +++ b/compiler/libsolidity/ast/Types.h @@ -172,10 +172,28 @@ class Type enum class Category { - Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, ArraySlice, - FixedBytes, Contract, Struct, Function, Enum, UserDefinedValueType, Tuple, - Mapping, TypeType, Modifier, Magic, Module, - InaccessibleDynamic, TvmCell, TvmSlice, TvmBuilder, TvmVector, Variant, + Address, + Integer, + RationalNumber, + StringLiteral, + Bool, + FixedPoint, + Array, + ArraySlice, + FixedBytes, + Contract, + Struct, + Function, + Enum, + UserDefinedValueType, + Tuple, + Mapping, + TypeType, + Modifier, + Magic, + Module, + InaccessibleDynamic, + TvmCell, TvmSlice, TvmBuilder, TvmVector, Variant, VarInteger, InitializerList, CallList, // <-- variables of that types can't be declared in solidity contract Optional, @@ -326,7 +344,7 @@ class Type /// Might return a null pointer if there is no fitting type. virtual Type const* mobileType() const { return this; } - /// Returns the list of all members of this type. Default implementation: no members apart from bound. + /// Returns the list of all members of this type. Default implementation: no members apart from attached functions. /// @param _currentScope scope in which the members are accessed. MemberList const& members(ASTNode const* _currentScope) const; /// Convenience method, returns the type of the given named member or an empty pointer if no such member exists. @@ -377,13 +395,28 @@ class Type /// Clears all internally cached values (if any). virtual void clearCache() const; + /// Scans all "using for" directives in the @a _scope for functions implementing + /// the operator represented by @a _token. Returns the set of all definitions where the type + /// of the first argument matches this type object. + /// + /// @note: If the AST has passed analysis without errors, + /// the function will find at most one definition for an operator. + /// + /// @param _unary If true, only definitions that accept exactly one argument are included. + /// Otherwise only definitions that accept exactly two arguments. + std::set> operatorDefinitions( + Token _token, + ASTNode const& _scope, + bool _unary + ) const; + private: /// @returns a member list containing all members added to this type by `using for` directives. - static MemberList::MemberMap boundFunctions(Type const& _type, ASTNode const& _scope); + static MemberList::MemberMap attachedFunctions(Type const& _type, ASTNode const& _scope); protected: /// @returns the members native to this type depending on the given context. This function - /// is used (in conjunction with boundFunctions to fill m_members below. + /// is used (in conjunction with attachedFunctions to fill m_members below. virtual MemberList::MemberMap nativeMembers(ASTNode const* /*_currentScope*/) const { return MemberList::MemberMap(); @@ -465,7 +498,7 @@ class IntegerType: public Type bool nameable() const override { return true; } std::string toString(bool _withoutDataLocation) const override; - + MemberList::MemberMap nativeMembers(ASTNode const*) const override; Type const* encodingType() const override { return this; } TypeResult interfaceType(bool) const override { return this; } @@ -835,7 +868,7 @@ class InitializerListType: public Type /** * Used for call list in external message creation - * tvm.buildExtMsg({...,call : {Contract.Func, arg1, arg2} ,...}) + * abi.encodeExtMsg({...,call : {Contract.Func, arg1, arg2} ,...}) */ class CallListType: public Type { @@ -866,6 +899,7 @@ class CompositeType: public Type /// elements of decomposition of these elements and so on, up to non-composite types. /// Each type is included only once. std::vector fullDecomposition() const; + TypeResult unaryOperatorResult(Token _operator) const override; protected: /// @returns a list of types that together make up the data part of this type. @@ -876,59 +910,6 @@ class CompositeType: public Type virtual std::vector decomposition() const = 0; }; -/** - * Base class used by types which are not value types and can be stored either in storage, memory - * or calldata. This is currently used by arrays and structs. - */ -class ReferenceType: public CompositeType -{ -protected: - explicit ReferenceType() {} - -public: - TypeResult unaryOperatorResult(Token _operator) const override; - TypeResult binaryOperatorResult(Token, Type const*) const override - { - return nullptr; - } - unsigned memoryHeadSize() const override { return 32; } - u256 memoryDataSize() const override = 0; - - unsigned calldataEncodedSize(bool) const override = 0; - unsigned calldataEncodedTailSize() const override = 0; - - /// @returns a copy of this type with location (recursively) changed to @a _location, - /// whereas isPointer is only shallowly changed - the deep copy is always a bound reference. - virtual std::unique_ptr copyForLocation(bool _isPointer) const = 0; - - Type const* mobileType() const override { return withLocation(true); } - bool hasSimpleZeroValueInMemory() const override { return false; } - - /// Storage references can be pointers or bound references. In general, local variables are of - /// pointer type, state variables are bound references. Assignments to pointers or deleting - /// them will not modify storage (that will only change the pointer). Assignment from - /// non-storage objects to a variable of storage pointer type is not possible. - /// For anything other than storage, this always returns true because assignments - /// never change the contents of the original value. - bool isPointer() const; - - bool operator==(ReferenceType const& /*_other*/) const - { - return true; - } - - Type const* withLocation(bool _isPointer) const; - -protected: - Type const* copyForLocationIfReference(Type const* _type) const; - /// @returns the suffix computed from the reference part to be used by identifier(); - std::string identifierLocationSuffix() const; - - // TODO DELETE - // it's useless parameter in TVM - bool m_isPointer = true; -}; - /** * The type of an array. The flavours are byte array (bytes), statically- ([]) * and dynamically-sized array ([]). @@ -936,23 +917,23 @@ class ReferenceType: public CompositeType * one slot). Dynamically sized arrays (including byte arrays) start with their size as a uint and * thus start on their own slot. */ -class ArrayType: public ReferenceType +class ArrayType: public CompositeType { public: /// Constructor for a byte array ("bytes") and string. explicit ArrayType(bool _isString = false); - /// Constructor for a dynamically sized array type ("type[]") + /// Constructor for a dynamically sized array type ("[]") ArrayType(Type const* _baseType): - ReferenceType(), - m_baseType(copyForLocationIfReference(_baseType)) + CompositeType(), + m_baseType(_baseType) { } - /// Constructor for a fixed-size array type ("type[20]") + /// Constructor for a fixed-size array type ("[]") ArrayType(Type const* _baseType, u256 _length): - ReferenceType(), - m_baseType(copyForLocationIfReference(_baseType)), + CompositeType(), + m_baseType(_baseType), m_hasDynamicLength(true), m_length(std::move(_length)) {} @@ -992,8 +973,6 @@ class ArrayType: public ReferenceType u256 const& length() const { return m_length; } u256 memoryDataSize() const override; - std::unique_ptr copyForLocation(bool _isPointer) const override; - /// The offset to advance in calldata to move from one array element to the next. unsigned calldataStride() const { return isByteArrayOrString() ? 1 : m_baseType->calldataHeadSize(); } /// The offset to advance in memory to move from one array element to the next. @@ -1021,10 +1000,10 @@ class ArrayType: public ReferenceType mutable std::optional m_interfaceType_library; }; -class ArraySliceType: public ReferenceType +class ArraySliceType: public CompositeType { public: - explicit ArraySliceType(ArrayType const& _arrayType): ReferenceType(), m_arrayType(_arrayType) {} + explicit ArraySliceType(ArrayType const& _arrayType): CompositeType(), m_arrayType(_arrayType) {} Category category() const override { return Category::ArraySlice; } BoolResult isImplicitlyConvertibleTo(Type const& _other) const override; @@ -1042,8 +1021,6 @@ class ArraySliceType: public ReferenceType ArrayType const& arrayType() const { return m_arrayType; } u256 memoryDataSize() const override { solAssert(false, ""); } - std::unique_ptr copyForLocation(bool) const override { solAssert(false, ""); } - protected: std::vector> makeStackItems() const override; std::vector decomposition() const override { return {m_arrayType.baseType()}; } @@ -1118,11 +1095,11 @@ class ContractType: public Type /** * The type of a struct instance, there is one distinct type per struct definition. */ -class StructType: public ReferenceType +class StructType: public CompositeType { public: explicit StructType(StructDefinition const& _struct): - ReferenceType(), m_struct(_struct) {} + CompositeType(), m_struct(_struct) {} Category category() const override { return Category::Struct; } BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; @@ -1148,8 +1125,6 @@ class StructType: public ReferenceType bool recursive() const; - std::unique_ptr copyForLocation(bool _isPointer) const override; - std::string canonicalName() const override; std::string signatureInExternalFunction(bool _structsByName) const override; @@ -1354,12 +1329,13 @@ class FunctionType: public Type AddressIsStdAddrWithoutAnyCast, ///< address.isStdAddrWithoutAnyCast for address AddressIsZero, ///< address.isStdZero() address.isExternZero() for address AddressMakeAddrExtern, ///< address.makeAddrExtern() for address - AddressMakeAddrNone, ///< address.makeAddrNone() for address AddressMakeAddrStd, ///< address.makeAddrStd() for address AddressTransfer, ///< address.transfer() AddressType, ///< address.getType() for address AddressUnpack, ///< address.unpack() for address + IntCast, ///< int a; a.cast(uint8); + VariantIsUint, VariantToUint, @@ -1376,13 +1352,14 @@ class FunctionType: public Type TVMSliceLoadFunctionParams, ///< slice.loadFunctionParams(function_name) TVMSliceLoadInt, ///< slice.loadInt() TVMSliceLoadIntQ, ///< slice.loadIntQ() + TVMSliceLoadLE, ///< slice.load[U]intLE(4|8)[Q]() TVMSliceLoadQ, ///< slice.loadQ(types...) TVMSliceLoadRef, ///< slice.loadRef() TVMSliceLoadSlice, ///< slice.loadSlice() TVMSliceLoadStateVars, ///< slice.loadStateVars(contract_name) + TVMSliceLoadTons, ///< slice.loadTons() TVMSliceLoadUint, ///< slice.loadUint() TVMSliceLoadUintQ, ///< slice.loadUintQ() - TVMSliceLoadLE, ///< slice.load[U]intLE(4|8)[Q]() TVMSlicePreLoadInt, ///< slice.preloadInt() TVMSlicePreLoadIntQ, ///< slice.preloadIntQ() TVMSlicePreLoadSlice, ///< slice.preloadSlice() @@ -1399,6 +1376,7 @@ class FunctionType: public Type TVMBuilderMethods, ///< builder.*() TVMBuilderStore, ///< builder.store(...) TVMBuilderStoreInt, ///< builder.storeInt() + TVMBuilderStoreTons, ///< builder.storeTons() TVMBuilderStoreUint, ///< builder.storeUint() TVMTuplePush, ///< tuple.push(...) @@ -1426,6 +1404,7 @@ class FunctionType: public Type SetFlag, ///< modify the default flag for the function call transfer Stoi, ///< stoi function to convert string to an integer BlockHash, ///< BLOCKHASH + BlobHash, ///< BLOBHASH AddMod, ///< ADDMOD MulMod, ///< MULMOD @@ -1448,23 +1427,28 @@ class FunctionType: public Type MathDivMod, ///< math.divmod() MathSign, ///< math.sign() + ABIBuildExtMsg, ///< abi.encodeExtMsg() + ABIBuildIntMsg, ///< abi.encodeIntMsg() + ABICodeSalt, ///< abi.codeSalt() + ABIDecodeData, ///< abi.decodeData(contract_name, slice) + ABIDecodeFunctionParams, ///< abi.decodeFunctionParams() + ABIEncodeBody, ///< abi.encodeBody() + ABIEncodeData, ///< abi.encodeData() + ABIEncodeStateInit, ///< abi.encodeStateInit() + ABIFunctionId, ///< abi.functionId(function_name) + ABISetCodeSalt, ///< abi.setCodeSalt() + ABIStateInitHash, ///< abi.stateInitHash() + TVMAccept, ///< tvm.accept() - TVMBuildDataInit, ///< tvm.buildDataInit() - TVMBuildExtMsg, ///< tvm.buildExtMsg() - TVMBuildIntMsg, ///< tvm.buildIntMsg() - TVMBuildStateInit, ///< tvm.buildStateInit() TVMBuyGas, ///< tvm.buyGas() TVMChecksign, ///< tvm.checkSign() TVMCode, ///< tvm.code() - TVMCodeSalt, ///< tvm.codeSalt() TVMCommit, ///< tvm.commit() TVMConfigParam, ///< tvm.configParam() TVMDeploy, ///< functions to deploy contract from contract TVMDump, ///< tvm.xxxdump() - TVMEncodeBody, ///< tvm.encodeBody() TVMExit, ///< tvm.exit() TVMExit1, ///< tvm.exit1() - TVMFunctionId, ///< tvm.functionId(function_name) TVMHash, ///< tvm.hash() TVMInitCodeHash, ///< tvm.initCodeHash() TVMPubkey, ///< tvm.pubkey() @@ -1473,7 +1457,6 @@ class FunctionType: public Type TVMReplayProtTime, ///< tvm.replayProtTime() TVMResetStorage, ///< tvm.resetStorage() TVMSendMsg, ///< tvm.sendMsg() - TVMSetCodeSalt, ///< tvm.setCodeSalt() TVMSetGasLimit, ///< tvm.setGasLimit() TVMSetPubkey, ///< tvm.setPubkey() TVMSetReplayProtTime, ///< tvm.setReplayProtTime() @@ -1563,7 +1546,7 @@ class FunctionType: public Type bool saltSet = false; /// true iff the function is called as arg1.fun(arg2, ..., argn). /// This is achieved through the "using for" directive. - bool bound = false; + bool hasBoundFirstArgument = false; static Options withArbitraryParameters() { @@ -1578,7 +1561,7 @@ class FunctionType: public Type result.gasSet = _type.gasSet(); result.valueSet = _type.valueSet(); result.saltSet = _type.saltSet(); - result.bound = _type.bound(); + result.hasBoundFirstArgument = _type.hasBoundFirstArgument(); return result; } }; @@ -1613,7 +1596,7 @@ class FunctionType: public Type ) { // In this constructor, only the "arbitrary Parameters" option should be used. - solAssert(!bound() && !gasSet() && !valueSet() && !saltSet()); + solAssert(!hasBoundFirstArgument() && !gasSet() && !valueSet() && !saltSet()); } /// Detailed constructor, use with care. @@ -1645,8 +1628,8 @@ class FunctionType: public Type "Return parameter names list must match return parameter types list!" ); solAssert( - !bound() || !m_parameterTypes.empty(), - "Attempted construction of bound function without self type" + !hasBoundFirstArgument() || !m_parameterTypes.empty(), + "Attempted construction of attached function without self type" ); } @@ -1663,7 +1646,7 @@ class FunctionType: public Type /// storage pointers) are replaced by InaccessibleDynamicType instances. TypePointers returnParameterTypesWithoutDynamicTypes() const; std::vector const& returnParameterNames() const { return m_returnParameterNames; } - /// @returns the "self" parameter type for a bound function + /// @returns the "self" parameter type for an attached function Type const* selfType() const; std::string richIdentifier() const override; @@ -1697,8 +1680,8 @@ class FunctionType: public Type /// @returns true if this function can take the given arguments (possibly /// after implicit conversion). - /// @param _selfType if the function is bound, this has to be supplied and is the type of the - /// expression the function is called on. + /// @param _selfType if the function is attached as a member function, this has to be supplied + /// and is the type of the expression the function is called on. bool canTakeArguments( FuncCallArguments const& _arguments, Type const* _selfType = nullptr @@ -1761,20 +1744,20 @@ class FunctionType: public Type bool gasSet() const { return m_options.gasSet; } bool valueSet() const { return m_options.valueSet; } bool saltSet() const { return m_options.saltSet; } - bool bound() const { return m_options.bound; } + bool hasBoundFirstArgument() const { return m_options.hasBoundFirstArgument; } /// @returns a copy of this type, where gas or value are set manually. This will never set one /// of the parameters to false. Type const* copyAndSetCallOptions(bool _setGas, bool _setValue, bool _setSalt) const; - /// @returns a copy of this function type with the `bound` flag set to true. + /// @returns a copy of this function type with the `hasBoundFirstArgument` flag set to true. /// Should only be called on library functions. - FunctionTypePointer asBoundFunction() const; + FunctionTypePointer withBoundFirstArgument() const; /// @returns a copy of this function type where the location of reference types is changed /// from CallData to Memory. This is the type that would be used when the function is /// called externally, as opposed to the parameter types that are available inside the function body. - /// Also supports variants to be used for library or bound calls. + /// Also supports variants to be used for library or attached function calls. /// @param _inLibrary if true, uses DelegateCall as location. FunctionTypePointer asExternallyCallableFunction(bool _inLibrary) const; @@ -1800,8 +1783,8 @@ class FunctionType: public Type class MappingType: public CompositeType { public: - MappingType(Type const* _keyType, Type const* _valueType): - m_keyType(_keyType), m_valueType(_valueType) {} + MappingType(Type const* _keyType, ASTString _keyName, Type const* _valueType, ASTString _valueName): + m_keyType(_keyType), m_keyName(_keyName), m_valueType(_valueType), m_valueName(_valueName) {} Category category() const override { return Category::Mapping; } @@ -1821,10 +1804,11 @@ class MappingType: public CompositeType std::vector> makeStackItems() const override; Type const* keyType() const { return m_keyType; } - Type const* realKeyType() const; + ASTString keyName() const { return m_keyName; } Type const* valueType() const { return m_valueType; } + ASTString valueName() const { return m_valueName; } + Type const* realKeyType() const; MemberList::MemberMap nativeMembers(ASTNode const*) const override; - TypeResult unaryOperatorResult(Token _operator) const override; protected: @@ -1832,7 +1816,9 @@ class MappingType: public CompositeType private: Type const* m_keyType; + ASTString m_keyName; Type const* m_valueType; + ASTString m_valueName; }; /** @@ -1913,6 +1899,7 @@ class TypeType: public Type bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } std::string toString(bool _withoutDataLocation) const override { return "type(" + m_actualType->toString(_withoutDataLocation) + ")"; } MemberList::MemberMap nativeMembers(ASTNode const* _currentScope) const override; + Type const* mobileType() const override { return nullptr; } BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; protected: @@ -2013,6 +2000,8 @@ class MagicType: public Type Type const* typeArgument() const; + Type const* mobileType() const override { return nullptr; } + protected: std::vector> makeStackItems() const override { return {}; } private: diff --git a/compiler/libsolidity/ast/UserDefinableOperators.h b/compiler/libsolidity/ast/UserDefinableOperators.h new file mode 100644 index 00000000..5b98524f --- /dev/null +++ b/compiler/libsolidity/ast/UserDefinableOperators.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include + +namespace solidity::frontend +{ + +std::vector const userDefinableOperators = { + // Bitwise + langutil::Token::BitOr, + langutil::Token::BitAnd, + langutil::Token::BitXor, + langutil::Token::BitNot, + // Arithmetic + langutil::Token::Add, + langutil::Token::Sub, + langutil::Token::Mul, + langutil::Token::Div, + langutil::Token::Mod, + // Comparison + langutil::Token::Equal, + langutil::Token::NotEqual, + langutil::Token::LessThan, + langutil::Token::GreaterThan, + langutil::Token::LessThanOrEqual, + langutil::Token::GreaterThanOrEqual, +}; + +} diff --git a/compiler/libsolidity/codegen/DictOperations.cpp b/compiler/libsolidity/codegen/DictOperations.cpp index b2a72d76..4bb4fde4 100644 --- a/compiler/libsolidity/codegen/DictOperations.cpp +++ b/compiler/libsolidity/codegen/DictOperations.cpp @@ -104,7 +104,7 @@ void GetFromDict::getDict() { // if op == GetSetFromMapping than stack: value key dict keyLength // else stack: key dict keyLength - const int saveStake = pusher.stackSize(); + const int saveStack = pusher.stackSize(); std::string opcode = "DICT" + typeToDictChar(&keyType); int take{}; int ret{}; @@ -208,7 +208,7 @@ void GetFromDict::getDict() { } pusher.endOpaque(take, ret); - pusher.ensureSize(saveStake -take + ret); + pusher.ensureSize(saveStack - take + ret); } void GetFromDict::checkExist() { diff --git a/compiler/libsolidity/codegen/PeepholeOptimizer.cpp b/compiler/libsolidity/codegen/PeepholeOptimizer.cpp index 301c3790..764ce853 100644 --- a/compiler/libsolidity/codegen/PeepholeOptimizer.cpp +++ b/compiler/libsolidity/codegen/PeepholeOptimizer.cpp @@ -16,6 +16,8 @@ #include +#include + #include "PeepholeOptimizer.hpp" #include "StackOpcodeSquasher.hpp" #include "TVM.hpp" @@ -28,6 +30,8 @@ using namespace solidity::util; namespace solidity::frontend { +bool PeepholeOptimizer::withBlockPush = false; + struct Result { int removeQty{}; @@ -48,16 +52,17 @@ struct Result { class PrivatePeepholeOptimizer { public: - explicit PrivatePeepholeOptimizer(std::vector> instructions, bool _withUnpackOpaque, bool _optimizeSlice) : + explicit PrivatePeepholeOptimizer(std::vector> instructions, bool _withUnpackOpaque, bool _optimizeSlice, bool _withTuck) : m_instructions{std::move(instructions)}, m_withUnpackOpaque{_withUnpackOpaque}, - m_optimizeSlice{_optimizeSlice} + m_optimizeSlice{_optimizeSlice}, + m_withTuck{_withTuck} { } vector> const &instructions() const { return m_instructions; } int nextCommandLine(int idx) const; - static int nextCommandLine(int idx, std::vector> instructions); + static int nextCommandLine(int idx, std::vector> const& instructions); Pointer get(int idx) const; bool valid(int idx) const; void remove(int idx); @@ -65,7 +70,7 @@ class PrivatePeepholeOptimizer { std::optional optimizeAt(int idx1) const; std::optional optimizeSlice(int idx1) const; static std::optional optimizeAt1(Pointer const& cmd1, bool m_withUnpackOpaque); - static std::optional optimizeAt2(Pointer const& cmd1, Pointer const& cmd2) ; + static std::optional optimizeAt2(Pointer const& cmd1, Pointer const& cmd2, bool _withTuck); static std::optional optimizeAt3(Pointer const& cmd1, Pointer const& cmd2, Pointer const& cmd3, bool m_withUnpackOpaque); static std::optional optimizeAt4(Pointer const& cmd1, Pointer const& cmd2, Pointer const& cmd3, Pointer const& cmd4); @@ -82,7 +87,6 @@ class PrivatePeepholeOptimizer { bool optimize(const std::function(int)> &f); static std::optional> isBLKDROP2(Pointerconst& node); - static std::optional isPUSH(Pointer const& node); static bigint pushintValue(Pointer const& node); static int fetchInt(Pointer const& node); static bool isNIP(Pointer const& node); @@ -103,13 +107,17 @@ class PrivatePeepholeOptimizer { std::vector> m_instructions{}; bool m_withUnpackOpaque{}; bool m_optimizeSlice{}; + bool m_withTuck{}; }; int PrivatePeepholeOptimizer::nextCommandLine(int idx) const { + if (idx == -1) { + return -1; + } return nextCommandLine(idx + 1, m_instructions); } -int PrivatePeepholeOptimizer::nextCommandLine(int idx, std::vector> instructions) { +int PrivatePeepholeOptimizer::nextCommandLine(int idx, std::vector> const& instructions) { solAssert(0 <= idx, ""); int n = instructions.size(); while (idx < n) { @@ -167,8 +175,10 @@ std::optional PrivatePeepholeOptimizer::optimizeSlice(int idx1) const { } std::optional PrivatePeepholeOptimizer::optimizeAt(const int idx1) const { + std::optional res; if (m_optimizeSlice) { - return optimizeSlice(idx1); + res = optimizeSlice(idx1); + if (res) return res; } int idx2 = nextCommandLine(idx1); @@ -184,8 +194,6 @@ std::optional PrivatePeepholeOptimizer::optimizeAt(const int idx1) const Pointer const &cmd5 = get(idx5); Pointer const &cmd6 = get(idx6); - std::optional res; - res = optimizeAt1(cmd1, m_withUnpackOpaque); if (res) return res; @@ -193,7 +201,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt(const int idx1) const if (res) return res; if (!cmd2) return {}; - res = optimizeAt2(cmd1, cmd2); + res = optimizeAt2(cmd1, cmd2, m_withTuck); if (res) return res; if (!cmd3) return {}; @@ -217,7 +225,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt(const int idx1) const std::optional PrivatePeepholeOptimizer::optimizeAt1(Pointer const& cmd1, bool m_withUnpackOpaque) { auto cmd1CodeBlock = to(cmd1.get()); - auto cmd1GenOpcode = to(cmd1.get()); + auto cmd1GenOpcode = to(cmd1.get()); auto cmd1IfElse = to(cmd1.get()); auto cmd1Sub = to(cmd1.get()); @@ -417,12 +425,22 @@ std::optional PrivatePeepholeOptimizer::optimizeAt1(Pointer int fi = nextCommandLine(0, f); Pointer a = t.at(ti); Pointer b = f.at(fi); - if (isPureGen01(*a) && - isPureGen01(*b) && - std::dynamic_pointer_cast(a) && - std::dynamic_pointer_cast(b) + if ((isPureGen01(*a) && to(a.get())) || + to(a.get()) || + to(a.get()) || + isPUSH(a) ) { - return Result{1, a, b, gen("CONDSEL")}; + Pointer newB; + if (to(b.get()) || + to(b.get()) || + to(b.get()) + ) { + newB = b; + } else if (auto index = isPUSH(b)) { + newB = makePUSH(*index + 2); // +2 because flag and value from first branch + } + if (newB) + return Result{1, a, newB, gen("CONDSEL")}; } } } @@ -526,13 +544,13 @@ std::optional PrivatePeepholeOptimizer::optimizeAt1(Pointer return {}; } -std::optional PrivatePeepholeOptimizer::optimizeAt2(Pointer const& cmd1, Pointer const& cmd2) { +std::optional PrivatePeepholeOptimizer::optimizeAt2(Pointer const& cmd1, Pointer const& cmd2, bool _withTuck) { using namespace MathConsts; - auto cmd1GenOp = to(cmd1.get()); + auto cmd1GenOp = to(cmd1.get()); auto cmd1Glob = to(cmd1.get()); auto cmd2Exc = to(cmd2.get()); - auto cmd2GenOpcode = to(cmd2.get()); + auto cmd2GenOpcode = to(cmd2.get()); auto cmd2Glob = to(cmd2.get()); auto cmd2IfElse = to(cmd2.get()); auto cmd1Ret = to(cmd1.get()); @@ -590,6 +608,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt2(Pointer return Result{2, makeBLKDROP2(n, 1)}; } } + // SWAP // POP S2 // @@ -597,6 +616,10 @@ std::optional PrivatePeepholeOptimizer::optimizeAt2(Pointer if (isSWAP(cmd1) && isPOP(cmd2) && isPOP(cmd2).value() == 2) { return Result{2, makeBLKDROP2(1, 2)}; } + // PUSH Si | gen01 + // DROP N + // + // DROP N-1 if ((isPUSH(cmd1) || isPureGen01(*cmd1)) && isDrop(cmd2)) { int qty = isDrop(cmd2).value(); if (qty == 1) { @@ -608,7 +631,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt2(Pointer // BLKPUSH N, index / DROP // BLKDROP N - if (isBLKPUSH1 && isDrop(cmd2)) { + if (PeepholeOptimizer::withBlockPush && isBLKPUSH1 && isDrop(cmd2)) { auto [qty, index] = isBLKPUSH1.value(); int diff = qty - isDrop(cmd2).value(); if (diff == 0) @@ -644,6 +667,20 @@ std::optional PrivatePeepholeOptimizer::optimizeAt2(Pointer auto [down, up] = _isBLKDROP2.value(); return Result{2, makeBLKDROP2(down, up - 1), cmd1}; } + // PUSH SN + // BLKDROP2 down, top + // => + // BLKDROP2 down, top-1 + // PUSH S? + if (_withTuck && isPUSH(cmd1) && _isBLKDROP2) { + int n = *isPUSH(cmd1); + auto [down, top] = _isBLKDROP2.value(); + if (n < top) { + return Result{2, makeBLKDROP2(down, top - 1), cmd1}; + } else if (n >= down + top - 1) { + return Result{2, makeBLKDROP2(down, top - 1), makePUSH(n - down)}; + } + } // BLKPUSH // BLKDROP2 // => @@ -816,23 +853,51 @@ std::optional PrivatePeepholeOptimizer::optimizeAt2(Pointer { return Result{2}; } + // SETGLOB N + // GETGLOB N + // + // DUP + // SETGLOB N if (cmd1Glob && cmd1Glob->opcode() == Glob::Opcode::SetOrSetVar && cmd2Glob && cmd2Glob->opcode() == Glob::Opcode::GetOrGetVar && cmd1Glob->index() == cmd2Glob->index() ) { return Result{2, makePUSH(0), makeSetGlob(cmd1Glob->index())}; } + // PUSHINT N + // ADDCONST ? | INC | DEC + // + // PUSHINT (N+delta) + if (is(cmd1, "PUSHINT") && isConstAdd(cmd2)) { + bigint n = pushintValue(cmd1); + bigint delta = getAddNum(cmd2); + // TODO check overflow + return Result{2, gen("PUSHINT " + toString(n + delta))}; + } + // PUSHINT N + // UFITS ? | FITS ? + // + // PUSHINT N + if (is(cmd1, "PUSHINT") && (is(cmd2, "UFITS") || is(cmd2, "FITS"))) { + bigint n = pushintValue(cmd1); + int bits = fetchInt(cmd2); + auto type = TypeProvider::integer(bits, + is(cmd2, "UFITS") ? IntegerType::Modifier::Unsigned : + IntegerType::Modifier::Signed); + if (type->minValue() <= n && n <= type->maxValue()) + return Result{2, gen("PUSHINT " + toString(n))}; + } if (isConstAdd(cmd1) && isConstAdd(cmd2)) { int final_add = getAddNum(cmd1) + getAddNum(cmd2); if (-128 <= final_add && final_add <= 127) return Result{2, gen("ADDCONST " + std::to_string(final_add))}; } - if ((is(cmd1, "INDEX_NOEXCEP") || is(cmd1, "INDEX_EXCEP")) && 0 <= strToInt(arg(cmd1)) && strToInt(arg(cmd1)) <= 3 && - (is(cmd2, "INDEX_NOEXCEP") || is(cmd2, "INDEX_EXCEP")) && 0 <= strToInt(arg(cmd2)) && strToInt(arg(cmd2)) <= 3) { + if ((is(cmd1, "INDEX_NOEXCEP") || is(cmd1, "INDEX_EXCEP")) && 0 <= fetchInt(cmd1) && fetchInt(cmd1) <= 3 && + (is(cmd2, "INDEX_NOEXCEP") || is(cmd2, "INDEX_EXCEP")) && 0 <= fetchInt(cmd2) && fetchInt(cmd2) <= 3) { return Result{2, gen("INDEX2 " + arg(cmd1) + ", " + arg(cmd2))}; } if (is(cmd1, "INDEX2") && - (is(cmd2, "INDEX_NOEXCEP") || is(cmd2, "INDEX_EXCEP")) && 0 <= strToInt(arg(cmd2)) && strToInt(arg(cmd2)) <= 3 + (is(cmd2, "INDEX_NOEXCEP") || is(cmd2, "INDEX_EXCEP")) && 0 <= fetchInt(cmd2) && fetchInt(cmd2) <= 3 ) { auto [i, j] = getIndexes(arg(cmd1)); if (0 <= i && i <= 3 && @@ -940,6 +1005,16 @@ std::optional PrivatePeepholeOptimizer::optimizeAt2(Pointer if (is(cmd1, "EQUAL")) return Result{2, gen("NEQ")}; if (is(cmd1, "NEQ")) return Result{2, gen("EQUAL")}; + if (is(cmd1, "LESSINT")) { // !(x < value) => x >= value => x > value-1 + int value = fetchInt(cmd1); + if (-128 <= value - 1 && value - 1 < 128) + return Result{2, gen("GTINT " + toString(value - 1))}; + } + if (is(cmd1, "GTINT")) { // !(x > value) => x <= value => x < value+1 + int value = fetchInt(cmd1); + if (-128 <= value + 1 && value + 1 < 128) + return Result{2, gen("LESSINT " + toString(value + 1))}; + } if (is(cmd1, "EQINT")) return Result{2, gen("NEQINT " + arg(cmd1))}; if (is(cmd1, "NEQINT")) return Result{2, gen("EQINT " + arg(cmd1))}; @@ -1039,7 +1114,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt2(Pointer if (lc->type() == LogCircuit::Type::AND && lc->body()->instructions().size() == 2) { auto cmd2_0 = lc->body()->instructions().at(0); auto cmd2_1 = lc->body()->instructions().at(1); - auto _true = to(cmd2_1.get()); + auto _true = to(cmd2_1.get()); if (isDrop(cmd2_0) == 1 && _true && _true->opcode() == "TRUE") { return Result{2}; } @@ -1051,8 +1126,8 @@ std::optional PrivatePeepholeOptimizer::optimizeAt2(Pointer // AND // => // - auto _true = to(cmd1.get()); - auto _and = to(cmd2.get()); + auto _true = to(cmd1.get()); + auto _and = to(cmd2.get()); if (_true && _true->opcode() == "TRUE" && _and && _and->opcode() == "AND") { return Result{2}; @@ -1106,7 +1181,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt2(Pointer // => // ABS if (is(cmd1, "ABS") && - cmd2GenOpcode->opcode() == "MODPOW2" && cmd2GenOpcode->arg() == "256" + cmd2GenOpcode && cmd2GenOpcode->opcode() == "MODPOW2" && cmd2GenOpcode->arg() == "256" ) { return Result{2, gen("ABS")}; } @@ -1125,7 +1200,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt2(Pointer // BLKPUSH Q, 0 / DUP // => // BLKPUSH N+Q, 0 - if (isBLKPUSH(cmd1) && isBLKPUSH(cmd2)) { + if (PeepholeOptimizer::withBlockPush && isBLKPUSH(cmd1) && isBLKPUSH(cmd2)) { auto [qty0, index0] = isBLKPUSH(cmd1).value(); auto [qty1, index1] = isBLKPUSH(cmd2).value(); if (index0 == 0 && index1 == 0 && qty0 + qty1 <= 15) @@ -1140,7 +1215,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt2(Pointer ) { // TODO add LD[I|U]LE[4|8] int n = isDrop(cmd2).value(); - Pointer newOpcode = gen("P" + cmd1GenOp->fullOpcode()); + Pointer newOpcode = gen("P" + cmd1GenOp->fullOpcode()); if (n == 1) { return Result{2, newOpcode}; } else { @@ -1148,6 +1223,17 @@ std::optional PrivatePeepholeOptimizer::optimizeAt2(Pointer } } + // 26 + 118 gas units + // PLDREF + // CTOS + // => + // 118 + 18 gas units + // LDREFRTOS + // NIP + if (is(cmd1, "PLDREF") && is(cmd2, "CTOS")) { + return Result{2, gen("LDREFRTOS"), makeBLKDROP2(1, 1)}; + } + return {}; } @@ -1157,7 +1243,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt3(Pointer auto cmd1PushCellOrSlice = to(cmd1.get()); auto isPUSH2 = isPUSH(cmd2); auto cmd2PushCellOrSlice = to(cmd2.get()); - auto cmd3GenOpcode = to(cmd3.get()); + auto cmd3GenOpcode = to(cmd3.get()); auto cmd3SubProgram = to(cmd3.get()); // NEW @@ -1225,16 +1311,33 @@ std::optional PrivatePeepholeOptimizer::optimizeAt3(Pointer if (-128 <= val - 1 && val - 1 < 128 && is(cmd3, "LEQ")) return Result{3, newCmd2, gen("GTINT " + toString(val - 1))}; } + // PUSHINT A + // PUSHINT B + // ADD | MUL + // + // PUSHINT A+B | PUSHINT A*B if (is(cmd1, "PUSHINT") && is(cmd2, "PUSHINT") && - cmd3GenOpcode && cmd3GenOpcode->opcode() == "MUL" + cmd3GenOpcode && isIn(cmd3GenOpcode->opcode(), "ADD", "MUL", "MAX") ) { bigint a = pushintValue(cmd1); bigint b = pushintValue(cmd2); - bigint c = a * b; + bigint c; + if (cmd3GenOpcode->opcode() == "ADD") + c = a + b; + else if (cmd3GenOpcode->opcode() == "MUL") + c = a * b; + else if (cmd3GenOpcode->opcode() == "MAX") + c = std::max(a, b); + else + solUnimplemented(""); return Result{3, gen("PUSHINT " + toString(c))}; } - + // PUSHINT A + // PUSHINT B + // DIV + // + // PUSHINT A/B if (is(cmd1, "PUSHINT") && is(cmd2, "PUSHINT") && cmd3GenOpcode && cmd3GenOpcode->opcode() == "DIV" @@ -1247,6 +1350,35 @@ std::optional PrivatePeepholeOptimizer::optimizeAt3(Pointer } } + // PUSHINT + // PUSH SN / gen01 + // ADD / MUL + // + // PUSH S(N-1) / gen01 + // ADDCONST / MULCONST + if (is(cmd1, "PUSHINT") && + (is(cmd3, "ADD") || is(cmd3, "MUL")) + ) { + bigint val = pushintValue(cmd1); + if (-128 <= val && val <= 127) { + if ((isPureGen01(*cmd2) && to(cmd2.get())) || + to(cmd2.get()) || + isPUSH(cmd2) + ) { + Pointer newCmd; + if (to(cmd2.get()) || + to(cmd2.get()) + ) { + newCmd = cmd2; + } else if (auto index = isPUSH(cmd2); index.has_value() && *index > 0) { + newCmd = makePUSH(*index - 1); + } + if (newCmd) + return Result{3, newCmd, gen((is(cmd3, "ADD") ? "ADDCONST " : "MULCONST ") + toString(val))}; + } + } + } + // TRUE // NEWC // STI 1 @@ -1281,7 +1413,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt3(Pointer // => // gen(0, 1) // BLKPUSH N+1, 0 - if ( + if (PeepholeOptimizer::withBlockPush && // ? isPureGen01(*cmd1) && isBLKPUSH(cmd2) && isPureGen01(*cmd3) && @@ -1316,7 +1448,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt3(Pointer if (cmd3SubProgram) { std::vector> const& instructions = cmd3SubProgram->block()->instructions(); if (instructions.size() == 1 && - *instructions.at(0) == *createNode(".inline concatenateStrings", 2, 1)) { + *instructions.at(0) == *createNode(".inline __concatenateStrings", 2, 1)) { string hexStr = cmd1PushCellOrSlice->chainBlob() + cmd2PushCellOrSlice->chainBlob(); return Result{3, makePushCellOrSlice(hexStr, false)}; } @@ -1335,7 +1467,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt3(Pointer ) { std::vector> const &cmds = ifRef->trueBody()->instructions(); if (cmds.size() == 1) { - if (auto gen = to(cmds.at(0).get())) { + if (auto gen = to(cmds.at(0).get())) { if (isIn(gen->fullOpcode(), ".inline c7_to_c4", ".inline upd_only_time_in_c4")) { return Result{2, cmd2}; } @@ -1356,6 +1488,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt4(Pointer bigint sum = 0; sum += (is(cmd2, "ADD") ? +1 : -1) * pushintValue(cmd1); sum += (is(cmd4, "ADD") ? +1 : -1) * pushintValue(cmd3); + // TODO DELETE return Result{4, gen("PUSHINT " + toString(sum)), gen("ADD")}; } } @@ -1371,6 +1504,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt4(Pointer gen("STSLICE")}; } } + // TODO if value on the top of the stack < 0 // ADDCONST/INC/DEC // UFIT/FIT N // ADDCONST/INC/DEC @@ -1389,6 +1523,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAt4(Pointer } } } + if (is(cmd1, "PUSHINT") && is(cmd2, "NEWC") && is(cmd3, "STSLICECONST") && @@ -1638,6 +1773,11 @@ std::optional PrivatePeepholeOptimizer::optimizeAtInf(int idx1) const { bitString += StrUtils::toBitString(num, len); opcodeQty += 2; i = nextCommandLine(j); + } else if (c2 && is(c1, "PUSHINT") && is(c2, "STVARUINT16", "STGRAMS")) { + bigint num = pushintValue(c1); + bitString += StrUtils::tonsToBinaryString(num); + opcodeQty += 2; + i = nextCommandLine(j); } else if (c2 && is(c1, "PUSHINT") && is(c2, "STZEROES")) { int len = fetchInt(c1); bitString += std::string(len, '0'); @@ -1648,12 +1788,6 @@ std::optional PrivatePeepholeOptimizer::optimizeAtInf(int idx1) const { bitString += StrUtils::toBitString(hexSlice); opcodeQty += 2; i = nextCommandLine(j); - } else if (c2 && is(c1, "PUSHINT") && is(c2, "STGRAMS")) { - bigint arg = pushintValue(c1); - bitString += StrUtils::tonsToBinaryString(arg); - opcodeQty += 2; - i = nextCommandLine(j); - break; } else { break; } @@ -1785,6 +1919,29 @@ std::optional PrivatePeepholeOptimizer::optimizeAtInf(int idx1) const { } } + // BLKSWAP n, 1 + // POP n+1 + // ... + // POP n+1 // n times + // => + // BLKDROP2 n, n+1 + if (isBLKSWAP(cmd1) && idx2 != -1) { + auto [n, up] = isBLKSWAP(cmd1).value(); + if (up == 1) { + int index = idx2; + bool ok = true; + int i = 0; + for (; ok && i < n && index != -1; ++i, index = nextCommandLine(index)) { + auto cmd = get(index); + ok &= isPOP(cmd).has_value() && isPOP(cmd) == n + 1; + } + ok &= i == n; + if (ok) { + return Result{n + 1, makeBLKDROP2(n, n + 1)}; + } + } + } + // ROLL n // ROLL n+1 // ROLL n+2 @@ -1816,34 +1973,45 @@ std::optional PrivatePeepholeOptimizer::optimizeAtInf(int idx1) const { // squash stack opcodes { - StackState bestState{}; - int bestQty{}; + struct BestResult { + StackState bestState; + int bestStartStackSize = 0; + int bestOpcodeQty = 0; + }; + std::optional bestResult; + + for (int startStackSize = 0; startStackSize <= StackState::MAX_STACK_DEPTH; ++startStackSize) + { + StackState state{startStackSize}; + int i = idx1; + int gasCost = 0; + int opcodeQty = 0; + while (true) { + if (i == -1) + break; + auto stack = to(get(i).get()); + if (!stack) + break; + if (!state.apply(*stack)) { + break; + } - StackState state; - int i = idx1; - int qty = 0; - while (true) { - if (i == -1) - break; - auto stack = to(get(i).get()); - if (!stack) - break; - if (!state.apply(*stack)) { - break; - } + ++opcodeQty; + gasCost += OpcodeUtils::gasCost(*stack); + auto newGasCost = StackOpcodeSquasher::gasCost(startStackSize, state, m_withTuck); + if (newGasCost.has_value() && newGasCost < gasCost) { + bestResult = BestResult{state, startStackSize, opcodeQty}; + } - ++qty; - int steps = StackOpcodeSquasher::steps(state); - if (steps != -1 && steps < qty) { - bestState = state; - bestQty = qty; + i = nextCommandLine(i); } - - i = nextCommandLine(i); } - if (bestQty != 0) { - return Result{bestQty, StackOpcodeSquasher::recover(bestState)}; + if (bestResult.has_value()) { + auto newOpcodes = StackOpcodeSquasher::recover(bestResult.value().bestStartStackSize, + bestResult.value().bestState, + m_withTuck); + return Result{bestResult.value().bestOpcodeQty, newOpcodes}; } } @@ -1908,6 +2076,7 @@ std::optional PrivatePeepholeOptimizer::optimizeAtInf(int idx1) const { } break; } + // TODO implement another cases default: ok = false; break; @@ -2000,7 +2169,7 @@ std::optional PrivatePeepholeOptimizer::squashPush(const int idx1) const auto cmd1Gen = to(cmd1.get()); auto cmd1PushCellOrSlice = to(cmd1.get()); - if (isPUSH(cmd1) && isPUSH(cmd2)) { + if (PeepholeOptimizer::withBlockPush && isPUSH(cmd1) && isPUSH(cmd2)) { int i = idx1, n = 0; while (isPUSH(get(i)) && isPUSH(get(i)).value() == isPUSH(cmd1).value()) { n++; @@ -2011,7 +2180,7 @@ std::optional PrivatePeepholeOptimizer::squashPush(const int idx1) const } } - if (isPUSH(cmd1) && isPUSH(cmd2) && isPUSH(cmd3)) { + if (PeepholeOptimizer::withBlockPush && isPUSH(cmd1) && isPUSH(cmd2) && isPUSH(cmd3)) { const int si = *isPUSH(cmd1); const int sj = *isPUSH(cmd2) - 1 == -1? si : *isPUSH(cmd2) - 1; const int sk = *isPUSH(cmd3) - 2 == -1? si : ( @@ -2021,7 +2190,8 @@ std::optional PrivatePeepholeOptimizer::squashPush(const int idx1) const return Result{3, makePUSH3(si, sj, sk)}; } } - if (isPUSH(cmd1) && isPUSH(cmd2)) { + + if (PeepholeOptimizer::withBlockPush && isPUSH(cmd1) && isPUSH(cmd2)) { const int si = *isPUSH(cmd1); const int sj = *isPUSH(cmd2) - 1 == -1? si : *isPUSH(cmd2) - 1; if (si <= 15 && sj <= 15) { @@ -2031,7 +2201,7 @@ std::optional PrivatePeepholeOptimizer::squashPush(const int idx1) const // TODO delete // squash PUSHINT, NEWDICT, NILL, FALSE, TRUE, (PUSHINT 0 NEWDICT PAIR) etc - if (isPureGen01(*cmd1)) { + if (PeepholeOptimizer::withBlockPush && isPureGen01(*cmd1)) { int i = idx1; int n = 0; while (true) { @@ -2049,7 +2219,7 @@ std::optional PrivatePeepholeOptimizer::squashPush(const int idx1) const } // squash PUSHREF/PUSHREFSLICE/CELL - if (cmd1PushCellOrSlice) { + if (PeepholeOptimizer::withBlockPush && cmd1PushCellOrSlice) { int i = idx1; int n = 0; while (true) { @@ -2067,42 +2237,34 @@ std::optional PrivatePeepholeOptimizer::squashPush(const int idx1) const } } - // SWAP - // OVER - // => - // TUCK - if (isXCHG_S0(cmd1) && isXCHG_S0(cmd1).value() == 1 && - isPUSH(cmd2) && *isPUSH(cmd2) == 1 - ) { - return Result{2, makeTUCK()}; - } - - // PUSH Si - // SWAP - // => - // PUXC Si, S-1 - if (isPUSH(cmd1) && - isXCHG_S0(cmd2) && isXCHG_S0(cmd2).value() == 1 - ) { - int i = *isPUSH(cmd1); - if (0 <= i && i <= 15) - return Result{2, makePUXC(i, -1)}; - } + if (m_withTuck) { + // PUSH Si + // SWAP + // => + // PUXC Si, S-1 + if (isPUSH(cmd1) && + isXCHG_S0(cmd2) && isXCHG_S0(cmd2).value() == 1 + ) { + int i = *isPUSH(cmd1); + if (0 <= i && i <= 15) + return Result{2, makePUXC(i, -1)}; + } - // XCHG Si - // PUSH Sj - // => - // XCPU Si, Sj - if (m_optimizeSlice && - isXCHG_S0(cmd1) && cmd2 && isPUSH(cmd2) - ) { - int i = isXCHG_S0(cmd1).value(); - int j = isPUSH(cmd2).value(); - if ( - 0 <= i && i <= 15 && - 0 <= j && j <= 15 + // XCHG Si + // PUSH Sj + // => + // XCPU Si, Sj + if (m_optimizeSlice && + isXCHG_S0(cmd1) && cmd2 && isPUSH(cmd2) ) { - return Result{2, makeXCPU(i, j)}; + int i = isXCHG_S0(cmd1).value(); + int j = isPUSH(cmd2).value(); + if ( + 0 <= i && i <= 15 && + 0 <= j && j <= 15 + ) { + return Result{2, makeXCPU(i, j)}; + } } } @@ -2201,26 +2363,14 @@ std::optional> PrivatePeepholeOptimizer::isBLKDROP2(Pointer< return std::nullopt; } -std::optional PrivatePeepholeOptimizer::isPUSH(Pointer const& node) { - auto stack = to(node.get()); - if (isStack(node, Stack::Opcode::PUSH_S)) { - return stack->i(); - } - if (isStack(node, Stack::Opcode::BLKPUSH)) { - if (stack->i() == 1) - return stack->j(); - } - return {}; -} - bigint PrivatePeepholeOptimizer::pushintValue(Pointer const& node) { solAssert(is(node, "PUSHINT"), ""); - auto g = dynamic_pointer_cast(node); + auto g = dynamic_pointer_cast(node); return bigint{g->arg()}; } int PrivatePeepholeOptimizer::fetchInt(Pointer const& node) { - auto g = dynamic_pointer_cast(node); + auto g = dynamic_pointer_cast(node); return strToInt(g->arg()); } @@ -2232,14 +2382,14 @@ bool PrivatePeepholeOptimizer::isNIP(Pointer const& node) { } std::string PrivatePeepholeOptimizer::arg(Pointer const& node) { - auto g = dynamic_pointer_cast(node); + auto g = dynamic_pointer_cast(node); solAssert(g, ""); return g->arg(); } template bool PrivatePeepholeOptimizer::is(Pointer const& node, Args&&... cmd) { - auto g = to(node.get()); + auto g = to(node.get()); return g && isIn(g->opcode(), std::forward(cmd)...); } @@ -2256,13 +2406,13 @@ bool PrivatePeepholeOptimizer::isExc(Pointer const& node, Args&&... } bool PrivatePeepholeOptimizer::isConstAdd(Pointer const& node) { - auto gen = to(node.get()); + auto gen = to(node.get()); return gen && isIn(gen->opcode(), "INC", "DEC", "ADDCONST"); } int PrivatePeepholeOptimizer::getAddNum(Pointer const& node) { solAssert(isConstAdd(node), ""); - auto gen = to(node.get()); + auto gen = to(node.get()); solAssert(gen, ""); if (gen->opcode() == "INC") { return +1; @@ -2283,10 +2433,10 @@ bool PrivatePeepholeOptimizer::isStack(Pointer const& node, Stack::O bool PrivatePeepholeOptimizer::isSimpleCommand(Pointer const& node) { + // See also isPureGen01 auto gen = to(node.get()); - // TODO add another Gen return gen && - (to(gen) || to(gen) || to(gen) || to(gen) || to(gen)) && + (to(gen) || to(gen) || to(gen) || to(gen) || to(gen)) && gen->take() == 0 && gen->ret() == 1; } @@ -2295,7 +2445,7 @@ bool PrivatePeepholeOptimizer::isAddOrSub(Pointer const& node) { } bool PrivatePeepholeOptimizer::isCommutative(Pointer const& node) { - auto g = dynamic_pointer_cast(node); + auto g = dynamic_pointer_cast(node); return g && isIn(g->fullOpcode(), "ADD", "AND", @@ -2310,7 +2460,23 @@ bool PrivatePeepholeOptimizer::isCommutative(Pointer const& node) { ); } +bool PeepholeOptimizer::visit(CodeBlock &_node) { + optimizeBlock(_node); + return true; +} + void PeepholeOptimizer::endVisit(CodeBlock &_node) { + optimizeBlock(_node); +} + +bool PeepholeOptimizer::visit(Function &/*_node*/) { + //if (_node.name() == "") + // _node.block()->accept(*this); + //return false; + return true; +} + +void PeepholeOptimizer::optimizeBlock(CodeBlock &_node) const { { std::optional r = PrivatePeepholeOptimizer::optimizeAt1(_node.shared_from_this(), m_withUnpackOpaque); if (r && r.value().commands.size() == 1) { @@ -2322,7 +2488,7 @@ void PeepholeOptimizer::endVisit(CodeBlock &_node) { std::vector> instructions = _node.instructions(); - PrivatePeepholeOptimizer optimizer{instructions, m_withUnpackOpaque, m_optimizeSlice}; + PrivatePeepholeOptimizer optimizer{instructions, m_withUnpackOpaque, m_optimizeSlice, m_withTuck}; optimizer.optimize([&](int index){ return optimizer.unsquash(m_withUnpackOpaque, index); }); diff --git a/compiler/libsolidity/codegen/PeepholeOptimizer.hpp b/compiler/libsolidity/codegen/PeepholeOptimizer.hpp index da5c4884..46e1facc 100644 --- a/compiler/libsolidity/codegen/PeepholeOptimizer.hpp +++ b/compiler/libsolidity/codegen/PeepholeOptimizer.hpp @@ -21,12 +21,19 @@ namespace solidity::frontend { class PeepholeOptimizer : public TvmAstVisitor { public: - explicit PeepholeOptimizer(bool _withUnpackOpaque, bool _optimizeSlice) - : m_withUnpackOpaque{_withUnpackOpaque}, m_optimizeSlice{_optimizeSlice} {} + explicit PeepholeOptimizer(bool _withUnpackOpaque, bool _optimizeSlice, bool _withTuck) + : m_withUnpackOpaque{_withUnpackOpaque}, m_optimizeSlice{_optimizeSlice}, m_withTuck{_withTuck} {} + bool visit(Function &_node) override; + bool visit(CodeBlock &_node) override; void endVisit(CodeBlock &_node) override; + private: + void optimizeBlock(CodeBlock &_node) const; private: bool m_withUnpackOpaque{}; bool m_optimizeSlice{}; + bool m_withTuck{}; + public: + static bool withBlockPush; }; } // end solidity::frontend diff --git a/compiler/libsolidity/codegen/StackOpcodeSquasher.cpp b/compiler/libsolidity/codegen/StackOpcodeSquasher.cpp index 6c1a45b4..ecd60e68 100644 --- a/compiler/libsolidity/codegen/StackOpcodeSquasher.cpp +++ b/compiler/libsolidity/codegen/StackOpcodeSquasher.cpp @@ -17,143 +17,441 @@ #include "StackOpcodeSquasher.hpp" using namespace solidity::frontend; +using namespace std; -StackState::StackState() { - m_hash = 0; - for (int i = 0; i < maxStackDepth; ++i) { +StackState::StackState(int _size) { + m_size = _size; + for (int8_t i = 0; i < m_size; ++i) { m_values[i] = i; - m_hash = m_hash * 31 + m_values[i]; } + updHash(); } bool StackState::apply(Stack const &opcode) { + auto execPUSHS = [this](int index) -> bool { + if (index < m_size && m_size + 1 <= StackState::MAX_STACK_DEPTH) { + int8_t val = m_values[index]; + for (int i = m_size - 1; 0 <= i; --i) + m_values[i + 1] = m_values[i]; + m_values[0] = val; + ++m_size; + return true; + } + return false; + }; + auto execPUXC = [this](int index, int j) -> bool { + if (j + 1 < m_size && index < m_size && m_size + 1 <= StackState::MAX_STACK_DEPTH) { + int8_t val = m_values[index]; + for (int i = m_size - 1; 0 <= i; --i) + m_values[i + 1] = m_values[i]; + m_values[0] = val; + std::swap(m_values[0], m_values[1]); + std::swap(m_values[0], m_values[j + 1]); + ++m_size; + return true; + } + return false; + }; + bool ok = false; + int const index0 = opcode.i(); + int const index1 = opcode.j(); + int const index2 = opcode.k(); switch (opcode.opcode()) { - case Stack::Opcode::BLKSWAP: { - int down = opcode.i(); - int up = opcode.j(); - if (down + up > maxStackDepth) { - break; - } - ok = true; - std::reverse(m_values.begin(), m_values.begin() + up); - std::reverse(m_values.begin() + up, m_values.begin() + up + down); - std::reverse(m_values.begin(), m_values.begin() + up + down); + case Stack::Opcode::BLKSWAP: { + int down = opcode.i(); + int up = opcode.j(); + if (down + up > m_size) { break; } - case Stack::Opcode::REVERSE: { - int qty = opcode.i(); - int index = opcode.j(); - if (index + qty > maxStackDepth) { - break; - } + ok = true; + std::reverse(m_values.begin(), m_values.begin() + up); + std::reverse(m_values.begin() + up, m_values.begin() + up + down); + std::reverse(m_values.begin(), m_values.begin() + up + down); + break; + } + case Stack::Opcode::REVERSE: { + int qty = opcode.i(); + int index = opcode.j(); + if (index + qty <= m_size) { + // [index..index+qty-1] std::reverse(m_values.begin() + index, m_values.begin() + index + qty); ok = true; + } + break; + } + case Stack::Opcode::XCHG: { + int i = opcode.i(); + int j = opcode.j(); + if (i >= m_size || j >= m_size) { break; } - case Stack::Opcode::XCHG: { - int i = opcode.i(); - int j = opcode.j(); - if (i >= maxStackDepth || j >= maxStackDepth) { - break; + solAssert(i != -1, ""); + solAssert(j != -1, ""); + std::swap(m_values[i], m_values[j]); + ok = true; + break; + } + case Stack::Opcode::DROP: { + int n = opcode.i(); + if (n < m_size) { + for (int i = n; i < m_size; ++i) + m_values[i - n] = m_values[i]; + m_size -= n; + ok = true; + } + break; + } + case Stack::Opcode::BLKDROP2: { + int const down = opcode.i(); + int const top = opcode.j(); + if (down + top <= m_size) { + for (int i = top + down; i < m_size; ++i) { + m_values[i - down] = m_values[i]; } - std::swap(m_values[i], m_values[j]); + m_size -= down; ok = true; - break; } - - // TODO handle these cases - case Stack::Opcode::DROP: - case Stack::Opcode::BLKDROP2: - case Stack::Opcode::POP_S: - case Stack::Opcode::BLKPUSH: - case Stack::Opcode::PUSH2_S: - case Stack::Opcode::PUSH3_S: - case Stack::Opcode::PUSH_S: - case Stack::Opcode::TUCK: - case Stack::Opcode::PUXC: - case Stack::Opcode::XCPU: { - ok = false; - break; + break; + } + case Stack::Opcode::POP_S: { + if (opcode.i() < m_size) { + m_values[opcode.i()] = m_values[0]; + for (int i = 1; i < m_size; ++i) + m_values[i - 1] = m_values[i]; + --m_size; + ok = true; } + break; } - m_hash = 0; - for (int i = 0; i < maxStackDepth; ++i) { - m_hash = m_hash * 31 + m_values[i]; + case Stack::Opcode::BLKPUSH: { + int const qty = opcode.i(); + int const index = opcode.j(); + if (m_size + qty <= StackState::MAX_STACK_DEPTH && index < m_size) { + for (int i = m_size - 1; 0 <= i; --i) + m_values[i + qty] = m_values[i]; + for (int i = qty - 1, j = index + qty; 0 <= i; --i, --j) + m_values[i] = m_values[j]; + m_size += qty; + ok = true; + } + break; + } + case Stack::Opcode::PUSH_S: { + int const index = opcode.i(); + if (execPUSHS(index)) { + ok = true; + } + break; + } + case Stack::Opcode::PUSH2_S: { + if (std::max(index0, index1) < m_size && m_size + 2 <= StackState::MAX_STACK_DEPTH) { + int8_t val0 = m_values[index0]; + int8_t val1 = m_values[index1]; + for (int i = m_size - 1; 0 <= i; --i) + m_values[i + 2] = m_values[i]; + m_values[1] = val0; + m_values[0] = val1; + m_size += 2; + ok = true; + } + break; + } + case Stack::Opcode::PUSH3_S: { + if (std::max(std::max(index0, index1), index2) < m_size && m_size + 3 <= StackState::MAX_STACK_DEPTH) { + int8_t val0 = m_values[index0]; + int8_t val1 = m_values[index1]; + int8_t val2 = m_values[index2]; + for (int i = m_size - 1; 0 <= i; --i) + m_values[i + 3] = m_values[i]; + m_values[2] = val0; + m_values[1] = val1; + m_values[0] = val2; + m_size += 3; + ok = true; + } + break; + } + case Stack::Opcode::XC2PU: { + if (std::max({index0, index1, index2, 1}) < m_size && m_size + 1 <= StackState::MAX_STACK_DEPTH) { + std::swap(m_values[1], m_values[index0]); + std::swap(m_values[0], m_values[index1]); + int8_t value = m_values[index2]; + for (int i = m_size - 1; 0 <= i; --i) + m_values[i + 1] = m_values[i]; + m_values[0] = value; + ++m_size; + ok = true; + } + break; + } + case Stack::Opcode::XCPU: { + if (std::max(index0, index1) < m_size && m_size + 1 <= StackState::MAX_STACK_DEPTH) { + std::swap(m_values[0], m_values[index0]); + int8_t value = m_values[index1]; + for (int i = m_size - 1; 0 <= i; --i) + m_values[i + 1] = m_values[i]; + m_values[0] = value; + ++m_size; + ok = true; + } + break; + } + case Stack::Opcode::PUXC: { + int const index = opcode.i(); + int const j = opcode.j(); + if (execPUXC(index, j)) { + ok = true; + } + break; } + case Stack::Opcode::XCHG2: { + int const i = opcode.i(); + int const j = opcode.j(); + if (std::max(i, j) < m_size) { + std::swap(m_values[1], m_values[i]); + std::swap(m_values[0], m_values[j]); + ok = true; + } + break; + } + case Stack::Opcode::XCHG3: { + int const i = opcode.i(); + int const j = opcode.j(); + int const k = opcode.k(); + if (std::max(std::max(2, i), std::max(j, k)) < m_size) { + std::swap(m_values[2], m_values[i]); + std::swap(m_values[1], m_values[j]); + std::swap(m_values[0], m_values[k]); + ok = true; + } + break; + } + case Stack::Opcode::PU2XC: { + if (execPUSHS(index0)) { + std::swap(m_values[0], m_values[1]); + if (execPUXC(index1 + 1, index2 + 1)) { + ok = true; + } + } + break; + } + case Stack::Opcode::PUXCPU: { + if (execPUXC(index0, index1) && execPUSHS(index2 + 1)) { + ok = true; + } + break; + } + case Stack::Opcode::XCPUXC: { + if (std::max(index0, 1) < m_size) { + std::swap(m_values[1], m_values[index0]); + if (execPUXC(index1, index2)) { + ok = true; + } + } + break; + } + case Stack::Opcode::PUXC2: { + if (std::max(std::max(1, index0), std::max(index1, index2)) < m_size && m_size + 1 <= StackState::MAX_STACK_DEPTH) { + int8_t val = m_values[index0]; + for (int i = m_size - 1; 0 <= i; --i) + m_values[i + 1] = m_values[i]; + m_values[0] = val; + ++m_size; + std::swap(m_values[2], m_values[0]); + std::swap(m_values[1], m_values[index1 + 1]); + std::swap(m_values[0], m_values[index2 + 1]); + ok = true; + } + break; + } + case Stack::Opcode::XCPU2: { + if (std::max(index0, std::max(index1, index2)) < m_size && m_size + 2 <= StackState::MAX_STACK_DEPTH) { + std::swap(m_values[0], m_values[index0]); + int8_t val1 = m_values[index1]; + int8_t val2 = m_values[index2]; + for (int i = m_size - 1; 0 <= i; --i) + m_values[i + 2] = m_values[i]; + m_values[1] = val1; + m_values[0] = val2; + m_size += 2; + ok = true; + } + break; + } + } + + if (ok) + updHash(); return ok; } -int StackOpcodeSquasher::steps(StackState const& _state) { - if (m_dp.empty()) { +void StackState::updHash() { + m_hash = m_size; + for (int i = 0; i < m_size; ++i) { + m_hash = m_hash * 31 + m_values[i]; + } +} + +std::optional StackOpcodeSquasher::gasCost(int startStackSize, StackState const& _state, bool _withCompoundOpcodes) { + if (m_dp[_withCompoundOpcodes][StackState::MAX_STACK_DEPTH].empty()) { init(); } - auto it = m_dp.find(_state); - if (it == m_dp.end()) { - return -1; + auto it = m_dp[_withCompoundOpcodes][startStackSize].find(_state); + if (it == m_dp[_withCompoundOpcodes][startStackSize].end()) { + return std::nullopt; } - return it->second; + return it->second.gasCost; } -std::unordered_map StackOpcodeSquasher::m_dp {}; -std::unordered_map>> StackOpcodeSquasher::m_prev {}; +std::array, StackState::MAX_STACK_DEPTH + 1>, 2> StackOpcodeSquasher::m_dp {}; void StackOpcodeSquasher::init() { - std::vector> edges; - for (int down = 1; down < StackState::maxStackDepth; ++down) { - for (int up = 1; down + up < StackState::maxStackDepth; ++up) { - edges.emplace_back(std::make_shared(Stack::Opcode::BLKSWAP, down, up)); - } - } - for (int i = 0; i < StackState::maxStackDepth; ++i) { - for (int j = i + 1; j < StackState::maxStackDepth; ++j) { - edges.emplace_back(std::make_shared(Stack::Opcode::XCHG, i, j)); - } - } - for (int i = 0; i < StackState::maxStackDepth; ++i) { - for (int n = 2; i + n <= StackState::maxStackDepth; ++n) { - edges.emplace_back(std::make_shared(Stack::Opcode::REVERSE, n, i)); - } - } + for (int _withCompoundOpcodes = 0; _withCompoundOpcodes <= 1; ++_withCompoundOpcodes) { + struct Edge { + Pointer opcode; + int gas{}; + }; + std::vector edges; + auto addEdge = [&](auto const& opcode){ + edges.emplace_back(Edge{opcode, OpcodeUtils::gasCost(*opcode)}); + }; + for (int i = 1; i < StackState::MAX_STACK_DEPTH; ++i) + addEdge(std::make_shared(Stack::Opcode::POP_S, i)); + for (int down = 1; down <= StackState::MAX_STACK_DEPTH; ++down) + for (int up = 1; down + up <= StackState::MAX_STACK_DEPTH; ++up) + addEdge(std::make_shared(Stack::Opcode::BLKDROP2, down, up)); + for (int n = 1; n <= StackState::MAX_STACK_DEPTH; ++n) + addEdge(std::make_shared(Stack::Opcode::DROP, n)); + for (int down = 1; down < StackState::MAX_STACK_DEPTH; ++down) + for (int up = 1; down + up < StackState::MAX_STACK_DEPTH; ++up) + addEdge(std::make_shared(Stack::Opcode::BLKSWAP, down, up)); + for (int i = 0; i < StackState::MAX_STACK_DEPTH; ++i) + for (int j = i + 1; j < StackState::MAX_STACK_DEPTH; ++j) + addEdge(std::make_shared(Stack::Opcode::XCHG, i, j)); + for (int i = 0; i < StackState::MAX_STACK_DEPTH; ++i) + for (int n = 2; i + n <= StackState::MAX_STACK_DEPTH; ++n) + addEdge(std::make_shared(Stack::Opcode::REVERSE, n, i)); + for (int i = 0; i < StackState::MAX_STACK_DEPTH; ++i) + addEdge(std::make_shared(Stack::Opcode::PUSH_S, i)); - //std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); - StackState state; - m_dp[state] = 0; - std::deque q; - q.push_back(state); - while (!q.empty()) { - state = q.front(); - q.pop_front(); - int8_t nextDp = m_dp.at(state) + 1; - if (nextDp == 4) { - break; + if (_withCompoundOpcodes) + { + for (int i = 0; i < StackState::MAX_STACK_DEPTH; ++i) + for (int qty = 2; i + 1 + qty <= StackState::MAX_STACK_DEPTH; ++qty) + addEdge(std::make_shared(Stack::Opcode::BLKPUSH, qty, i)); + for (int i = 0; i < StackState::MAX_STACK_DEPTH; ++i) + for (int j = 0; j < StackState::MAX_STACK_DEPTH; ++j) + addEdge(std::make_shared(Stack::Opcode::PUSH2_S, i, j)); + for (int i = 1; i < StackState::MAX_STACK_DEPTH; ++i) + for (int j = 0; j < StackState::MAX_STACK_DEPTH; ++j) + addEdge(std::make_shared(Stack::Opcode::XCPU, i, j)); + for (int i = 0; i < StackState::MAX_STACK_DEPTH; ++i) + for (int j = -1; j < StackState::MAX_STACK_DEPTH; ++j) + addEdge(std::make_shared(Stack::Opcode::PUXC, i, j)); + for (int i = 0; i < StackState::MAX_STACK_DEPTH; ++i) + for (int j = 0; j < StackState::MAX_STACK_DEPTH; ++j) + addEdge(std::make_shared(Stack::Opcode::XCHG2, i, j)); + for (int i = 0; i < StackState::MAX_STACK_DEPTH; ++i) + for (int j = 0; j < StackState::MAX_STACK_DEPTH; ++j) + for (int k = 0; k < StackState::MAX_STACK_DEPTH; ++k) { + addEdge(std::make_shared(Stack::Opcode::XC2PU, i, j, k)); + addEdge(std::make_shared(Stack::Opcode::XCPU2, i, j, k)); + addEdge(std::make_shared(Stack::Opcode::XCHG3, i, j, k)); + } + for (int i = 0; i < StackState::MAX_STACK_DEPTH; ++i) + for (int j = -1; j + 1 < StackState::MAX_STACK_DEPTH; ++j) + for (int k = -1; k + 1 < StackState::MAX_STACK_DEPTH; ++k) + addEdge(std::make_shared(Stack::Opcode::PUXC2, i, j, k)); + for (int i = 0; i < StackState::MAX_STACK_DEPTH; ++i) + for (int j = 0; j < StackState::MAX_STACK_DEPTH; ++j) + for (int k = -1; k + 1 < StackState::MAX_STACK_DEPTH; ++k) + addEdge(std::make_shared(Stack::Opcode::XCPUXC, i, j, k)); + for (int i = 0; i < StackState::MAX_STACK_DEPTH; ++i) + for (int j = -1; j + 1 < StackState::MAX_STACK_DEPTH; ++j) + for (int k = -1; k + 1 < StackState::MAX_STACK_DEPTH; ++k) + addEdge(std::make_shared(Stack::Opcode::PUXCPU, i, j, k)); + for (int i = 0; i < StackState::MAX_STACK_DEPTH; ++i) + for (int j = -1; j + 1 < StackState::MAX_STACK_DEPTH; ++j) + for (int k = -2; k + 2 < StackState::MAX_STACK_DEPTH; ++k) + addEdge(std::make_shared(Stack::Opcode::PU2XC, i, j, k)); + for (int i = 0; i < StackState::MAX_STACK_DEPTH; ++i) + for (int j = 0; j < StackState::MAX_STACK_DEPTH; ++j) + for (int k = 0; k < StackState::MAX_STACK_DEPTH; ++k) + addEdge(std::make_shared(Stack::Opcode::PUSH3_S, i, j, k)); } - for (Pointer const &e: edges) { - StackState nextState = state; - nextState.apply(*e.get()); - auto it = m_dp.find(nextState); - if (it == m_dp.end()) { - m_dp.emplace_hint(it, nextState, nextDp); - m_prev.emplace(nextState, std::make_pair(state, e)); - q.push_back(nextState); + + std::stable_sort(edges.begin(), edges.end(), [](Edge const &a, Edge const &b){ + return a.gas < b.gas; + }); + + int const MAX_DEPTH = _withCompoundOpcodes ? 2 : MAX_NEW_OPCODES; + using namespace std::chrono; + //auto begin = high_resolution_clock::now(); + for (int stackSize = 0; stackSize <= StackState::MAX_STACK_DEPTH; ++stackSize) { + struct QData { + StackState state; + int gas{}; + int8_t opcodeQty{}; + }; + std::unordered_map dp; + auto comp = [](QData const& x, QData const& y){ + if (x.gas != y.gas) + return x.gas < y.gas; + return x.state.getHash() < y.state.getHash(); + }; + auto q = std::set(comp); + { + StackState state{stackSize}; + dp.emplace(state, DpState{0, StackState{stackSize}, nullptr}); + q.emplace(QData{state, 0, 0}); + } + while (!q.empty()) { + auto front = q.begin(); + StackState const state = front->state; + int8_t const nextOpcodeQty = front->opcodeQty + 1; + int const gas = front->gas; + q.erase(front); + for (Edge const &e: edges) { + StackState nextState = state; + if (nextState.apply(*e.opcode)) { + auto it = dp.find(nextState); + int nextGasCost = gas + e.gas; + if (it == dp.end()) { + dp.emplace(nextState, DpState{nextGasCost, state, e.opcode}); + if (nextOpcodeQty < MAX_DEPTH) + q.emplace(QData{nextState, nextGasCost, nextOpcodeQty}); + } else if (it->second.gasCost > nextGasCost) { + q.erase(QData{nextState, it->second.gasCost}); + dp.erase(it); + dp.emplace(nextState, DpState{nextGasCost, state, e.opcode}); + if (nextOpcodeQty < MAX_DEPTH) { + q.emplace(QData{nextState, nextGasCost, nextOpcodeQty}); + } + } + } + } } + m_dp[_withCompoundOpcodes][stackSize] = dp; } + //auto end = high_resolution_clock::now(); + //auto duration = duration_cast(end - begin); + //std::cerr << "Time difference = " << duration.count() << "[ms]" << std::endl; + //double size = m_dp.at(_withCompoundOpcodes).at(StackState::MAX_STACK_DEPTH).size(); + //std::cerr << "dp.size() = " << size << ": " << (size * 20) / (1024 * 1024) << " MB" << std::endl; } - //std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); - //std::cerr << "Time difference = " << std::chrono::duration_cast(end - begin).count() << "[ms]" << std::endl; } - -std::vector> StackOpcodeSquasher::recover(StackState state) { +std::vector> StackOpcodeSquasher::recover(int startStackSize, StackState state, bool _withCompoundOpcodes) { std::vector> res; - StackState start; + StackState start{startStackSize}; + auto const& prev = m_dp.at(_withCompoundOpcodes).at(startStackSize); while (state != start) { - std::pair> p = m_prev.at(state); - state = p.first; - res.push_back(p.second); + auto const& it = prev.at(state); + state = it.prevState; + res.push_back(it.opcode); } std::reverse(res.begin(), res.end()); return res; } - diff --git a/compiler/libsolidity/codegen/StackOpcodeSquasher.hpp b/compiler/libsolidity/codegen/StackOpcodeSquasher.hpp index a14ee027..d6d530d9 100644 --- a/compiler/libsolidity/codegen/StackOpcodeSquasher.hpp +++ b/compiler/libsolidity/codegen/StackOpcodeSquasher.hpp @@ -14,48 +14,68 @@ #pragma once #include "TvmAst.hpp" +#include #include namespace solidity::frontend { - class StackState { - public: - constexpr static int maxStackDepth = 9; - - StackState(); - bool apply(Stack const& opcode); - bool operator==(const StackState &other) const { - return m_values == other.m_values; - } - bool operator!=(const StackState &other) const { - return m_values != other.m_values; - } - std::size_t getHash() const { return m_hash; } - private: - std::size_t m_hash{}; - std::array m_values{}; - }; +constexpr static int MAX_NEW_OPCODES = 3; + +class StackState { +public: + constexpr static int MAX_STACK_DEPTH = 9; + + explicit StackState(int _size); + explicit StackState(int8_t _size, std::array _values) : m_size{_size}, m_values{_values} { + updHash(); + } + bool apply(Stack const& opcode); + bool operator==(const StackState &other) const { + if (m_size != other.m_size) + return false; + for (int i = 0; i < m_size; ++i) + if (m_values[i] != other.m_values[i]) + return false; + return true; + } + bool operator!=(const StackState &other) const { + return !operator==(other); + } + std::size_t getHash() const { return m_hash; } + int8_t size() const { return m_size; } + std::array const& values() const { return m_values; } +private: + void updHash(); +private: + std::size_t m_hash{}; + int8_t m_size; + std::array m_values{}; +}; - class StackOpcodeSquasher { - public: - static int steps(StackState const& _state); - private: - static void init(); - public: - static std::vector> recover(StackState state); - private: - static std::unordered_map m_dp; - static std::unordered_map>> m_prev; +class StackOpcodeSquasher { +public: + static std::optional gasCost(int startStackSize, StackState const& _state, bool _withCompoundOpcodes); +private: + static void init(); +public: + static std::vector> recover(int startStackSize, StackState state, bool _withCompoundOpcodes); + struct DpState { + int gasCost; + StackState prevState; + Pointer opcode; }; +private: + static std::array, StackState::MAX_STACK_DEPTH + 1>, 2> m_dp; +}; } // end solidity::frontend namespace std { template <> struct hash { - std::size_t operator()(const solidity::frontend::StackState& k) const { - return k.getHash(); - } +std::size_t operator()(const solidity::frontend::StackState& k) const { + return k.getHash(); +} }; } diff --git a/compiler/libsolidity/codegen/StackOptimizer.cpp b/compiler/libsolidity/codegen/StackOptimizer.cpp index a2f13468..756a7bfe 100644 --- a/compiler/libsolidity/codegen/StackOptimizer.cpp +++ b/compiler/libsolidity/codegen/StackOptimizer.cpp @@ -63,7 +63,7 @@ bool StackOptimizer::visit(TvmException &_node) { return false; } -bool StackOptimizer::visit(GenOpcode &_node) { +bool StackOptimizer::visit(StackOpcode &_node) { delta(- _node.take() + _node.ret()); return false; } @@ -98,38 +98,39 @@ bool StackOptimizer::visit(Glob &_node) { bool StackOptimizer::visit(Stack &_node) { int delta{}; switch (_node.opcode()) { - case Stack::Opcode::DROP: - case Stack::Opcode::BLKDROP2: - delta = - _node.i(); - break; - - case Stack::Opcode::POP_S: - delta = -1; - break; - - case Stack::Opcode::BLKPUSH: - delta = _node.i(); - break; - case Stack::Opcode::PUSH2_S: - delta = 2; - break; - case Stack::Opcode::PUSH3_S: - delta = 3; - break; - case Stack::Opcode::PUSH_S: - delta = 1; - break; - - case Stack::Opcode::BLKSWAP: - case Stack::Opcode::REVERSE: - case Stack::Opcode::XCHG: - break; - - case Stack::Opcode::TUCK: - case Stack::Opcode::PUXC: - case Stack::Opcode::XCPU: - delta = 1; - break; + case Stack::Opcode::BLKPUSH: + delta = _node.i(); + break; + case Stack::Opcode::DROP: + case Stack::Opcode::BLKDROP2: + delta = - _node.i(); + break; + case Stack::Opcode::POP_S: + delta = -1; + break; + case Stack::Opcode::BLKSWAP: + case Stack::Opcode::REVERSE: + case Stack::Opcode::XCHG: + case Stack::Opcode::XCHG2: + case Stack::Opcode::XCHG3: + break; + case Stack::Opcode::PUXC: + case Stack::Opcode::XCPU: + case Stack::Opcode::XC2PU: + case Stack::Opcode::PUXC2: + case Stack::Opcode::XCPUXC: + case Stack::Opcode::PUSH_S: + delta = 1; + break; + case Stack::Opcode::PUSH2_S: + case Stack::Opcode::XCPU2: + case Stack::Opcode::PUXCPU: + case Stack::Opcode::PU2XC: + delta = 2; + break; + case Stack::Opcode::PUSH3_S: + delta = 3; + break; } this->delta(delta); return false; @@ -318,8 +319,11 @@ bool StackOptimizer::successfullyUpdate(int index, std::vector(op.get())) return false; + size_t index2 = index + 1; + while (index2 < instructions.size() && isLoc(instructions.at(index2))) + ++index2; + auto stack = to(op.get()); - bool cmd1IsPUSH= stack && stack->opcode() == Stack::Opcode::PUSH_S; bool ok = false; std::vector> commands; @@ -332,7 +336,7 @@ bool StackOptimizer::successfullyUpdate(int index, std::vector(op.get()); gen && gen->isPure() && std::make_pair(gen->take(), gen->ret()) == std::make_pair(0, 1) ) { - Simulator sim{instructions.begin() + index + 1, instructions.end(), 1, 1}; + Simulator sim{instructions.begin() + index + 1, instructions.end(), 1, 1, false, true}; bool good = true; { auto glob = to(op.get()); @@ -355,6 +359,129 @@ bool StackOptimizer::successfullyUpdate(int index, std::vector + // ... + // BLKSWAP i-1, 1 + if (!ok && isPUSH(op) && isPUSH(op).value() == 0) { + Simulator sim{instructions.begin() + index + 1, instructions.end(), 2, 1, true}; + if (sim.wasSet()) { + ok = true; + commands.insert(commands.end(), sim.commands().begin(), sim.commands().end()); + auto iter = sim.getIter(); + auto popSi = isPOP(*iter); + solAssert(popSi && *popSi >= 2, ""); + commands.emplace_back(makeBLKSWAP(*popSi - 1, 1)); + ++iter; + for ( ; iter != instructions.end(); ++iter) { + commands.emplace_back(*iter); + } + } + } + + // ROLLREV X + // ... + // POP Si + // => + // DROP + // ... + // BLKSWAP 1, i-1 + if (!ok && isBLKSWAP(op)) { + auto [bottom, top] = isBLKSWAP(op).value(); + if (top == 1) { + Simulator sim{instructions.begin() + index + 1, instructions.end(), bottom + 1, 1, true}; + if (sim.wasSet()) { + ok = true; + commands.emplace_back(makeDROP()); + commands.insert(commands.end(), sim.commands().begin(), sim.commands().end()); + auto iter = sim.getIter(); + auto popSi = isPOP(*iter); + solAssert(popSi && *popSi >= 2, ""); + commands.emplace_back(makeBLKSWAP(*popSi - 1, 1)); + ++iter; + for ( ; iter != instructions.end(); ++iter) { + commands.emplace_back(*iter); + } + } + } + } + + // gen01 + // ... + // POP Si + // => + // ... + // BLKSWAP 1, i-1 + if (!ok && isPureGen01(*op)) { + Simulator sim{instructions.begin() + index + 1, instructions.end(), 1, 1, true}; + if (sim.wasSet()) { + ok = true; + commands.insert(commands.end(), sim.commands().begin(), sim.commands().end()); + auto iter = sim.getIter(); + auto popSi = isPOP(*iter); + solAssert(popSi && *popSi >= 2, ""); + commands.emplace_back(makeBLKSWAP(*popSi - 1, 1)); + ++iter; + for ( ; iter != instructions.end(); ++iter) { + commands.emplace_back(*iter); + } + } + } + + // BLKSWAP N, 1 + // PUSH S[N] + // ... + // POP S? + // => + // ... + // BLKSWAP ?, 1 + if (!ok && isBLKSWAP(op) && isBLKSWAP(op).value().second == 1) { + int const n = isBLKSWAP(op).value().first; + if (index2 != instructions.size()) { + auto pushS = isPUSH(instructions.at(index2)); + if (pushS && pushS.value() == n) { + Simulator sim{instructions.begin() + index2 + 1, instructions.end(), n + 2, 1, true}; + if (sim.wasSet()) { + ok = true; + commands.insert(commands.end(), instructions.begin() + index + 1, instructions.begin() + index2); + commands.insert(commands.end(), sim.commands().begin(), sim.commands().end()); + auto iter = sim.getIter(); + auto popSi = isPOP(*iter); + solAssert(popSi && *popSi >= 2, ""); + commands.emplace_back(makeBLKSWAP(*popSi - 1, 1)); + ++iter; + for ( ; iter != instructions.end(); ++iter) { + commands.emplace_back(*iter); + } + } + } + } + } + + // POP S[i] + // PUSH S[i-1] + // ... + // POP S? + // => + // ... + // POP S? + if (!ok && isPOP(op)) { + if (index2 != instructions.size()) { + auto pushS = isPUSH(instructions.at(index2)); + if (pushS && *pushS + 1 == *isPOP(op)) { + Simulator sim{instructions.begin() + index2 + 1, instructions.end(), *isPOP(op) + 1, 1, true}; + if (sim.wasSet()) { + ok = true; + // we take original commands because we don't remove value from stack + commands.insert(commands.end(), instructions.begin() + index + 1, instructions.begin() + index2); + commands.insert(commands.end(), instructions.begin() + index2 + 1, instructions.end()); + } + } + } + } + // POP Si // => // DROP @@ -402,10 +529,15 @@ bool StackOptimizer::successfullyUpdate(int index, std::vectori(); - // try to delete values + // PUSH Si + // ... S[0..i-1] aren not used anymore + // => + // BLKDROP i + // PUSH S0 + // ... if (Si <= scopeSize() && Si > 0) { Simulator sim{instructions.begin() + index + 1, instructions.end(), Si + 1, Si}; if (sim.success()) { @@ -433,6 +565,10 @@ bool StackOptimizer::successfullyUpdate(int index, std::vector + // ... if (!ok && isPureGen01(*op)) { int startStackSize = 1; Simulator sim{instructions.begin() + index + 1, instructions.end(), startStackSize, 1}; @@ -442,6 +578,11 @@ bool StackOptimizer::successfullyUpdate(int index, std::vector + // DROP + // ... if (!ok && !isDrop(op)) { bool isPrevFlag{}; if (index > 0) { @@ -458,7 +599,30 @@ bool StackOptimizer::successfullyUpdate(int index, std::vector + // NIP + // ... + //if (!ok && !isPOP(op) && !isDrop(op)) { + // if (scopeSize() >= 2) { + // auto beg = instructions.begin() + index; + // Simulator sim{beg, instructions.end(), 2, 1}; + // if (sim.success()) { + // ok = true; + // commands.emplace_back(makeBLKDROP2(1, 1)); + // commands.insert(commands.end(), sim.commands().begin(), sim.commands().end()); + // } + // } + //} + + // Delete useless variable from the stack + // DROP N + // ... + // => + // DROP N+1 + // ... if (!ok && isDrop(op)) { int n = isDrop(op).value(); auto beg = instructions.begin() + index + 1; diff --git a/compiler/libsolidity/codegen/StackOptimizer.hpp b/compiler/libsolidity/codegen/StackOptimizer.hpp index 4ccf7fbb..055f024b 100644 --- a/compiler/libsolidity/codegen/StackOptimizer.hpp +++ b/compiler/libsolidity/codegen/StackOptimizer.hpp @@ -19,44 +19,44 @@ #include namespace solidity::frontend { - class StackOptimizer : public TvmAstVisitor { - public: - bool visit(DeclRetFlag &_node) override; - bool visit(Opaque &_node) override; - bool visit(HardCode &_node) override; - bool visit(Loc &_node) override; - bool visit(TvmReturn &_node) override; - bool visit(ReturnOrBreakOrCont &_node) override; - bool visit(TvmException &_node) override; - bool visit(GenOpcode &_node) override; - bool visit(PushCellOrSlice &_node) override; - bool visit(Glob &_node) override; - bool visit(Stack &_node) override; - bool visit(CodeBlock &_node) override; - bool visit(SubProgram &_node) override; - bool visit(LogCircuit &_node) override; - bool visit(TvmIfElse &_node) override; - bool visit(TvmRepeat &_node) override; - bool visit(TvmUntil &_node) override; - bool visit(TryCatch &_node) override; - bool visit(While &_node) override; - bool visit(Function &_node) override; - bool visit(Contract &_node) override; - void endVisit(CodeBlock &_node) override; - protected: - bool visitNode(TvmAstNode const&) override; - void endVisitNode(TvmAstNode const&) override; - private: - bool successfullyUpdate(int index, std::vector>& instructions); - void initStack(int size); - void delta(int delta); - int size(); - int scopeSize(); - void startScope(); - void endScope(); - private: - bool m_didSome{}; - std::vector m_stackSize; - }; +class StackOptimizer : public TvmAstVisitor { +public: + bool visit(DeclRetFlag &_node) override; + bool visit(Opaque &_node) override; + bool visit(HardCode &_node) override; + bool visit(Loc &_node) override; + bool visit(TvmReturn &_node) override; + bool visit(ReturnOrBreakOrCont &_node) override; + bool visit(TvmException &_node) override; + bool visit(StackOpcode &_node) override; + bool visit(PushCellOrSlice &_node) override; + bool visit(Glob &_node) override; + bool visit(Stack &_node) override; + bool visit(CodeBlock &_node) override; + bool visit(SubProgram &_node) override; + bool visit(LogCircuit &_node) override; + bool visit(TvmIfElse &_node) override; + bool visit(TvmRepeat &_node) override; + bool visit(TvmUntil &_node) override; + bool visit(TryCatch &_node) override; + bool visit(While &_node) override; + bool visit(Function &_node) override; + bool visit(Contract &_node) override; + void endVisit(CodeBlock &_node) override; +protected: + bool visitNode(TvmAstNode const&) override; + void endVisitNode(TvmAstNode const&) override; +private: + bool successfullyUpdate(int index, std::vector>& instructions); + void initStack(int size); + void delta(int delta); + int size(); + int scopeSize(); + void startScope(); + void endScope(); +private: + bool m_didSome{}; + std::vector m_stackSize; +}; } // end solidity::frontend diff --git a/compiler/libsolidity/codegen/TVMABI.cpp b/compiler/libsolidity/codegen/TVMABI.cpp index 0b16b212..f05467b0 100644 --- a/compiler/libsolidity/codegen/TVMABI.cpp +++ b/compiler/libsolidity/codegen/TVMABI.cpp @@ -13,7 +13,7 @@ #include -#include "libsolutil/picosha2.h" +#include #include #include @@ -48,7 +48,7 @@ Json::Value TVMABI::generateFunctionIdsJson( if (map.count("constructor") == 0) { map["constructor"] = encoder.calculateConstructorFunctionID(); } - for (VariableDeclaration const* vd : ctx.notConstantStateVariables()) { + for (VariableDeclaration const* vd : ctx.c4StateVariables()) { if (vd->isPublic()) { std::vector outputs = {vd}; uint32_t functionId = encoder.calculateFunctionIDWithReason( @@ -81,10 +81,10 @@ TVMABI::generatePrivateFunctionIdsJson( Json::Value ids{Json::arrayValue}; Pointer codeContract = TVMContractCompiler::generateContractCode(&contract, _sourceUnits, pragmaHelper); for (Pointer const& fun : codeContract->functions()) { + FunctionDefinition const* def = fun->functionDefinition(); if (fun->type() == Function::FunctionType::Fragment && fun->functionId()) { Json::Value func{Json::objectValue}; - FunctionDefinition const* def = fun->functionDefinition(); - func["scope"] = def->annotation().contract->name(); + func["scope"] = def->isFree() ? "" : def->annotation().contract->name(); func["sign"] = def->externalSignature(); func["id"] = fun->functionId().value(); ids.append(func); @@ -117,7 +117,7 @@ Json::Value TVMABI::generateABIJson( Json::Value root(Json::objectValue); root["ABI version"] = 2; - root["version"] = "2.3"; + root["version"] = "2.4"; // header { @@ -148,7 +148,7 @@ Json::Value TVMABI::generateABIJson( } // add public state variables to functions - for (VariableDeclaration const* vd : ctx.notConstantStateVariables()) { + for (VariableDeclaration const* vd : ctx.c4StateVariables()) { if (vd->isPublic()) { functions.append(toJson(vd->name(), {}, {vd})); } @@ -183,17 +183,6 @@ Json::Value TVMABI::generateABIJson( root["events"] = eventAbi; } - // data - { - Json::Value data(Json::arrayValue); - for (const auto &[v, index] : ctx.getStaticVariables()) { - Json::Value cur = setupNameTypeComponents(v->name(), v->type()); - cur["key"] = index; - data.append(cur); - } - root["data"] = data; - } - // fields { Json::Value fields(Json::arrayValue); @@ -210,11 +199,26 @@ Json::Value TVMABI::generateABIJson( Json::Value field(Json::objectValue); field["name"] = name; field["type"] = type; + field["init"] = name == "_pubkey"; fields.append(field); } - for (VariableDeclaration const* stateVar : ctx.notConstantStateVariables()) { - Json::Value cur = setupNameTypeComponents(stateVar->name(), stateVar->type()); + std::vector stateVars = ctx.c4StateVariables(); + std::set usedNames; + std::vector> namesTypes; + for (VariableDeclaration const * var : stateVars | boost::adaptors::reversed) { + std::string name = var->name(); + if (usedNames.count(name) != 0) + name = var->annotation().contract->name() + "$" + var->name(); + solAssert(usedNames.count(name) == 0, ""); + usedNames.insert(name); + namesTypes.emplace_back(name, var->type(), var->isStatic()); + } + std::reverse(namesTypes.begin(), namesTypes.end()); + + for (auto const&[name, type, isStatic] : namesTypes) { + Json::Value cur = setupNameTypeComponents(name, type); + cur["init"] = isStatic; fields.append(cur); } root["fields"] = fields; @@ -257,10 +261,6 @@ void TVMABI::generateABI( print(root["functions"], out); *out << "\t" << "],\n"; - *out << "\t" << R"("data": [)" << "\n"; - printData(root["data"], out); - *out << "\t" << "],\n"; - *out << "\t" << R"("events": [)" << "\n"; print(root["events"], out); @@ -451,6 +451,8 @@ Json::Value TVMABI::setupNameTypeComponents(const string &name, const Type *type auto varInt = to(type); typeName = varInt->toString(false); boost::algorithm::to_lower(typeName); + } else if (auto* fixedBytesType = to(type)) { + typeName = "fixedbytes" + toString(fixedBytesType->numBytes()); } else if (ti.isNumeric) { if (to(type)) { typeName = "bool"; @@ -1038,12 +1040,12 @@ void ChainDataEncoder::createMsgBody( } // arg[n-1], ..., arg[1], arg[0], builder -// Target: create and append to msgBuilder the message body +// Target: create and append to `builder` the args void ChainDataEncoder::encodeParameters( const std::vector & _types, DecodePositionAbiV2 &position ) { - // builder must be situated on top stack + // builder must be located on the top of stack std::vector typesOnStack{_types.rbegin(), _types.rend()}; while (!typesOnStack.empty()) { const int argQty = typesOnStack.size(); @@ -1058,6 +1060,8 @@ void ChainDataEncoder::encodeParameters( for (const ASTPointer& m : members | boost::adaptors::reversed) { typesOnStack.push_back(m->type()); } + } else if (auto userDefType = to(type)) { + typesOnStack.push_back(&userDefType->underlyingType()); } else { if (position.loadNextCell(type)) { // arg[n-1], ..., arg[1], arg[0], builder diff --git a/compiler/libsolidity/codegen/TVMAnalyzer.cpp b/compiler/libsolidity/codegen/TVMAnalyzer.cpp index 61cfbd60..f2f8854f 100644 --- a/compiler/libsolidity/codegen/TVMAnalyzer.cpp +++ b/compiler/libsolidity/codegen/TVMAnalyzer.cpp @@ -36,7 +36,7 @@ bool TVMAnalyzer::analyze(const SourceUnit &_sourceUnit) bool TVMAnalyzer::visit(MemberAccess const& _node) { auto funType = to(_node.annotation().type); if (funType) { - if (funType->bound()) { + if (funType->hasBoundFirstArgument()) { auto printError = [&]{ m_errorReporter.fatalTypeError( 5939_error, diff --git a/compiler/libsolidity/codegen/TVMCommons.cpp b/compiler/libsolidity/codegen/TVMCommons.cpp index 1f7f0d3d..eb9217ca 100644 --- a/compiler/libsolidity/codegen/TVMCommons.cpp +++ b/compiler/libsolidity/codegen/TVMCommons.cpp @@ -220,12 +220,14 @@ vector getContractsChain(ContractDefinition const *c return contracts; } -std::vector notConstantStateVariables(ContractDefinition const* _contract) { +std::vector stateVariables(ContractDefinition const* _contract, + bool _onlyNoStorage) { std::vector variableDeclarations; std::vector mainChain = getContractsChain(_contract); for (ContractDefinition const * contract: mainChain) { for (VariableDeclaration const *variable: contract->stateVariables()) { - if (!variable->isConstant()) { + if (!variable->isConstant() && (_onlyNoStorage == variable->isNoStorage()) + ) { variableDeclarations.push_back(variable); } } diff --git a/compiler/libsolidity/codegen/TVMCommons.hpp b/compiler/libsolidity/codegen/TVMCommons.hpp index 8029df55..4c26e9b3 100644 --- a/compiler/libsolidity/codegen/TVMCommons.hpp +++ b/compiler/libsolidity/codegen/TVMCommons.hpp @@ -150,7 +150,7 @@ realDictKeyValue(Type const* type); std::vector getContractsChain(ContractDefinition const* contract); -std::vector notConstantStateVariables(ContractDefinition const* contract); +std::vector stateVariables(ContractDefinition const* contract, bool _withNoStorage); std::vector> getContractFunctionPairs(ContractDefinition const* contract); diff --git a/compiler/libsolidity/codegen/TVMConstants.hpp b/compiler/libsolidity/codegen/TVMConstants.hpp index f6fe0c13..98025223 100644 --- a/compiler/libsolidity/codegen/TVMConstants.hpp +++ b/compiler/libsolidity/codegen/TVMConstants.hpp @@ -74,7 +74,6 @@ namespace TvmConst { const int ConstructorIsCalledTwice = 51; const int ReplayProtection = 52; const int PopFromEmptyArray = 54; - const int InsertPubkeyException = 55; const int MessageIsExpired = 57; const int MessageHasNoSignButHasPubkey = 58; const int NoFallback = 60; diff --git a/compiler/libsolidity/codegen/TVMContractCompiler.cpp b/compiler/libsolidity/codegen/TVMContractCompiler.cpp index 5d312114..54932458 100644 --- a/compiler/libsolidity/codegen/TVMContractCompiler.cpp +++ b/compiler/libsolidity/codegen/TVMContractCompiler.cpp @@ -36,132 +36,6 @@ using namespace solidity::frontend; using namespace std; using namespace solidity::util; -TVMConstructorCompiler::TVMConstructorCompiler(StackPusher &pusher) : m_pusher{pusher} { - -} - -void TVMConstructorCompiler::dfs(ContractDefinition const *c) { - if (used[c]) { - return; - } - used[c] = true; - dfsOrder.push_back(c); - path[c] = dfsOrder; - for (const ASTPointer& inherSpec : c->baseContracts()) { - auto base = to(inherSpec->name().annotation().referencedDeclaration); - ast_vec const* agrs = inherSpec->arguments(); - if (agrs != nullptr && !agrs->empty()) { - m_args[base] = inherSpec->arguments(); - dfs(base); - } - } - if (c->constructor() != nullptr) { - for (const ASTPointer &modInvoc : c->constructor()->modifiers()) { - auto base = to(modInvoc->name().annotation().referencedDeclaration); - if (base != nullptr) { - if (modInvoc->arguments() != nullptr) { - m_args[base] = modInvoc->arguments(); - dfs(base); - } - } - } - } - dfsOrder.pop_back(); -} - -Pointer TVMConstructorCompiler::generateConstructors() { - FunctionDefinition const* constructor = m_pusher.ctx().getContract()->constructor(); - m_pusher.ctx().setCurrentFunction(constructor, "constructor"); - - { - ChainDataEncoder encode{&m_pusher}; - uint32_t functionId = - constructor != nullptr ? - encode.calculateFunctionIDWithReason(constructor, ReasonOfOutboundMessage::RemoteCallInternal) : - encode.calculateConstructorFunctionID(); - m_pusher.ctx().addPublicFunction(functionId, "constructor"); - } - - m_pusher.fixStack(+1); // push encoded params of constructor - m_pusher.fixStack(+1); // functionID - m_pusher.drop(); - - c4ToC7WithMemoryInitAndConstructorProtection(); - - std::vector linearizedBaseContracts = - m_pusher.ctx().getContract()->annotation().linearizedBaseContracts; // from derived to base - for (ContractDefinition const* c : linearizedBaseContracts) - dfs(c); - - int take{}; - if (constructor == nullptr) { - m_pusher << "ENDS"; - } else { - take = constructor->parameters().size(); - vector types = getParams(constructor->parameters()).first; - ChainDataDecoder{&m_pusher}.decodeFunctionParameters(types, false); - m_pusher.getStack().change(-static_cast(constructor->parameters().size())); - for (const ASTPointer& variable: constructor->parameters()) - m_pusher.getStack().add(variable.get(), true); - } - solAssert(m_pusher.stackSize() == take, ""); - std::set areParamsOnStack; - areParamsOnStack.insert(linearizedBaseContracts.at(0)); - for (ContractDefinition const* c : linearizedBaseContracts | boost::adaptors::reversed) - if (c->constructor() == nullptr || c->constructor()->parameters().empty()) - areParamsOnStack.insert(c); - - bool haveConstructor = false; - for (ContractDefinition const* c : linearizedBaseContracts | boost::adaptors::reversed) { - if (c->constructor() == nullptr) - continue; - haveConstructor = true; - for (ContractDefinition const* parent : path[c]) { - if (areParamsOnStack.count(parent) == 0) { - areParamsOnStack.insert(parent); - for (size_t i = 0; i < parent->constructor()->parameters().size(); ++i) { - TVMExpressionCompiler(m_pusher).acceptExpr((*m_args[parent])[i].get(), true); - m_pusher.getStack().add(parent->constructor()->parameters()[i].get(), false); - } - } - } - int take2 = c->constructor()->parameters().size(); - StackPusher pusher = m_pusher; - pusher.clear(); - pusher.takeLast(take2); - TVMFunctionCompiler::generateFunctionWithModifiers(pusher, c->constructor(), false); - m_pusher.fixStack(-take2); // fix stack - m_pusher.add(pusher); - } - - if (!haveConstructor) - m_pusher << "ACCEPT"; - -// solAssert(m_pusher.stackSize() == 0, ""); - m_pusher.pushFragmentInCallRef(0, 0, "c7_to_c4"); - m_pusher._throw("THROW 0"); - - m_pusher.ctx().resetCurrentFunction(); - Pointer block = m_pusher.getBlock(); - // take slice (contains params) and functionID - Pointer f = createNode(2, 0, "constructor", nullopt, Function::FunctionType::Fragment, block); - return f; -} - -void TVMConstructorCompiler::c4ToC7WithMemoryInitAndConstructorProtection() { - // copy c4 to c7 - m_pusher.was_c4_to_c7_called(); - m_pusher.fixStack(-1); // fix stack - - m_pusher.startContinuation(); - m_pusher.pushFragment(0, 0, "c4_to_c7_with_init_storage"); - m_pusher.endContinuationFromRef(); - m_pusher._if(); - - // generate constructor protection - m_pusher.getGlob(TvmConst::C7::ConstructorFlag); - m_pusher._throw("THROWIF " + toString(TvmConst::RuntimeException::ConstructorIsCalledTwice)); -} void TVMContractCompiler::printFunctionIds( ContractDefinition const& contract, @@ -173,7 +47,7 @@ void TVMContractCompiler::printFunctionIds( void TVMContractCompiler::printPrivateFunctionIds( ContractDefinition const& contract, - std::vector> _sourceUnits, + std::vector> const& _sourceUnits, PragmaDirectiveHelper const& pragmaHelper ) { Json::Value functionIds = TVMABI::generatePrivateFunctionIdsJson(contract, _sourceUnits, pragmaHelper); @@ -202,7 +76,7 @@ void TVMContractCompiler::generateABI( void TVMContractCompiler::generateCodeAndSaveToFile( const std::string& fileName, ContractDefinition const& contract, - std::vector> _sourceUnits, + std::vector>const& _sourceUnits, PragmaDirectiveHelper const &pragmaHelper ) { Pointer codeContract = generateContractCode(&contract, _sourceUnits, pragmaHelper); @@ -221,7 +95,7 @@ void TVMContractCompiler::generateCodeAndSaveToFile( Pointer TVMContractCompiler::generateContractCode( ContractDefinition const *contract, - std::vector> _sourceUnits, + std::vector>const& _sourceUnits, PragmaDirectiveHelper const &pragmaHelper ) { std::vector> functions; @@ -289,7 +163,7 @@ TVMContractCompiler::generateContractCode( if (!ctx.isStdlib()) { functions.emplace_back(TVMFunctionCompiler::generateC4ToC7(ctx)); - functions.emplace_back(TVMFunctionCompiler::generateC4ToC7WithInitMemory(ctx)); + functions.emplace_back(TVMFunctionCompiler::generateDefaultC4(ctx)); { StackPusher pusher{&ctx}; Pointer f = pusher.generateC7ToC4(false); @@ -310,7 +184,7 @@ TVMContractCompiler::generateContractCode( } } - for (VariableDeclaration const* vd : ctx.notConstantStateVariables()) { + for (VariableDeclaration const* vd : ctx.c4StateVariables()) { if (vd->isPublic()) { StackPusher pusher{&ctx}; Pointer f = TVMFunctionCompiler::generateGetter(pusher, vd); @@ -433,22 +307,31 @@ void TVMContractCompiler::optimizeCode(Pointer& c) { LogCircuitExpander lce; c->accept(lce); - StackOptimizer opt; - c->accept(opt); + { + StackOptimizer opt; + c->accept(opt); + } + + lce = LogCircuitExpander{}; + c->accept(lce); - PeepholeOptimizer peepHole{false, false}; - c->accept(peepHole); + for (int i = 0; i < 10; ++i) { // TODO + PeepholeOptimizer::withBlockPush = false; // TODO + PeepholeOptimizer peepHole{false, false, false}; + c->accept(peepHole); + PeepholeOptimizer::withBlockPush = true; - opt = StackOptimizer{}; - c->accept(opt); + StackOptimizer opt; + c->accept(opt); + } - peepHole = PeepholeOptimizer{false, false}; + PeepholeOptimizer peepHole = PeepholeOptimizer{false, false, false}; c->accept(peepHole); - peepHole = PeepholeOptimizer{true, false}; + peepHole = PeepholeOptimizer{true, false, false}; // TODO provide mask with bits c->accept(peepHole); - peepHole = PeepholeOptimizer{true, true}; + peepHole = PeepholeOptimizer{true, true, true}; // TODO provide mask with bits c->accept(peepHole); LocSquasher sq = LocSquasher{}; diff --git a/compiler/libsolidity/codegen/TVMContractCompiler.hpp b/compiler/libsolidity/codegen/TVMContractCompiler.hpp index 37d07006..a0d34438 100644 --- a/compiler/libsolidity/codegen/TVMContractCompiler.hpp +++ b/compiler/libsolidity/codegen/TVMContractCompiler.hpp @@ -24,27 +24,12 @@ namespace solidity::frontend { class TVMCompilerContext; -class TVMConstructorCompiler: private boost::noncopyable { - StackPusher& m_pusher; - std::map> path; - std::vector dfsOrder; - std::map used; - std::map> const*> m_args; - -public: - explicit TVMConstructorCompiler(StackPusher& pusher); - void dfs(ContractDefinition const* c); - Pointer generateConstructors(); -private: - void c4ToC7WithMemoryInitAndConstructorProtection(); -}; - class TVMContractCompiler: private boost::noncopyable { public: static void printFunctionIds(ContractDefinition const& contract, PragmaDirectiveHelper const& pragmaHelper); static void printPrivateFunctionIds( ContractDefinition const& contract, - std::vector> _sourceUnits, + std::vector> const& _sourceUnits, PragmaDirectiveHelper const& pragmaHelper ); static void generateABI( @@ -54,14 +39,14 @@ class TVMContractCompiler: private boost::noncopyable { std::vector const& pragmaDirectives ); static void generateCodeAndSaveToFile( - const std::string& fileName, + std::string const& fileName, ContractDefinition const& contract, - std::vector> _sourceUnits, + std::vector> const& _sourceUnits, PragmaDirectiveHelper const &pragmaHelper ); static Pointer generateContractCode( ContractDefinition const* contract, - std::vector> _sourceUnits, + std::vector>const& _sourceUnits, PragmaDirectiveHelper const& pragmaHelper ); static void optimizeCode(Pointer& c); diff --git a/compiler/libsolidity/codegen/TVMExpressionCompiler.cpp b/compiler/libsolidity/codegen/TVMExpressionCompiler.cpp index 1245f0fa..ce718acb 100644 --- a/compiler/libsolidity/codegen/TVMExpressionCompiler.cpp +++ b/compiler/libsolidity/codegen/TVMExpressionCompiler.cpp @@ -395,7 +395,7 @@ void TVMExpressionCompiler::compareSlices(Token op) { } void TVMExpressionCompiler::compareStrings(Token op) { - m_pusher.pushFragmentInCallRef(2, 1, "compareLongStrings"); + m_pusher.pushFragmentInCallRef(2, 1, "__compareStrings"); switch(op) { case Token::GreaterThan: m_pusher << "ISPOS"; @@ -448,7 +448,7 @@ void TVMExpressionCompiler::visitBinaryOperationForString( if (op == Token::Add) { pushLeft(); pushRight(); - m_pusher.pushFragmentInCallRef(2, 1, "concatenateStrings"); + m_pusher.pushFragmentInCallRef(2, 1, "__concatenateStrings"); } else if (op == Token::Equal || op == Token::NotEqual) { visitBinaryOperationForTvmCell(pushLeft, pushRight, op); } else if (TokenTraits::isCompareOp(op)){ @@ -522,6 +522,14 @@ void TVMExpressionCompiler::visit2(BinaryOperation const &_binaryOperation) { m_pusher.convert(rightTargetType, rt); }; + if (_binaryOperation.userDefinedFunctionType()) { + acceptLeft(); + acceptRight(); + FunctionDefinition const* functionDef = *_binaryOperation.annotation().userDefinedFunction; + m_pusher.pushCallOrCallRef(functionDef, std::nullopt, false); + return ; + } + if (isString(commonType)) { visitBinaryOperationForString(acceptLeft, acceptRight, op); return; @@ -550,11 +558,9 @@ void TVMExpressionCompiler::visit2(BinaryOperation const &_binaryOperation) { if (op == Token::SHR) cast_error(_binaryOperation, "Unsupported operation >>>"); if (op == Token::Comma) cast_error(_binaryOperation, "Unsupported operation ,"); - const auto& val = ExprUtils::constValue(_binaryOperation.rightExpression()); - std::optional rightValue; - if (val.has_value()) - rightValue = val; - visitMathBinaryOperation(op, commonType, acceptRight, rightValue); + const auto& leftValue = ExprUtils::constValue(_binaryOperation.leftExpression()); + const auto& rightValue = ExprUtils::constValue(_binaryOperation.rightExpression()); + visitMathBinaryOperation(op, commonType, leftValue, acceptRight, rightValue); } bool TVMExpressionCompiler::isCheckFitUseless(Type const* commonType, Token op) { @@ -572,6 +578,7 @@ bool TVMExpressionCompiler::isCheckFitUseless(Type const* commonType, Token op) void TVMExpressionCompiler::visitMathBinaryOperation( const Token op, Type const* commonType, + const std::optional& leftValue, const std::function& pushRight, const std::optional& rightValue ) { @@ -595,11 +602,18 @@ void TVMExpressionCompiler::visitMathBinaryOperation( solUnimplemented(""); } } else { - pushRight(); - m_pusher.dup2(); - m_pusher << "OR"; - m_pusher._throw("THROWIFNOT " + toString(TvmConst::RuntimeException::Exponent00)); - m_pusher.pushFragmentInCallRef(2, 1, "__exp"); + if (leftValue.has_value() && leftValue == 2) { + m_pusher.drop(); + pushRight(); + m_pusher << "POW2"; + } else { + pushRight(); + m_pusher.pushS(1); + m_pusher.pushS(1); + m_pusher << "OR"; + m_pusher._throw("THROWIFNOT " + toString(TvmConst::RuntimeException::Exponent00)); + m_pusher.pushFragmentInCallRef(2, 1, "__exp"); + } } checkOverflow = true; } else { @@ -642,7 +656,12 @@ void TVMExpressionCompiler::visitMathBinaryOperation( else if (op == Token::BitAnd) m_pusher << "AND"; else if (op == Token::BitOr) m_pusher << "OR"; else if (op == Token::SHL) { - m_pusher << "LSHIFT"; + if (leftValue.has_value() && leftValue == 1) { + m_pusher.dropUnder(1, 1); + m_pusher << "POW2"; + } else { + m_pusher << "LSHIFT"; + } checkOverflow = true; } else if (op == Token::SAR) m_pusher << "RSHIFT"; @@ -812,7 +831,7 @@ void TVMExpressionCompiler::visitMagic(MemberAccess const &_memberAccess) { }; Type const* exprType = _memberAccess.expression().annotation().type; - auto magicType = dynamic_cast(exprType); + auto magicType = to(exprType); ASTString const& member = _memberAccess.memberName(); switch (magicType->kind()) { @@ -841,14 +860,14 @@ void TVMExpressionCompiler::visitMagic(MemberAccess const &_memberAccess) { } case MagicType::Kind::MetaType: { if (member == "min" || member == "max") { - auto const* arg = dynamic_cast(_memberAccess.expression().annotation().type); + auto const* arg = to(_memberAccess.expression().annotation().type); string opcode = "PUSHINT "; const Type *argType = arg->typeArgument(); - if (auto const* integerType = dynamic_cast(argType)) + if (auto const* integerType = to(argType)) opcode += toString(member == "min" ? integerType->minValue() : integerType->maxValue()); - else if (auto const* varInt = dynamic_cast(argType)) + else if (auto const* varInt = to(argType)) opcode += toString(member == "min" ? varInt->asIntegerType().minValue() : varInt->asIntegerType().maxValue()); - else if (auto const* enumType = dynamic_cast(argType)) + else if (auto const* enumType = to(argType)) opcode += toString(member == "min" ? enumType->minValue() : enumType->maxValue()); else solAssert(false, "min/max not available for the given type."); @@ -892,14 +911,19 @@ void TVMExpressionCompiler::visit2(MemberAccess const &_node) { if (category == Type::Category::TypeType) { auto typeType = to(_node.expression().annotation().type); - if (auto enumType = dynamic_cast(typeType->actualType())) { - unsigned int value = enumType->memberValue(_node.memberName()); + auto actualType = typeType->actualType(); + if (actualType->category() == Type::Category::Address) { + solAssert(memberName == "addrNone", ""); + m_pusher.pushSlice("x2_"); + return; + } + if (auto enumType = to(actualType)) { + unsigned int value = enumType->memberValue(memberName); m_pusher << "PUSHINT " + toString(value); return; } - - auto conType = to(typeType->actualType()); - if (conType->contractDefinition().isLibrary()) { + auto conType = to(actualType); + if (conType != nullptr && conType->contractDefinition().isLibrary()) { auto funType = to(_node.annotation().type); if (funType) { auto funDef = to(&funType->declaration()); @@ -907,11 +931,9 @@ void TVMExpressionCompiler::visit2(MemberAccess const &_node) { return ; } } - if (fold_constants(&_node)) { return; } - if (tryPushConstant(_node.annotation().referencedDeclaration)) { return; } @@ -991,21 +1013,19 @@ void TVMExpressionCompiler::visit2(IndexRangeAccess const &indexRangeAccess) { auto baseArrayType = to(baseType); if (baseArrayType->isByteArrayOrString()) { acceptExpr(&indexRangeAccess.baseExpression()); // bytes - if (indexRangeAccess.startExpression()) { + if (indexRangeAccess.startExpression()) compileNewExpr(indexRangeAccess.startExpression()); - } - else { + else m_pusher.pushInt(0); - } + if (indexRangeAccess.endExpression()) { compileNewExpr(indexRangeAccess.endExpression()); - m_pusher << "FALSE"; + m_pusher.pushFragmentInCallRef(3, 1, "__arraySlice"); } else { - m_pusher.pushInt(0); + m_pusher.pushInt(0xFFFF'FFFF); m_pusher << "TRUE"; + m_pusher.pushFragmentInCallRef(4, 1, "__subCell"); } - - m_pusher.pushFragmentInCallRef(4, 1, "bytes_substr"); return; } } @@ -1329,9 +1349,9 @@ bool TVMExpressionCompiler::tryAssignLValue(Assignment const &_assignment) { m_pusher.blockSwap(1, expandedLValueSize + 1); // expanded... l r if (isString(commonType)) { - m_pusher.pushFragmentInCallRef(2, 1, "concatenateStrings"); + m_pusher.pushFragmentInCallRef(2, 1, "__concatenateStrings"); } else { - visitMathBinaryOperation(binOp, commonType, nullptr, nullopt); + visitMathBinaryOperation(binOp, commonType, nullopt, nullptr, nullopt); } if (isCurrentResultNeeded()) { diff --git a/compiler/libsolidity/codegen/TVMExpressionCompiler.hpp b/compiler/libsolidity/codegen/TVMExpressionCompiler.hpp index f0f6f6a1..17680071 100644 --- a/compiler/libsolidity/codegen/TVMExpressionCompiler.hpp +++ b/compiler/libsolidity/codegen/TVMExpressionCompiler.hpp @@ -68,6 +68,7 @@ class TVMExpressionCompiler { void visitMathBinaryOperation( Token op, Type const* commonType, + const std::optional& leftValue, const std::function& pushRight, const std::optional& rightValue ); diff --git a/compiler/libsolidity/codegen/TVMFunctionCall.cpp b/compiler/libsolidity/codegen/TVMFunctionCall.cpp index ad26d41e..9322524c 100644 --- a/compiler/libsolidity/codegen/TVMFunctionCall.cpp +++ b/compiler/libsolidity/codegen/TVMFunctionCall.cpp @@ -40,6 +40,7 @@ FunctionCallCompiler::FunctionCallCompiler( m_pusher{m_pusher}, m_exprCompiler{m_pusher}, m_functionCall{_functionCall}, + m_memberAccess{to(&m_functionCall.expression())}, m_arguments{_functionCall.arguments()}, m_funcType{to(m_functionCall.expression().annotation().type)}, m_retType{m_functionCall.annotation().type}, @@ -59,7 +60,6 @@ void FunctionCallCompiler::structConstructorCall() { } void FunctionCallCompiler::compile() { - auto _memberAccess = to(&m_functionCall.expression()); auto reportError = [&](){ cast_error(m_functionCall, "Unsupported function call"); }; @@ -76,14 +76,14 @@ void FunctionCallCompiler::compile() { } if (checkRemoteMethodCall(m_functionCall) || - (_memberAccess != nullptr && libraryCall(*_memberAccess)) || + (m_memberAccess != nullptr && libraryCall(*m_memberAccess)) || checkForMappingOrCurrenciesMethods() || checkNewExpression() || checkAddressThis() || checkSolidityUnits() || checkLocalFunctionOrLibCallOrFuncVarCall()) { // do nothing - } else if (_memberAccess != nullptr && getType(&_memberAccess->expression())->category() == Type::Category::Struct) { + } else if (m_memberAccess != nullptr && getType(&m_memberAccess->expression())->category() == Type::Category::Struct) { if (!structMethodCall()) { reportError(); } @@ -96,63 +96,76 @@ void FunctionCallCompiler::compile() { typeConversion(); } } else { - if (_memberAccess != nullptr) { - auto category = getType(&_memberAccess->expression())->category(); - auto ident = to(&_memberAccess->expression()); + if (m_memberAccess != nullptr) { + auto category = getType(&m_memberAccess->expression())->category(); + auto ident = to(&m_memberAccess->expression()); if (category == Type::Category::Array) { - arrayMethods(*_memberAccess); + arrayMethods(*m_memberAccess); } else if (category == Type::Category::TvmSlice) { - sliceMethods(*_memberAccess); + sliceMethods(*m_memberAccess); } else if (category == Type::Category::TvmBuilder) { - builderMethods(*_memberAccess); + builderMethods(*m_memberAccess); } else if ( - checkForTvmVectorMethods(*_memberAccess, category) || - checkForOptionalMethods(*_memberAccess)) + checkForTvmVectorMethods(*m_memberAccess, category) || + checkForOptionalMethods(*m_memberAccess)) { // nothing } else if (category == Type::Category::Magic && ident != nullptr && ident->name() == "tvm") { - if (m_funcType->kind() == FunctionType::Kind::TVMBuildIntMsg) { - tvmBuildIntMsg(); - } else if (m_funcType->kind() == FunctionType::Kind::TVMBuildDataInit) { - tvmBuildDataInit(); - } else if (m_funcType->kind() == FunctionType::Kind::TVMBuildExtMsg) { - tvmBuildMsgMethod(); + if (m_funcType->kind() == FunctionType::Kind::ABIBuildIntMsg) { + abiBuildIntMsg(); + } else if (m_funcType->kind() == FunctionType::Kind::ABIEncodeData) { + abiBuildDataInit(); + } else if (m_funcType->kind() == FunctionType::Kind::ABIBuildExtMsg) { + abiBuildExtMsg(); } else if ( - checkForTvmSendFunction(*_memberAccess) || - checkForTvmConfigParamFunction(*_memberAccess) || - checkForTvmFunction(*_memberAccess) || - checkForTvmDeployMethods(*_memberAccess, category) + checkForTvmSendFunction(*m_memberAccess) || + checkForTvmConfigParamFunction(*m_memberAccess) || + checkForTvmFunction(*m_memberAccess) || + checkTvmABIDeployMethods(category) ) { // do nothing } else { reportError(); } } else if (category == Type::Category::Magic && ident != nullptr && ident->name() == "rnd") { - rndFunction(*_memberAccess); + rndFunction(*m_memberAccess); } else if (category == Type::Category::Magic && ident != nullptr && ident->name() == "gosh") { goshFunction(); } else if (category == Type::Category::Magic && ident != nullptr && ident->name() == "msg") { - msgFunction(*_memberAccess); + msgFunction(*m_memberAccess); } else if (category == Type::Category::Magic && ident != nullptr && ident->name() == "abi") { - abiFunction(); + if (m_funcType->kind() == FunctionType::Kind::ABIDecodeData) + abiDecodeData(); + else if (m_funcType->kind() == FunctionType::Kind::ABIEncodeBody) + abiEncodeBody(); + else if (m_funcType->kind() == FunctionType::Kind::ABIBuildIntMsg) + abiBuildIntMsg(); + else if (m_funcType->kind() == FunctionType::Kind::ABIBuildExtMsg) + abiBuildExtMsg(); + else if (m_funcType->kind() == FunctionType::Kind::ABIEncodeData) + abiBuildDataInit(); + else if (!checkTvmABIDeployMethods(category)) + abiFunction(); } else if (category == Type::Category::Magic && ident != nullptr && ident->name() == "math") { - mathFunction(*_memberAccess); + mathFunction(*m_memberAccess); } else if (category == Type::Category::Address) { addressMethod(); } else if (category == Type::Category::TvmCell) { - cellMethods(*_memberAccess); + cellMethods(*m_memberAccess); + } else if (category == Type::Category::Integer) { + integerMethods(); } else if (category == Type::Category::Variant) { - variantMethods(*_memberAccess); - } else if (isSuper(&_memberAccess->expression())) { - superFunctionCall(*_memberAccess); + variantMethods(*m_memberAccess); + } else if (isSuper(&m_memberAccess->expression())) { + superFunctionCall(*m_memberAccess); } else if (category == Type::Category::TypeType) { - Type const* actualType = to(_memberAccess->expression().annotation().type)->actualType(); - if (checkBaseContractCall(*_memberAccess)) { + Type const* actualType = to(m_memberAccess->expression().annotation().type)->actualType(); + if (checkBaseContractCall(*m_memberAccess)) { // nothing } else if (to(actualType)) { - userDefinedValueMethods(*_memberAccess); + userDefinedValueMethods(*m_memberAccess); } else if (to(actualType)) { - addressMethods(*_memberAccess); + addressMethods(*m_memberAccess); } else { reportError(); } @@ -177,12 +190,10 @@ void FunctionCallCompiler::arrayPush(StackPusher& pusher, Type const* arrayBaseT } bool FunctionCallCompiler::checkForMappingOrCurrenciesMethods() { - auto expr = &m_functionCall.expression(); - auto ma = to(expr); - if (ma == nullptr || !to(ma->expression().annotation().type)) + if (m_memberAccess == nullptr || !to(m_memberAccess->expression().annotation().type)) return false; - const ASTString &memberName = ma->memberName(); + const ASTString &memberName = m_memberAccess->memberName(); if (isIn(memberName, "delMin", "delMax")) { mappingDelMinOrMax(memberName == std::string{"delMin"}); } else if (isIn(memberName, "at", "fetch", "exists", "replace", "add", @@ -204,28 +215,24 @@ bool FunctionCallCompiler::checkForMappingOrCurrenciesMethods() { } void FunctionCallCompiler::mappingDelMinOrMax(bool isDelMin) { - auto memberAccess = to(&m_functionCall.expression()); - Type const* keyType{}; Type const* valueType{}; - std::tie(keyType, valueType) = dictKeyValue(memberAccess->expression().annotation().type); + std::tie(keyType, valueType) = dictKeyValue(m_memberAccess->expression().annotation().type); - DelMinOrMax d{m_pusher, *keyType, *valueType, isDelMin, memberAccess}; + DelMinOrMax d{m_pusher, *keyType, *valueType, isDelMin, m_memberAccess}; d.delMinOrMax(); } void FunctionCallCompiler::mappingGetSet() { - auto memberAccess = to(&m_functionCall.expression()); - Type const* keyType{}; Type const* valueType{}; - std::tie(keyType, valueType) = dictKeyValue(memberAccess->expression().annotation().type); + std::tie(keyType, valueType) = dictKeyValue(m_memberAccess->expression().annotation().type); - const ASTString &memberName = memberAccess->memberName(); + const ASTString &memberName = m_memberAccess->memberName(); if (isIn(memberName, "fetch", "at")) { pushArgs(); // index m_pusher.prepareKeyForDictOperations(keyType, false); - acceptExpr(&memberAccess->expression()); // index dict + acceptExpr(&m_memberAccess->expression()); // index dict if (memberName == "fetch") m_pusher.getDict(*keyType, *valueType, GetDictOperation::Fetch); else @@ -233,13 +240,11 @@ void FunctionCallCompiler::mappingGetSet() { } else if (memberName == "exists") { pushArgs(); // index m_pusher.prepareKeyForDictOperations(keyType, false); - acceptExpr(&memberAccess->expression()); // index dict + acceptExpr(&m_memberAccess->expression()); // index dict m_pusher.getDict(*keyType, *valueType, GetDictOperation::Exist); } else if (isIn(memberName, "getDel")) { const int stackSize = m_pusher.stackSize(); - - auto ma = to(&m_functionCall.expression()); - const LValueInfo lValueInfo = m_exprCompiler.expandLValue(&ma->expression(), true); + const LValueInfo lValueInfo = m_exprCompiler.expandLValue(&m_memberAccess->expression(), true); pushArgAndConvert(0); // lValue... map key m_pusher.prepareKeyForDictOperations(keyType, false); @@ -252,8 +257,7 @@ void FunctionCallCompiler::mappingGetSet() { m_exprCompiler.collectLValue(lValueInfo, true, false); // value } else if (isIn(memberName, "replace", "add", "getSet", "getAdd", "getReplace")) { const int stackSize = m_pusher.stackSize(); - auto ma = to(&m_functionCall.expression()); - const LValueInfo lValueInfo = m_exprCompiler.expandLValue(&ma->expression(), true); // lValue... map + const LValueInfo lValueInfo = m_exprCompiler.expandLValue(&m_memberAccess->expression(), true); // lValue... map pushArgAndConvert(1); // lValue... map value const DataType& dataType = m_pusher.prepareValueForDictOperations(keyType, valueType); // lValue... map value' pushArgAndConvert(0); // mapLValue... map value key @@ -295,46 +299,40 @@ void FunctionCallCompiler::mappingGetSet() { } void FunctionCallCompiler::mappingMinMaxMethod(bool isMin) { - auto expr = &m_functionCall.expression(); - auto memberAccess = to(expr); - Type const* keyType{}; Type const* valueType{}; - std::tie(keyType, valueType) = dictKeyValue(memberAccess->expression().annotation().type); + std::tie(keyType, valueType) = dictKeyValue(m_memberAccess->expression().annotation().type); - acceptExpr(&memberAccess->expression()); // dict + acceptExpr(&m_memberAccess->expression()); // dict DictMinMax compiler{m_pusher, *keyType, *valueType, isMin}; compiler.minOrMax(); } void FunctionCallCompiler::mappingPrevNextMethods() { - auto expr = &m_functionCall.expression(); - auto memberAccess = to(expr); Type const* keyType{}; Type const* valueType{}; - std::tie(keyType, valueType) = dictKeyValue(memberAccess->expression().annotation().type); + std::tie(keyType, valueType) = dictKeyValue(m_memberAccess->expression().annotation().type); pushArgAndConvert(0); // index m_pusher.prepareKeyForDictOperations(keyType, true); // index' - acceptExpr(&memberAccess->expression()); // index' dict + acceptExpr(&m_memberAccess->expression()); // index' dict m_pusher.pushInt(dictKeyLength(keyType)); // index' dict nbits - DictPrevNext compiler{m_pusher, *keyType, *valueType, memberAccess->memberName()}; + DictPrevNext compiler{m_pusher, *keyType, *valueType, m_memberAccess->memberName()}; compiler.prevNext(); } void FunctionCallCompiler::mappingKeysOrValues(bool areKeys) { m_pusher.pushEmptyArray(); - auto ma = to(&m_functionCall.expression()); - acceptExpr(&ma->expression()); + acceptExpr(&m_memberAccess->expression()); // array map m_pusher.pushS(0); // array map map Type const* mapKeyType{}; Type const* mapValueType{}; - std::tie(mapKeyType, mapValueType) = realDictKeyValue(ma->expression().annotation().type); + std::tie(mapKeyType, mapValueType) = realDictKeyValue(m_memberAccess->expression().annotation().type); DictMinMax compiler{m_pusher, *mapKeyType, *mapValueType, true}; compiler.minOrMax(); // array map minPair @@ -388,9 +386,7 @@ void FunctionCallCompiler::mappingKeysOrValues(bool areKeys) { } void FunctionCallCompiler::mappingEmpty() { - auto expr = &m_functionCall.expression(); - auto ma = to(expr); - acceptExpr(&ma->expression()); + acceptExpr(&m_memberAccess->expression()); m_pusher << "DICTEMPTY"; } @@ -436,8 +432,6 @@ void FunctionCallCompiler::addressMethods(MemberAccess const &_node) { m_pusher << "ENDC"; m_pusher << "CTOS"; // extAddress } - } else if (_node.memberName() == "makeAddrNone") { - m_pusher.pushSlice("x2_"); } else if (_node.memberName() == "makeAddrStd") { const auto& wid = ExprUtils::constValue(*m_arguments.at(0)); const auto& val = ExprUtils::constValue(*m_arguments.at(1)); @@ -478,10 +472,10 @@ bool FunctionCallCompiler::libraryCall(MemberAccess const& ma) { } else { // using MathLib for uint; // a.add(b); - const int stakeSize0 = m_pusher.stackSize(); + const int stackSize0 = m_pusher.stackSize(); const LValueInfo lValueInfo = m_exprCompiler.expandLValue(&ma.expression(), true); - const int stakeSize1 = m_pusher.stackSize(); - const int lValueQty = stakeSize1 - stakeSize0; + const int stackSize1 = m_pusher.stackSize(); + const int lValueQty = stackSize1 - stackSize0; pushArgs(); m_pusher.pushCallOrCallRef( @@ -501,55 +495,106 @@ bool FunctionCallCompiler::libraryCall(MemberAccess const& ma) { } std::function FunctionCallCompiler::generateDataSection( + bool data_map_supported, const std::function& pushKey, Expression const* vars, ContractType const* ct ) { - return [pushKey, this, vars, ct]() { - // creat dict with variable values - m_pusher << "NEWDICT"; - // stake: builder dict - - IntegerType keyType = getKeyTypeOfC4(); - Type const* valueType = TypeProvider::uint256(); - - pushKey(); - const DataType& dataType = m_pusher.prepareValueForDictOperations(&keyType, valueType); - m_pusher.pushInt(0); // index of pubkey - // stack: dict value key - m_pusher.rot(); - // stack: value key dict - m_pusher.setDict(getKeyTypeOfC4(), *valueType, dataType); - // stack: dict' - if (vars) { + auto getDeclAndIndex = + [](std::vector> staticVars, const std::string& name) + { + auto pos = find_if(staticVars.begin(), staticVars.end(), [&](auto v) { + return v.first->name() == name; + }); + solAssert(pos != staticVars.end(), ""); + return *pos; + }; + + if (data_map_supported) { + return [pushKey, this, vars, ct, getDeclAndIndex]() { + // creat dict with variable values + m_pusher << "NEWDICT"; + // stack: builder dict + IntegerType keyType = getKeyTypeOfC4(); + Type const* valueType = TypeProvider::uint256(); + + pushKey(); + const DataType& dataType = m_pusher.prepareValueForDictOperations(&keyType, valueType); + m_pusher.pushInt(0); // index of pubkey + // stack: dict value key + m_pusher.rot(); + // stack: value key dict + m_pusher.setDict(getKeyTypeOfC4(), *valueType, dataType); + // stack: dict' + if (vars) { + std::vector _pragmaDirectives; + PragmaDirectiveHelper pragmaHelper{_pragmaDirectives}; + TVMCompilerContext cc{&ct->contractDefinition(), pragmaHelper}; + std::vector> staticVars = cc.getStaticVariables(); + auto initVars = to(vars); + for (size_t i = 0; i < initVars->names().size(); ++i) { + const ASTPointer &name = initVars->names().at(i); + const auto &[varDecl, varIndex] = getDeclAndIndex(staticVars, *name); + valueType = varDecl->type(); + pushExprAndConvert(initVars->options().at(i).get(), valueType); // stack: dict value + const DataType& dataType2 = m_pusher.prepareValueForDictOperations(&keyType, valueType); + m_pusher.pushInt(varIndex); + // stack: dict value key + m_pusher.rot(); + // stack: value key dict + m_pusher.setDict(getKeyTypeOfC4(), *varDecl->type(), dataType2); + // stack: dict' + } + } + m_pusher << "NEWC"; + m_pusher << "STDICT"; + m_pusher << "ENDC"; + }; + } else { + return [pushKey, this, vars, ct, getDeclAndIndex]() { std::vector _pragmaDirectives; PragmaDirectiveHelper pragmaHelper{_pragmaDirectives}; - TVMCompilerContext cc{&ct->contractDefinition(), pragmaHelper}; - std::vector> staticVars = cc.getStaticVariables(); - auto getDeclAndIndex = [&](const std::string& name) { - auto pos = find_if(staticVars.begin(), staticVars.end(), [&](auto v) { return v.first->name() == name; }); - solAssert(pos != staticVars.end(), ""); - return *pos; - }; - auto initVars = to(vars); - for (size_t i = 0; i < initVars->names().size(); ++i) { - const ASTPointer &name = initVars->names().at(i); - const auto &[varDecl, varIndex] = getDeclAndIndex(*name); - valueType = varDecl->type(); - pushExprAndConvert(initVars->options().at(i).get(), valueType); // stack: dict value - const DataType& dataType2 = m_pusher.prepareValueForDictOperations(&keyType, valueType); - m_pusher.pushInt(varIndex); - // stack: dict value key - m_pusher.rot(); - // stack: value key dict - m_pusher.setDict(getKeyTypeOfC4(), *varDecl->type(), dataType2); - // stack: dict' + solAssert(ct, "ct == null"); + TVMCompilerContext ctx{&ct->contractDefinition(), pragmaHelper}; + + std::map varValue; + if (vars != nullptr) { + std::vector> staticVars = ctx.getStaticVariables(); + auto initVars = to(vars); + for (size_t i = 0; i < initVars->names().size(); ++i) { + const ASTPointer &name = initVars->names().at(i); + const auto &[varDecl, varIndex] = getDeclAndIndex(staticVars, *name); + varValue[varDecl] = initVars->options().at(i).get(); + } } - } - m_pusher << "NEWC"; - m_pusher << "STDICT"; - m_pusher << "ENDC"; - }; + + std::vector stateVars = ctx.c4StateVariables(); + for (VariableDeclaration const* var : stateVars | boost::adaptors::reversed) { + if (varValue.count(var) == 0) + m_pusher.pushDefaultValue(var->type()); + else + pushExprAndConvert(varValue.at(var), var->type()); + } + + if (ctx.storeTimestampInC4()) + m_pusher.pushInt(0); + pushKey(); + m_pusher << "NEWC"; + m_pusher << "STU 256"; + if (ctx.storeTimestampInC4()) + m_pusher << "STU 64"; + m_pusher << "STZERO"; // constructor flag + if (ctx.usage().hasAwaitCall()) + m_pusher << "STZERO"; + const std::vector& memberTypes = ctx.c4StateVariableTypes(); + if (!memberTypes.empty()) { + ChainDataEncoder encoder{&m_pusher}; + DecodePositionAbiV2 position{ctx.getOffsetC4(), ctx.usage().hasAwaitCall() ? 1 : 0, memberTypes}; + encoder.encodeParameters(memberTypes, position); + } + m_pusher << "ENDC"; + }; + } } bool FunctionCallCompiler::checkRemoteMethodCall(FunctionCall const &_functionCall) { @@ -1010,7 +1055,7 @@ void FunctionCallCompiler::generateExtInboundMsg( solAssert(stackSize + 1 == m_pusher.stackSize(), ""); } -void FunctionCallCompiler::tvmBuildIntMsg() { +void FunctionCallCompiler::abiBuildIntMsg() { const int stackSize = m_pusher.stackSize(); int destArg = -1; @@ -1118,7 +1163,7 @@ void FunctionCallCompiler::tvmBuildIntMsg() { solAssert(m_pusher.stackSize() == stackSize + 1, ""); } -void FunctionCallCompiler::tvmBuildDataInit() { +void FunctionCallCompiler::abiBuildDataInit() { const int stackSize = m_pusher.stackSize(); int keyArg = -1; int varArg = -1; @@ -1152,13 +1197,16 @@ void FunctionCallCompiler::tvmBuildDataInit() { } }; ContractType const* ct{}; - if (varArg != -1) { - Type const* type = m_arguments[contrArg]->annotation().type; + if (contrArg != -1) { + Type const* type = m_arguments.at(contrArg)->annotation().type; auto tt = dynamic_cast(type); type = tt->actualType(); ct = to(type); } + + bool data_map_supported = m_memberAccess->memberName() == "encodeOldDataInit"; generateDataSection( + data_map_supported, pushKey, varArg != -1 ? m_arguments[varArg].get() : nullptr, ct @@ -1167,7 +1215,7 @@ void FunctionCallCompiler::tvmBuildDataInit() { solAssert(m_pusher.stackSize() == stackSize + 1, ""); } -void FunctionCallCompiler::tvmBuildMsgMethod() { +void FunctionCallCompiler::abiBuildExtMsg() { int destArg = -1; int callArg = -1; int timeArg = -1; @@ -1247,12 +1295,11 @@ void FunctionCallCompiler::tvmBuildMsgMethod() { functionDefinition, funcCall->arguments()); } -bool FunctionCallCompiler::checkForTvmDeployMethods(MemberAccess const &_node, Type::Category category) { - if (category != Type::Category::Magic || ((m_funcType->kind() != FunctionType::Kind::TVMDeploy) - && m_funcType->kind() != FunctionType::Kind::TVMBuildStateInit)) +bool FunctionCallCompiler::checkTvmABIDeployMethods(Type::Category category) { + if (category != Type::Category::Magic) return false; - if (_node.memberName() == "buildStateInit") { + if (m_funcType->kind() == FunctionType::Kind::ABIEncodeStateInit) { int keyArg = -1; int varArg = -1; int contrArg = -1; @@ -1326,29 +1373,64 @@ bool FunctionCallCompiler::checkForTvmDeployMethods(MemberAccess const &_node, T type = tt->actualType(); ct = to(type); } - exprs[StateInitMembers::Data] = generateDataSection(pushKey, + exprs[StateInitMembers::Data] = generateDataSection(false, pushKey, hasVars ? m_arguments[varArg].get() : nullptr, ct); } } - buildStateInit(exprs); + encodeStateInit(exprs); return true; } - if (_node.memberName() == "insertPubkey") { + if (m_funcType->kind() == FunctionType::Kind::ABIStateInitHash) { pushArgs(); - m_pusher.pushFragmentInCallRef(2, 1, "insert_pubkey"); + m_pusher.pushFragmentInCallRef(4, 1, "__stateInitHash"); return true; } - if (_node.memberName() == "stateInitHash") { - pushArgs(); - m_pusher.pushFragmentInCallRef(4, 1, "stateInitHash"); - return true; + return false; +} + +void FunctionCallCompiler::abiDecodeData() { + pushArgAndConvert(1); + decodeData(); +} + +int FunctionCallCompiler::decodeData() { + std::vector stateVarTypes; + auto retTuple = to(m_retType); + for (Type const* type : retTuple->components()) { + stateVarTypes.push_back(type); } - return false; + // lvalue.. slice + ChainDataDecoder decoder{&m_pusher}; + decoder.decodeData(0, 0, stateVarTypes); + // lvalue.. stateVars... + return stateVarTypes.size(); +} + +int FunctionCallCompiler::decodeFunctionParams() { + CallableDeclaration const *functionDefinition = getFunctionDeclarationOrConstructor( + m_arguments.at(0).get()); + if (functionDefinition) { + // lvalue.. slice + auto fd = to(functionDefinition); + bool isResponsible = fd->isResponsible(); + if (isResponsible) { + m_pusher << "LDU 32"; + } + // lvalue.. callback slice + ChainDataDecoder decoder{&m_pusher}; + vector types = getParams(functionDefinition->parameters()).first; + decoder.decodePublicFunctionParameters(types, isResponsible, true); + + return functionDefinition->parameters().size() + (isResponsible ? 1 : 0); + } + + m_pusher << "ENDS"; + return 0; } void FunctionCallCompiler::sliceMethods(MemberAccess const &_node) { @@ -1426,36 +1508,9 @@ void FunctionCallCompiler::sliceMethods(MemberAccess const &_node) { const LValueInfo lValueInfo = m_exprCompiler.expandLValue(&_node.expression(), true); int paramQty = -1; if (isIn(memberName, "loadFunctionParams", "decodeFunctionParams")) { - CallableDeclaration const *functionDefinition = getFunctionDeclarationOrConstructor( - m_arguments.at(0).get()); - if (functionDefinition) { - - // lvalue.. slice - auto fd = to(functionDefinition); - bool isResponsible = fd->isResponsible(); - if (isResponsible) { - m_pusher << "LDU 32"; - } - // lvalue.. callback slice - ChainDataDecoder decoder{&m_pusher}; - vector types = getParams(functionDefinition->parameters()).first; - decoder.decodePublicFunctionParameters(types, isResponsible, true); - - paramQty = functionDefinition->parameters().size() + (isResponsible ? 1 : 0); - } + paramQty = decodeFunctionParams(); } else if (isIn(memberName, "loadStateVars", "decodeStateVars")) { - std::vector stateVarTypes; - auto retTuple = to(m_retType); - for (Type const* type : retTuple->components()) { - stateVarTypes.push_back(type); - } - - // lvalue.. slice - ChainDataDecoder decoder{&m_pusher}; - decoder.decodeData(0, 0, stateVarTypes); - // lvalue.. stateVars... - - paramQty = stateVarTypes.size(); + paramQty = decodeData(); } else { solUnimplemented(""); } @@ -1533,7 +1588,8 @@ void FunctionCallCompiler::sliceMethods(MemberAccess const &_node) { } else if (memberName == "loadTons") { opcode = "LDGRAMS"; } else if (memberName == "loadSlice") { - if (m_arguments.size() == 1) { + const auto& value2 = m_arguments.size() == 2 ? ExprUtils::constValue(*m_arguments[1]) : nullopt; + if (m_arguments.size() == 1 || (value2.has_value() && value2.value() == 0)) { if (value.has_value() && 0 < value && value <= 256) { m_pusher << "LDSLICE " + value->str(); } else { @@ -2078,10 +2134,10 @@ void FunctionCallCompiler::arrayMethods(MemberAccess const &_node) { acceptExpr(&_node.expression()); pushArgs(); if (m_arguments.size() == 1) { - m_pusher.pushFragmentInCallRef(2, 1, "__substr_from"); - } else { - m_pusher.pushFragmentInCallRef(3, 1, "__substr_from_count"); + m_pusher.pushInt(0xFFFF'FFFF); } + m_pusher << "TRUE"; + m_pusher.pushFragmentInCallRef(4, 1, "__subCell"); } else if (_node.memberName() == "find") { acceptExpr(&_node.expression()); pushArgs(); @@ -2147,7 +2203,7 @@ void FunctionCallCompiler::arrayMethods(MemberAccess const &_node) { } else if (_node.memberName() == "append") { const LValueInfo lValueInfo = m_exprCompiler.expandLValue(&_node.expression(), true); pushArgAndConvert(0); - m_pusher.pushFragmentInCallRef(2, 1, "concatenateStrings"); + m_pusher.pushFragmentInCallRef(2, 1, "__concatenateStrings"); m_exprCompiler.collectLValue(lValueInfo, true, false); } else { solUnimplemented(""); @@ -2255,6 +2311,18 @@ void FunctionCallCompiler::cellMethods(MemberAccess const &_node) { solUnimplemented(""); } +void FunctionCallCompiler::integerMethods() { + acceptExpr(&m_memberAccess->expression()); + switch (m_funcType->kind()) { + case FunctionType::Kind::IntCast: { + m_pusher.convert(m_retType, m_memberAccess->expression().annotation().type); + break; + } + default: + solUnimplemented(""); + } +} + void FunctionCallCompiler::variantMethods(MemberAccess const& _node) { auto isUint = [&](){ @@ -2289,8 +2357,7 @@ void FunctionCallCompiler::variantMethods(MemberAccess const& _node) { } void FunctionCallCompiler::addressMethod() { - auto _node = to(&m_functionCall.expression()); - if (_node->memberName() == "transfer") { // addr.transfer(...) + if (m_memberAccess->memberName() == "transfer") { // addr.transfer(...) std::map exprs; std::map constParams{{TvmConst::int_msg_info::ihr_disabled, "1"}, {TvmConst::int_msg_info::bounce, "1"}}; std::function appendBody; @@ -2327,7 +2394,7 @@ void FunctionCallCompiler::addressMethod() { }; }; - exprs[TvmConst::int_msg_info::dest] = &_node->expression(); + exprs[TvmConst::int_msg_info::dest] = &m_memberAccess->expression(); int argumentQty = static_cast(m_arguments.size()); if (!m_names.empty() || argumentQty == 0) { @@ -2390,26 +2457,26 @@ void FunctionCallCompiler::addressMethod() { } } m_pusher.sendIntMsg(exprs, constParams, appendBody, pushSendrawmsgFlag, false, 0, appendStateInit); - } else if (_node->memberName() == "isStdZero") { - acceptExpr(&_node->expression()); + } else if (m_memberAccess->memberName() == "isStdZero") { + acceptExpr(&m_memberAccess->expression()); m_pusher.pushZeroAddress(); m_pusher << "SDEQ"; - } else if (_node->memberName() == "isExternZero") { - acceptExpr(&_node->expression()); + } else if (m_memberAccess->memberName() == "isExternZero") { + acceptExpr(&m_memberAccess->expression()); m_pusher.pushSlice("x401_"); m_pusher << "SDEQ"; - } else if (_node->memberName() == "isNone") { - acceptExpr(&_node->expression()); + } else if (m_memberAccess->memberName() == "isNone") { + acceptExpr(&m_memberAccess->expression()); m_pusher.pushSlice("x2_"); m_pusher << "SDEQ"; - } else if (_node->memberName() == "unpack") { - acceptExpr(&_node->expression()); + } else if (m_memberAccess->memberName() == "unpack") { + acceptExpr(&m_memberAccess->expression()); m_pusher << "REWRITESTDADDR"; - } else if (_node->memberName() == "getType") { - acceptExpr(&_node->expression()); + } else if (m_memberAccess->memberName() == "getType") { + acceptExpr(&m_memberAccess->expression()); m_pusher << "PLDU 2"; - } else if (_node->memberName() == "isStdAddrWithoutAnyCast") { - acceptExpr(&_node->expression()); + } else if (m_memberAccess->memberName() == "isStdAddrWithoutAnyCast") { + acceptExpr(&m_memberAccess->expression()); // t = (2, u, x, s); check t[0] == 2 and t[1] is null m_pusher << "PARSEMSGADDR"; m_pusher.pushS(0); @@ -2697,164 +2764,7 @@ void FunctionCallCompiler::goshFunction() { m_pusher << opcode; } -bool FunctionCallCompiler::checkForTvmFunction(const MemberAccess &_node) { - if (_node.memberName() == "pubkey") { // tvm.pubkey - m_pusher.getGlob(TvmConst::C7::TvmPubkey); - } else if (_node.memberName() == "setPubkey") { // tvm.setPubkey - pushArgs(); - m_pusher.setGlob(TvmConst::C7::TvmPubkey); - } else if (_node.memberName() == "accept") { // tvm.accept - m_pusher << "ACCEPT"; - } else if (_node.memberName() == "hash") { // tvm.hash - pushArgs(); - switch (m_arguments.at(0)->annotation().type->category()) { - case Type::Category::TvmCell: - case Type::Category::Array: - case Type::Category::StringLiteral: - m_pusher << "HASHCU"; - break; - case Type::Category::TvmSlice: - m_pusher << "HASHSU"; - break; - default: - solUnimplemented(""); - } - } else if (_node.memberName() == "checkSign") { // tvm.checkSign - size_t cnt = m_arguments.size(); - if (getType(m_arguments[0].get())->category() == Type::Category::TvmSlice) { - pushArgs(); - m_pusher << "CHKSIGNS"; - } else { - pushArgAndConvert(0); - if (cnt == 4) { - pushArgAndConvert(2); - pushArgAndConvert(1); - m_pusher << "NEWC"; - m_pusher << "STU 256"; - m_pusher << "STU 256"; - m_pusher << "ENDC"; - m_pusher << "CTOS"; - } else { - pushArgAndConvert(1); - } - pushArgAndConvert(cnt - 1); - m_pusher << "CHKSIGNU"; - } - } else if (_node.memberName() == "setcode") { // tvm.setcode - pushArgs(); - m_pusher << "SETCODE"; - } else if (_node.memberName() == "bindump") { // tvm.bindump - pushArgs(); - if (getType(m_arguments[0].get())->category() == Type::Category::TvmCell) - m_pusher << "CTOS"; - m_pusher << "BINDUMP"; - m_pusher.drop(); - } else if (_node.memberName() == "hexdump") { // tvm.hexdump - pushArgs(); - if (getType(m_arguments[0].get())->category() == Type::Category::TvmCell) - m_pusher << "CTOS"; - m_pusher << "HEXDUMP"; - m_pusher.drop(); - } else if (_node.memberName() == "setCurrentCode") { // tvm.setCurrentCode - const int stackSize = m_pusher.stackSize(); - pushArgs(); - - m_pusher << "CTOS"; - m_pusher.pushS(0); - m_pusher.pushSlice("x" + TvmConst::Selector::RootCodeCell()); - m_pusher << "SDEQ"; - - m_pusher.startOpaque(); - m_pusher.startContinuation(); - m_pusher << "PLDREFIDX 1"; - m_pusher << "CTOS"; - m_pusher.endContinuation(); - m_pusher._if(); - m_pusher.endOpaque(2, 1); - - m_pusher << "PLDREF"; - m_pusher << "CTOS"; - m_pusher << "BLESS"; - m_pusher.popC3(); - solAssert(stackSize == m_pusher.stackSize(), ""); - } else if (_node.memberName() == "getData") { // tvm.getData - m_pusher.pushRoot(); - } else if (_node.memberName() == "setData") { // tvm.setData - pushArgs(); - m_pusher.popRoot(); - } else if (_node.memberName() == "rawCommit") { // tvm.rawCommit - m_pusher << "COMMIT"; - } else if (_node.memberName() == "commit") { // tvm.commit - m_pusher.pushFragmentInCallRef(0, 0, "c7_to_c4"); - m_pusher << "COMMIT"; - } else if (_node.memberName() == "log") { // tvm.log - compileLog(); - } else if (_node.memberName() == "resetStorage") { //tvm.resetStorage - m_pusher.resetAllStateVars(); - } else if (_node.memberName() == "functionId") { // tvm.functionId - auto callDef = getFunctionDeclarationOrConstructor(m_arguments.at(0).get()); - ChainDataEncoder encoder(&m_pusher); - uint32_t funcID; - if (callDef == nullptr) { - funcID = encoder.calculateConstructorFunctionID(); - } else { - bool isManuallyOverridden{}; - std::tie(funcID, isManuallyOverridden) = encoder.calculateFunctionID(callDef); - if (!isManuallyOverridden) { - funcID &= 0x7FFFFFFFu; - } - } - m_pusher.pushInt(funcID); - } else if (_node.memberName() == "encodeBody") { // tvm.encodeBody - CallableDeclaration const* callDef = getFunctionDeclarationOrConstructor(m_arguments.at(0).get()); - if (callDef == nullptr) { // if no constructor (default constructor) - m_pusher << "NEWC"; - ChainDataEncoder{&m_pusher}.createDefaultConstructorMessage2(); - } else { - auto funcDef = to(callDef); - const bool needCallback = funcDef->isResponsible(); - const int shift = needCallback ? 1 : 0; - std::optional callbackFunctionId; - if (needCallback) { - CallableDeclaration const* callback = getFunctionDeclarationOrConstructor(m_arguments.at(1).get()); - callbackFunctionId = ChainDataEncoder{&m_pusher}.calculateFunctionIDWithReason(callback, ReasonOfOutboundMessage::RemoteCallInternal); - } - const ast_vec ¶meters = callDef->parameters(); - std::vector types = getParams(parameters).first; - DecodePositionAbiV2 position{32, 0, types}; - for (int i = m_arguments.size() - 1; i >= 1 + shift; --i) { - acceptExpr(m_arguments.at(i).get()); - } - m_pusher << "NEWC"; - ChainDataEncoder{&m_pusher}.createMsgBody( - convertArray(parameters), - ChainDataEncoder{&m_pusher}.calculateFunctionIDWithReason(callDef, ReasonOfOutboundMessage::RemoteCallInternal), - callbackFunctionId, - position - ); - } - m_pusher << "ENDC"; - } else if (_node.memberName() == "rawReserve") { - pushArgs(); - int n = m_arguments.size(); - solAssert(isIn(n, 2, 3), ""); - m_pusher << (n == 2 ? "RAWRESERVE" : "RAWRESERVEX"); - } else if (isIn(_node.memberName(), "exit", "exit1")) { - m_pusher.was_c4_to_c7_called(); - m_pusher.fixStack(-1); // fix stack - - m_pusher.startContinuation(); - m_pusher.pushFragment(0, 0, "c7_to_c4"); - m_pusher.endContinuationFromRef(); - m_pusher.ifNot(); - - if (_node.memberName() == "exit") - m_pusher._throw("THROW 0"); - else - m_pusher._throw("THROW 1"); - } else if (_node.memberName() == "code") { - m_pusher << "MYCODE"; - } else if (_node.memberName() == "codeSalt") { +void FunctionCallCompiler::codeSalt() { pushArgs(); string getSaltFromUsualSelector = R"( PLDREF @@ -2909,8 +2819,10 @@ CALLREF { boost::replace_all(code, "PrivateOpcode1", TvmConst::Selector::PrivateOpcode1()); std::vector codeLines = split(code); m_pusher.push(createNode(codeLines, 1, 1, false)); - } else if (_node.memberName() == "setCodeSalt") { - pushArgAndConvert(0); +} + +void FunctionCallCompiler::setCodeSalt() { + pushArgAndConvert(0); m_pusher << "CTOS"; // sliceCode pushArgAndConvert(1); // sliceCode salt string insertSaltInUsualSelector = R"( @@ -2990,6 +2902,177 @@ CALLREF { boost::replace_all(code, "PrivateOpcode1", TvmConst::Selector::PrivateOpcode1()); std::vector codeLines = split(code); m_pusher.push(createNode(codeLines, 2, 1, false)); +} + +void FunctionCallCompiler::functionId() { + auto callDef = getFunctionDeclarationOrConstructor(m_arguments.at(0).get()); + ChainDataEncoder encoder(&m_pusher); + uint32_t funcID; + if (callDef == nullptr) { + funcID = encoder.calculateConstructorFunctionID(); + } else { + bool isManuallyOverridden{}; + std::tie(funcID, isManuallyOverridden) = encoder.calculateFunctionID(callDef); + if (!isManuallyOverridden) { + funcID &= 0x7FFFFFFFu; + } + } + m_pusher.pushInt(funcID); +} + +void FunctionCallCompiler::abiEncodeBody() { + CallableDeclaration const* callDef = getFunctionDeclarationOrConstructor(m_arguments.at(0).get()); + if (callDef == nullptr) { // if no constructor (default constructor) + m_pusher << "NEWC"; + ChainDataEncoder{&m_pusher}.createDefaultConstructorMessage2(); + } else { + auto funcDef = to(callDef); + const bool needCallback = funcDef->isResponsible(); + const int shift = needCallback ? 1 : 0; + std::optional callbackFunctionId; + if (needCallback) { + CallableDeclaration const* callback = getFunctionDeclarationOrConstructor(m_arguments.at(1).get()); + callbackFunctionId = ChainDataEncoder{&m_pusher}.calculateFunctionIDWithReason(callback, ReasonOfOutboundMessage::RemoteCallInternal); + } + const ast_vec ¶meters = callDef->parameters(); + std::vector types = getParams(parameters).first; + DecodePositionAbiV2 position{32, 0, types}; + for (int i = m_arguments.size() - 1; i >= 1 + shift; --i) { + acceptExpr(m_arguments.at(i).get()); + } + m_pusher << "NEWC"; + ChainDataEncoder{&m_pusher}.createMsgBody( + convertArray(parameters), + ChainDataEncoder{&m_pusher}.calculateFunctionIDWithReason(callDef, ReasonOfOutboundMessage::RemoteCallInternal), + callbackFunctionId, + position + ); + } + m_pusher << "ENDC"; +} + +bool FunctionCallCompiler::checkForTvmFunction(const MemberAccess &_node) { + if (_node.memberName() == "pubkey") { // tvm.pubkey + m_pusher.getGlob(TvmConst::C7::TvmPubkey); + } else if (_node.memberName() == "setPubkey") { // tvm.setPubkey + pushArgs(); + m_pusher.setGlob(TvmConst::C7::TvmPubkey); + } else if (_node.memberName() == "accept") { // tvm.accept + m_pusher << "ACCEPT"; + } else if (_node.memberName() == "hash") { // tvm.hash + pushArgs(); + switch (m_arguments.at(0)->annotation().type->category()) { + case Type::Category::TvmCell: + case Type::Category::Array: + case Type::Category::StringLiteral: + m_pusher << "HASHCU"; + break; + case Type::Category::TvmSlice: + m_pusher << "HASHSU"; + break; + default: + solUnimplemented(""); + } + } else if (_node.memberName() == "checkSign") { // tvm.checkSign + size_t cnt = m_arguments.size(); + if (getType(m_arguments[0].get())->category() == Type::Category::TvmSlice) { + pushArgs(); + m_pusher << "CHKSIGNS"; + } else { + pushArgAndConvert(0); + if (cnt == 4) { + pushArgAndConvert(2); + pushArgAndConvert(1); + m_pusher << "NEWC"; + m_pusher << "STU 256"; + m_pusher << "STU 256"; + m_pusher << "ENDC"; + m_pusher << "CTOS"; + } else { + pushArgAndConvert(1); + } + pushArgAndConvert(cnt - 1); + m_pusher << "CHKSIGNU"; + } + } else if (_node.memberName() == "setcode") { // tvm.setcode + pushArgs(); + m_pusher << "SETCODE"; + } else if (_node.memberName() == "bindump") { // tvm.bindump + pushArgs(); + if (getType(m_arguments[0].get())->category() == Type::Category::TvmCell) + m_pusher << "CTOS"; + m_pusher << "BINDUMP"; + m_pusher.drop(); + } else if (_node.memberName() == "hexdump") { // tvm.hexdump + pushArgs(); + if (getType(m_arguments[0].get())->category() == Type::Category::TvmCell) + m_pusher << "CTOS"; + m_pusher << "HEXDUMP"; + m_pusher.drop(); + } else if (_node.memberName() == "setCurrentCode") { // tvm.setCurrentCode + const int stackSize = m_pusher.stackSize(); + pushArgs(); + + m_pusher << "CTOS"; + m_pusher.pushS(0); + m_pusher.pushSlice("x" + TvmConst::Selector::RootCodeCell()); + m_pusher << "SDEQ"; + + m_pusher.startOpaque(); + m_pusher.startContinuation(); + m_pusher << "PLDREFIDX 1"; + m_pusher << "CTOS"; + m_pusher.endContinuation(); + m_pusher._if(); + m_pusher.endOpaque(2, 1); + + m_pusher << "PLDREF"; + m_pusher << "CTOS"; + m_pusher << "BLESS"; + m_pusher.popC3(); + solAssert(stackSize == m_pusher.stackSize(), ""); + } else if (_node.memberName() == "getData") { // tvm.getData + m_pusher.pushRoot(); + } else if (_node.memberName() == "setData") { // tvm.setData + pushArgs(); + m_pusher.popRoot(); + } else if (_node.memberName() == "rawCommit") { // tvm.rawCommit + m_pusher << "COMMIT"; + } else if (_node.memberName() == "commit") { // tvm.commit + m_pusher.pushFragmentInCallRef(0, 0, "c7_to_c4"); + m_pusher << "COMMIT"; + } else if (_node.memberName() == "log") { // tvm.log + compileLog(); + } else if (_node.memberName() == "resetStorage") { //tvm.resetStorage + m_pusher.resetAllStateVars(); + } else if (_node.memberName() == "functionId") { // tvm.functionId + functionId(); + } else if (_node.memberName() == "encodeBody") { // tvm.encodeBody + abiEncodeBody(); + } else if (_node.memberName() == "rawReserve") { + pushArgs(); + int n = m_arguments.size(); + solAssert(isIn(n, 2, 3), ""); + m_pusher << (n == 2 ? "RAWRESERVE" : "RAWRESERVEX"); + } else if (isIn(_node.memberName(), "exit", "exit1")) { + m_pusher.was_c4_to_c7_called(); + m_pusher.fixStack(-1); // fix stack + + m_pusher.startContinuation(); + m_pusher.pushFragment(0, 0, "c7_to_c4"); + m_pusher.endContinuationFromRef(); + m_pusher.ifNot(); + + if (_node.memberName() == "exit") + m_pusher._throw("THROW 0"); + else + m_pusher._throw("THROW 1"); + } else if (_node.memberName() == "code") { + m_pusher << "MYCODE"; + } else if (_node.memberName() == "codeSalt") { + codeSalt(); + } else if (_node.memberName() == "setCodeSalt") { + setCodeSalt(); } else if (_node.memberName() == "replayProtTime") { m_pusher.getGlob(TvmConst::C7::ReplayProtTime); } else if (_node.memberName() == "setReplayProtTime") { @@ -3013,45 +3096,62 @@ CALLREF { void FunctionCallCompiler::abiFunction() { switch (m_funcType->kind()) { - case FunctionType::Kind::ABIEncode: { - std::vector types; - for (ASTPointer const& arg : m_arguments) { - types.emplace_back(arg->annotation().type->mobileType()); - } - DecodePositionAbiV2 position{0, 0, types}; + case FunctionType::Kind::ABIEncode: { + std::vector types; + for (ASTPointer const& arg : m_arguments) { + types.emplace_back(arg->annotation().type->mobileType()); + } + DecodePositionAbiV2 position{0, 0, types}; - for (ASTPointer const& arg : m_arguments | boost::adaptors::reversed) { - acceptExpr(arg.get()); - } - m_pusher << "NEWC"; - ChainDataEncoder encoder{&m_pusher}; - encoder.encodeParameters(types, position); - m_pusher << "ENDC"; - break; + for (ASTPointer const& arg : m_arguments | boost::adaptors::reversed) { + acceptExpr(arg.get()); } - case FunctionType::Kind::ABIDecode: { - std::vector types; - auto te = to(m_arguments.at(1).get()); - if (te) { - for (const ASTPointer& e : te->components()) { - auto const* argTypeType = dynamic_cast(e->annotation().type); - Type const* actualType = argTypeType->actualType(); - types.emplace_back(actualType); - } - } else { - auto const* argTypeType = dynamic_cast(m_arguments.at(1)->annotation().type); + m_pusher << "NEWC"; + ChainDataEncoder encoder{&m_pusher}; + encoder.encodeParameters(types, position); + m_pusher << "ENDC"; + break; + } + case FunctionType::Kind::ABIDecode: { + std::vector types; + auto te = to(m_arguments.at(1).get()); + if (te) { + for (const ASTPointer& e : te->components()) { + auto const* argTypeType = dynamic_cast(e->annotation().type); Type const* actualType = argTypeType->actualType(); types.emplace_back(actualType); } - - acceptExpr(m_arguments.at(0).get()); - m_pusher << "CTOS"; - ChainDataDecoder decoder{&m_pusher}; - decoder.decodeData(0, 0, types); - break; + } else { + auto const* argTypeType = dynamic_cast(m_arguments.at(1)->annotation().type); + Type const* actualType = argTypeType->actualType(); + types.emplace_back(actualType); } - default: - cast_error(m_functionCall, "Not supported"); + + acceptExpr(m_arguments.at(0).get()); + m_pusher << "CTOS"; + ChainDataDecoder decoder{&m_pusher}; + decoder.decodeData(0, 0, types); + break; + } + case FunctionType::Kind::ABICodeSalt: { + codeSalt(); + break; + } + case FunctionType::Kind::ABISetCodeSalt: { + setCodeSalt(); + break; + } + case FunctionType::Kind::ABIFunctionId: { + functionId(); + break; + } + case FunctionType::Kind::ABIDecodeFunctionParams: { + pushArgAndConvert(1); + decodeFunctionParams(); + break; + } + default: + cast_error(m_functionCall, "Not supported"); } } @@ -3142,12 +3242,11 @@ bool FunctionCallCompiler::checkAddressThis() { } void FunctionCallCompiler::createObject() { - Type const* resultType = m_functionCall.annotation().type; - switch (resultType->category()) { - case Type::Category::TvmCell: { - m_pusher.pushDefaultValue(resultType); + switch (m_retType->category()) { + case Type::Category::TvmCell: + case Type::Category::TvmBuilder: + m_pusher.pushDefaultValue(m_retType); break; - } default: solUnimplemented(""); } @@ -3156,14 +3255,13 @@ void FunctionCallCompiler::createObject() { void FunctionCallCompiler::typeConversion() { solAssert(m_arguments.size() == 1, ""); Type const* argType = m_arguments[0]->annotation().type; - Type const* resultType = m_functionCall.annotation().type; if (auto funCall = to(m_arguments[0].get())) { if (*funCall->annotation().kind == FunctionCallKind::TypeConversion && funCall->arguments().size() == 1) { // c(b(a)), e.g. uint8 x; int(uint(x)); auto a = to(funCall->arguments().at(0)->annotation().type); auto b = to(funCall->annotation().type); - auto c = to(resultType); + auto c = to(m_retType); if (a && b && c) { if (!a->isSigned() && !b->isSigned() && c->isSigned() && a->numBits() < b->numBits() && b->numBits() == c->numBits() @@ -3176,9 +3274,51 @@ void FunctionCallCompiler::typeConversion() { } } + auto getDigits = [](Type const* type)-> int { + if (auto fix = to(type)) + return fix->fractionalDigits(); + if (isIn(type->category(), Type::Category::Integer, Type::Category::VarInteger, Type::Category::Enum)) + return 0; + solUnimplemented(""); + }; + + auto adjustDigits = [&]() { + int const delta = getDigits(m_retType) - getDigits(argType->mobileType()); + if (delta > 0) { + m_pusher.pushInt(MathConsts::power10().at(delta)); + m_pusher << "MUL"; + } + if (delta < 0) { + m_pusher.pushInt(MathConsts::power10().at(-delta)); + m_pusher << "DIV"; + } + }; + solAssert(m_arguments.size() == 1, ""); acceptExpr(m_arguments.at(0).get()); - m_pusher.convert(resultType, argType); + switch (m_retType->category()) { + case Type::Category::Enum: + case Type::Category::FixedPoint: + case Type::Category::Integer: + case Type::Category::VarInteger: + if (argType->category() == Type::Category::FixedBytes) { + // do nothing + } else { + adjustDigits(); + if (!argType->isImplicitlyConvertibleTo(*m_retType)) + m_pusher.checkFit(m_retType); + } + break; + case Type::Category::Address: + case Type::Category::Contract: + case Type::Category::FixedBytes: + case Type::Category::Array: + case Type::Category::TvmSlice: + m_pusher.convert(m_retType, argType); + break; + default: + solUnimplemented(m_retType->humanReadableName()); + } } bool FunctionCallCompiler::checkLocalFunctionOrLibCall(const Identifier *identifier) { @@ -3351,6 +3491,7 @@ bool FunctionCallCompiler::checkSolidityUnits() { return true; } case FunctionType::Kind::Format: { + const int stackSize = m_pusher.stackSize(); auto literal = to(m_arguments[0].get()); std::string formatStr = literal->value(); size_t pos = 0; @@ -3360,7 +3501,7 @@ bool FunctionCallCompiler::checkSolidityUnits() { size_t close_pos = formatStr.find('}', pos); if (pos == string::npos || close_pos == string::npos) break; - if ((formatStr[pos + 1] != ':') && (close_pos != pos + 1)) { + if (formatStr[pos + 1] != ':' && close_pos != pos + 1) { pos++; continue; } @@ -3371,30 +3512,30 @@ bool FunctionCallCompiler::checkSolidityUnits() { formatStr = formatStr.substr(close_pos + 1); pos = 0; } - // create new vector(TvmBuilder) - m_pusher.pushDefaultValue(TypeProvider::tvmtuple(TypeProvider::tvmbuilder())); - // create new builder to store data in it + // stack: Stack(TvmBuilder) m_pusher << "NEWC"; + m_pusher << "NULL"; + m_pusher << "TUPLE 2"; auto pushConstStr = [&](const string& constStr) { if (!constStr.empty()) { size_t maxSlice = TvmConst::CellBitLength / 8; for(size_t i = 0; i < constStr.length(); i += maxSlice) { m_pusher.pushString(constStr.substr(i, min(maxSlice, constStr.length() - i)), true); - // stack: BldrList builder Slice - m_pusher.pushFragmentInCallRef(3, 2, "storeStringInBuilders"); + // stack: Stack(TvmBuilder) slice + m_pusher.pushFragmentInCallRef(2, 1, "__appendSliceToStringBuilder"); } - // stack: BldrList builder + // stack: Stack(TvmBuilder) slice } }; for (size_t it = 0; it < substrings.size(); it++) { - // stack: vector(TvmBuilder) builder + // stack: Stack(TvmBuilder) pushConstStr(substrings[it].first); Type::Category cat = m_arguments[it + 1]->annotation().type->category(); Type const *argType = m_arguments[it + 1]->annotation().type; if (cat == Type::Category::Integer || cat == Type::Category::RationalNumber) { - // stack: vector(TvmBuilder) builder + // stack: Stack(TvmBuilder) std::string format = substrings[it].second; bool leadingZeroes = !format.empty() && (format[0] == '0'); bool isHex = !format.empty() && (format.back() == 'x' || format.back() == 'X'); @@ -3407,65 +3548,50 @@ bool FunctionCallCompiler::checkSolidityUnits() { int width = 0; if (format.length() > 0) width = std::stoi(format); - if (width < 0) - solUnimplemented("Width should be a positive integer."); - auto mt = m_arguments[it + 1]->annotation().type->mobileType(); - auto isInt = dynamic_cast(mt); + if (width < 0 || width > 127) + cast_error(m_functionCall, "Width should be in range of 0 to 127."); acceptExpr(m_arguments[it + 1].get()); - if (isInt->isSigned()) - m_pusher << "ABS"; + // stack: stack x m_pusher.pushInt(width); m_pusher << (leadingZeroes ? "TRUE" : "FALSE"); + // stack: stack x width leadingZeroes if (isHex) { if (isLower) m_pusher << "TRUE"; else m_pusher << "FALSE"; - } - if (isInt->isSigned()) { - acceptExpr(m_arguments[it + 1].get()); - m_pusher << "ISNEG"; - } else { - m_pusher << "FALSE"; - } - // stack: vector(TvmBuilder) builder abs(number) width leadingZeroes addMinus - if (isHex) { - m_pusher.pushFragmentInCallRef(7, 2, "convertIntToHexStr"); + m_pusher.pushFragmentInCallRef(5, 1, "__convertIntToHexString"); } else { - m_pusher.pushFragmentInCallRef(6, 2, "convertIntToDecStr"); + m_pusher.pushFragmentInCallRef(4, 1, "__convertIntToString"); } - // stack: vector(TvmBuilder) builder } else { acceptExpr(m_arguments[it + 1].get()); m_pusher.pushInt(9); - m_pusher.pushFragmentInCallRef(4, 2, "convertFixedPointToString"); + m_pusher.pushInt(MathConsts::power10().at(9)); + m_pusher.pushFragmentInCallRef(4, 1, "__convertFixedPointToString"); } } else if (cat == Type::Category::Address) { - // stack: vector(TvmBuilder) builder acceptExpr(m_arguments[it + 1].get()); - // stack: vector(TvmBuilder) builder address - m_pusher.pushFragmentInCallRef(3, 2, "convertAddressToHexString"); - // stack: vector(TvmBuilder) builder + m_pusher.pushFragmentInCallRef(2, 1, "__convertAddressToHexString"); } else if (isStringOrStringLiteralOrBytes(argType)) { - // stack: vector(TvmBuilder) builder acceptExpr(m_arguments[it + 1].get()); - // stack: vector(TvmBuilder) builder string(cell) m_pusher << "CTOS"; - // stack: vector(TvmBuilder) builder string(slice) - m_pusher.pushFragmentInCallRef(3, 2, "storeStringInBuilders"); - // stack: vector(TvmBuilder) builder + m_pusher.pushFragmentInCallRef(2, 1, "__appendStringToStringBuilder"); } else if (cat == Type::Category::FixedPoint) { int power = to(argType)->fractionalDigits(); acceptExpr(m_arguments[it + 1].get()); m_pusher.pushInt(power); - m_pusher.pushFragmentInCallRef(4, 2, "convertFixedPointToString"); + m_pusher.pushInt(MathConsts::power10().at(power)); + m_pusher.pushFragmentInCallRef(4, 1, "__convertFixedPointToString"); } else { cast_error(*m_arguments[it + 1].get(), "Unsupported argument type"); } } pushConstStr(formatStr); - m_pusher.pushFragmentInCallRef(2, 1, "assembleList"); + m_pusher.pushFragmentInCallRef(1, 1, "__makeString"); + + solAssert(stackSize + 1 == m_pusher.stackSize(), ""); return true; } case FunctionType::Kind::Stoi: { @@ -3483,9 +3609,9 @@ bool FunctionCallCompiler::checkLocalFunctionOrLibCallOrFuncVarCall() { auto expr = &m_functionCall.expression(); if (auto identifier = to(expr); identifier && checkLocalFunctionOrLibCall(identifier)) { } else if (expr->annotation().type->category() == Type::Category::Function) { - if (auto ma = to(expr)) { - auto category = getType(&ma->expression())->category(); - if (category == Type::Category::TypeType || isSuper(&ma->expression())) { + if (m_memberAccess) { + auto category = getType(&m_memberAccess->expression())->category(); + if (category == Type::Category::TypeType || isSuper(&m_memberAccess->expression())) { // calling of base/super method or typeTypeMethods return false; } @@ -3536,6 +3662,7 @@ bool FunctionCallCompiler::createNewContract() { bool hasVars = varInit != nullptr; auto ct = to(newExpr->typeName().annotation().type); stateInitExprs[StateInitMembers::Data] = generateDataSection( + false, pushKey, hasVars ? varInit : nullptr, ct @@ -3551,7 +3678,7 @@ bool FunctionCallCompiler::createNewContract() { }; } - buildStateInit(stateInitExprs); + encodeStateInit(stateInitExprs); // stack: stateInit solAssert(ss + 1 == m_pusher.stackSize(), ""); @@ -3765,8 +3892,7 @@ bool FunctionCallCompiler::checkNewExpression() { if (to(&m_functionCall.expression()) == nullptr) { return false; } - Type const *resultType = m_functionCall.annotation().type; - if (resultType->category() == Type::Category::Contract) { + if (m_retType->category() == Type::Category::Contract) { cast_error(m_functionCall, R"(Unsupported contract creating. Use call options: "stateInit", "value", "flag")"); } @@ -3777,7 +3903,7 @@ bool FunctionCallCompiler::checkNewExpression() { void FunctionCallCompiler::creatArrayWithDefaultValue() { std::optional num = ExprUtils::constValue(*m_arguments.at(0)); if (num.has_value() && num.value() == 0) { - auto arrayType = to(m_functionCall.annotation().type); + auto arrayType = to(m_retType); m_pusher.pushDefaultValue(arrayType); return ; } @@ -3799,7 +3925,7 @@ void FunctionCallCompiler::creatArrayWithDefaultValue() { void FunctionCallCompiler::honestArrayCreation(bool onlyDict) { const int stackSize = m_pusher.stackSize(); - auto arrayType = to(m_functionCall.annotation().type); + auto arrayType = to(m_retType); IntegerType const& key = getArrayKeyType(); Type const* arrayBaseType = arrayType->baseType(); @@ -3836,18 +3962,17 @@ void FunctionCallCompiler::honestArrayCreation(bool onlyDict) { } bool FunctionCallCompiler::structMethodCall() { - auto ma = to(&m_functionCall.expression()); - if (ma->memberName() != "unpack") { + if (m_memberAccess->memberName() != "unpack") { return false; } - acceptExpr(&ma->expression()); - auto structType = to(getType(&ma->expression())); + acceptExpr(&m_memberAccess->expression()); + auto structType = to(getType(&m_memberAccess->expression())); int memberQty = structType->structDefinition().members().size(); m_pusher.untuple(memberQty); return true; } -void FunctionCallCompiler::buildStateInit(std::map> exprs) { +void FunctionCallCompiler::encodeStateInit(std::map> exprs) { solAssert(exprs.count(StateInitMembers::Special) == 0, ""); solAssert(exprs.count(StateInitMembers::Library) == 0, ""); solAssert(exprs.count(StateInitMembers::Code) == 1, "Code must present"); @@ -3864,7 +3989,7 @@ void FunctionCallCompiler::buildStateInit(std::map 0) { exprs.at(StateInitMembers::SplitDepth)(); @@ -3874,11 +3999,11 @@ void FunctionCallCompiler::buildStateInit(std::map> exprs); + void encodeStateInit(std::map> exprs); std::function generateDataSection( + bool data_map_supported, const std::function& pushKey, Expression const* vars, ContractType const* ct @@ -138,6 +147,7 @@ class FunctionCallCompiler { StackPusher& m_pusher; TVMExpressionCompiler m_exprCompiler; FunctionCall const& m_functionCall; + MemberAccess const* m_memberAccess{}; std::vector> m_arguments; FunctionType const* m_funcType{}; Type const* m_retType{}; diff --git a/compiler/libsolidity/codegen/TVMFunctionCompiler.cpp b/compiler/libsolidity/codegen/TVMFunctionCompiler.cpp index dc8fd07f..652c9e9c 100644 --- a/compiler/libsolidity/codegen/TVMFunctionCompiler.cpp +++ b/compiler/libsolidity/codegen/TVMFunctionCompiler.cpp @@ -15,6 +15,7 @@ */ #include +#include #include #include @@ -153,38 +154,48 @@ TVMFunctionCompiler::generateC4ToC7(TVMCompilerContext& ctx) { pusher << "LDU 64 ; pubkey timestamp c4"; } pusher << "LDU 1 ; ctor flag"; - pusher.dropUnder(1, 1); // ignore if (pusher.ctx().usage().hasAwaitCall()) { pusher << "LDI 1 ; await flag"; pusher.dropUnder(1, 1); } - if (!pusher.ctx().notConstantStateVariables().empty()) { - pusher.getStack().change(+1); // slice - // slice on stack - std::vector stateVarTypes = pusher.ctx().notConstantStateVariableTypes(); - const int ss = pusher.stackSize(); - ChainDataDecoder decoder{&pusher}; - decoder.decodeData(pusher.ctx().getOffsetC4(), - pusher.ctx().usage().hasAwaitCall() ? 1 : 0, - stateVarTypes); - - const int varQty = stateVarTypes.size(); - if (pusher.ctx().tooMuchStateVariables()) { - for (int i = 0; i < TvmConst::C7::FirstIndexForVariables; ++i) { - pusher.getGlob(i); - } - pusher.blockSwap(varQty, TvmConst::C7::FirstIndexForVariables); - pusher.makeTuple(varQty + TvmConst::C7::FirstIndexForVariables); - pusher.popC7(); - } else { - for (int i = varQty - 1; i >= 0; --i) { - pusher.setGlob(TvmConst::C7::FirstIndexForVariables + i); - } - } - solAssert(ss - 1 == pusher.stackSize(), ""); + + pusher.getStack().change(+1); // slice + // slice on stack + std::vector stateVars = pusher.ctx().c4StateVariables(); + std::vector stateVarTypes; + std::transform(stateVars.begin(), stateVars.end(), std::back_inserter(stateVarTypes), + [](VariableDeclaration const * var){ + return var->type(); + }); + const int ss = pusher.stackSize(); + ChainDataDecoder decoder{&pusher}; + decoder.decodeData(pusher.ctx().getOffsetC4(), + pusher.ctx().usage().hasAwaitCall() ? 1 : 0, + stateVarTypes); + + int const varQty = stateVarTypes.size(); + auto const nostorageStateVars = pusher.ctx().nostorageStateVars(); + int const nostorageVarQty = nostorageStateVars.size(); + if (pusher.ctx().tooMuchStateVariables()) { + for (VariableDeclaration const * var : nostorageStateVars) + pusher.pushDefaultValue(var->type()); + for (int i = 0; i < TvmConst::C7::FirstIndexForVariables; ++i) + pusher.getGlob(i); + pusher.blockSwap(varQty + nostorageVarQty, TvmConst::C7::FirstIndexForVariables); + pusher.makeTuple(varQty + nostorageVarQty + TvmConst::C7::FirstIndexForVariables); + pusher.popC7(); } else { - pusher << "ENDS"; + for (VariableDeclaration const * var : nostorageStateVars) + pusher.pushDefaultValue(var->type()); + for (VariableDeclaration const * var : nostorageStateVars | boost::adaptors::reversed) + pusher.setGlob(var); + for (int i = varQty - 1; i >= 0; --i) + pusher.setGlob(TvmConst::C7::FirstIndexForVariables + i); } + solAssert(ss - 1 == pusher.stackSize(), ""); + + pusher.fixStack(+1); // fix stack + pusher.setGlob(TvmConst::C7::ConstructorFlag); if (pusher.ctx().storeTimestampInC4()) { pusher.setGlob(TvmConst::C7::ReplayProtTime); @@ -198,79 +209,33 @@ TVMFunctionCompiler::generateC4ToC7(TVMCompilerContext& ctx) { return f; } -Pointer -TVMFunctionCompiler::generateC4ToC7WithInitMemory(TVMCompilerContext& ctx) { - std::string const name = "c4_to_c7_with_init_storage"; - ctx.setCurrentFunction(nullptr, name); +Pointer TVMFunctionCompiler::generateDefaultC4(TVMCompilerContext& ctx) { StackPusher pusher{&ctx}; - TVMFunctionCompiler funCompiler{pusher, pusher.ctx().getContract()}; - - pusher.pushRoot(); - pusher << "CTOS"; - pusher << "SBITS"; - pusher << "GTINT 1"; - - pusher.startContinuation(); - pusher.pushFragment(0, 0, "c4_to_c7"); - pusher.endContinuationFromRef(); - - pusher.startContinuation(); - pusher.pushInt(0); - pusher.pushRoot(); - pusher << "CTOS"; - pusher << "PLDDICT ; D"; - - int varQty = 0; - bool tooMuchStateVars = pusher.ctx().tooMuchStateVariables(); - if (tooMuchStateVars) { - for (int i = 0; i < TvmConst::C7::FirstIndexForVariables; ++i) { - pusher.getGlob(i); - ++varQty; - } - } - int shift = 0; - for (VariableDeclaration const* v : pusher.ctx().notConstantStateVariables()) { - if (v->isStatic()) { - pusher.pushInt(TvmConst::C4::PersistenceMembersStartIndex + shift++); // dict vars... index dict - pusher.pushS(1 + varQty); // dict vars... index dict - pusher.getDict(getKeyTypeOfC4(), *v->type(), GetDictOperation::GetFromMapping); - } else { - pusher.pushDefaultValue(v->type()); - } - ++varQty; - } - if (tooMuchStateVars) { - pusher.makeTuple(varQty); - pusher.popC7(); - } else { - auto x = pusher.ctx().notConstantStateVariables(); // move - for (VariableDeclaration const* v : x | boost::adaptors::reversed) { - pusher.setGlob(v); - } - } - - pusher.pushInt(64); - pusher.startOpaque(); - pusher.pushAsym("DICTUGET"); - pusher._throw("THROWIFNOT " + toString(TvmConst::RuntimeException::NoPubkeyInC4)); - pusher.endOpaque(3, 1); - - pusher << "PLDU 256"; - pusher.setGlob(TvmConst::C7::TvmPubkey); - pusher << "PUSHINT 0 ; timestamp"; - pusher.setGlob(TvmConst::C7::ReplayProtTime); - - for (VariableDeclaration const *variable: pusher.ctx().notConstantStateVariables()) { - if (auto value = variable->value().get()) { - funCompiler.acceptExpr(value); - pusher.setGlob(variable); - } + std::vector stateVars = ctx.c4StateVariables(); + for (VariableDeclaration const* var : stateVars | boost::adaptors::reversed) + pusher.pushDefaultValue(var->type()); + if (ctx.storeTimestampInC4()) + pusher.pushInt(0); + pusher.pushInt(0); // pubkey + pusher << "NEWC"; + pusher << "STU 256"; + if (ctx.storeTimestampInC4()) + pusher << "STU 64"; + pusher << "STZERO"; // constructor flag + if (ctx.usage().hasAwaitCall()) { + pusher << "STZERO"; + } + const std::vector& memberTypes = ctx.c4StateVariableTypes(); + if (!memberTypes.empty()) { + ChainDataEncoder encoder{&pusher}; + DecodePositionAbiV2 position{ctx.getOffsetC4(), ctx.usage().hasAwaitCall() ? 1 : 0, memberTypes}; + encoder.encodeParameters(memberTypes, position); } - pusher.endContinuation(); - pusher.ifElse(); + pusher << "ENDC"; - ctx.resetCurrentFunction(); - return createNode(0, 0, name, nullopt, Function::FunctionType::Fragment, pusher.getBlock()); + Pointer block = pusher.getBlock(); + auto f = createNode(0, 0, "default_data_cell", nullopt, Function::FunctionType::Fragment, block); + return f; } Pointer @@ -412,13 +377,13 @@ TVMFunctionCompiler::generatePublicFunction(TVMCompilerContext& ctx, FunctionDef funCompiler.pushLocation(*function); const bool isResponsible = function->isResponsible(); if (isResponsible) { - const int saveStakeSize = pusher.stackSize(); + const int saveStackSize = pusher.stackSize(); pusher << "LDU 32"; // callbackId slice pusher.getGlob(TvmConst::C7::ReturnParams); // callbackId slice c7[4] pusher.blockSwap(1, 2); // slice c7[4] callbackId pusher.setIndexQ(TvmConst::C7::ReturnParam::CallbackFunctionId); // slice c7[4] pusher.setGlob(TvmConst::C7::ReturnParams); // slice - solAssert(saveStakeSize == pusher.stackSize(), ""); + solAssert(saveStackSize == pusher.stackSize(), ""); } funCompiler.decodeFunctionParamsAndInitVars(isResponsible); funCompiler.pushLocation(*function, true); @@ -502,8 +467,10 @@ TVMFunctionCompiler::generatePublicFunctionSelector(TVMCompilerContext& ctx, Con ctx.setCurrentFunction(nullptr, name); StackPusher pusher{&ctx}; const std::vector>& functions = pusher.ctx().getPublicFunctions(); + TVMFunctionCompiler compiler{pusher, contract}; - compiler.buildPublicFunctionSelector(functions, 0, functions.size()); + PublicFunctionSelector pfs{int(functions.size())}; + compiler.buildPublicFunctionSelector(functions, 0, functions.size(), pfs); ctx.resetCurrentFunction(); return createNode(1, 1, name, nullopt, Function::FunctionType::Fragment, pusher.getBlock()); } @@ -1608,8 +1575,13 @@ void TVMFunctionCompiler::setGlobSenderAddressIfNeed() { void TVMFunctionCompiler::setCtorFlag() { m_pusher.pushRoot(); m_pusher << "CTOS"; - m_pusher << "SBITS"; - m_pusher << "NEQINT 1"; + bool hasTime = m_pusher.ctx().storeTimestampInC4(); + if (hasTime) + m_pusher.pushInt(256 + 64); + else + m_pusher.pushInt(256); + m_pusher << "SDSKIPFIRST"; + m_pusher << "PLDI 1"; m_pusher.setGlob(TvmConst::C7::ConstructorFlag); } @@ -1642,11 +1614,10 @@ Pointer TVMFunctionCompiler::generateMainExternal( TVMFunctionCompiler f{pusher, contract}; f.setCopyleftAndTryCatch(); - f.setCtorFlag(); // TODO unit with setCopyleftAndTryCatch f.setGlobSenderAddressIfNeed(); pusher.pushS(1); - pusher.pushFragmentInCallRef(0, 0, "c4_to_c7_with_init_storage"); + pusher.pushFragmentInCallRef(0, 0, "c4_to_c7"); f.checkSignatureAndReadPublicKey(); if (pusher.ctx().afterSignatureCheck()) { @@ -1654,8 +1625,10 @@ Pointer TVMFunctionCompiler::generateMainExternal( pusher.pushS(3); pusher.pushInlineFunction("afterSignatureCheck", 2, 1); } else { - if (pusher.ctx().pragmaHelper().hasTime()) f.defaultReplayProtection(); - if (pusher.ctx().pragmaHelper().hasExpire()) f.expire(); + if (pusher.ctx().pragmaHelper().hasTime()) + f.defaultReplayProtection(); + if (pusher.ctx().pragmaHelper().hasExpire()) + f.expire(); } // msg_body @@ -1756,7 +1729,7 @@ void TVMFunctionCompiler::defaultReplayProtection() { // msgSlice m_pusher << "LDU 64 ; timestamp msgSlice"; m_pusher.exchange(1); - m_pusher.pushFragment(1, 0, "replay_protection"); + m_pusher.pushFragment(1, 0, "__replayProtection"); } void TVMFunctionCompiler::expire() { @@ -1921,10 +1894,10 @@ void TVMFunctionCompiler::updC4IfItNeeds() { if (m_function->stateMutability() == StateMutability::NonPayable) { m_pusher.pushFragmentInCallRef(0, 0, "c7_to_c4"); } else { - // if it's external message than save values for replay protection + // if it's external message, then we save values for replay protection if (m_pusher.ctx().afterSignatureCheck() == nullptr && m_pusher.ctx().pragmaHelper().hasTime() && - m_pusher.ctx().notConstantStateVariables().size() >= 2 // just optimization + m_pusher.ctx().c4StateVariables().size() >= 2 // just optimization: if varQty == 1, then it's better to call c7_to_c4 ) { m_pusher.pushS(0); m_pusher.startContinuation(); @@ -2008,15 +1981,9 @@ void TVMFunctionCompiler::pushReceiveOrFallback() { void TVMFunctionCompiler::buildPublicFunctionSelector( const std::vector>& functions, int left, - int right + int right, + PublicFunctionSelector const& pfs ) { - int qty = right - left; - int blockSize = 1; - while (4 * blockSize < qty) { - blockSize *= 4; - } - solAssert(4 * blockSize >= qty, ""); - auto pushOne = [&](uint32_t functionId, const std::string& name) { m_pusher.pushS(0); m_pusher.pushInt(functionId); @@ -2028,28 +1995,25 @@ void TVMFunctionCompiler::buildPublicFunctionSelector( m_pusher.ifJmp(); }; - // stack: functionID - if (right - left <= 4) { - for (int i = left; i < right; ++i) { - const auto& [functionId, name] = functions.at(i); + int const n = right - left; + std::vector const& sizes = pfs.groupSizes(n); + int pos = left; + for (int const groupSize : sizes) { + if (groupSize == 1) { + const auto& [functionId, name] = functions.at(pos); pushOne(functionId, name); + } else { + const auto& [functionId, name] = functions.at(pos + groupSize - 1); + m_pusher.pushS(0); + m_pusher.pushInt(functionId); + m_pusher << "LEQ"; + m_pusher.startContinuation(); + buildPublicFunctionSelector(functions, pos, pos + groupSize, pfs); + m_pusher.endContinuationFromRef(); + m_pusher.ifJmp(); + } - } else { - for (int i = left; i < right; i += blockSize) { - int j = std::min(i + blockSize, right); - const auto& [functionId, name] = functions.at(j - 1); - if (j - i == 1) { - pushOne(functionId, name); - } else { - m_pusher.pushS(0); - m_pusher.pushInt(functionId); - m_pusher << "LEQ"; - m_pusher.startContinuation(); - buildPublicFunctionSelector(functions, i, j); - m_pusher.endContinuationFromRef(); - m_pusher.ifJmp(); - } - } + pos += groupSize; } } @@ -2058,3 +2022,196 @@ void TVMFunctionCompiler::pushLocation(const ASTNode& node, bool reset) { const int line = reset ? 0 : sr.position.line + 1; m_pusher.pushLoc(sr.sourceName, line); } + +TVMConstructorCompiler::TVMConstructorCompiler(StackPusher &pusher) : + TVMFunctionCompiler{pusher, pusher.ctx().getContract()}, + m_pusher{pusher} +{ + +} + +void TVMConstructorCompiler::dfs(ContractDefinition const *c) { + if (used[c]) { + return; + } + used[c] = true; + dfsOrder.push_back(c); + path[c] = dfsOrder; + for (const ASTPointer& inherSpec : c->baseContracts()) { + auto base = to(inherSpec->name().annotation().referencedDeclaration); + ast_vec const* agrs = inherSpec->arguments(); + if (agrs != nullptr && !agrs->empty()) { + m_args[base] = inherSpec->arguments(); + dfs(base); + } + } + if (c->constructor() != nullptr) { + for (const ASTPointer &modInvoc : c->constructor()->modifiers()) { + auto base = to(modInvoc->name().annotation().referencedDeclaration); + if (base != nullptr) { + if (modInvoc->arguments() != nullptr) { + m_args[base] = modInvoc->arguments(); + dfs(base); + } + } + } + } + dfsOrder.pop_back(); +} + +Pointer TVMConstructorCompiler::generateConstructors() { + FunctionDefinition const* constructor = m_pusher.ctx().getContract()->constructor(); + m_pusher.ctx().setCurrentFunction(constructor, "constructor"); + + { + ChainDataEncoder encode{&m_pusher}; + uint32_t functionId = + constructor != nullptr ? + encode.calculateFunctionIDWithReason(constructor, ReasonOfOutboundMessage::RemoteCallInternal) : + encode.calculateConstructorFunctionID(); + m_pusher.ctx().addPublicFunction(functionId, "constructor"); + } + + m_pusher.fixStack(+1); // push encoded params of constructor + m_pusher.fixStack(+1); // functionID + m_pusher.drop(); + + beginConstructor(); + + std::vector linearizedBaseContracts = + m_pusher.ctx().getContract()->annotation().linearizedBaseContracts; // from derived to base + for (ContractDefinition const* c : linearizedBaseContracts) + dfs(c); + + int take{}; + if (constructor == nullptr) { + m_pusher << "ENDS"; + } else { + take = constructor->parameters().size(); + vector types = getParams(constructor->parameters()).first; + ChainDataDecoder{&m_pusher}.decodeFunctionParameters(types, false); + m_pusher.getStack().change(-static_cast(constructor->parameters().size())); + for (const ASTPointer& variable: constructor->parameters()) + m_pusher.getStack().add(variable.get(), true); + } + solAssert(m_pusher.stackSize() == take, ""); + std::set areParamsOnStack; + areParamsOnStack.insert(linearizedBaseContracts.at(0)); + for (ContractDefinition const* c : linearizedBaseContracts | boost::adaptors::reversed) + if (c->constructor() == nullptr || c->constructor()->parameters().empty()) + areParamsOnStack.insert(c); + + bool haveConstructor = false; + for (ContractDefinition const* c : linearizedBaseContracts | boost::adaptors::reversed) { + if (c->constructor() == nullptr) + continue; + haveConstructor = true; + for (ContractDefinition const* parent : path[c]) { + if (areParamsOnStack.count(parent) == 0) { + areParamsOnStack.insert(parent); + for (size_t i = 0; i < parent->constructor()->parameters().size(); ++i) { + TVMExpressionCompiler(m_pusher).acceptExpr((*m_args[parent])[i].get(), true); + m_pusher.getStack().add(parent->constructor()->parameters()[i].get(), false); + } + } + } + int take2 = c->constructor()->parameters().size(); + StackPusher pusher = m_pusher; + pusher.clear(); + pusher.takeLast(take2); + TVMFunctionCompiler::generateFunctionWithModifiers(pusher, c->constructor(), false); + m_pusher.fixStack(-take2); // fix stack + m_pusher.add(pusher); + } + + if (!haveConstructor) + m_pusher << "ACCEPT"; + +// solAssert(m_pusher.stackSize() == 0, ""); + m_pusher.pushFragmentInCallRef(0, 0, "c7_to_c4"); + m_pusher._throw("THROW 0"); + + m_pusher.ctx().resetCurrentFunction(); + Pointer block = m_pusher.getBlock(); + // take slice (contains params) and functionID + Pointer f = createNode(2, 0, "constructor", nullopt, Function::FunctionType::Fragment, block); + return f; +} + +void TVMConstructorCompiler::beginConstructor() { + // copy c4 to c7 + m_pusher.was_c4_to_c7_called(); + m_pusher.fixStack(-1); // fix stack + + m_pusher.startContinuation(); + m_pusher.pushFragment(0, 0, "c4_to_c7"); + m_pusher.endContinuationFromRef(); + m_pusher._if(); + + // set state var, e.g. int m_x = 123; + for (VariableDeclaration const *variable: m_pusher.ctx().c4StateVariables()) { + if (Expression const* value = variable->value().get()) { + acceptExpr(value); + m_pusher.setGlob(variable); + } + } + + + // generate constructor protection + m_pusher.getGlob(TvmConst::C7::ConstructorFlag); + m_pusher._throw("THROWIF " + toString(TvmConst::RuntimeException::ConstructorIsCalledTwice)); +} + +PublicFunctionSelector::PublicFunctionSelector(int _n) { + maxPath = vector(_n + 1, INF); + sumPaths = vector(_n + 1, INF); + prev = vector>(_n + 1); + maxPath[0] = INF; + for (int n = 1; n <= _n; ++n) { + maxPath[n] = INF; + dfs(0, n); + } + //for (int n = 1; n <= _n; ++n) cout + // << std::setw(2) << n << ": " + // << std::setw(4) << int(double(sumPaths[n]) / n) << " " + // << std::setw(4) << maxPath[n] + // << endl; +} + +void PublicFunctionSelector::dfs(int pos, int n) { + if (curGroupSize.size() > 4) + return; + int curSum = std::accumulate(curGroupSize.begin(), curGroupSize.end(), 0); + if (curSum > n) + return; + + if (curSum == n) { + int curMaxPath = 0; + int curSumPath = 0; + for (int i = 0; i < int(curGroupSize.size()); ++i) { + int giSize = curGroupSize.at(i); + if (giSize == 1) { + curMaxPath = max(curMaxPath, FAIL_JMP * i + OK_JMP); + curSumPath += FAIL_JMP * i + OK_JMP; + } else { + curMaxPath = max(curMaxPath, FAIL_JMP * i + OK_JMP + maxPath.at(giSize)); + curSumPath += (FAIL_JMP * i + OK_JMP) * giSize + sumPaths.at(giSize); + } + } + if (maxPath[n] > curMaxPath || (maxPath[n] == curMaxPath && sumPaths[n] > curSumPath)) { + maxPath[n] = curMaxPath; + sumPaths[n] = curSumPath; + prev[n] = curGroupSize; + } + } else if (pos < int(curGroupSize.size())) { + ++curGroupSize[pos]; + dfs(pos, n); + dfs(pos + 1, n); + --curGroupSize[pos]; + } else { + curGroupSize.push_back(1); + dfs(pos, n); + dfs(pos + 1, n); + curGroupSize.pop_back(); + } +} diff --git a/compiler/libsolidity/codegen/TVMFunctionCompiler.hpp b/compiler/libsolidity/codegen/TVMFunctionCompiler.hpp index 353cfddc..2615f4c5 100644 --- a/compiler/libsolidity/codegen/TVMFunctionCompiler.hpp +++ b/compiler/libsolidity/codegen/TVMFunctionCompiler.hpp @@ -20,13 +20,13 @@ namespace solidity::frontend { - +class PublicFunctionSelector; class TVMFunctionCompiler: public ASTConstVisitor, private boost::noncopyable { -private: +protected: TVMFunctionCompiler(StackPusher& pusher, ContractDefinition const *contract); - +private: TVMFunctionCompiler( StackPusher& pusher, int modifier, @@ -39,7 +39,7 @@ class TVMFunctionCompiler: public ASTConstVisitor, private boost::noncopyable public: static Pointer updateOnlyTime(TVMCompilerContext& ctx); static Pointer generateC4ToC7(TVMCompilerContext& ctx); - static Pointer generateC4ToC7WithInitMemory(TVMCompilerContext& ctx); + static Pointer generateDefaultC4(TVMCompilerContext& ctx); static Pointer generateBuildTuple(TVMCompilerContext& ctx, std::string const& name, const std::vector& types); static Pointer generateNewArrays(TVMCompilerContext& ctx, std::string const& name, FunctionCall const* arr); static Pointer generateConstArrays(TVMCompilerContext& ctx, std::string const& name, TupleExpression const* arr); @@ -127,7 +127,8 @@ class TVMFunctionCompiler: public ASTConstVisitor, private boost::noncopyable void updC4IfItNeeds(); void pushReceiveOrFallback(); - void buildPublicFunctionSelector(const std::vector>& functions, int left, int right); + void buildPublicFunctionSelector(const std::vector>& functions, int left, int right, + PublicFunctionSelector const& pfs); void pushLocation(const ASTNode& node, bool reset = false); private: @@ -142,4 +143,35 @@ class TVMFunctionCompiler: public ASTConstVisitor, private boost::noncopyable const bool m_pushArgs{}; }; +class TVMConstructorCompiler: public TVMFunctionCompiler { + StackPusher& m_pusher; + std::map> path; + std::vector dfsOrder; + std::map used; + std::map> const*> m_args; + +public: + explicit TVMConstructorCompiler(StackPusher& pusher); + void dfs(ContractDefinition const* c); + Pointer generateConstructors(); +private: + void beginConstructor(); +}; + +class PublicFunctionSelector { +public: + explicit PublicFunctionSelector(int n); + std::vector const& groupSizes(int n) const { return prev.at(n); } +private: + void dfs(int pos, int n); +private: + std::vector curGroupSize; + const int INF = 1e9; + const int OK_JMP = 18 + 23 + 18 + 126; // DUP / PUSHINT ? / LEQ / IFJMPREF + const int FAIL_JMP = 18 + 23 + 18 + 26; // DUP / PUSHINT ? / LEQ / IFJMPREF + std::vector maxPath; + std::vector sumPaths; + std::vector> prev; +}; + } // end solidity::frontend diff --git a/compiler/libsolidity/codegen/TVMPusher.cpp b/compiler/libsolidity/codegen/TVMPusher.cpp index f3704078..4e5c9548 100644 --- a/compiler/libsolidity/codegen/TVMPusher.cpp +++ b/compiler/libsolidity/codegen/TVMPusher.cpp @@ -63,7 +63,7 @@ void StackPusher::pushLog() { // TODO move to function compiler Pointer StackPusher::generateC7ToC4(bool forAwait) { - const std::vector& memberTypes = m_ctx->notConstantStateVariableTypes(); + const std::vector& memberTypes = m_ctx->c4StateVariableTypes(); const int stateVarQty = memberTypes.size(); if (ctx().tooMuchStateVariables()) { const int saveStack = stackSize(); @@ -75,13 +75,11 @@ Pointer StackPusher::generateC7ToC4(bool forAwait) { drop(TvmConst::C7::FirstIndexForVariables); solAssert(saveStack + stateVarQty == stackSize(), ""); } else { - for (int i = stateVarQty - 1; i >= 0; --i) { + for (int i = stateVarQty - 1; i >= 0; --i) getGlob(TvmConst::C7::FirstIndexForVariables + i); - } } - if (ctx().storeTimestampInC4()) { + if (ctx().storeTimestampInC4()) getGlob(TvmConst::C7::ReplayProtTime); - } getGlob(TvmConst::C7::TvmPubkey); *this << "NEWC"; *this << "STU 256"; @@ -704,7 +702,7 @@ StackPusher& StackPusher::operator<<(std::string const& opcode) { } void StackPusher::push(std::string const& cmd) { - Pointer opcode = gen(cmd); + Pointer opcode = gen(cmd); change(opcode->take(), opcode->ret()); m_instructions.back().push_back(opcode); } @@ -976,14 +974,15 @@ void StackPusher::makeTuple(int qty) { } else { solAssert(qty <= 255, ""); pushInt(qty); - auto opcode = createNode("TUPLEVAR", qty + 1, 1); + auto opcode = createNode("TUPLEVAR", qty + 1, 1); m_instructions.back().push_back(opcode); change(qty + 1, 1); } } void StackPusher::resetAllStateVars() { - std::vector const stateVariables = ctx().notConstantStateVariables(); + std::vector const stateVariables = ctx().c4StateVariables(); + std::vector const nostorageStateVariables = ctx().nostorageStateVars(); if (m_ctx->tooMuchStateVariables()) { pushC7(); *this << "FALSE"; @@ -991,12 +990,18 @@ void StackPusher::resetAllStateVars() { unpackFirst(TvmConst::C7::FirstIndexForVariables); for (VariableDeclaration const *variable: stateVariables) pushDefaultValue(variable->type()); - const int stateVarQty = stateVariables.size(); + for (VariableDeclaration const *variable: nostorageStateVariables) + pushDefaultValue(variable->type()); + const int stateVarQty = stateVariables.size() + nostorageStateVariables.size(); makeTuple(TvmConst::C7::FirstIndexForVariables + stateVarQty); popC7(); } else { for (VariableDeclaration const *variable: stateVariables) pushDefaultValue(variable->type()); + for (VariableDeclaration const *variable: nostorageStateVariables) + pushDefaultValue(variable->type()); + for (VariableDeclaration const *variable: nostorageStateVariables | boost::adaptors::reversed) + setGlob(variable); for (VariableDeclaration const *variable: stateVariables | boost::adaptors::reversed) setGlob(variable); } @@ -1051,7 +1056,7 @@ void StackPusher::popC7() { } void StackPusher::execute(int take, int ret) { - auto opcode = createNode("EXECUTE", take, ret); + auto opcode = createNode("EXECUTE", take, ret); change(take, ret); m_instructions.back().push_back(opcode); } @@ -1074,11 +1079,6 @@ void StackPusher::pushS(int i) { change(+1); } -void StackPusher::dup2() { - m_instructions.back().push_back(makePUSH2(1, 0)); - change(+2); -} - void StackPusher::pushS2(int i, int j) { solAssert(i >= 0 && j >= 0, ""); m_instructions.back().push_back(makePUSH2(i, j)); @@ -1106,7 +1106,7 @@ bool StackPusher::fastLoad(const Type* type) { *this << "LDDICT"; } else { startOpaque(); - const int saveStakeSize = stackSize(); + const int saveStackSize = stackSize(); auto opt = to(type); auto f = [&](bool reverseOrder) { @@ -1144,7 +1144,7 @@ bool StackPusher::fastLoad(const Type* type) { endContinuation(); fixStack(-1); // fix stack if (!hasLock()) { - solAssert(saveStakeSize == stackSize(), ""); + solAssert(saveStackSize == stackSize(), ""); } startContinuation(); @@ -1153,13 +1153,13 @@ bool StackPusher::fastLoad(const Type* type) { endContinuation(); fixStack(-1); // fix stack if (!hasLock()) { - solAssert(saveStakeSize == stackSize(), ""); + solAssert(saveStackSize == stackSize(), ""); } ifElse(); fixStack(+1); // fix stack if (!hasLock()) { - solAssert(saveStakeSize + 1 == stackSize(), ""); + solAssert(saveStackSize + 1 == stackSize(), ""); } endOpaque(1, 2); } @@ -1595,9 +1595,10 @@ void StackPusher::checkFit(Type const *type) { switch (type->category()) { case Type::Category::Integer: { auto it = to(type); - if (it->isSigned()) - *this << "FITS " + toString(it->numBits()); - else + if (it->isSigned()) { + if (it->numBits() != 257) + *this << "FITS " + toString(it->numBits()); + } else *this << "UFITS " + toString(it->numBits()); break; } @@ -1609,14 +1610,27 @@ void StackPusher::checkFit(Type const *type) { *this << "UFITS " + toString(fp->numBits()); break; } - case Type::Category::VarInteger: { auto varInt = to(type); checkFit(&varInt->asIntegerType()); break; } + case Type::Category::Enum: { + auto enumType = to(type); + int size = enumType->numberOfMembers(); + // TODO special case if size == 2**p + pushS(0); + pushInt(size); // x x size + *this << "LESS"; // x x-1 + *this << "AND"; // x (x-1) + this->_throw("THROWIFNOT 4"); + break; + } default: - solUnimplemented(""); + solUnimplemented(type->humanReadableName()); break; } } @@ -1658,7 +1672,7 @@ void StackPusher::pushCallOrCallRef( void StackPusher::pushFragment(int take, int ret, const std::string& functionName) { solAssert(!ctx().callGraph().tryToAddEdge(ctx().currentFunctionName(), functionName), ""); change(take, ret); - auto opcode = createNode(".inline " + functionName, take, ret); + auto opcode = createNode(".inline " + functionName, take, ret); m_instructions.back().push_back(opcode); } @@ -2133,8 +2147,14 @@ void TVMCompilerContext::initMembers(ContractDefinition const *contract) { m_contract = contract; ignoreIntOverflow = m_pragmaHelper.hasIgnoreIntOverflow(); - for (VariableDeclaration const *variable: notConstantStateVariables()) { - m_stateVarIndex[variable] = TvmConst::C7::FirstIndexForVariables + m_stateVarIndex.size(); + auto const c4StateVars = c4StateVariables(); + for (VariableDeclaration const *variable : c4StateVars) { + int index = TvmConst::C7::FirstIndexForVariables + m_stateVarIndex.size(); + m_stateVarIndex[variable] = index; + } + for (VariableDeclaration const *variable : nostorageStateVars()) { + int index = TvmConst::C7::FirstIndexForVariables + m_stateVarIndex.size(); + m_stateVarIndex[variable] = index; } } @@ -2151,17 +2171,21 @@ int TVMCompilerContext::getStateVarIndex(VariableDeclaration const *variable) co return m_stateVarIndex.at(variable); } -std::vector TVMCompilerContext::notConstantStateVariables() const { - return ::notConstantStateVariables(getContract()); +std::vector TVMCompilerContext::c4StateVariables() const { + return ::stateVariables(getContract(), false); +} + +std::vector TVMCompilerContext::nostorageStateVars() const { + return ::stateVariables(getContract(), true); } bool TVMCompilerContext::tooMuchStateVariables() const { - return notConstantStateVariables().size() >= TvmConst::C7::FirstIndexForVariables + 6; + return c4StateVariables().size() + nostorageStateVars().size() >= TvmConst::C7::FirstIndexForVariables + 6; } -std::vector TVMCompilerContext::notConstantStateVariableTypes() const { +std::vector TVMCompilerContext::c4StateVariableTypes() const { std::vector types; - for (VariableDeclaration const * var : notConstantStateVariables()) { + for (VariableDeclaration const * var : c4StateVariables()) { types.emplace_back(var->type()); } return types; @@ -2249,7 +2273,7 @@ int TVMCompilerContext::getOffsetC4() const { std::vector> TVMCompilerContext::getStaticVariables() const { int shift = 0; std::vector> res; - for (VariableDeclaration const* v : notConstantStateVariables()) { + for (VariableDeclaration const* v : c4StateVariables()) { if (v->isStatic()) { res.emplace_back(v, TvmConst::C4::PersistenceMembersStartIndex + shift++); } @@ -2299,7 +2323,7 @@ void StackPusher::pushDefaultValue(Type const* _type) { switch (cat) { case Type::Category::Address: case Type::Category::Contract: - pushZeroAddress(); + pushSlice("x2_"); // addr_none$00 = MsgAddressExt; break; case Type::Category::Bool: case Type::Category::FixedBytes: diff --git a/compiler/libsolidity/codegen/TVMPusher.hpp b/compiler/libsolidity/codegen/TVMPusher.hpp index 1880fb08..a3237ab5 100644 --- a/compiler/libsolidity/codegen/TVMPusher.hpp +++ b/compiler/libsolidity/codegen/TVMPusher.hpp @@ -83,9 +83,10 @@ class TVMCompilerContext { TVMCompilerContext(ContractDefinition const* contract, PragmaDirectiveHelper const& pragmaHelper); void initMembers(ContractDefinition const* contract); int getStateVarIndex(VariableDeclaration const *variable) const; - std::vector notConstantStateVariables() const; + std::vector c4StateVariables() const; + std::vector nostorageStateVars() const; bool tooMuchStateVariables() const; - std::vector notConstantStateVariableTypes() const; + std::vector c4StateVariableTypes() const; PragmaDirectiveHelper const& pragmaHelper() const; bool isStdlib() const; std::string getFunctionInternalName(FunctionDefinition const* _function, bool calledByPoint = true) const; @@ -268,8 +269,7 @@ class StackPusher { void setGlob(int index); void setGlob(VariableDeclaration const * vd); void pushS(int i); - void dup2(); - void pushS2(int i, int j); + void pushS2(int i, int j); // TODO delete void popS(int i); void pushInt(const bigint& i); void stzeroes(int qty); diff --git a/compiler/libsolidity/codegen/TVMSimulator.cpp b/compiler/libsolidity/codegen/TVMSimulator.cpp index f1899ac7..301e6252 100644 --- a/compiler/libsolidity/codegen/TVMSimulator.cpp +++ b/compiler/libsolidity/codegen/TVMSimulator.cpp @@ -26,14 +26,21 @@ void Simulator::run(const std::vector>::const_iterator _beg, auto nextIter = m_iter + 1; if (nextIter != _end && isPopAndDrop(*m_iter, *nextIter)) { solAssert(m_isDropped, ""); - solAssert(!m_unableToConvertOpcode, ""); + solAssert(m_ableToConvertOpcode, ""); m_iter += 2; break; } auto node = m_iter->get(); node->accept(*this); - if (m_unableToConvertOpcode || m_isDropped) { + if (m_wasSet && m_stopSimulationIfSet) { + break; + } + if (m_wasMoved && m_stopSimulationIfMoved) { + ++m_iter; + break; + } + if (!m_ableToConvertOpcode || m_isDropped) { ++m_iter; break; } @@ -48,7 +55,6 @@ void Simulator::run(const std::vector>::const_iterator _beg, bool Simulator::visit(AsymGen &/*_node*/) { solUnimplemented(""); - return false; } bool Simulator::visit(DeclRetFlag &_node) { @@ -97,7 +103,7 @@ bool Simulator::visit(ReturnOrBreakOrCont &_node) { } m_wasReturnBreakContinue = true; Simulator sim{_node.body()->instructions().begin(), _node.body()->instructions().end(), m_stackSize, m_segment}; - if (sim.m_unableToConvertOpcode || !sim.m_isDropped) { + if (!sim.m_ableToConvertOpcode || !sim.m_isDropped) { // check if value has been dropped because in otherwise, this value is from scope that hasn't been dropped unableToConvertOpcode(); } else { @@ -119,7 +125,7 @@ bool Simulator::visit(TvmException &_node) { return false; } -bool Simulator::visit(GenOpcode &_node) { +bool Simulator::visit(StackOpcode &_node) { if (_node.take() > rest()) { unableToConvertOpcode(); } else { @@ -173,176 +179,202 @@ bool Simulator::visit(Stack &_node) { int newj = j < maxDownIndex ? j : j - m_segment; int newk = k < maxDownIndex ? k : k - m_segment; switch (_node.opcode()) { - case Stack::Opcode::PUSH_S: { - if (minUpIndex <= i && i <= maxDownIndex) { - unableToConvertOpcode(); - } else if (i < maxDownIndex) { - m_commands.emplace_back(_node.shared_from_this()); - ++m_stackSize; - } else if (i > maxDownIndex) { - m_commands.emplace_back(createNode(_node.opcode(), newi)); - ++m_stackSize; - } - break; + case Stack::Opcode::PUSH_S: { + if (minUpIndex <= i && i <= maxDownIndex) { + unableToConvertOpcode(); + } else if (i < maxDownIndex) { + m_commands.emplace_back(_node.shared_from_this()); + ++m_stackSize; + } else if (i > maxDownIndex) { + m_commands.emplace_back(createNode(_node.opcode(), newi)); + ++m_stackSize; } - case Stack::Opcode::POP_S: { - if (m_segment == 1 && i == 1 && m_stackSize == 2) { // it equals to BLKDROP2 1, 1 - m_isDropped = true; - } else if (rest() == 0) { - unableToConvertOpcode(); - } else if (i == maxDownIndex && m_segment == 1) { - m_wasSet = true; - --m_stackSize; // TODO delete this - // don't push to m_commands, just ignore the opcode - } else if (i < minUpIndex) { - --m_stackSize; - m_commands.emplace_back(_node.shared_from_this()); - } else if (i > maxDownIndex) { - // here _node.i() >= 2 - --m_stackSize; - m_commands.emplace_back(createNode(_node.opcode(), newi)); - } else { - unableToConvertOpcode(); - } - break; + break; + } + case Stack::Opcode::POP_S: { + if (m_segment == 1 && i == 1 && m_stackSize == 2) { // it equals to BLKDROP2 1, 1 + m_isDropped = true; + } else if (rest() == 0) { + unableToConvertOpcode(); + } else if (i == maxDownIndex && m_segment == 1) { + m_wasSet = true; + --m_stackSize; // TODO delete this + // don't push to m_commands, just ignore the opcode + } else if (i < minUpIndex) { + --m_stackSize; + m_commands.emplace_back(_node.shared_from_this()); + } else if (i > maxDownIndex) { + // here _node.i() >= 2 + --m_stackSize; + m_commands.emplace_back(createNode(_node.opcode(), newi)); + } else { + unableToConvertOpcode(); } - case Stack::Opcode::DROP: { - int n = i; - if (m_stackSize <= n) { - m_isDropped = true; - if (n - m_segment > 0) - m_commands.emplace_back(makeDROP(n - m_segment)); - } else if (rest() >= n) { - m_stackSize -= n; - m_commands.emplace_back(makeDROP(n)); - } else { - unableToConvertOpcode(); - } - break; + break; + } + case Stack::Opcode::DROP: { + int n = i; + if (m_stackSize <= n) { + m_isDropped = true; + if (n - m_segment > 0) + m_commands.emplace_back(makeDROP(n - m_segment)); + } else if (rest() >= n) { + m_stackSize -= n; + m_commands.emplace_back(makeDROP(n)); + } else { + unableToConvertOpcode(); } + break; + } - case Stack::Opcode::BLKDROP2: { - int drop = i; - int rest = j; - if (rest >= m_stackSize) { - m_commands.emplace_back(makeBLKDROP2(drop, rest - m_segment)); - } else if (rest <= this->rest() && rest + drop >= m_stackSize) { - if (drop - m_segment > 0) { - m_commands.emplace_back(makeBLKDROP2(drop - m_segment, rest)); - } - m_isDropped = true; - } else if (rest + drop <= this->rest()) { - m_commands.emplace_back(makeBLKDROP2(drop, rest)); - m_stackSize -= drop; - } else { - unableToConvertOpcode(); - } - break; - } - case Stack::Opcode::BLKPUSH: { - int n = i; - int maxDownPushIndex = j; - int minUpPushIndex = maxDownPushIndex - n + 1; // include - if (std::max(minUpIndex, minUpPushIndex) > std::min(maxDownIndex, maxDownPushIndex)) { - m_commands.emplace_back(makeBLKPUSH(n, newj)); - m_stackSize += n; - } else { - unableToConvertOpcode(); + case Stack::Opcode::BLKDROP2: { + int drop = i; + int rest = j; + if (rest >= m_stackSize) { + m_commands.emplace_back(makeBLKDROP2(drop, rest - m_segment)); + } else if (rest <= this->rest() && rest + drop >= m_stackSize) { + if (drop - m_segment > 0) { + m_commands.emplace_back(makeBLKDROP2(drop - m_segment, rest)); } - break; + m_isDropped = true; + } else if (rest + drop <= this->rest()) { + m_commands.emplace_back(makeBLKDROP2(drop, rest)); + m_stackSize -= drop; + } else { + unableToConvertOpcode(); } - case Stack::Opcode::REVERSE: { - int n = i; - int minUpReverseIndex = j; - int maxDownReverseIndex = minUpReverseIndex + n - 1; // include - if (std::max(minUpIndex, minUpReverseIndex) > std::min(maxDownIndex, maxDownReverseIndex)) { - if (maxDownIndex < minUpReverseIndex) { - m_commands.emplace_back(makeREVERSE(n, j - m_segment)); - } else { - m_commands.emplace_back(_node.shared_from_this()); - } - } else { - unableToConvertOpcode(); - } - break; + break; + } + case Stack::Opcode::BLKPUSH: { + int n = i; + int maxDownPushIndex = j; + int minUpPushIndex = maxDownPushIndex - n + 1; // include + if (std::max(minUpIndex, minUpPushIndex) > std::min(maxDownIndex, maxDownPushIndex)) { + m_commands.emplace_back(makeBLKPUSH(n, newj)); + m_stackSize += n; + } else { + unableToConvertOpcode(); } - case Stack::Opcode::BLKSWAP: - if (i == 1 && j + i == m_stackSize) { - unableToConvertOpcode(); - if (m_segment == 1) { - m_wasMoved = true; - } - } else if (i + j <= rest()) { - m_commands.emplace_back(makeBLKSWAP(i, j)); - } else if (j >= m_stackSize) { - if (j - m_segment >= 1) { - m_commands.emplace_back(makeBLKSWAP(i, j - m_segment)); - } - m_stackSize += i; // we take i element and push ones to the top of the stack - } else { - unableToConvertOpcode(); - } - break; - case Stack::Opcode::XCHG: - if ((minUpIndex <= i && i <= maxDownIndex) || - (minUpIndex <= j && j <= maxDownIndex)) { // it's ok if j==-1 - unableToConvertOpcode(); - } else { - m_commands.emplace_back(createNode(_node.opcode(), newi, newj)); - } - break; - case Stack::Opcode::PUSH2_S: - if ((minUpIndex <= i && i <= maxDownIndex) || - (minUpIndex <= j && j <= maxDownIndex)) { - unableToConvertOpcode(); - } else { - m_commands.emplace_back(createNode(_node.opcode(), newi, newj)); - } - m_stackSize += 2; - break; - case Stack::Opcode::PUSH3_S: - if ((minUpIndex <= i && i <= maxDownIndex) || - (minUpIndex <= j && j <= maxDownIndex) || - (minUpIndex <= k && k <= maxDownIndex)) { - unableToConvertOpcode(); + break; + } + case Stack::Opcode::REVERSE: { + int n = i; + int minUpReverseIndex = j; + int maxDownReverseIndex = minUpReverseIndex + n - 1; // include + if (m_segment == 1 && minUpReverseIndex <= m_stackSize - 1 && m_stackSize - 1 <= maxDownReverseIndex) { + if (i - 1 >= 2) + m_commands.emplace_back(makeREVERSE(i - 1, j)); + m_stackSize = minUpReverseIndex + (maxDownReverseIndex - (m_stackSize - 1)) + 1; + } else if (std::max(minUpIndex, minUpReverseIndex) > std::min(maxDownIndex, maxDownReverseIndex)) { + if (maxDownIndex < minUpReverseIndex) { + m_commands.emplace_back(makeREVERSE(n, j - m_segment)); } else { - m_commands.emplace_back(createNode(_node.opcode(), newi, newj, newk)); - } - m_stackSize += 3; - break; - - case Stack::Opcode::TUCK: - if (rest() >= 2) { m_commands.emplace_back(_node.shared_from_this()); - ++m_stackSize; - } else { - unableToConvertOpcode(); } - break; - - case Stack::Opcode::PUXC: - if ((minUpIndex <= i && i <= maxDownIndex) || - (minUpIndex <= j && j <= maxDownIndex) || // note: it's false if j==-1 - rest() == 0 // because we make PUSH Si, SWAP ... - ) { - unableToConvertOpcode(); - } else { - m_commands.emplace_back(createNode(_node.opcode(), newi, newj)); - ++m_stackSize; + } else { + unableToConvertOpcode(); + } + break; + } + case Stack::Opcode::BLKSWAP: { + // int down = i; + // int up = j; + if (i == 1 && j + i == m_stackSize && m_segment == 1 && m_stopSimulationIfMoved) { + m_wasMoved = true; // TODO call unableToConvertOpcode with reason: moved, set, etc + } else if (j <= rest() && i + j >= m_stackSize) { + if (i - m_segment >= 1) + m_commands.emplace_back(makeBLKSWAP(i - m_segment, j)); + m_stackSize -= j; + } else if (i + j <= rest()) { + m_commands.emplace_back(makeBLKSWAP(i, j)); + } else if (j >= m_stackSize) { + if (j - m_segment >= 1) { + m_commands.emplace_back(makeBLKSWAP(i, j - m_segment)); } - break; - - case Stack::Opcode::XCPU: { - if ((minUpIndex <= i && i <= maxDownIndex) || - (minUpIndex <= j && j <= maxDownIndex) - ) { - unableToConvertOpcode(); - } else { - m_commands.emplace_back(createNode(_node.opcode(), newi, newj)); - ++m_stackSize; + m_stackSize += i; // we take i elements and push ones to the top of the stack + } else { + unableToConvertOpcode(); + } + break; + } + case Stack::Opcode::XCHG: + solAssert(i < j, ""); + solAssert(j != -1, ""); + if (m_segment == 1 && i == m_stackSize - 1) { + if (j >= 2) { + m_commands.emplace_back(makeREVERSE(j, 0)); + if (j - i - 1 >= 1) + m_commands.emplace_back(makeBLKSWAP(j - i - 1, 1)); + m_commands.emplace_back(makeREVERSE(j, 0)); } - break; + m_stackSize = j + 1; + } else if (m_segment == 1 && j == m_stackSize - 1) { + if (i > 0) + m_commands.emplace_back(makeBLKSWAP(1, i)); + if (j - 1 >= 1) + m_commands.emplace_back(makeBLKSWAP(j - 1, 1)); + m_stackSize = i + 1; + } else if ((minUpIndex <= i && i <= maxDownIndex) || + (minUpIndex <= j && j <= maxDownIndex)) { // it's ok if j==-1 + unableToConvertOpcode(); + } else { + m_commands.emplace_back(createNode(_node.opcode(), newi, newj)); + } + break; + case Stack::Opcode::PUSH2_S: + if ((minUpIndex <= i && i <= maxDownIndex) || + (minUpIndex <= j && j <= maxDownIndex)) { + unableToConvertOpcode(); + } else { + m_commands.emplace_back(createNode(_node.opcode(), newi, newj)); + } + m_stackSize += 2; + break; + case Stack::Opcode::PUSH3_S: + if ((minUpIndex <= i && i <= maxDownIndex) || + (minUpIndex <= j && j <= maxDownIndex) || + (minUpIndex <= k && k <= maxDownIndex)) { + unableToConvertOpcode(); + } else { + m_commands.emplace_back(createNode(_node.opcode(), newi, newj, newk)); + } + m_stackSize += 3; + break; + + case Stack::Opcode::PUXC: + if ((minUpIndex <= i && i <= maxDownIndex) || + (minUpIndex <= j && j <= maxDownIndex) || // note: it's false if j==-1 + rest() == 0 // because we make PUSH Si, SWAP ... + ) { + unableToConvertOpcode(); + } else { + m_commands.emplace_back(createNode(_node.opcode(), newi, newj)); + ++m_stackSize; } + break; + + case Stack::Opcode::XCPU: { + if ((minUpIndex <= i && i <= maxDownIndex) || + (minUpIndex <= j && j <= maxDownIndex) + ) { + unableToConvertOpcode(); + } else { + m_commands.emplace_back(createNode(_node.opcode(), newi, newj)); + ++m_stackSize; + } + break; + } + case Stack::Opcode::PUXC2: + case Stack::Opcode::XC2PU: + case Stack::Opcode::XCPU2: + case Stack::Opcode::XCHG2: + case Stack::Opcode::XCPUXC: + case Stack::Opcode::PUXCPU: + case Stack::Opcode::PU2XC: + case Stack::Opcode::XCHG3: { + // TODO implement + unableToConvertOpcode(); + } } return false; @@ -485,7 +517,7 @@ void Simulator::endVisit(CodeBlock &/*_node*/) { std::optional, bool>> Simulator::trySimulate(CodeBlock const& body, int begStackSize, int /*endStackSize*/) { Simulator sim{body.instructions().begin(), body.instructions().end(), begStackSize, m_segment}; - if (sim.m_unableToConvertOpcode || sim.m_wasSet) { + if (!sim.m_ableToConvertOpcode || sim.m_wasSet) { unableToConvertOpcode(); return {}; } @@ -511,7 +543,7 @@ bool Simulator::isPopAndDrop(Pointer const& a, Pointer c } bool Simulator::success() const { - if (m_unableToConvertOpcode) { + if (!m_ableToConvertOpcode) { return false; } return m_isDropped && !m_wasSet; diff --git a/compiler/libsolidity/codegen/TVMSimulator.hpp b/compiler/libsolidity/codegen/TVMSimulator.hpp index cbfe849e..0e2604b8 100644 --- a/compiler/libsolidity/codegen/TVMSimulator.hpp +++ b/compiler/libsolidity/codegen/TVMSimulator.hpp @@ -19,82 +19,89 @@ #include "TvmAstVisitor.hpp" namespace solidity::frontend { - class Simulator : public TvmAstVisitor { - public: - explicit Simulator( - std::vector>::const_iterator _beg, - std::vector>::const_iterator _end, - int _startSize, - int _segment - ) : - m_segment{_segment}, - m_stackSize{_startSize} - { - run(_beg, _end); - } - private: - void run(std::vector>::const_iterator _beg, - std::vector>::const_iterator _end); - public: - bool visit(AsymGen &_node) override; - bool visit(DeclRetFlag &_node) override; - bool visit(Opaque &_node) override; - bool visit(HardCode &_node) override; - bool visit(Loc &_node) override; - bool visit(TvmReturn &_node) override; - bool visit(ReturnOrBreakOrCont &_node) override; - bool visit(TvmException &_node) override; - bool visit(GenOpcode &_node) override; - bool visit(PushCellOrSlice &_node) override; - bool visit(Glob &_node) override; - bool visit(Stack &_node) override; - bool visit(CodeBlock &_node) override; - bool visit(SubProgram &_node) override; - bool visit(LogCircuit &_node) override; - bool visit(TryCatch &_node) override; - bool visit(TvmIfElse &_node) override; - bool visit(TvmRepeat &_node) override; - bool visit(TvmUntil &_node) override; - bool visit(While &_node) override; - bool visit(Contract &_node) override; - bool visit(Function &_node) override; - void endVisit(CodeBlock &_node) override; +class Simulator : public TvmAstVisitor { +public: + explicit Simulator( + std::vector>::const_iterator _beg, + std::vector>::const_iterator _end, + int _stackSize, + int _segment, + bool _stopSimulationIfSet = false, + bool _stopSimulationIfMoved = false + ) : + m_stopSimulationIfSet{_stopSimulationIfSet}, + m_stopSimulationIfMoved{_stopSimulationIfMoved}, + m_segment{_segment}, + m_stackSize{_stackSize} + { + solAssert(_segment <= _stackSize, ""); + run(_beg, _end); + } +private: + void run(std::vector>::const_iterator _beg, + std::vector>::const_iterator _end); +public: + bool visit(AsymGen &_node) override; + bool visit(DeclRetFlag &_node) override; + bool visit(Opaque &_node) override; + bool visit(HardCode &_node) override; + bool visit(Loc &_node) override; + bool visit(TvmReturn &_node) override; + bool visit(ReturnOrBreakOrCont &_node) override; + bool visit(TvmException &_node) override; + bool visit(StackOpcode &_node) override; + bool visit(PushCellOrSlice &_node) override; + bool visit(Glob &_node) override; + bool visit(Stack &_node) override; + bool visit(CodeBlock &_node) override; + bool visit(SubProgram &_node) override; + bool visit(LogCircuit &_node) override; + bool visit(TryCatch &_node) override; + bool visit(TvmIfElse &_node) override; + bool visit(TvmRepeat &_node) override; + bool visit(TvmUntil &_node) override; + bool visit(While &_node) override; + bool visit(Contract &_node) override; + bool visit(Function &_node) override; + void endVisit(CodeBlock &_node) override; - std::optional, bool>> trySimulate(CodeBlock const& block, int begStackSize, int endStackSize); - bool isPopAndDrop(Pointer const& a, Pointer const& b); + std::optional, bool>> trySimulate(CodeBlock const& block, int begStackSize, int endStackSize); + bool isPopAndDrop(Pointer const& a, Pointer const& b); - bool success() const; - bool wasSet() const { return m_wasSet; } - bool wasMoved() const { return m_wasMoved; } - std::vector> const& commands() const { return m_commands; } - std::vector>::const_iterator getIter() const { return m_iter; } - std::set setGlobIndexes() const { return m_setGlobs; } - bool wasCall() const { return m_wasCall; } + bool success() const; + bool wasSet() const { return m_wasSet; } + bool wasMoved() const { return m_wasMoved; } + std::vector> const& commands() const { return m_commands; } + std::vector>::const_iterator getIter() const { return m_iter; } + std::set setGlobIndexes() const { return m_setGlobs; } + bool wasCall() const { return m_wasCall; } - protected: - bool visitNode(TvmAstNode const&) override; - void endVisitNode(TvmAstNode const&) override; +protected: + bool visitNode(TvmAstNode const&) override; + void endVisitNode(TvmAstNode const&) override; - private: - int rest() const; - void unableToConvertOpcode() { - m_unableToConvertOpcode = true; - } +private: + int rest() const; + void unableToConvertOpcode() { + m_ableToConvertOpcode = false; + } - private: - bool m_wasSet{}; - bool m_wasMoved{}; - bool m_unableToConvertOpcode{}; - bool m_isDropped{}; - bool m_wasReturnBreakContinue{}; +private: + bool m_wasSet{}; + bool m_stopSimulationIfSet{}; + bool m_stopSimulationIfMoved{}; + bool m_wasMoved{}; + bool m_ableToConvertOpcode = true; + bool m_isDropped{}; + bool m_wasReturnBreakContinue{}; - int const m_segment{}; - int m_stackSize{}; - std::vector> m_commands; - std::vector>::const_iterator m_iter{}; - std::set m_setGlobs; - bool m_wasCall{}; - }; + int const m_segment; + int m_stackSize{}; + std::vector> m_commands; + std::vector>::const_iterator m_iter{}; + std::set m_setGlobs; + bool m_wasCall{}; +}; } // end solidity::frontend diff --git a/compiler/libsolidity/codegen/TVMTypeChecker.cpp b/compiler/libsolidity/codegen/TVMTypeChecker.cpp index cea3b3c7..bdafb33f 100644 --- a/compiler/libsolidity/codegen/TVMTypeChecker.cpp +++ b/compiler/libsolidity/codegen/TVMTypeChecker.cpp @@ -166,9 +166,18 @@ bool TVMTypeChecker::visit(TryStatement const& _tryStatement) { return true; } -bool TVMTypeChecker::visit(VariableDeclaration const& _node) { - if (_node.isStateVariable() && _node.type()->category() == Type::Category::TvmSlice) { - m_errorReporter.typeError(9191_error, _node.location(), "This type can't be used for state variables."); +bool TVMTypeChecker::visit(VariableDeclaration const& _variable) { + if (_variable.isStateVariable()) { + ASTString const& name = _variable.name(); + if (name == "_pubkey" || name == "_timestamp" || name == "_constructorFlag") + m_errorReporter.typeError(7984_error, _variable.location(), "The name \"" + name + "\" is reserved."); + Type::Category const category = _variable.type()->category(); + if (category == Type::Category::TvmSlice || category == Type::Category::TvmVector) + m_errorReporter.typeError(9191_error, _variable.location(), "This type can't be used for state variables."); + if (_variable.isNoStorage() && _variable.isStatic()) + m_errorReporter.typeError(4161_error, _variable.location(), R"(State variable can not be marked as "nostorage" and "static" simultaneously.)"); + if (_variable.isNoStorage() && _variable.value()) + m_errorReporter.typeError(4161_error, _variable.location(), "\"nostorage\" state variable can not be initialized here."); } return true; } @@ -262,6 +271,8 @@ bool TVMTypeChecker::visit(IndexRangeAccess const& indexRangeAccess) { void TVMTypeChecker::checkDeprecation(FunctionCall const& _functionCall) { auto memberAccess = to(&_functionCall.expression()); ASTString const& memberName = memberAccess ? memberAccess->memberName() : ""; + auto magicType = memberAccess ? to(memberAccess->expression().annotation().type) : nullptr; + MagicType::Kind kind = magicType ? magicType->kind() : MagicType::Kind::TVM; Type const* expressionType = _functionCall.expression().annotation().type; switch (expressionType->category()) { case Type::Category::Function: { @@ -272,63 +283,104 @@ void TVMTypeChecker::checkDeprecation(FunctionCall const& _functionCall) { "\".reset()\" is deprecated. Use \"delete ;\"."); break; case FunctionType::Kind::TVMSliceLoad: - if (memberName == "decode") { + if (memberName == "decode") m_errorReporter.warning(9518_error, _functionCall.location(), "\".decode()\" is deprecated. Use \".load()\""); - } break; case FunctionType::Kind::TVMSliceLoadQ: - if (memberName == "decodeQ") { + if (memberName == "decodeQ") m_errorReporter.warning(6501_error, _functionCall.location(), "\".decodeQ()\" is deprecated. Use \".loadQ()\""); - } break; case FunctionType::Kind::TVMSliceLoadFunctionParams: - if (memberName == "decodeFunctionParams") { - m_errorReporter.warning(9789_error, _functionCall.location(), - "\".decodeFunctionParams()\" is deprecated. Use \".loadFunctionParams()\""); - } + m_errorReporter.warning(9789_error, _functionCall.location(), + "\".loadFunctionParams()\" and \".decodeFunctionParams()\" are deprecated. Use \"abi.decodeFunctionParams()\""); break; case FunctionType::Kind::TVMSliceLoadStateVars: - if (memberName == "decodeStateVars") { - m_errorReporter.warning(3738_error, _functionCall.location(), - "\".decodeStateVars()\" is deprecated. Use \".loadStateVars()\""); - } + m_errorReporter.warning(2953_error, _functionCall.location(), + "\".loadStateVars()\" and \".decodeStateVars()\" are deprecated. Use \"abi.decodeData()\""); break; case FunctionType::Kind::TVMSliceLoadUint: - if (memberName == "loadUnsigned") { + if (memberName == "loadUnsigned") m_errorReporter.warning(5093_error, _functionCall.location(), "\".loadUnsigned()\" is deprecated. Use \".loadUint()\""); - } break; case FunctionType::Kind::TVMSliceLoadInt: - if (memberName == "loadSigned") { + if (memberName == "loadSigned") m_errorReporter.warning(1581_error, _functionCall.location(), "\".loadSigned()\" is deprecated. Use \".loadInt()\""); - } break; case FunctionType::Kind::TVMBuilderStoreUint: - if (memberName == "storeUnsigned") { + if (memberName == "storeUnsigned") m_errorReporter.warning(1214_error, _functionCall.location(), "\".storeUnsigned()\" is deprecated. Use \".storeUint()\""); - } break; case FunctionType::Kind::TVMBuilderStoreInt: - if (memberName == "storeSigned") { + if (memberName == "storeSigned") m_errorReporter.warning(9509_error, _functionCall.location(), "\".storeSigned()\" is deprecated. Use \".storeInt()\""); - } break; - case FunctionType::Kind::ByteToSlice: { + case FunctionType::Kind::ByteToSlice: m_errorReporter.warning(9791_error, _functionCall.location(), "\".toSlice()\" is deprecated. Use explicit conversion: \"TvmSlice()\""); break; - } - case FunctionType::Kind::StringToSlice: { + case FunctionType::Kind::StringToSlice: m_errorReporter.warning(6953_error, _functionCall.location(), "\".toSlice()\" is deprecated. Use explicit conversion: \"TvmSlice()\""); break; - } + case FunctionType::Kind::ABIStateInitHash: + if (kind == MagicType::Kind::TVM) + m_errorReporter.warning(6336_error, _functionCall.location(), + "\"tvm.stateInitHash()\" is deprecated. Use: \"abi.stateInitHash()\""); + break; + case FunctionType::Kind::ABIEncodeStateInit: + if (kind == MagicType::Kind::TVM) + m_errorReporter.warning(1078_error, _functionCall.location(), + "\"tvm.buildStateInit()\" is deprecated. Use: \"abi.encodeStateInit()\""); + break; + case FunctionType::Kind::ABIEncodeData: + if (kind == MagicType::Kind::TVM) + m_errorReporter.warning(5638_error, _functionCall.location(), + "\"tvm.buildDataInit()\" is deprecated. Use: \"abi.encodeData()\""); + break; + case FunctionType::Kind::ABICodeSalt: + if (kind == MagicType::Kind::TVM) + m_errorReporter.warning(9082_error, _functionCall.location(), + "\"tvm.codeSalt()\" is deprecated. Use: \"abi.codeSalt()\""); + break; + case FunctionType::Kind::ABISetCodeSalt: + if (kind == MagicType::Kind::TVM) + m_errorReporter.warning(2638_error, _functionCall.location(), + "\"tvm.setCodeSalt()\" is deprecated. Use: \"abi.setCodeSalt()\""); + break; + case FunctionType::Kind::ABIFunctionId: + if (kind == MagicType::Kind::TVM) + m_errorReporter.warning(4767_error, _functionCall.location(), + "\"tvm.functionId()\" is deprecated. Use: \"abi.functionId()\""); + break; + case FunctionType::Kind::ABIBuildExtMsg: + if (kind == MagicType::Kind::TVM) + m_errorReporter.warning(9856_error, _functionCall.location(), + "\"tvm.buildExtMsg()\" is deprecated. Use: \"abi.encodeExtMsg()\""); + break; + case FunctionType::Kind::ABIBuildIntMsg: + if (kind == MagicType::Kind::TVM) + m_errorReporter.warning(4063_error, _functionCall.location(), + "\"tvm.buildIntMsg()\" is deprecated. Use: \"abi.encodeIntMsg()\""); + break; + case FunctionType::Kind::ABIEncodeBody: + if (kind == MagicType::Kind::TVM) + m_errorReporter.warning(7329_error, _functionCall.location(), + "\"tvm.encodeBody()\" is deprecated. Use: \"abi.encodeBody()\""); + break; + case FunctionType::Kind::TVMSliceLoadTons: + m_errorReporter.warning(1085_error, _functionCall.location(), + "\".loadTons()\" is deprecated. Use \".load(coins)\""); + break; + case FunctionType::Kind::TVMBuilderStoreTons: + m_errorReporter.warning(7954_error, _functionCall.location(), + "\".storeTons()\" is deprecated. Use \"coins x = ...; .store(x)\""); + break; default: break; } @@ -406,7 +458,7 @@ bool TVMTypeChecker::visit(FunctionCall const& _functionCall) { ABITypeSize size{structType}; if (size.maxBits > 1023 || size.maxRefs > 4) m_errorReporter.warning( - 228_error, arg->location(), + 3185_error, arg->location(), "The structure may not fit to the builder." " Store manually structure's members to several builders." ); diff --git a/compiler/libsolidity/codegen/TVMTypeChecker.hpp b/compiler/libsolidity/codegen/TVMTypeChecker.hpp index 0c37f2aa..07a23eb4 100644 --- a/compiler/libsolidity/codegen/TVMTypeChecker.hpp +++ b/compiler/libsolidity/codegen/TVMTypeChecker.hpp @@ -27,7 +27,7 @@ class TVMTypeChecker : public ASTConstVisitor { public: bool visit(TryStatement const& _node) override; - bool visit(VariableDeclaration const& _node) override; + bool visit(VariableDeclaration const& _variable) override; bool visit(Mapping const& _mapping) override; bool visit(FunctionDefinition const& fc) override; bool visit(ContractDefinition const& ) override; diff --git a/compiler/libsolidity/codegen/TvmAst.cpp b/compiler/libsolidity/codegen/TvmAst.cpp index ed62fb07..3aa57da5 100644 --- a/compiler/libsolidity/codegen/TvmAst.cpp +++ b/compiler/libsolidity/codegen/TvmAst.cpp @@ -158,7 +158,7 @@ bool HardCode::operator==(TvmAstNode const& _node) const { return g && std::tie(m_code, m_take, m_ret) == std::tie(g->m_code, g->m_take, g->m_ret); } -GenOpcode::GenOpcode(const std::string& opcode, int take, int ret, bool _isPure) : Gen{_isPure}, m_take{take}, m_ret{ret} { +StackOpcode::StackOpcode(const std::string& opcode, int take, int ret, bool _isPure) : Gen{_isPure}, m_take{take}, m_ret{ret} { vector lines = split(opcode, ';'); solAssert(lines.size() <= 2, ""); @@ -173,11 +173,11 @@ GenOpcode::GenOpcode(const std::string& opcode, int take, int ret, bool _isPure) } -void GenOpcode::accept(TvmAstVisitor& _visitor) { +void StackOpcode::accept(TvmAstVisitor& _visitor) { _visitor.visit(*this); } -std::string GenOpcode::fullOpcode() const { +std::string StackOpcode::fullOpcode() const { std::string ret = m_opcode; if (!m_arg.empty()) ret += " " + m_arg; @@ -186,8 +186,8 @@ std::string GenOpcode::fullOpcode() const { return ret; } -bool GenOpcode::operator==(TvmAstNode const& _node) const { - auto gen = to(&_node); +bool StackOpcode::operator==(TvmAstNode const& _node) const { + auto gen = to(&_node); if (gen) { if ( (isIn(fullOpcode(), "TRUE", "PUSHINT -1") && isIn(gen->fullOpcode(), "TRUE", "PUSHINT -1")) || @@ -462,7 +462,7 @@ void Contract::accept(TvmAstVisitor& _visitor) { } namespace solidity::frontend { -Pointer gen(const std::string& cmd) { +Pointer gen(const std::string& cmd) { std::string op; std::string param; { @@ -602,6 +602,7 @@ Pointer gen(const std::string& cmd) { {"PLDU", {1, 1}}, {"PLDULE4", {1, 1}}, {"PLDULE8", {1, 1}}, + {"POW2", {1, 1}}, {"RAND", {1, 1}}, {"SBITS", {1, 1, true}}, {"SDEMPTY", {1, 1, true}}, @@ -755,34 +756,34 @@ Pointer gen(const std::string& cmd) { {"SPLIT", {3, 2}} }; - Pointer opcode; + Pointer opcode; if (opcodes.count(op)) { OpcodeParams params = opcodes.at(op); - opcode = createNode(cmd, params.take, params.ret, params.isPure); + opcode = createNode(cmd, params.take, params.ret, params.isPure); } else if (dictSet()) { - opcode = createNode(cmd, 4, 1); + opcode = createNode(cmd, 4, 1); } else if (dictReplaceOrAdd()) { - opcode = createNode(cmd, 4, 2); + opcode = createNode(cmd, 4, 2); } else if (f("TUPLE")) { int ret = boost::lexical_cast(param); - opcode = createNode(cmd, ret, 1); + opcode = createNode(cmd, ret, 1); } else if (f("UNTUPLE")) { int ret = boost::lexical_cast(param); - opcode = createNode(cmd, 1, ret); + opcode = createNode(cmd, 1, ret); } else if (f("UNPACKFIRST")) { int ret = boost::lexical_cast(param); - opcode = createNode(cmd, 1, ret); + opcode = createNode(cmd, 1, ret); } else if (f("LSHIFT") || f("RSHIFT")) { if (param.empty()) { - opcode = createNode(cmd, 2, 1); + opcode = createNode(cmd, 2, 1); } else { - opcode = createNode(cmd, 1, 1); + opcode = createNode(cmd, 1, 1); } } else if (f("MULRSHIFT")) { if (param.empty()) { - opcode = createNode(cmd, 3, 1); + opcode = createNode(cmd, 3, 1); } else { - opcode = createNode(cmd, 2, 1); + opcode = createNode(cmd, 2, 1); } } else { solUnimplemented("Unknown opcode: " + cmd); @@ -948,10 +949,10 @@ Pointer makePUSHREF(const std::string& data) { return createNode(PushCellOrSlice::Type::PUSHREF, data, nullptr); } -Pointer makeREVERSE(int i, int j) { - solAssert(i >= 2, ""); - solAssert(j >= 0, ""); - return createNode(Stack::Opcode::REVERSE, i, j); +Pointer makeREVERSE(int qty, int index) { + solAssert(qty >= 2, ""); + solAssert(index >= 0, ""); + return createNode(Stack::Opcode::REVERSE, qty, index); } Pointer makeROT() { @@ -967,10 +968,6 @@ Pointer makeBLKSWAP(int down, int top) { return createNode(Stack::Opcode::BLKSWAP, down, top); } -Pointer makeTUCK() { - return createNode(Stack::Opcode::TUCK); -} - Pointer makePUXC(int i, int j) { solAssert(0 <= i && i <= 15, ""); solAssert(-1 <= j && j <= 14, ""); @@ -991,6 +988,7 @@ Pointer flipIfElse(TvmIfElse const& node) { } bool isPureGen01(TvmAstNode const& node) { + // See also isSimpleCommand auto gen = to(&node); return gen && gen->isPure() && gen->take() == 0 && gen->ret() == 1; } @@ -1055,18 +1053,34 @@ std::optional isPOP(Pointer const& node) { return {}; } +std::optional isPUSH(Pointer const& node) { + if (auto stack = to(node.get())) { + switch (stack->opcode()) { + case Stack::Opcode::PUSH_S: + return stack->i(); + case Stack::Opcode::BLKPUSH: + if (stack->i() == 1) + return stack->j(); + break; + default: + break; + } + } + return {}; +} + std::optional> isBLKPUSH(Pointer const& node) { - auto stack = to(node.get()); - if (stack) { + if (auto stack = to(node.get())) { switch (stack->opcode()) { - case Stack::Opcode::BLKPUSH: - return {{stack->i(), stack->j()}}; - case Stack::Opcode::PUSH_S: - if (stack->i() == 0) - return {{1, 0}}; - break; - default: - break; + case Stack::Opcode::BLKPUSH: + return {{stack->i(), stack->j()}}; + case Stack::Opcode::PUSH_S: + if (stack->i() == 0) + return {{1, 0}}; + break; + // TODO add PUSH_S and PUSH2_S + default: + break; } } return {}; @@ -1154,4 +1168,89 @@ Pointer getZeroOrNullAlignment(bool isZero, bool isSwap, bool isNot) { return std::make_shared(cmd); } +int OpcodeUtils::gasCost(Stack const& opcode) { + int i = opcode.i(); + int j = opcode.j(); + //int k = opcode.k(); + switch (opcode.opcode()) { + case Stack::Opcode::POP_S: + return 18; + case Stack::Opcode::DROP: { + int n = i; + if (n == 1 || n == 2) + return 18; // "DROP" "DROP2" + if (n <= 15) + return 26; // BLKDROP + return 18 + 18; // PUSHINT N + DROPX + } + case Stack::Opcode::BLKDROP2: { + if (i > 15 || j > 15) + solUnimplemented(""); + return 26; + } + case Stack::Opcode::BLKSWAP: { + int bottom = i; + int top = j; + if (bottom == 1 && top == 1) { + return 18; // SWAP + } else if (bottom == 1 && top == 2) { + return 18; // "ROT"; + } else if (bottom == 2 && top == 1) { + return 18; // "ROTREV"; + } else if (bottom == 2 && top == 2) { + return 18; // "SWAP2"; + } else if (1 <= bottom && bottom <= 16 && 1 <= top && top <= 16) { + return 26; // "ROLL " "ROLLREV " "BLKSWAP" + } else { + solUnimplemented(""); // "ROLLX" "ROLLREVX" "BLKSWX" + } + } + case Stack::Opcode::BLKPUSH: { + if ((i == 2 && j == 1) || (i == 2 && j == 3)) { + return 18; // "DUP2" "OVER2" + } else { + if (i > 15) + solAssert(j == 0, ""); + int rest = i; + int cost = 0; + while (rest > 0) { + cost += 26; // "BLKPUSH " + rest -= 15; + } + return cost; + } + } + case Stack::Opcode::PUSH2_S: + if ((i == 1 && j == 0) || (i == 3 && j == 2)) + return 18; // "DUP2" "OVER2" + return 26; // "PUSH2" + case Stack::Opcode::REVERSE: + if ((i == 2 && j == 0) || (i == 3 && j == 0)) + return 18; // "SWAP" "XCHG S2" + else if (2 <= i && i <= 17 && 0 <= j && j <= 15) + return 26; // "REVERSE" + solUnimplemented(""); + case Stack::Opcode::XCHG: + if (i == 0 || i == 1) + return 18; // "XCHG Sj" "XCHG s1, Sj" + return 26; // XCHG Si, Sj + case Stack::Opcode::PUSH_S: + return 18; + case Stack::Opcode::XCHG3: + case Stack::Opcode::XCHG2: + case Stack::Opcode::XCPU: + case Stack::Opcode::PUXC: + return 26; + case Stack::Opcode::PUSH3_S: + case Stack::Opcode::XC2PU: + case Stack::Opcode::XCPU2: + case Stack::Opcode::PUXC2: + case Stack::Opcode::XCPUXC: + case Stack::Opcode::PUXCPU: + case Stack::Opcode::PU2XC: + return 34; + } + solUnimplemented(""); +} + } // end solidity::frontend diff --git a/compiler/libsolidity/codegen/TvmAst.hpp b/compiler/libsolidity/codegen/TvmAst.hpp index 67142a62..96395d9a 100644 --- a/compiler/libsolidity/codegen/TvmAst.hpp +++ b/compiler/libsolidity/codegen/TvmAst.hpp @@ -74,9 +74,17 @@ class Stack : public TvmAstNode { REVERSE, // REVERSE 2, 0 | REVERSE 3, 0 XCHG, // XCHG S0 S1 | XCHG S0 S2, - TUCK, + // Compound stack manipulation primitives + XCHG3, + XCHG2, + XCPU, //XCPU s1,s1 == TUCK PUXC, - XCPU, + XC2PU, + XCPU2, + PUXC2, + XCPUXC, + PUXCPU, + PU2XC }; explicit Stack(Opcode opcode, int i = -1, int j = -1, int k = -1); void accept(TvmAstVisitor& _visitor) override; @@ -178,9 +186,9 @@ class HardCode : public Gen { int m_ret{}; }; -class GenOpcode : public Gen { +class StackOpcode : public Gen { public: - explicit GenOpcode(const std::string& opcode, int take, int ret, bool _isPure = false); + explicit StackOpcode(const std::string& opcode, int take, int ret, bool _isPure = false); void accept(TvmAstVisitor& _visitor) override; std::string fullOpcode() const; std::string const &opcode() const { return m_opcode; } @@ -261,7 +269,7 @@ class PushCellOrSlice : public Gen { PUSHREFSLICE_COMPUTE, PUSHREF, PUSHREFSLICE, - CELL, + CELL, // TODO DELETE PUSHSLICE }; PushCellOrSlice(Type type, std::string blob, Pointer child) : @@ -519,7 +527,7 @@ class Contract : public TvmAstNode { std::map m_privateFunctions; }; -Pointer gen(const std::string& cmd); +Pointer gen(const std::string& cmd); Pointer genPushSlice(const std::string& _str); Pointer makePushCellOrSlice(std::string const& hexStr, bool toSlice); Pointer makeDROP(int cnt = 1); @@ -541,11 +549,10 @@ Pointer makeGetGlob(int i); Pointer makeSetGlob(int i); Pointer makeBLKDROP2(int droppedCount, int leftCount); Pointer makePUSHREF(const std::string& data = ""); -Pointer makeREVERSE(int i, int j); +Pointer makeREVERSE(int qty, int index); Pointer makeROT(); Pointer makeROTREV(); Pointer makeBLKSWAP(int down, int top); -Pointer makeTUCK(); Pointer makePUXC(int i, int j); Pointer makeXCPU(int i, int j); Pointer flipIfElse(TvmIfElse const& node); @@ -555,6 +562,7 @@ bool isSWAP(Pointer const& node); std::optional> isBLKSWAP(Pointer const& node); std::optional isDrop(Pointer const& node); std::optional isPOP(Pointer const& node); +std::optional isPUSH(Pointer const& node); std::optional> isBLKPUSH(Pointer const& node); bool isXCHG(Pointer const& node, int i, int j); std::optional isXCHG_S0(Pointer const& node); @@ -565,4 +573,8 @@ int getRootBitSize(PushCellOrSlice const &_node); Pointer getZeroOrNullAlignment(bool isZero, bool isSwap, bool isNot); +namespace OpcodeUtils{ + int gasCost(Stack const& opcode); +} + } // end solidity::frontend diff --git a/compiler/libsolidity/codegen/TvmAstVisitor.cpp b/compiler/libsolidity/codegen/TvmAstVisitor.cpp index 544edcf0..b122369d 100644 --- a/compiler/libsolidity/codegen/TvmAstVisitor.cpp +++ b/compiler/libsolidity/codegen/TvmAstVisitor.cpp @@ -20,7 +20,6 @@ #include #include #include "TVMCommons.hpp" -#include "TVMABI.hpp" using namespace solidity::frontend; @@ -70,7 +69,7 @@ bool Printer::visit(TvmReturn &_node) { if (_node.withAlt()) { m_out << "ALT"; } - endL(); + m_out << std::endl; return false; } @@ -92,9 +91,11 @@ bool Printer::visit(TvmException &_node) { return false; } -bool Printer::visit(GenOpcode &_node) { +bool Printer::visit(StackOpcode &_node) { tabs(); if (_node.fullOpcode() == "BITNOT") m_out << "NOT"; + //else if (_node.fullOpcode() == "STVARUINT16") m_out << "STGRAMS"; + //else if (_node.fullOpcode() == "LDVARUINT16") m_out << "LDGRAMS"; else if (_node.fullOpcode() == "TUPLE 1") m_out << "SINGLE"; else if (_node.fullOpcode() == "TUPLE 2") m_out << "PAIR"; else if (_node.fullOpcode() == "TUPLE 3") m_out << "TRIPLE"; @@ -106,8 +107,7 @@ bool Printer::visit(GenOpcode &_node) { if (ret <= 15) { m_out << "UNTUPLE " << ret; } else { - m_out << "PUSHINT " << ret; - endL(); + m_out << "PUSHINT " << ret << std::endl; tabs(); m_out << "UNTUPLEVAR"; } @@ -116,8 +116,7 @@ bool Printer::visit(GenOpcode &_node) { if (ret <= 15) { m_out << "UNPACKFIRST " << ret; } else { - m_out << "PUSHINT " << ret; - endL(); + m_out << "PUSHINT " << ret << std::endl; tabs(); m_out << "UNPACKFIRSTVAR"; } @@ -178,10 +177,10 @@ bool Printer::visit(PushCellOrSlice &_node) { m_out << ".cell {"; break; } - endL(); + m_out << std::endl; ++m_tab; - if (!_node.blob().empty()) { + if (!_node.blob().empty() && _node.blob() != "x") { tabs(); m_out << ".blob " << _node.blob() << std::endl; } @@ -237,7 +236,7 @@ bool Printer::visit(Glob &_node) { m_out << "POP C7"; break; } - endL(); + m_out << std::endl; return false; } @@ -281,181 +280,203 @@ bool Printer::visit(Stack &_node) { }; switch (_node.opcode()) { - case Stack::Opcode::DROP: { - drop(i); - break; + case Stack::Opcode::DROP: { + drop(i); + break; + } + case Stack::Opcode::PUSH_S: + solAssert(j == -1, ""); + if (i == 0) { + m_out << "DUP"; + } else if (i == 1) { + m_out << "OVER"; + } else { + m_out << "PUSH S" << i; } - case Stack::Opcode::PUSH_S: - solAssert(j == -1, ""); - if (i == 0) { - m_out << "DUP"; - } else if (i == 1) { - m_out << "OVER"; + break; + case Stack::Opcode::XCHG: { + if (i == 0) { + if (j == 1) { + m_out << "SWAP"; } else { - m_out << "PUSH S" << i; + m_out << "XCHG S" << j; } - break; - case Stack::Opcode::XCHG: { - if (i == 0) { - if (j == 1) { - m_out << "SWAP"; - } else { - m_out << "XCHG S" << j; - } + } else { + m_out << "XCHG S" << i << ", S" << j; + } + break; + } + case Stack::Opcode::BLKDROP2: + if (i > 15 || j > 15) { + printPushInt(i); + m_out << std::endl; + tabs(); + printPushInt(j); + m_out << std::endl; + tabs(); + m_out << "BLKSWX" << std::endl; + tabs(); + drop(i); + } else { + solAssert((i >= 2 && j >= 1) || (i >= 1 && j >= 2), ""); + m_out << "BLKDROP2"; + printIndexes(); + } + break; + case Stack::Opcode::PUSH2_S: + if (i == 1 && j == 0) + m_out << "DUP2"; + else if (i == 3 && j == 2) + m_out << "OVER2"; + else { + m_out << "PUSH2"; + printSS(); + } + break; + case Stack::Opcode::POP_S: + if (i == 1) { + m_out << "NIP"; + } else { + m_out << "POP"; + printSS(); + } + break; + case Stack::Opcode::BLKSWAP: { + int bottom = _node.i(); + int top = _node.j(); + if (bottom == 1 && top == 1) { + m_out << "SWAP"; + } else if (bottom == 1 && top == 2) { + m_out << "ROT"; + } else if (bottom == 2 && top == 1) { + m_out << "ROTREV"; + } else if (bottom == 2 && top == 2) { + m_out << "SWAP2"; + } else if (1 <= bottom && bottom <= 16 && 1 <= top && top <= 16) { + if (bottom == 1) { + m_out << "ROLL " << top; + } else if (top == 1) { + m_out << "ROLLREV " << bottom; } else { - m_out << "XCHG S" << i << ", S" << j; + m_out << "BLKSWAP"; + printIndexes(); } - break; - } - case Stack::Opcode::BLKDROP2: - if (i > 15 || j > 15) { - printPushInt(i); + } else { + if (bottom == 1) { + printPushInt(top); m_out << std::endl; tabs(); - printPushInt(j); + m_out << "ROLLX"; + } else if (top == 1) { + printPushInt(bottom); m_out << std::endl; tabs(); - m_out << "BLKSWX" << std::endl; - tabs(); - drop(i); - } else { - solAssert((i >= 2 && j >= 1) || (i >= 1 && j >= 2), ""); - m_out << "BLKDROP2"; - printIndexes(); - } - break; - case Stack::Opcode::PUSH2_S: - if (i == 1 && j == 0) - m_out << "DUP2"; - else if (i == 3 && j == 2) - m_out << "OVER2"; - else { - m_out << "PUSH2"; - printSS(); - } - break; - case Stack::Opcode::POP_S: - if (i == 1) { - m_out << "NIP"; - } else { - m_out << "POP"; - printSS(); - } - break; - case Stack::Opcode::BLKSWAP: { - int bottom = _node.i(); - int top = _node.j(); - if (bottom == 1 && top == 1) { - m_out << "SWAP"; - } else if (bottom == 1 && top == 2) { - m_out << "ROT"; - } else if (bottom == 2 && top == 1) { - m_out << "ROTREV"; - } else if (bottom == 2 && top == 2) { - m_out << "SWAP2"; - } else if (1 <= bottom && bottom <= 16 && 1 <= top && top <= 16) { - if (bottom == 1) { - m_out << "ROLL " << top; - } else if (top == 1) { - m_out << "ROLLREV " << bottom; - } else { - m_out << "BLKSWAP"; - printIndexes(); - } + m_out << "ROLLREVX"; } else { - if (bottom == 1) { - printPushInt(top); - m_out << std::endl; - tabs(); - m_out << "ROLLX"; - } else if (top == 1) { - printPushInt(bottom); - m_out << std::endl; - tabs(); - m_out << "ROLLREVX"; - } else { - printPushInt(bottom); - m_out << std::endl; - tabs(); - printPushInt(top); - m_out << std::endl; - tabs(); - m_out << "BLKSWX"; - } - } - break; - } - case Stack::Opcode::REVERSE: - solAssert(2 <= i, ""); - if (i == 2 && j == 0) { - m_out << "SWAP"; - } else if (i == 3 && j == 0) { - m_out << "XCHG S2"; - } else if (2 <= i && i <= 17 && 0 <= j && j <= 15) { - m_out << "REVERSE"; - printIndexes(); - } else { - printPushInt(i); + printPushInt(bottom); m_out << std::endl; tabs(); - printPushInt(j); + printPushInt(top); m_out << std::endl; tabs(); - m_out << "REVX"; + m_out << "BLKSWX"; } - break; - case Stack::Opcode::BLKPUSH: - if (i == 2 && j == 1) { - m_out << "DUP2"; - } else if (i == 2 && j == 3) { - m_out << "OVER2"; - } else { - if (i > 15) - solAssert(j == 0, ""); - int rest = i; - bool first = true; - while (rest > 0) { - if (!first) { - m_out << std::endl; - tabs(); - } - m_out << "BLKPUSH " << std::min(15, rest) << ", " << j; - - rest -= 15; - first = false; + } + break; + } + case Stack::Opcode::REVERSE: + solAssert(2 <= i, ""); + if (i == 2 && j == 0) { + m_out << "SWAP"; + } else if (i == 3 && j == 0) { + m_out << "XCHG S2"; + } else if (2 <= i && i <= 17 && 0 <= j && j <= 15) { + m_out << "REVERSE"; + printIndexes(); + } else { + printPushInt(i); + m_out << std::endl; + tabs(); + printPushInt(j); + m_out << std::endl; + tabs(); + m_out << "REVX"; + } + break; + case Stack::Opcode::BLKPUSH: + if (i == 2 && j == 1) { + m_out << "DUP2"; + } else if (i == 2 && j == 3) { + m_out << "OVER2"; + } else { + if (i > 15) + solAssert(j == 0, ""); + int rest = i; + bool first = true; + while (rest > 0) { + if (!first) { + m_out << std::endl; + tabs(); } - } - break; - case Stack::Opcode::PUSH3_S: - m_out << "PUSH3"; - printSS(); - break; + m_out << "BLKPUSH " << std::min(15, rest) << ", " << j; - case Stack::Opcode::TUCK: + rest -= 15; + first = false; + } + } + break; + case Stack::Opcode::PUSH3_S: + m_out << "PUSH3"; + printSS(); + break; + case Stack::Opcode::PUXC: + m_out << "PUXC S" << i << ", S" << j; + break; + case Stack::Opcode::XCPU: + if (i == 1 && j == 1) m_out << "TUCK"; - break; - - case Stack::Opcode::PUXC: - m_out << "PUXC S" << i << ", S" << j; - break; - - case Stack::Opcode::XCPU: + else m_out << "XCPU S" << i << ", S" << j; - break; + break; + case Stack::Opcode::XC2PU: + m_out << "XC2PU S" << i << ", S" << j << ", S" << k; + break; + case Stack::Opcode::XCHG2: + m_out << "XCHG2 S" << i << ", S" << j; + break; + case Stack::Opcode::XCHG3: + m_out << "XCHG3 S" << i << ", S" << j << ", S" << k; + break; + case Stack::Opcode::XCPU2: + m_out << "XCPU2 S" << i << ", S" << j << ", S" << k; + break; + case Stack::Opcode::PUXC2: + m_out << "PUXC2 S" << i << ", S" << j << ", S" << k; + break; + case Stack::Opcode::PUXCPU: + m_out << "PUXCPU S" << i << ", S" << j << ", S" << k; + break; + case Stack::Opcode::XCPUXC: + m_out << "XCPUXC S" << i << ", S" << j << ", S" << k; + break; + case Stack::Opcode::PU2XC: + m_out << "PU2XC S" << i << ", S" << j << ", S" << k; + break; } - endL(); + + m_out << std::endl; return false; } bool Printer::visit(CodeBlock &_node) { switch (_node.type()) { - case CodeBlock::Type::None: - break; - default: - tabs(); - m_out << CodeBlock::toString(_node.type()) << " {" << std::endl; - ++m_tab; - break; + case CodeBlock::Type::None: + break; + default: + tabs(); + m_out << CodeBlock::toString(_node.type()) << " {" << std::endl; + ++m_tab; + break; } for (Pointer const& inst : _node.instructions()) { @@ -477,38 +498,38 @@ bool Printer::visit(CodeBlock &_node) { bool Printer::visit(SubProgram &_node) { switch (_node.block()->type()) { - case CodeBlock::Type::None: - solUnimplemented(""); - case CodeBlock::Type::PUSHCONT: - _node.block()->accept(*this); + case CodeBlock::Type::None: + solUnimplemented(""); + case CodeBlock::Type::PUSHCONT: + _node.block()->accept(*this); - tabs(); - if (_node.isJmp()) { - m_out << "JMPX"; - } else { - m_out << "CALLX"; - } - endL(); + tabs(); + if (_node.isJmp()) { + m_out << "JMPX"; + } else { + m_out << "CALLX"; + } + m_out << std::endl; - break; - case CodeBlock::Type::PUSHREFCONT: - tabs(); - if (_node.isJmp()) { - m_out << "JMPREF {"; - } else { - m_out << "CALLREF {"; - } - endL(); + break; + case CodeBlock::Type::PUSHREFCONT: + tabs(); + if (_node.isJmp()) { + m_out << "JMPREF {"; + } else { + m_out << "CALLREF {"; + } + m_out << std::endl; - ++m_tab; - for (Pointer const& i : _node.block()->instructions()) { - i->accept(*this); - } - --m_tab; + ++m_tab; + for (Pointer const& i : _node.block()->instructions()) { + i->accept(*this); + } + --m_tab; - tabs(); - m_out << "}" << std::endl; - break; + tabs(); + m_out << "}" << std::endl; + break; } return false; } @@ -540,73 +561,79 @@ bool Printer::visit(LogCircuit &_node) { bool Printer::visit(TvmIfElse &_node) { if (_node.falseBody() == nullptr) { - switch (_node.trueBody()->type()) { - case CodeBlock::Type::None: - solUnimplemented(""); - break; - case CodeBlock::Type::PUSHCONT: - _node.trueBody()->accept(*this); + switch (_node.trueBody()->type()) { + case CodeBlock::Type::None: + solUnimplemented(""); + break; + case CodeBlock::Type::PUSHCONT: + _node.trueBody()->accept(*this); - tabs(); - m_out << "IF"; - if (_node.withNot()) - m_out << "NOT"; - if (_node.withJmp()) - m_out << "JMP"; - endL(); - - break; - case CodeBlock::Type::PUSHREFCONT: - tabs(); - m_out << "IF"; - if (_node.withNot()) - m_out << "NOT"; - if (_node.withJmp()) - m_out << "JMP"; - m_out << "REF {"; - endL(); + tabs(); + m_out << "IF"; + if (_node.withNot()) + m_out << "NOT"; + if (_node.withJmp()) + m_out << "JMP"; + m_out << std::endl; - ++m_tab; - for (Pointer const& i : _node.trueBody()->instructions()) { - i->accept(*this); - } - --m_tab; + break; + case CodeBlock::Type::PUSHREFCONT: + tabs(); + m_out << "IF"; + if (_node.withNot()) + m_out << "NOT"; + if (_node.withJmp()) + m_out << "JMP"; + m_out << "REF {" << std::endl; - tabs(); - m_out << "}" << std::endl; - break; + ++m_tab; + for (Pointer const& i : _node.trueBody()->instructions()) { + i->accept(*this); + } + --m_tab; + + tabs(); + m_out << "}" << std::endl; + break; } } else { if (_node.trueBody()->type() == CodeBlock::Type::PUSHREFCONT && _node.falseBody()->type() == CodeBlock::Type::PUSHREFCONT ) { + tabs(); m_out << "IFREFELSEREF" << std::endl; for (Pointer const& body : {_node.trueBody(), _node.falseBody()}) { + tabs(); m_out << "{" << std::endl; ++m_tab; for (Pointer const &n: body->instructions()) { n->accept(*this); } --m_tab; + tabs(); m_out << "}" << std::endl; } } else if (_node.trueBody()->type() == CodeBlock::Type::PUSHREFCONT) { _node.falseBody()->accept(*this); + tabs(); m_out << "IFREFELSE {" << std::endl; ++m_tab; for (Pointer const& n : _node.trueBody()->instructions()) { n->accept(*this); } --m_tab; + tabs(); m_out << "}" << std::endl; } else if (_node.falseBody()->type() == CodeBlock::Type::PUSHREFCONT) { _node.trueBody()->accept(*this); + tabs(); m_out << "IFELSEREF {" << std::endl; ++m_tab; for (Pointer const& n : _node.falseBody()->instructions()) { n->accept(*this); } --m_tab; + tabs(); m_out << "}" << std::endl; } else { _node.trueBody()->accept(*this); @@ -615,8 +642,7 @@ bool Printer::visit(TvmIfElse &_node) { solUnimplemented(""); tabs(); - m_out << "IFELSE"; - endL(); + m_out << "IFELSE" << std::endl; } } return false; @@ -703,17 +729,6 @@ bool Printer::visit(Contract &_node) { m_out << std::endl; } - m_out << ".fragment default_data_dict_cell, {" << std::endl; - m_out << " PUSHINT 0" << std::endl; - m_out << " NEWC" << std::endl; - m_out << " STU 256" << std::endl; - m_out << " PUSHINT 0" << std::endl; - m_out << " NEWDICT" << std::endl; - m_out << " PUSHINT 64" << std::endl; - m_out << " DICTUSETB" << std::endl; - m_out << "}" << std::endl; - m_out << std::endl; - m_out << "; The code below forms a value of the StateInit type." << std::endl; m_out << ".blob x4_ ; split_depth = nothing" << std::endl; m_out << ".blob x4_ ; special = nothing" << std::endl; @@ -786,10 +801,7 @@ bool Printer::visit(Contract &_node) { m_out << ".blob xc_ ; data = just" << std::endl; m_out << ".cell { " << std::endl; - m_out << " .blob xc_" << std::endl; - m_out << " .cell { " << std::endl; - m_out << " .inline-computed-cell default_data_dict_cell, 0" << std::endl; - m_out << " }" << std::endl; + m_out << " .inline-computed-cell default_data_cell, 0" << std::endl; m_out << "}" << std::endl; m_out << ".blob x4_ ; library = hme_empty" << std::endl; } @@ -811,10 +823,6 @@ bool Printer::visitNode(TvmAstNode const&) { solUnimplemented(""); } -void Printer::endL() { - m_out << std::endl; -} - void Printer::tabs() { solAssert(m_tab >= 0, ""); m_out << std::string(m_tab, '\t'); @@ -1003,9 +1011,8 @@ bool LogCircuitExpander::isPureOperation(Pointer const& op) { return true; } - auto stack = to(op.get()); - if (stack && stack->opcode() == Stack::Opcode::PUSH_S) { - int index = stack->i(); + if (auto push = isPUSH(op)) { + int index = *push; if (index + 1 < m_stackSize) { m_newInst.emplace_back(makePUSH(index)); } else { diff --git a/compiler/libsolidity/codegen/TvmAstVisitor.hpp b/compiler/libsolidity/codegen/TvmAstVisitor.hpp index faeb362f..992d4672 100644 --- a/compiler/libsolidity/codegen/TvmAstVisitor.hpp +++ b/compiler/libsolidity/codegen/TvmAstVisitor.hpp @@ -37,7 +37,7 @@ class TvmAstVisitor { virtual bool visit(TvmReturn &_node) { return visitNode(_node); } virtual bool visit(ReturnOrBreakOrCont &_node) { return visitNode(_node); } virtual bool visit(TvmException &_node) { return visitNode(_node); } - virtual bool visit(GenOpcode &_node) { return visitNode(_node); } + virtual bool visit(StackOpcode &_node) { return visitNode(_node); } virtual bool visit(PushCellOrSlice &_node) { return visitNode(_node); } virtual bool visit(Glob &_node) { return visitNode(_node); } virtual bool visit(Stack &_node) { return visitNode(_node); } @@ -69,7 +69,7 @@ class Printer : public TvmAstVisitor { bool visit(TvmReturn &_node) override; bool visit(ReturnOrBreakOrCont &_node) override; bool visit(TvmException &_node) override; - bool visit(GenOpcode &_node) override; + bool visit(StackOpcode &_node) override; bool visit(PushCellOrSlice &_node) override; bool visit(Glob &_node) override; bool visit(Stack &_node) override; @@ -86,7 +86,6 @@ class Printer : public TvmAstVisitor { protected: bool visitNode(TvmAstNode const&) override; private: - void endL(); void tabs(); void printPushInt(std::string const& arg, std::string const& comment = ""); void printPushInt(int i); diff --git a/compiler/libsolidity/experimental/analysis/Analysis.cpp b/compiler/libsolidity/experimental/analysis/Analysis.cpp new file mode 100644 index 00000000..44170067 --- /dev/null +++ b/compiler/libsolidity/experimental/analysis/Analysis.cpp @@ -0,0 +1,204 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#include +#include +#include +#include +#include +#include +#include + +using namespace solidity::langutil; +using namespace solidity::frontend::experimental; + +// TODO: creating all of them for all nodes up front may be wasteful, we should improve the mechanism. +struct Analysis::AnnotationContainer +{ + TypeClassRegistration::Annotation typeClassRegistrationAnnotation; + TypeRegistration::Annotation typeRegistrationAnnotation; + TypeInference::Annotation typeInferenceAnnotation; +}; + +struct Analysis::GlobalAnnotationContainer +{ + FunctionDependencyAnalysis::GlobalAnnotation functionDependencyGraphAnnotation; + TypeClassRegistration::GlobalAnnotation typeClassRegistrationAnnotation; + TypeRegistration::GlobalAnnotation typeRegistrationAnnotation; + TypeInference::GlobalAnnotation typeInferenceAnnotation; +}; + +template<> +TypeClassRegistration::Annotation& solidity::frontend::experimental::detail::AnnotationFetcher::get(ASTNode const& _node) +{ + return analysis.annotationContainer(_node).typeClassRegistrationAnnotation; +} + +template<> +TypeClassRegistration::GlobalAnnotation const& solidity::frontend::experimental::detail::ConstAnnotationFetcher::get() const +{ + return analysis.annotationContainer().typeClassRegistrationAnnotation; +} + +template<> +TypeClassRegistration::GlobalAnnotation& solidity::frontend::experimental::detail::AnnotationFetcher::get() +{ + return analysis.annotationContainer().typeClassRegistrationAnnotation; +} + +template<> +TypeClassRegistration::Annotation const& solidity::frontend::experimental::detail::ConstAnnotationFetcher::get(ASTNode const& _node) const +{ + return analysis.annotationContainer(_node).typeClassRegistrationAnnotation; +} + +template<> +FunctionDependencyAnalysis::GlobalAnnotation const& solidity::frontend::experimental::detail::ConstAnnotationFetcher::get() const +{ + return analysis.annotationContainer().functionDependencyGraphAnnotation; +} + +template<> +FunctionDependencyAnalysis::GlobalAnnotation& solidity::frontend::experimental::detail::AnnotationFetcher::get() +{ + return analysis.annotationContainer().functionDependencyGraphAnnotation; +} + +template<> +TypeRegistration::Annotation& solidity::frontend::experimental::detail::AnnotationFetcher::get(ASTNode const& _node) +{ + return analysis.annotationContainer(_node).typeRegistrationAnnotation; +} + +template<> +TypeRegistration::GlobalAnnotation const& solidity::frontend::experimental::detail::ConstAnnotationFetcher::get() const +{ + return analysis.annotationContainer().typeRegistrationAnnotation; +} + +template<> +TypeRegistration::GlobalAnnotation& solidity::frontend::experimental::detail::AnnotationFetcher::get() +{ + return analysis.annotationContainer().typeRegistrationAnnotation; +} + +template<> +TypeRegistration::Annotation const& solidity::frontend::experimental::detail::ConstAnnotationFetcher::get(ASTNode const& _node) const +{ + return analysis.annotationContainer(_node).typeRegistrationAnnotation; +} + +template<> +TypeInference::Annotation& solidity::frontend::experimental::detail::AnnotationFetcher::get(ASTNode const& _node) +{ + return analysis.annotationContainer(_node).typeInferenceAnnotation; +} + +template<> +TypeInference::Annotation const& solidity::frontend::experimental::detail::ConstAnnotationFetcher::get(ASTNode const& _node) const +{ + return analysis.annotationContainer(_node).typeInferenceAnnotation; +} + +template<> +TypeInference::GlobalAnnotation const& solidity::frontend::experimental::detail::ConstAnnotationFetcher::get() const +{ + return analysis.annotationContainer().typeInferenceAnnotation; +} + +template<> +TypeInference::GlobalAnnotation& solidity::frontend::experimental::detail::AnnotationFetcher::get() +{ + return analysis.annotationContainer().typeInferenceAnnotation; +} + +Analysis::AnnotationContainer& Analysis::annotationContainer(ASTNode const& _node) +{ + solAssert(_node.id() > 0); + size_t id = static_cast(_node.id()); + solAssert(id <= m_maxAstId); + return m_annotations[id]; +} + +Analysis::AnnotationContainer const& Analysis::annotationContainer(ASTNode const& _node) const +{ + solAssert(_node.id() > 0); + size_t id = static_cast(_node.id()); + solAssert(id <= m_maxAstId); + return m_annotations[id]; +} + +Analysis::Analysis(langutil::ErrorReporter& _errorReporter, uint64_t _maxAstId): + m_errorReporter(_errorReporter), + m_maxAstId(_maxAstId), + m_annotations(std::make_unique(static_cast(_maxAstId + 1))), + m_globalAnnotation(std::make_unique()) +{ +} + +Analysis::~Analysis() +{} + +template +std::tuple...> makeIndexTuple(std::index_sequence) { + return std::make_tuple( std::integral_constant{}...); +} + +bool Analysis::check(std::vector> const& _sourceUnits) +{ + using AnalysisSteps = std::tuple< + SyntaxRestrictor, + TypeClassRegistration, + TypeRegistration, + // TODO move after step introduced in https://github.com/ethereum/solidity/pull/14578, but before TypeInference + FunctionDependencyAnalysis, + TypeInference, + DebugWarner + >; + + return std::apply([&](auto... _indexTuple) { + return ([&](auto&& _step) { + for (auto source: _sourceUnits) + if (!_step.analyze(*source)) + return false; + return true; + }(std::tuple_element_t{*this}) && ...); + }, makeIndexTuple(std::make_index_sequence>{})); + +/* + { + SyntaxRestrictor syntaxRestrictor{*this}; + for (auto source: _sourceUnits) + if (!syntaxRestrictor.analyze(*source)) + return false; + } + + { + TypeRegistration typeRegistration{*this}; + for (auto source: _sourceUnits) + if (!typeRegistration.analyze(*source)) + return false; + } + { + TypeInference typeInference{*this}; + for (auto source: _sourceUnits) + if (!typeInference.analyze(*source)) + return false; + } + return true; + */ +} diff --git a/compiler/libsolidity/experimental/analysis/Analysis.h b/compiler/libsolidity/experimental/analysis/Analysis.h new file mode 100644 index 00000000..dde3a24e --- /dev/null +++ b/compiler/libsolidity/experimental/analysis/Analysis.h @@ -0,0 +1,115 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include + +#include +#include +#include + +namespace solidity::frontend +{ +class SourceUnit; +class ASTNode; +} + +namespace solidity::langutil +{ +class ErrorReporter; +} + +namespace solidity::frontend::experimental +{ + +class TypeSystem; + +class Analysis; + +namespace detail +{ +template +struct AnnotationFetcher +{ + Analysis& analysis; + typename Step::Annotation& get(ASTNode const& _node); + typename Step::GlobalAnnotation& get(); +}; +template +struct ConstAnnotationFetcher +{ + Analysis const& analysis; + typename Step::Annotation const& get(ASTNode const& _node) const; + typename Step::GlobalAnnotation const& get() const; +}; +} + +class Analysis +{ + struct AnnotationContainer; + struct GlobalAnnotationContainer; + +public: + Analysis(langutil::ErrorReporter& _errorReporter, uint64_t _maxAstId); + Analysis(Analysis const&) = delete; + ~Analysis(); + Analysis const& operator=(Analysis const&) = delete; + bool check(std::vector> const& _sourceUnits); + langutil::ErrorReporter& errorReporter() { return m_errorReporter; } + uint64_t maxAstId() const { return m_maxAstId; } + TypeSystem& typeSystem() { return m_typeSystem; } + TypeSystem const& typeSystem() const { return m_typeSystem; } + + template + typename Step::Annotation& annotation(ASTNode const& _node) + { + return detail::AnnotationFetcher{*this}.get(_node); + } + + template + typename Step::Annotation const& annotation(ASTNode const& _node) const + { + return detail::ConstAnnotationFetcher{*this}.get(_node); + } + + template + typename Step::GlobalAnnotation& annotation() + { + return detail::AnnotationFetcher{*this}.get(); + } + + template + typename Step::GlobalAnnotation const& annotation() const + { + return detail::ConstAnnotationFetcher{*this}.get(); + } + + AnnotationContainer& annotationContainer(ASTNode const& _node); + AnnotationContainer const& annotationContainer(ASTNode const& _node) const; + GlobalAnnotationContainer& annotationContainer() { return *m_globalAnnotation; } + GlobalAnnotationContainer const& annotationContainer() const { return *m_globalAnnotation; } + +private: + langutil::ErrorReporter& m_errorReporter; + TypeSystem m_typeSystem; + uint64_t m_maxAstId = 0; + std::unique_ptr m_annotations; + std::unique_ptr m_globalAnnotation; +}; + +} diff --git a/compiler/libsolidity/experimental/analysis/DebugWarner.cpp b/compiler/libsolidity/experimental/analysis/DebugWarner.cpp new file mode 100644 index 00000000..2b5c23b9 --- /dev/null +++ b/compiler/libsolidity/experimental/analysis/DebugWarner.cpp @@ -0,0 +1,50 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include +#include +#include + +#include + +using namespace solidity::frontend; +using namespace solidity::frontend::experimental; +using namespace solidity::langutil; + +DebugWarner::DebugWarner(Analysis& _analysis): m_analysis(_analysis), m_errorReporter(_analysis.errorReporter()) +{} + +bool DebugWarner::analyze(ASTNode const& _astRoot) +{ + _astRoot.accept(*this); + return !Error::containsErrors(m_errorReporter.errors()); +} + +bool DebugWarner::visitNode(ASTNode const& _node) +{ + std::optional const& inferredType = m_analysis.annotation(_node).type; + if (inferredType.has_value()) + m_errorReporter.info( + 4164_error, + _node.location(), + "Inferred type: " + TypeEnvironmentHelpers{m_analysis.typeSystem().env()}.typeToString(*inferredType) + ); + return true; +} diff --git a/compiler/libsolidity/experimental/analysis/DebugWarner.h b/compiler/libsolidity/experimental/analysis/DebugWarner.h new file mode 100644 index 00000000..a7b5ac6d --- /dev/null +++ b/compiler/libsolidity/experimental/analysis/DebugWarner.h @@ -0,0 +1,42 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include + +#include + +namespace solidity::frontend::experimental +{ +class Analysis; + +class DebugWarner: public ASTConstVisitor +{ +public: + DebugWarner(Analysis& _analysis); + + bool analyze(ASTNode const& _astRoot); + +private: + bool visitNode(ASTNode const& _node) override; + + Analysis& m_analysis; + langutil::ErrorReporter& m_errorReporter; +}; + +} diff --git a/compiler/libsolidity/experimental/analysis/FunctionDependencyAnalysis.cpp b/compiler/libsolidity/experimental/analysis/FunctionDependencyAnalysis.cpp new file mode 100644 index 00000000..332f7620 --- /dev/null +++ b/compiler/libsolidity/experimental/analysis/FunctionDependencyAnalysis.cpp @@ -0,0 +1,72 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include +#include + +using namespace solidity::frontend::experimental; +using namespace solidity::util; + +FunctionDependencyAnalysis::FunctionDependencyAnalysis(Analysis& _analysis): + m_analysis(_analysis), + m_errorReporter(_analysis.errorReporter()) +{ +} + +bool FunctionDependencyAnalysis::analyze(SourceUnit const& _sourceUnit) +{ + _sourceUnit.accept(*this); + return !m_errorReporter.hasErrors(); +} + +bool FunctionDependencyAnalysis::visit(FunctionDefinition const& _functionDefinition) +{ + solAssert(!m_currentFunction); + m_currentFunction = &_functionDefinition; + // Insert a function definition pointer that maps to an empty set; the pointed to set will later be + // populated in ``endVisit(Identifier const& _identifier)`` if ``m_currentFunction`` references another. + auto [_, inserted] = annotation().functionCallGraph.edges.try_emplace( + m_currentFunction, std::set>{} + ); + solAssert(inserted); + return true; +} + +void FunctionDependencyAnalysis::endVisit(FunctionDefinition const&) +{ + m_currentFunction = nullptr; +} + +void FunctionDependencyAnalysis::endVisit(Identifier const& _identifier) +{ + auto const* callee = dynamic_cast(_identifier.annotation().referencedDeclaration); + // Check that the identifier is within a function body and is a function, and add it to the graph + // as an ``m_currentFunction`` -> ``callee`` edge. + if (m_currentFunction && callee) + addEdge(m_currentFunction, callee); +} + +void FunctionDependencyAnalysis::addEdge(FunctionDefinition const* _caller, FunctionDefinition const* _callee) +{ + annotation().functionCallGraph.edges[_caller].insert(_callee); +} + +FunctionDependencyAnalysis::GlobalAnnotation& FunctionDependencyAnalysis::annotation() +{ + return m_analysis.annotation(); +} diff --git a/compiler/libsolidity/experimental/analysis/FunctionDependencyAnalysis.h b/compiler/libsolidity/experimental/analysis/FunctionDependencyAnalysis.h new file mode 100644 index 00000000..acd3f1b2 --- /dev/null +++ b/compiler/libsolidity/experimental/analysis/FunctionDependencyAnalysis.h @@ -0,0 +1,57 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include +#include +#include +#include + +#include + +namespace solidity::frontend::experimental +{ + +class Analysis; + +class FunctionDependencyAnalysis: private ASTConstVisitor +{ +public: + FunctionDependencyAnalysis(Analysis& _analysis); + bool analyze(SourceUnit const& _sourceUnit); + + struct Annotation {}; + struct GlobalAnnotation + { + FunctionDependencyGraph functionCallGraph; + }; + +private: + bool visit(FunctionDefinition const& _functionDefinition) override; + void endVisit(FunctionDefinition const&) override; + void endVisit(Identifier const& _identifier) override; + void addEdge(FunctionDefinition const* _caller, FunctionDefinition const* _callee); + GlobalAnnotation& annotation(); + + Analysis& m_analysis; + langutil::ErrorReporter& m_errorReporter; + FunctionDefinition const* m_currentFunction = nullptr; +}; + +} diff --git a/compiler/libsolidity/experimental/analysis/SyntaxRestrictor.cpp b/compiler/libsolidity/experimental/analysis/SyntaxRestrictor.cpp new file mode 100644 index 00000000..41961528 --- /dev/null +++ b/compiler/libsolidity/experimental/analysis/SyntaxRestrictor.cpp @@ -0,0 +1,111 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include + +#include + +using namespace solidity::frontend; +using namespace solidity::frontend::experimental; +using namespace solidity::langutil; + +SyntaxRestrictor::SyntaxRestrictor(Analysis& _analysis): m_errorReporter(_analysis.errorReporter()) +{} + +bool SyntaxRestrictor::analyze(ASTNode const& _astRoot) +{ + _astRoot.accept(*this); + return !Error::containsErrors(m_errorReporter.errors()); +} + +bool SyntaxRestrictor::visitNode(ASTNode const& _node) +{ + if (!_node.experimentalSolidityOnly()) + m_errorReporter.syntaxError(9282_error, _node.location(), "Unsupported AST node."); + return false; +} + +bool SyntaxRestrictor::visit(ContractDefinition const& _contractDefinition) +{ + if (_contractDefinition.contractKind() != ContractKind::Contract) + m_errorReporter.syntaxError(9159_error, _contractDefinition.location(), "Only contracts are supported."); + if (!_contractDefinition.baseContracts().empty()) + m_errorReporter.syntaxError(5731_error, _contractDefinition.location(), "Inheritance unsupported."); + return true; +} + +bool SyntaxRestrictor::visit(FunctionDefinition const& _functionDefinition) +{ + if (!_functionDefinition.isImplemented()) + m_errorReporter.syntaxError(1741_error, _functionDefinition.location(), "Functions must be implemented."); + if (!_functionDefinition.modifiers().empty()) + m_errorReporter.syntaxError(9988_error, _functionDefinition.location(), "Function may not have modifiers."); + if (_functionDefinition.overrides()) + m_errorReporter.syntaxError(5044_error, _functionDefinition.location(), "Function may not have override specifiers."); + solAssert(!_functionDefinition.returnParameterList()); + if (_functionDefinition.isFree()) + { + if (_functionDefinition.stateMutability() != StateMutability::NonPayable) + m_errorReporter.syntaxError(5714_error, _functionDefinition.location(), "Free functions may not have a mutability."); + } + else + { + if (_functionDefinition.isFallback()) + { + if (_functionDefinition.visibility() != Visibility::External) + m_errorReporter.syntaxError(7341_error, _functionDefinition.location(), "Fallback function must be external."); + } + else + m_errorReporter.syntaxError(4496_error, _functionDefinition.location(), "Only fallback functions are supported in contracts."); + } + + return true; +} + +bool SyntaxRestrictor::visit(VariableDeclarationStatement const& _variableDeclarationStatement) +{ + if (_variableDeclarationStatement.declarations().size() == 1) + { + if (!_variableDeclarationStatement.declarations().front()) + m_errorReporter.syntaxError(9658_error, _variableDeclarationStatement.initialValue()->location(), "Variable declaration has to declare a single variable."); + } + else + m_errorReporter.syntaxError(3520_error, _variableDeclarationStatement.initialValue()->location(), "Variable declarations can only declare a single variable."); + return true; +} + +bool SyntaxRestrictor::visit(VariableDeclaration const& _variableDeclaration) +{ + if (_variableDeclaration.value()) + m_errorReporter.syntaxError(1801_error, _variableDeclaration.value()->location(), "Variable declarations with initial value not supported."); + if (_variableDeclaration.isStateVariable()) + m_errorReporter.syntaxError(6388_error, _variableDeclaration.location(), "State variables are not supported."); + if (!_variableDeclaration.isLocalVariable()) + m_errorReporter.syntaxError(8953_error, _variableDeclaration.location(), "Only local variables are supported."); + if (_variableDeclaration.mutability() != VariableDeclaration::Mutability::Mutable) + m_errorReporter.syntaxError(2934_error, _variableDeclaration.location(), "Only mutable variables are supported."); + if (_variableDeclaration.isIndexed()) + m_errorReporter.syntaxError(9603_error, _variableDeclaration.location(), "Indexed variables are not supported."); + if (!_variableDeclaration.noVisibilitySpecified()) + m_errorReporter.syntaxError(8809_error, _variableDeclaration.location(), "Variables with visibility not supported."); + if (_variableDeclaration.overrides()) + m_errorReporter.syntaxError(6175_error, _variableDeclaration.location(), "Variables with override specifier not supported."); + return true; +} diff --git a/compiler/libsolidity/experimental/analysis/SyntaxRestrictor.h b/compiler/libsolidity/experimental/analysis/SyntaxRestrictor.h new file mode 100644 index 00000000..8fdaa11b --- /dev/null +++ b/compiler/libsolidity/experimental/analysis/SyntaxRestrictor.h @@ -0,0 +1,66 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include + +#include +#include + +namespace solidity::frontend::experimental +{ +class Analysis; + +class SyntaxRestrictor: public ASTConstVisitor +{ +public: + SyntaxRestrictor(Analysis& _analysis); + + bool analyze(ASTNode const& _astRoot); + +private: + /// Default visit will reject all AST nodes that are not explicitly allowed. + bool visitNode(ASTNode const& _node) override; + + bool visit(SourceUnit const&) override { return true; } + bool visit(PragmaDirective const&) override { return true; } + bool visit(ImportDirective const&) override { return true; } + bool visit(ContractDefinition const& _contractDefinition) override; + bool visit(FunctionDefinition const& _functionDefinition) override; + bool visit(ExpressionStatement const&) override { return true; } + bool visit(FunctionCall const&) override { return true; } + bool visit(Assignment const&) override { return true; } + bool visit(Block const&) override { return true; } + bool visit(Identifier const&) override { return true; } + bool visit(IdentifierPath const&) override { return true; } + bool visit(IfStatement const&) override { return true; } + bool visit(VariableDeclarationStatement const&) override; + bool visit(VariableDeclaration const&) override; + bool visit(ElementaryTypeName const&) override { return true; } + bool visit(ParameterList const&) override { return true; } + bool visit(Return const&) override { return true; } + bool visit(MemberAccess const&) override { return true; } + bool visit(BinaryOperation const&) override { return true; } + bool visit(ElementaryTypeNameExpression const&) override { return true; } + bool visit(TupleExpression const&) override { return true; } + bool visit(Literal const&) override { return true; } + + langutil::ErrorReporter& m_errorReporter; +}; + +} diff --git a/compiler/libsolidity/experimental/analysis/TypeClassRegistration.cpp b/compiler/libsolidity/experimental/analysis/TypeClassRegistration.cpp new file mode 100644 index 00000000..fe70a44d --- /dev/null +++ b/compiler/libsolidity/experimental/analysis/TypeClassRegistration.cpp @@ -0,0 +1,62 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + + +#include + +#include + +#include +#include + +using namespace solidity::frontend::experimental; +using namespace solidity::langutil; + +TypeClassRegistration::TypeClassRegistration(Analysis& _analysis): + m_analysis(_analysis), + m_errorReporter(_analysis.errorReporter()), + m_typeSystem(_analysis.typeSystem()) +{ +} + +bool TypeClassRegistration::analyze(SourceUnit const& _sourceUnit) +{ + _sourceUnit.accept(*this); + return !m_errorReporter.hasErrors(); +} + +bool TypeClassRegistration::visit(TypeClassDefinition const& _typeClassDefinition) +{ + std::variant typeClassOrError = m_typeSystem.declareTypeClass( + _typeClassDefinition.name(), + &_typeClassDefinition + ); + + m_analysis.annotation(_typeClassDefinition).typeClass = std::visit( + util::GenericVisitor{ + [](TypeClass _class) -> TypeClass { return _class; }, + [&](std::string _error) -> TypeClass { + m_errorReporter.fatalTypeError(4767_error, _typeClassDefinition.location(), _error); + util::unreachable(); + } + }, + typeClassOrError + ); + + return true; +} diff --git a/compiler/libsolidity/experimental/analysis/TypeClassRegistration.h b/compiler/libsolidity/experimental/analysis/TypeClassRegistration.h new file mode 100644 index 00000000..c0c1c354 --- /dev/null +++ b/compiler/libsolidity/experimental/analysis/TypeClassRegistration.h @@ -0,0 +1,58 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include +#include + +namespace solidity::langutil +{ +class ErrorReporter; +} + +namespace solidity::frontend::experimental +{ + +class Analysis; +class TypeSystem; + +class TypeClassRegistration: public ASTConstVisitor +{ +public: + struct Annotation + { + // Type classes. + std::optional typeClass; + }; + struct GlobalAnnotation + { + }; + + TypeClassRegistration(Analysis& _analysis); + + bool analyze(SourceUnit const& _sourceUnit); + +private: + bool visit(TypeClassDefinition const& _typeClassDefinition) override; + + Analysis& m_analysis; + langutil::ErrorReporter& m_errorReporter; + TypeSystem& m_typeSystem; +}; + +} diff --git a/compiler/libsolidity/experimental/analysis/TypeInference.cpp b/compiler/libsolidity/experimental/analysis/TypeInference.cpp new file mode 100644 index 00000000..a82496b7 --- /dev/null +++ b/compiler/libsolidity/experimental/analysis/TypeInference.cpp @@ -0,0 +1,1160 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +using namespace solidity; +using namespace solidity::frontend; +using namespace solidity::frontend::experimental; +using namespace solidity::langutil; + +TypeInference::TypeInference(Analysis& _analysis): + m_analysis(_analysis), + m_errorReporter(_analysis.errorReporter()), + m_typeSystem(_analysis.typeSystem()), + m_env(&m_typeSystem.env()), + m_voidType(m_typeSystem.type(PrimitiveType::Void, {})), + m_wordType(m_typeSystem.type(PrimitiveType::Word, {})), + m_integerType(m_typeSystem.type(PrimitiveType::Integer, {})), + m_unitType(m_typeSystem.type(PrimitiveType::Unit, {})), + m_boolType(m_typeSystem.type(PrimitiveType::Bool, {})) +{ + TypeSystemHelpers helper{m_typeSystem}; + + auto declareBuiltinClass = [&](std::string _name, BuiltinClass _class) -> TypeClass { + auto result = m_typeSystem.declareTypeClass(_name, nullptr); + if (auto error = std::get_if(&result)) + solAssert(!error, *error); + TypeClass declaredClass = std::get(result); + // TODO: validation? + solAssert(annotation().builtinClassesByName.emplace(_name, _class).second); + return annotation().builtinClasses.emplace(_class, declaredClass).first->second; + }; + + auto registeredTypeClass = [&](BuiltinClass _builtinClass) -> TypeClass { + return annotation().builtinClasses.at(_builtinClass); + }; + + auto defineConversion = [&](BuiltinClass _builtinClass, PrimitiveType _fromType, std::string _functionName) { + annotation().typeClassFunctions[registeredTypeClass(_builtinClass)] = {{ + std::move(_functionName), + helper.functionType( + m_typeSystem.type(_fromType, {}), + m_typeSystem.typeClassInfo(registeredTypeClass(_builtinClass)).typeVariable + ), + }}; + }; + + auto defineBinaryMonoidalOperator = [&](BuiltinClass _builtinClass, Token _token, std::string _functionName) { + Type typeVar = m_typeSystem.typeClassInfo(registeredTypeClass(_builtinClass)).typeVariable; + annotation().operators.emplace(_token, std::make_tuple(registeredTypeClass(_builtinClass), _functionName)); + annotation().typeClassFunctions[registeredTypeClass(_builtinClass)] = {{ + std::move(_functionName), + helper.functionType( + helper.tupleType({typeVar, typeVar}), + typeVar + ) + }}; + }; + + auto defineBinaryCompareOperator = [&](BuiltinClass _builtinClass, Token _token, std::string _functionName) { + Type typeVar = m_typeSystem.typeClassInfo(registeredTypeClass(_builtinClass)).typeVariable; + annotation().operators.emplace(_token, std::make_tuple(registeredTypeClass(_builtinClass), _functionName)); + annotation().typeClassFunctions[registeredTypeClass(_builtinClass)] = {{ + std::move(_functionName), + helper.functionType( + helper.tupleType({typeVar, typeVar}), + m_typeSystem.type(PrimitiveType::Bool, {}) + ) + }}; + }; + + declareBuiltinClass("integer", BuiltinClass::Integer); + declareBuiltinClass("*", BuiltinClass::Mul); + declareBuiltinClass("+", BuiltinClass::Add); + declareBuiltinClass("==", BuiltinClass::Equal); + declareBuiltinClass("<", BuiltinClass::Less); + declareBuiltinClass("<=", BuiltinClass::LessOrEqual); + declareBuiltinClass(">", BuiltinClass::Greater); + declareBuiltinClass(">=", BuiltinClass::GreaterOrEqual); + + defineConversion(BuiltinClass::Integer, PrimitiveType::Integer, "fromInteger"); + + defineBinaryMonoidalOperator(BuiltinClass::Mul, Token::Mul, "mul"); + defineBinaryMonoidalOperator(BuiltinClass::Add, Token::Add, "add"); + + defineBinaryCompareOperator(BuiltinClass::Equal, Token::Equal, "eq"); + defineBinaryCompareOperator(BuiltinClass::Less, Token::LessThan, "lt"); + defineBinaryCompareOperator(BuiltinClass::LessOrEqual, Token::LessThanOrEqual, "leq"); + defineBinaryCompareOperator(BuiltinClass::Greater, Token::GreaterThan, "gt"); + defineBinaryCompareOperator(BuiltinClass::GreaterOrEqual, Token::GreaterThanOrEqual, "geq"); +} + +bool TypeInference::analyze(SourceUnit const& _sourceUnit) +{ + _sourceUnit.accept(*this); + return !m_errorReporter.hasErrors(); +} + +bool TypeInference::visit(FunctionDefinition const& _functionDefinition) +{ + solAssert(m_expressionContext == ExpressionContext::Term); + auto& functionAnnotation = annotation(_functionDefinition); + if (functionAnnotation.type) + return false; + + ScopedSaveAndRestore signatureRestore(m_currentFunctionType, std::nullopt); + + Type argumentsType = m_typeSystem.freshTypeVariable({}); + Type returnType = m_typeSystem.freshTypeVariable({}); + Type functionType = TypeSystemHelpers{m_typeSystem}.functionType(argumentsType, returnType); + + m_currentFunctionType = functionType; + functionAnnotation.type = functionType; + + + _functionDefinition.parameterList().accept(*this); + unify(argumentsType, type(_functionDefinition.parameterList()), _functionDefinition.parameterList().location()); + if (_functionDefinition.experimentalReturnExpression()) + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Type}; + _functionDefinition.experimentalReturnExpression()->accept(*this); + unify( + returnType, + type(*_functionDefinition.experimentalReturnExpression()), + _functionDefinition.experimentalReturnExpression()->location() + ); + } + else + unify(returnType, m_unitType, _functionDefinition.location()); + + if (_functionDefinition.isImplemented()) + _functionDefinition.body().accept(*this); + + return false; +} + +void TypeInference::endVisit(Return const& _return) +{ + solAssert(m_currentFunctionType); + Type functionReturnType = std::get<1>(TypeSystemHelpers{m_typeSystem}.destFunctionType(*m_currentFunctionType)); + if (_return.expression()) + unify(functionReturnType, type(*_return.expression()), _return.location()); + else + unify(functionReturnType, m_unitType, _return.location()); +} + +void TypeInference::endVisit(ParameterList const& _parameterList) +{ + auto& listAnnotation = annotation(_parameterList); + solAssert(!listAnnotation.type); + listAnnotation.type = TypeSystemHelpers{m_typeSystem}.tupleType( + _parameterList.parameters() | ranges::views::transform([&](auto _arg) { return type(*_arg); }) | ranges::to> + ); +} + +bool TypeInference::visit(TypeClassDefinition const& _typeClassDefinition) +{ + solAssert(m_expressionContext == ExpressionContext::Term); + auto& typeClassDefinitionAnnotation = annotation(_typeClassDefinition); + if (typeClassDefinitionAnnotation.type) + return false; + + typeClassDefinitionAnnotation.type = type(&_typeClassDefinition, {}); + + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Type}; + _typeClassDefinition.typeVariable().accept(*this); + } + + std::map functionTypes; + + solAssert(m_analysis.annotation(_typeClassDefinition).typeClass.has_value()); + TypeClass typeClass = m_analysis.annotation(_typeClassDefinition).typeClass.value(); + Type typeVar = m_typeSystem.typeClassVariable(typeClass); + auto& typeMembersAnnotation = annotation().members[typeConstructor(&_typeClassDefinition)]; + + for (auto subNode: _typeClassDefinition.subNodes()) + { + subNode->accept(*this); + auto const* functionDefinition = dynamic_cast(subNode.get()); + solAssert(functionDefinition); + auto functionType = type(*functionDefinition); + if (!functionTypes.emplace(functionDefinition->name(), functionType).second) + m_errorReporter.fatalTypeError(3195_error, functionDefinition->location(), "Function in type class declared multiple times."); + auto typeVars = TypeEnvironmentHelpers{*m_env}.typeVars(functionType); + if (typeVars.size() != 1) + m_errorReporter.fatalTypeError(8379_error, functionDefinition->location(), "Function in type class may only depend on the type class variable."); + unify(typeVars.front(), typeVar, functionDefinition->location()); + typeMembersAnnotation[functionDefinition->name()] = TypeMember{functionType}; + } + + annotation().typeClassFunctions[typeClass] = std::move(functionTypes); + + for (auto [functionName, functionType]: functionTypes) + { + TypeEnvironmentHelpers helper{*m_env}; + auto typeVars = helper.typeVars(functionType); + if (typeVars.empty()) + m_errorReporter.typeError(1723_error, _typeClassDefinition.location(), "Function " + functionName + " does not depend on class variable."); + if (typeVars.size() > 2) + m_errorReporter.typeError(6387_error, _typeClassDefinition.location(), "Function " + functionName + " depends on multiple type variables."); + if (!m_env->typeEquals(typeVars.front(), typeVar)) + m_errorReporter.typeError(1807_error, _typeClassDefinition.location(), "Function " + functionName + " depends on invalid type variable."); + } + + unify(type(_typeClassDefinition.typeVariable()), m_typeSystem.freshTypeVariable({{typeClass}}), _typeClassDefinition.location()); + for (auto instantiation: m_analysis.annotation(_typeClassDefinition).instantiations | ranges::views::values) + // TODO: recursion-safety? Order of instantiation? + instantiation->accept(*this); + + return false; +} + +bool TypeInference::visit(BinaryOperation const& _binaryOperation) +{ + auto& operationAnnotation = annotation(_binaryOperation); + solAssert(!operationAnnotation.type); + TypeSystemHelpers helper{m_typeSystem}; + switch (m_expressionContext) + { + case ExpressionContext::Term: + if (auto* operatorInfo = util::valueOrNullptr(annotation().operators, _binaryOperation.getOperator())) + { + auto [typeClass, functionName] = *operatorInfo; + // TODO: error robustness? + Type functionType = m_env->fresh(annotation().typeClassFunctions.at(typeClass).at(functionName)); + + _binaryOperation.leftExpression().accept(*this); + _binaryOperation.rightExpression().accept(*this); + + Type argTuple = helper.tupleType({type(_binaryOperation.leftExpression()), type(_binaryOperation.rightExpression())}); + Type resultType = m_typeSystem.freshTypeVariable({}); + Type genericFunctionType = helper.functionType(argTuple, resultType); + unify(functionType, genericFunctionType, _binaryOperation.location()); + + operationAnnotation.type = resultType; + } + else if (_binaryOperation.getOperator() == Token::Colon) + { + _binaryOperation.leftExpression().accept(*this); + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Type}; + _binaryOperation.rightExpression().accept(*this); + } + Type leftType = type(_binaryOperation.leftExpression()); + unify(leftType, type(_binaryOperation.rightExpression()), _binaryOperation.location()); + operationAnnotation.type = leftType; + } + else + { + m_errorReporter.typeError(4504_error, _binaryOperation.location(), "Binary operation in term context not yet supported."); + operationAnnotation.type = m_typeSystem.freshTypeVariable({}); + } + return false; + case ExpressionContext::Type: + if (_binaryOperation.getOperator() == Token::Colon) + { + _binaryOperation.leftExpression().accept(*this); + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Sort}; + _binaryOperation.rightExpression().accept(*this); + } + Type leftType = type(_binaryOperation.leftExpression()); + unify(leftType, type(_binaryOperation.rightExpression()), _binaryOperation.location()); + operationAnnotation.type = leftType; + } + else if (_binaryOperation.getOperator() == Token::RightArrow) + { + _binaryOperation.leftExpression().accept(*this); + _binaryOperation.rightExpression().accept(*this); + operationAnnotation.type = helper.functionType(type(_binaryOperation.leftExpression()), type(_binaryOperation.rightExpression())); + } + else if (_binaryOperation.getOperator() == Token::BitOr) + { + _binaryOperation.leftExpression().accept(*this); + _binaryOperation.rightExpression().accept(*this); + operationAnnotation.type = helper.sumType({type(_binaryOperation.leftExpression()), type(_binaryOperation.rightExpression())}); + } + else + { + m_errorReporter.typeError(1439_error, _binaryOperation.location(), "Invalid binary operations in type context."); + operationAnnotation.type = m_typeSystem.freshTypeVariable({}); + } + return false; + case ExpressionContext::Sort: + m_errorReporter.typeError(1017_error, _binaryOperation.location(), "Invalid binary operation in sort context."); + operationAnnotation.type = m_typeSystem.freshTypeVariable({}); + return false; + } + return false; +} + +void TypeInference::endVisit(VariableDeclarationStatement const& _variableDeclarationStatement) +{ + solAssert(m_expressionContext == ExpressionContext::Term); + if (_variableDeclarationStatement.declarations().size () != 1) + { + m_errorReporter.typeError(2655_error, _variableDeclarationStatement.location(), "Multi variable declaration not supported."); + return; + } + Type variableType = type(*_variableDeclarationStatement.declarations().front()); + if (_variableDeclarationStatement.initialValue()) + unify(variableType, type(*_variableDeclarationStatement.initialValue()), _variableDeclarationStatement.location()); +} + +bool TypeInference::visit(VariableDeclaration const& _variableDeclaration) +{ + solAssert(!_variableDeclaration.value()); + auto& variableAnnotation = annotation(_variableDeclaration); + solAssert(!variableAnnotation.type); + + switch (m_expressionContext) + { + case ExpressionContext::Term: + if (_variableDeclaration.typeExpression()) + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Type}; + _variableDeclaration.typeExpression()->accept(*this); + variableAnnotation.type = type(*_variableDeclaration.typeExpression()); + return false; + } + variableAnnotation.type = m_typeSystem.freshTypeVariable({}); + return false; + case ExpressionContext::Type: + variableAnnotation.type = m_typeSystem.freshTypeVariable({}); + if (_variableDeclaration.typeExpression()) + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Sort}; + _variableDeclaration.typeExpression()->accept(*this); + unify(*variableAnnotation.type, type(*_variableDeclaration.typeExpression()), _variableDeclaration.typeExpression()->location()); + } + return false; + case ExpressionContext::Sort: + m_errorReporter.typeError(2399_error, _variableDeclaration.location(), "Variable declaration in sort context."); + variableAnnotation.type = m_typeSystem.freshTypeVariable({}); + return false; + } + util::unreachable(); +} + +void TypeInference::endVisit(IfStatement const& _ifStatement) +{ + auto& ifAnnotation = annotation(_ifStatement); + solAssert(!ifAnnotation.type); + + if (m_expressionContext != ExpressionContext::Term) + { + m_errorReporter.typeError(2015_error, _ifStatement.location(), "If statement outside term context."); + ifAnnotation.type = m_typeSystem.freshTypeVariable({}); + return; + } + + unify(type(_ifStatement.condition()), m_boolType, _ifStatement.condition().location()); + + ifAnnotation.type = m_unitType; +} + +void TypeInference::endVisit(Assignment const& _assignment) +{ + auto& assignmentAnnotation = annotation(_assignment); + solAssert(!assignmentAnnotation.type); + + if (m_expressionContext != ExpressionContext::Term) + { + m_errorReporter.typeError(4337_error, _assignment.location(), "Assignment outside term context."); + assignmentAnnotation.type = m_typeSystem.freshTypeVariable({}); + return; + } + + Type leftType = type(_assignment.leftHandSide()); + unify(leftType, type(_assignment.rightHandSide()), _assignment.location()); + assignmentAnnotation.type = leftType; +} + +experimental::Type TypeInference::handleIdentifierByReferencedDeclaration(langutil::SourceLocation _location, Declaration const& _declaration) +{ + switch (m_expressionContext) + { + case ExpressionContext::Term: + { + if ( + !dynamic_cast(&_declaration) && + !dynamic_cast(&_declaration) && + !dynamic_cast(&_declaration) && + !dynamic_cast(&_declaration) + ) + { + SecondarySourceLocation ssl; + ssl.append("Referenced node.", _declaration.location()); + m_errorReporter.fatalTypeError(3101_error, _location, ssl, "Attempt to type identifier referring to unexpected node."); + } + + auto& declarationAnnotation = annotation(_declaration); + if (!declarationAnnotation.type) + _declaration.accept(*this); + + solAssert(declarationAnnotation.type); + + if (dynamic_cast(&_declaration)) + return *declarationAnnotation.type; + else if (dynamic_cast(&_declaration)) + return polymorphicInstance(*declarationAnnotation.type); + else if (dynamic_cast(&_declaration)) + { + solAssert(TypeEnvironmentHelpers{*m_env}.typeVars(*declarationAnnotation.type).empty()); + return *declarationAnnotation.type; + } + else if (dynamic_cast(&_declaration)) + { + // TODO: can we avoid this? + Type type = *declarationAnnotation.type; + if (TypeSystemHelpers{m_typeSystem}.isTypeFunctionType(type)) + type = std::get<1>(TypeSystemHelpers{m_typeSystem}.destTypeFunctionType(type)); + return polymorphicInstance(type); + } + else + solAssert(false); + break; + } + case ExpressionContext::Type: + { + if ( + !dynamic_cast(&_declaration) && + !dynamic_cast(&_declaration) + ) + { + SecondarySourceLocation ssl; + ssl.append("Referenced node.", _declaration.location()); + m_errorReporter.fatalTypeError(2217_error, _location, ssl, "Attempt to type identifier referring to unexpected node."); + } + + // TODO: Assert that this is a type class variable declaration? + auto& declarationAnnotation = annotation(_declaration); + if (!declarationAnnotation.type) + _declaration.accept(*this); + + solAssert(declarationAnnotation.type); + + if (dynamic_cast(&_declaration)) + return *declarationAnnotation.type; + else if (dynamic_cast(&_declaration)) + return polymorphicInstance(*declarationAnnotation.type); + else + solAssert(false); + break; + } + case ExpressionContext::Sort: + { + if (auto const* typeClassDefinition = dynamic_cast(&_declaration)) + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Term}; + typeClassDefinition->accept(*this); + + solAssert(m_analysis.annotation(*typeClassDefinition).typeClass.has_value()); + TypeClass typeClass = m_analysis.annotation(*typeClassDefinition).typeClass.value(); + return m_typeSystem.freshTypeVariable(Sort{{typeClass}}); + } + else + { + m_errorReporter.typeError(2599_error, _location, "Expected type class."); + return m_typeSystem.freshTypeVariable({}); + } + break; + } + } + util::unreachable(); +} + +bool TypeInference::visit(Identifier const& _identifier) +{ + auto& identifierAnnotation = annotation(_identifier); + solAssert(!identifierAnnotation.type); + + if (auto const* referencedDeclaration = _identifier.annotation().referencedDeclaration) + { + identifierAnnotation.type = handleIdentifierByReferencedDeclaration(_identifier.location(), *referencedDeclaration); + return false; + } + + switch (m_expressionContext) + { + case ExpressionContext::Term: + // TODO: error handling + solAssert(false); + break; + case ExpressionContext::Type: + // TODO: register free type variable name! + identifierAnnotation.type = m_typeSystem.freshTypeVariable({}); + return false; + case ExpressionContext::Sort: + // TODO: error handling + solAssert(false); + break; + } + + return false; +} + +void TypeInference::endVisit(TupleExpression const& _tupleExpression) +{ + auto& expressionAnnotation = annotation(_tupleExpression); + solAssert(!expressionAnnotation.type); + + TypeSystemHelpers helper{m_typeSystem}; + auto componentTypes = _tupleExpression.components() | ranges::views::transform([&](auto _expr) -> Type { + auto& componentAnnotation = annotation(*_expr); + solAssert(componentAnnotation.type); + return *componentAnnotation.type; + }) | ranges::to>; + switch (m_expressionContext) + { + case ExpressionContext::Term: + case ExpressionContext::Type: + expressionAnnotation.type = helper.tupleType(componentTypes); + break; + case ExpressionContext::Sort: + { + Type type = m_typeSystem.freshTypeVariable({}); + for (auto componentType: componentTypes) + unify(type, componentType, _tupleExpression.location()); + expressionAnnotation.type = type; + break; + } + } +} + +bool TypeInference::visit(IdentifierPath const& _identifierPath) +{ + auto& identifierAnnotation = annotation(_identifierPath); + solAssert(!identifierAnnotation.type); + + if (auto const* referencedDeclaration = _identifierPath.annotation().referencedDeclaration) + { + identifierAnnotation.type = handleIdentifierByReferencedDeclaration(_identifierPath.location(), *referencedDeclaration); + return false; + } + + // TODO: error handling + solAssert(false); +} + +bool TypeInference::visit(TypeClassInstantiation const& _typeClassInstantiation) +{ + ScopedSaveAndRestore activeInstantiations{m_activeInstantiations, m_activeInstantiations + std::set{&_typeClassInstantiation}}; + // Note: recursion is resolved due to special handling during unification. + auto& instantiationAnnotation = annotation(_typeClassInstantiation); + if (instantiationAnnotation.type) + return false; + instantiationAnnotation.type = m_voidType; + std::optional typeClass = std::visit(util::GenericVisitor{ + [&](ASTPointer _typeClassName) -> std::optional { + if (auto const* typeClassDefinition = dynamic_cast(_typeClassName->annotation().referencedDeclaration)) + { + // visiting the type class will re-visit this instantiation + typeClassDefinition->accept(*this); + // TODO: more error handling? Should be covered by the visit above. + solAssert(m_analysis.annotation(*typeClassDefinition).typeClass.has_value()); + return m_analysis.annotation(*typeClassDefinition).typeClass.value(); + } + else + { + m_errorReporter.typeError(9817_error, _typeClassInstantiation.typeClass().location(), "Expected type class."); + return std::nullopt; + } + }, + [&](Token _token) -> std::optional { + if (auto builtinClass = builtinClassFromToken(_token)) + if (auto typeClass = util::valueOrNullptr(annotation().builtinClasses, *builtinClass)) + return *typeClass; + m_errorReporter.typeError(2658_error, _typeClassInstantiation.location(), "Invalid type class name."); + return std::nullopt; + } + }, _typeClassInstantiation.typeClass().name()); + if (!typeClass) + return false; + + // TODO: _typeClassInstantiation.typeConstructor().accept(*this); ? + auto typeConstructor = m_analysis.annotation(_typeClassInstantiation.typeConstructor()).typeConstructor; + if (!typeConstructor) + { + m_errorReporter.typeError(2138_error, _typeClassInstantiation.typeConstructor().location(), "Invalid type constructor."); + return false; + } + + std::vector arguments; + Arity arity{ + {}, + *typeClass + }; + + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Type}; + if (_typeClassInstantiation.argumentSorts()) + { + _typeClassInstantiation.argumentSorts()->accept(*this); + auto& argumentSortAnnotation = annotation(*_typeClassInstantiation.argumentSorts()); + solAssert(argumentSortAnnotation.type); + arguments = TypeSystemHelpers{m_typeSystem}.destTupleType(*argumentSortAnnotation.type); + arity.argumentSorts = arguments | ranges::views::transform([&](Type _type) { + return m_env->sort(_type); + }) | ranges::to>; + } + } + + Type instanceType{TypeConstant{*typeConstructor, arguments}}; + + std::map functionTypes; + + for (auto subNode: _typeClassInstantiation.subNodes()) + { + auto const* functionDefinition = dynamic_cast(subNode.get()); + solAssert(functionDefinition); + subNode->accept(*this); + if (!functionTypes.emplace(functionDefinition->name(), type(*functionDefinition)).second) + m_errorReporter.typeError(3654_error, subNode->location(), "Duplicate definition of function " + functionDefinition->name() + " during type class instantiation."); + } + + if (auto error = m_typeSystem.instantiateClass(instanceType, arity)) + m_errorReporter.typeError(5094_error, _typeClassInstantiation.location(), *error); + + auto const& classFunctions = annotation().typeClassFunctions.at(*typeClass); + + TypeEnvironment newEnv = m_env->clone(); + if (!newEnv.unify(m_typeSystem.typeClassVariable(*typeClass), instanceType).empty()) + { + m_errorReporter.typeError(4686_error, _typeClassInstantiation.location(), "Unification of class and instance variable failed."); + return false; + } + + for (auto [name, classFunctionType]: classFunctions) + { + if (!functionTypes.count(name)) + { + m_errorReporter.typeError(6948_error, _typeClassInstantiation.location(), "Missing function: " + name); + continue; + } + Type instanceFunctionType = functionTypes.at(name); + functionTypes.erase(name); + + if (!newEnv.typeEquals(instanceFunctionType, classFunctionType)) + m_errorReporter.typeError(7428_error, _typeClassInstantiation.location(), "Type mismatch for function " + name + " " + TypeEnvironmentHelpers{newEnv}.typeToString(instanceFunctionType) + " != " + TypeEnvironmentHelpers{newEnv}.typeToString(classFunctionType)); + } + + if (!functionTypes.empty()) + m_errorReporter.typeError(4873_error, _typeClassInstantiation.location(), "Additional functions in class instantiation."); + + return false; +} + +bool TypeInference::visit(MemberAccess const& _memberAccess) +{ + if (m_expressionContext != ExpressionContext::Term) + { + m_errorReporter.typeError(5195_error, _memberAccess.location(), "Member access outside term context."); + annotation(_memberAccess).type = m_typeSystem.freshTypeVariable({}); + return false; + } + return true; +} + +experimental::Type TypeInference::memberType(Type _type, std::string _memberName, langutil::SourceLocation _location) +{ + Type resolvedType = m_env->resolve(_type); + TypeSystemHelpers helper{m_typeSystem}; + if (helper.isTypeConstant(resolvedType)) + { + auto constructor = std::get<0>(helper.destTypeConstant(resolvedType)); + if (auto* typeMember = util::valueOrNullptr(annotation().members.at(constructor), _memberName)) + return polymorphicInstance(typeMember->type); + else + { + m_errorReporter.typeError(5755_error, _location, fmt::format("Member {} not found in type {}.", _memberName, TypeEnvironmentHelpers{*m_env}.typeToString(_type))); + return m_typeSystem.freshTypeVariable({}); + } + } + else + { + m_errorReporter.typeError(5104_error, _location, "Unsupported member access expression."); + return m_typeSystem.freshTypeVariable({}); + } +} + +void TypeInference::endVisit(MemberAccess const& _memberAccess) +{ + auto& memberAccessAnnotation = annotation(_memberAccess); + solAssert(!memberAccessAnnotation.type); + Type expressionType = type(_memberAccess.expression()); + memberAccessAnnotation.type = memberType(expressionType, _memberAccess.memberName(), _memberAccess.location()); +} + +bool TypeInference::visit(TypeDefinition const& _typeDefinition) +{ + bool isBuiltIn = dynamic_cast(_typeDefinition.typeExpression()); + + TypeSystemHelpers helper{m_typeSystem}; + auto& typeDefinitionAnnotation = annotation(_typeDefinition); + if (typeDefinitionAnnotation.type) + return false; + + if (_typeDefinition.arguments()) + _typeDefinition.arguments()->accept(*this); + + std::vector arguments; + if (_typeDefinition.arguments()) + for (size_t i = 0; i < _typeDefinition.arguments()->parameters().size(); ++i) + arguments.emplace_back(m_typeSystem.freshTypeVariable({})); + + Type definedType = type(&_typeDefinition, arguments); + if (arguments.empty()) + typeDefinitionAnnotation.type = definedType; + else + typeDefinitionAnnotation.type = helper.typeFunctionType(helper.tupleType(arguments), definedType); + + std::optional underlyingType; + + if (isBuiltIn) + // TODO: This special case should eventually become user-definable. + underlyingType = m_wordType; + else if (_typeDefinition.typeExpression()) + { + ScopedSaveAndRestore expressionContext{m_expressionContext, ExpressionContext::Type}; + _typeDefinition.typeExpression()->accept(*this); + + underlyingType = annotation(*_typeDefinition.typeExpression()).type; + } + + TypeConstructor constructor = typeConstructor(&_typeDefinition); + auto [members, newlyInserted] = annotation().members.emplace(constructor, std::map{}); + solAssert(newlyInserted, fmt::format("Members of type '{}' are already defined.", m_typeSystem.constructorInfo(constructor).name)); + if (underlyingType) + { + members->second.emplace("abs", TypeMember{helper.functionType(*underlyingType, definedType)}); + members->second.emplace("rep", TypeMember{helper.functionType(definedType, *underlyingType)}); + } + + if (helper.isPrimitiveType(definedType, PrimitiveType::Pair)) + { + solAssert(isBuiltIn); + solAssert(arguments.size() == 2); + members->second.emplace("first", TypeMember{helper.functionType(definedType, arguments[0])}); + members->second.emplace("second", TypeMember{helper.functionType(definedType, arguments[1])}); + } + + return false; +} + +bool TypeInference::visit(FunctionCall const&) { return true; } +void TypeInference::endVisit(FunctionCall const& _functionCall) +{ + auto& functionCallAnnotation = annotation(_functionCall); + solAssert(!functionCallAnnotation.type); + + Type functionType = type(_functionCall.expression()); + + TypeSystemHelpers helper{m_typeSystem}; + std::vector argTypes; + for (auto arg: _functionCall.arguments()) + { + switch (m_expressionContext) + { + case ExpressionContext::Term: + case ExpressionContext::Type: + argTypes.emplace_back(type(*arg)); + break; + case ExpressionContext::Sort: + m_errorReporter.typeError(9173_error, _functionCall.location(), "Function call in sort context."); + functionCallAnnotation.type = m_typeSystem.freshTypeVariable({}); + break; + } + } + + switch (m_expressionContext) + { + case ExpressionContext::Term: + { + Type argTuple = helper.tupleType(argTypes); + Type resultType = m_typeSystem.freshTypeVariable({}); + Type genericFunctionType = helper.functionType(argTuple, resultType); + unify(functionType, genericFunctionType, _functionCall.location()); + functionCallAnnotation.type = resultType; + break; + } + case ExpressionContext::Type: + { + Type argTuple = helper.tupleType(argTypes); + Type resultType = m_typeSystem.freshTypeVariable({}); + Type genericFunctionType = helper.typeFunctionType(argTuple, resultType); + unify(functionType, genericFunctionType, _functionCall.location()); + functionCallAnnotation.type = resultType; + break; + } + case ExpressionContext::Sort: + solAssert(false); + } +} + +// TODO: clean up rational parsing +namespace +{ + +std::optional parseRational(std::string const& _value) +{ + rational value; + try + { + auto radixPoint = find(_value.begin(), _value.end(), '.'); + + if (radixPoint != _value.end()) + { + if ( + !all_of(radixPoint + 1, _value.end(), util::isDigit) || + !all_of(_value.begin(), radixPoint, util::isDigit) + ) + return std::nullopt; + + // Only decimal notation allowed here, leading zeros would switch to octal. + auto fractionalBegin = find_if_not( + radixPoint + 1, + _value.end(), + [](char const& a) { return a == '0'; } + ); + + rational numerator; + rational denominator(1); + + denominator = bigint(std::string(fractionalBegin, _value.end())); + denominator /= boost::multiprecision::pow( + bigint(10), + static_cast(distance(radixPoint + 1, _value.end())) + ); + numerator = bigint(std::string(_value.begin(), radixPoint)); + value = numerator + denominator; + } + else + value = bigint(_value); + return value; + } + catch (...) + { + return std::nullopt; + } +} + +/// Checks whether _mantissa * (10 ** _expBase10) fits into 4096 bits. +bool fitsPrecisionBase10(bigint const& _mantissa, uint32_t _expBase10) +{ + double const log2Of10AwayFromZero = 3.3219280948873624; + return fitsPrecisionBaseX(_mantissa, log2Of10AwayFromZero, _expBase10); +} + +std::optional rationalValue(Literal const& _literal) +{ + rational value; + try + { + ASTString valueString = _literal.valueWithoutUnderscores(); + + auto expPoint = find(valueString.begin(), valueString.end(), 'e'); + if (expPoint == valueString.end()) + expPoint = find(valueString.begin(), valueString.end(), 'E'); + + if (boost::starts_with(valueString, "0x")) + { + // process as hex + value = bigint(valueString); + } + else if (expPoint != valueString.end()) + { + // Parse mantissa and exponent. Checks numeric limit. + std::optional mantissa = parseRational(std::string(valueString.begin(), expPoint)); + + if (!mantissa) + return std::nullopt; + value = *mantissa; + + // 0E... is always zero. + if (value == 0) + return std::nullopt; + + bigint exp = bigint(std::string(expPoint + 1, valueString.end())); + + if (exp > std::numeric_limits::max() || exp < std::numeric_limits::min()) + return std::nullopt; + + uint32_t expAbs = bigint(abs(exp)).convert_to(); + + if (exp < 0) + { + if (!fitsPrecisionBase10(abs(value.denominator()), expAbs)) + return std::nullopt; + value /= boost::multiprecision::pow( + bigint(10), + expAbs + ); + } + else if (exp > 0) + { + if (!fitsPrecisionBase10(abs(value.numerator()), expAbs)) + return std::nullopt; + value *= boost::multiprecision::pow( + bigint(10), + expAbs + ); + } + } + else + { + // parse as rational number + std::optional tmp = parseRational(valueString); + if (!tmp) + return std::nullopt; + value = *tmp; + } + } + catch (...) + { + return std::nullopt; + } + switch (_literal.subDenomination()) + { + case Literal::SubDenomination::None: + case Literal::SubDenomination::Second: + break; + case Literal::SubDenomination::Minute: + value *= bigint("60"); + break; + case Literal::SubDenomination::Hour: + value *= bigint("3600"); + break; + case Literal::SubDenomination::Day: + value *= bigint("86400"); + break; + case Literal::SubDenomination::Week: + value *= bigint("604800"); + break; + case Literal::SubDenomination::Year: + value *= bigint("31536000"); + break; + default: + solUnimplemented("TODO DELETE: ADD another cases!"); + } + + return value; +} +} + +bool TypeInference::visit(Literal const& _literal) +{ + auto& literalAnnotation = annotation(_literal); + if (_literal.token() != Token::Number) + { + m_errorReporter.typeError(4316_error, _literal.location(), "Only number literals are supported."); + return false; + } + std::optional value = rationalValue(_literal); + if (!value) + { + m_errorReporter.typeError(6739_error, _literal.location(), "Invalid number literals."); + return false; + } + if (value->denominator() != 1) + { + m_errorReporter.typeError(2345_error, _literal.location(), "Only integers are supported."); + return false; + } + literalAnnotation.type = m_typeSystem.freshTypeVariable(Sort{{annotation().builtinClasses.at(BuiltinClass::Integer)}}); + return false; +} + + +namespace +{ +// TODO: put at a nice place to deduplicate. +TypeRegistration::TypeClassInstantiations const& typeClassInstantiations(Analysis const& _analysis, TypeClass _class) +{ + auto const* typeClassDeclaration = _analysis.typeSystem().typeClassDeclaration(_class); + if (typeClassDeclaration) + return _analysis.annotation(*typeClassDeclaration).instantiations; + // TODO: better mechanism than fetching by name. + auto const& annotation = _analysis.annotation(); + auto const& inferenceAnnotation = _analysis.annotation(); + return annotation.builtinClassInstantiations.at( + inferenceAnnotation.builtinClassesByName.at( + _analysis.typeSystem().typeClassName(_class) + ) + ); +} +} + +experimental::Type TypeInference::polymorphicInstance(Type const& _scheme) +{ + return m_env->fresh(_scheme); +} + +void TypeInference::unify(Type _a, Type _b, langutil::SourceLocation _location) +{ + TypeSystemHelpers helper{m_typeSystem}; + auto unificationFailures = m_env->unify(_a, _b); + + if (!m_activeInstantiations.empty()) + { + // TODO: This entire logic is superfluous - I thought mutually recursive dependencies between + // class instantiations are a problem, but in fact they're not, they just resolve to mutually recursive + // functions that are fine. So instead, all instantiations can be registered with the type system directly + // when visiting the type class (assuming that they all work out) - and then all instantiations can be checked + // individually, which should still catch all actual issues (while allowing recursions). + // Original comment: Attempt to resolve interdependencies between type class instantiations. + std::vector missingInstantiations; + bool recursion = false; + bool onlyMissingInstantiations = [&]() { + for (auto failure: unificationFailures) + { + if (auto* sortMismatch = std::get_if(&failure)) + if (helper.isTypeConstant(sortMismatch->type)) + { + TypeConstructor constructor = std::get<0>(helper.destTypeConstant(sortMismatch->type)); + for (auto typeClass: sortMismatch->sort.classes) + { + if (auto const* instantiation = util::valueOrDefault(typeClassInstantiations(m_analysis, typeClass), constructor, nullptr)) + { + if (m_activeInstantiations.count(instantiation)) + { + langutil::SecondarySourceLocation ssl; + for (auto activeInstantiation: m_activeInstantiations) + ssl.append("Involved instantiation", activeInstantiation->location()); + m_errorReporter.typeError( + 3573_error, + _location, + ssl, + "Recursion during type class instantiation." + ); + recursion = true; + return false; + } + missingInstantiations.emplace_back(instantiation); + } + else + return false; + } + continue; + } + return false; + } + return true; + }(); + + if (recursion) + return; + + if (onlyMissingInstantiations) + { + for (auto instantiation: missingInstantiations) + instantiation->accept(*this); + unificationFailures = m_env->unify(_a, _b); + } + } + + for (auto failure: unificationFailures) + { + TypeEnvironmentHelpers envHelper{*m_env}; + std::visit(util::GenericVisitor{ + [&](TypeEnvironment::TypeMismatch _typeMismatch) { + m_errorReporter.typeError( + 8456_error, + _location, + fmt::format( + "Cannot unify {} and {}.", + envHelper.typeToString(_typeMismatch.a), + envHelper.typeToString(_typeMismatch.b) + ) + ); + }, + [&](TypeEnvironment::SortMismatch _sortMismatch) { + m_errorReporter.typeError(3111_error, _location, fmt::format( + "{} does not have sort {}", + envHelper.typeToString(_sortMismatch.type), + TypeSystemHelpers{m_typeSystem}.sortToString(_sortMismatch.sort) + )); + }, + [&](TypeEnvironment::RecursiveUnification _recursiveUnification) { + m_errorReporter.typeError( + 6460_error, + _location, + fmt::format( + "Recursive unification: {} occurs in {}.", + envHelper.typeToString(_recursiveUnification.var), + envHelper.typeToString(_recursiveUnification.type) + ) + ); + } + }, failure); + } +} + +experimental::Type TypeInference::type(ASTNode const& _node) const +{ + auto result = annotation(_node).type; + solAssert(result); + return *result; +} +TypeConstructor TypeInference::typeConstructor(Declaration const* _type) const +{ + if (auto const& constructor = m_analysis.annotation(*_type).typeConstructor) + return *constructor; + m_errorReporter.fatalTypeError(5904_error, _type->location(), "Unregistered type."); + util::unreachable(); +} +experimental::Type TypeInference::type(Declaration const* _type, std::vector _arguments) const +{ + return m_typeSystem.type(typeConstructor(_type), std::move(_arguments)); +} + +bool TypeInference::visitNode(ASTNode const& _node) +{ + m_errorReporter.fatalTypeError(5348_error, _node.location(), "Unsupported AST node during type inference."); + return false; +} + +TypeInference::Annotation& TypeInference::annotation(ASTNode const& _node) +{ + return m_analysis.annotation(_node); +} + +TypeInference::Annotation const& TypeInference::annotation(ASTNode const& _node) const +{ + return m_analysis.annotation(_node); +} + +TypeInference::GlobalAnnotation& TypeInference::annotation() +{ + return m_analysis.annotation(); +} diff --git a/compiler/libsolidity/experimental/analysis/TypeInference.h b/compiler/libsolidity/experimental/analysis/TypeInference.h new file mode 100644 index 00000000..ae86de5f --- /dev/null +++ b/compiler/libsolidity/experimental/analysis/TypeInference.h @@ -0,0 +1,123 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include +#include + +#include + +namespace solidity::frontend::experimental +{ + +class Analysis; + +class TypeInference: public ASTConstVisitor +{ +public: + TypeInference(Analysis& _analysis); + + bool analyze(SourceUnit const& _sourceUnit); + + struct Annotation + { + /// Expressions, variable declarations, function declarations. + std::optional type; + }; + struct TypeMember + { + Type type; + }; + struct GlobalAnnotation + { + std::map builtinClasses; + std::map builtinClassesByName; + std::map> typeClassFunctions; + std::map> operators; + std::map> members; + }; + bool visit(Block const&) override { return true; } + bool visit(VariableDeclarationStatement const&) override { return true; } + void endVisit(VariableDeclarationStatement const& _variableDeclarationStatement) override; + bool visit(VariableDeclaration const& _variableDeclaration) override; + + bool visit(FunctionDefinition const& _functionDefinition) override; + bool visit(ParameterList const&) override { return true; } + void endVisit(ParameterList const& _parameterList) override; + bool visit(SourceUnit const&) override { return true; } + bool visit(ContractDefinition const&) override { return true; } + bool visit(ImportDirective const&) override { return true; } + bool visit(PragmaDirective const&) override { return false; } + + bool visit(IfStatement const&) override { return true; } + void endVisit(IfStatement const& _ifStatement) override; + bool visit(ExpressionStatement const&) override { return true; } + bool visit(Assignment const&) override { return true; } + void endVisit(Assignment const& _assignment) override; + bool visit(Identifier const&) override; + bool visit(IdentifierPath const&) override; + bool visit(FunctionCall const& _functionCall) override; + void endVisit(FunctionCall const& _functionCall) override; + bool visit(Return const&) override { return true; } + void endVisit(Return const& _return) override; + + bool visit(MemberAccess const& _memberAccess) override; + void endVisit(MemberAccess const& _memberAccess) override; + + bool visit(TypeClassDefinition const& _typeClassDefinition) override; + bool visit(TypeClassInstantiation const& _typeClassInstantiation) override; + bool visit(TupleExpression const&) override { return true; } + void endVisit(TupleExpression const& _tupleExpression) override; + bool visit(TypeDefinition const& _typeDefinition) override; + + bool visitNode(ASTNode const& _node) override; + + bool visit(BinaryOperation const& _operation) override; + + bool visit(Literal const& _literal) override; +private: + Analysis& m_analysis; + langutil::ErrorReporter& m_errorReporter; + TypeSystem& m_typeSystem; + TypeEnvironment* m_env = nullptr; + Type m_voidType; + Type m_wordType; + Type m_integerType; + Type m_unitType; + Type m_boolType; + std::optional m_currentFunctionType; + + Type type(ASTNode const& _node) const; + + Annotation& annotation(ASTNode const& _node); + Annotation const& annotation(ASTNode const& _node) const; + GlobalAnnotation& annotation(); + + void unify(Type _a, Type _b, langutil::SourceLocation _location = {}); + /// Creates a polymorphic instance of a global type scheme + Type polymorphicInstance(Type const& _scheme); + Type memberType(Type _type, std::string _memberName, langutil::SourceLocation _location = {}); + enum class ExpressionContext { Term, Type, Sort }; + Type handleIdentifierByReferencedDeclaration(langutil::SourceLocation _location, Declaration const& _declaration); + TypeConstructor typeConstructor(Declaration const* _type) const; + Type type(Declaration const* _type, std::vector _arguments) const; + ExpressionContext m_expressionContext = ExpressionContext::Term; + std::set> m_activeInstantiations; +}; + +} diff --git a/compiler/libsolidity/experimental/analysis/TypeRegistration.cpp b/compiler/libsolidity/experimental/analysis/TypeRegistration.cpp new file mode 100644 index 00000000..e9ac5286 --- /dev/null +++ b/compiler/libsolidity/experimental/analysis/TypeRegistration.cpp @@ -0,0 +1,207 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + + +#include +#include +#include +#include + +using namespace solidity::frontend; +using namespace solidity::frontend::experimental; +using namespace solidity::langutil; + +TypeRegistration::TypeRegistration(Analysis& _analysis): + m_analysis(_analysis), + m_errorReporter(_analysis.errorReporter()), + m_typeSystem(_analysis.typeSystem()) +{ +} + +bool TypeRegistration::analyze(SourceUnit const& _sourceUnit) +{ + _sourceUnit.accept(*this); + return !m_errorReporter.hasErrors(); +} + +bool TypeRegistration::visit(TypeClassDefinition const& _typeClassDefinition) +{ + if (annotation(_typeClassDefinition).typeConstructor) + return false; + annotation(_typeClassDefinition).typeConstructor = m_typeSystem.declareTypeConstructor( + _typeClassDefinition.name(), + "t_" + *_typeClassDefinition.annotation().canonicalName + "_" + util::toString(_typeClassDefinition.id()), + 0, + &_typeClassDefinition + ); + return true; +} + +bool TypeRegistration::visit(Builtin const& _builtin) +{ + if (annotation(_builtin).typeConstructor) + return false; + + auto primitiveType = [&](std::string _name) -> std::optional { + if (_name == "void") return PrimitiveType::Void; + if (_name == "fun") return PrimitiveType::Function; + if (_name == "unit") return PrimitiveType::Unit; + if (_name == "pair") return PrimitiveType::Pair; + if (_name == "word") return PrimitiveType::Word; + if (_name == "integer") return PrimitiveType::Integer; + if (_name == "bool") return PrimitiveType::Bool; + return std::nullopt; + }(_builtin.nameParameter()); + + if (!primitiveType.has_value()) + m_errorReporter.fatalTypeError( + 7758_error, + _builtin.location(), + "Expected the name of a built-in primitive type." + ); + + annotation(_builtin).typeConstructor = m_typeSystem.constructor(primitiveType.value()); + return true; +} + +void TypeRegistration::endVisit(ElementaryTypeNameExpression const & _typeNameExpression) +{ + if (annotation(_typeNameExpression).typeConstructor) + return; + + // TODO: this is not visited in the ElementaryTypeNameExpression visit - is that intentional? + _typeNameExpression.type().accept(*this); + if (auto constructor = annotation(_typeNameExpression.type()).typeConstructor) + annotation(_typeNameExpression).typeConstructor = constructor; + else + solAssert(m_errorReporter.hasErrors()); +} + +bool TypeRegistration::visit(UserDefinedTypeName const& _userDefinedTypeName) +{ + if (annotation(_userDefinedTypeName).typeConstructor) + return false; + auto const* declaration = _userDefinedTypeName.pathNode().annotation().referencedDeclaration; + if (!declaration) + { + // TODO: fatal/non-fatal + m_errorReporter.fatalTypeError(5096_error, _userDefinedTypeName.pathNode().location(), "Expected declaration."); + return false; + } + declaration->accept(*this); + if (!(annotation(_userDefinedTypeName).typeConstructor = annotation(*declaration).typeConstructor)) + { + // TODO: fatal/non-fatal + m_errorReporter.fatalTypeError(9831_error, _userDefinedTypeName.pathNode().location(), "Expected type declaration."); + return false; + } + return true; +} + +bool TypeRegistration::visit(TypeClassInstantiation const& _typeClassInstantiation) +{ + if (annotation(_typeClassInstantiation).typeConstructor) + return false; + _typeClassInstantiation.typeConstructor().accept(*this); + auto typeConstructor = annotation(_typeClassInstantiation.typeConstructor()).typeConstructor; + if (!typeConstructor) + { + m_errorReporter.typeError(5577_error, _typeClassInstantiation.typeConstructor().location(), "Invalid type name."); + return false; + } + auto* instantiations = std::visit(util::GenericVisitor{ + [&](ASTPointer _path) -> TypeClassInstantiations* + { + if (TypeClassDefinition const* classDefinition = dynamic_cast(_path->annotation().referencedDeclaration)) + return &annotation(*classDefinition).instantiations; + m_errorReporter.typeError(3570_error, _typeClassInstantiation.typeClass().location(), "Expected a type class."); + return nullptr; + }, + [&](Token _token) -> TypeClassInstantiations* + { + if (auto typeClass = builtinClassFromToken(_token)) + return &annotation().builtinClassInstantiations[*typeClass]; + m_errorReporter.typeError(5262_error, _typeClassInstantiation.typeClass().location(), "Expected a type class."); + return nullptr; + } + }, _typeClassInstantiation.typeClass().name()); + + if (!instantiations) + return false; + + if ( + auto [instantiation, newlyInserted] = instantiations->emplace(*typeConstructor, &_typeClassInstantiation); + !newlyInserted + ) + { + SecondarySourceLocation ssl; + ssl.append("Previous instantiation.", instantiation->second->location()); + m_errorReporter.typeError(6620_error, _typeClassInstantiation.location(), ssl, "Duplicate type class instantiation."); + } + + return true; +} + +bool TypeRegistration::visit(TypeDefinition const& _typeDefinition) +{ + return !annotation(_typeDefinition).typeConstructor.has_value(); +} + +void TypeRegistration::endVisit(TypeDefinition const& _typeDefinition) +{ + if (annotation(_typeDefinition).typeConstructor.has_value()) + return; + + if (auto const* builtin = dynamic_cast(_typeDefinition.typeExpression())) + { + auto [previousDefinitionIt, inserted] = annotation().builtinTypeDefinitions.try_emplace( + builtin->nameParameter(), + &_typeDefinition + ); + + if (inserted) + annotation(_typeDefinition).typeConstructor = annotation(*builtin).typeConstructor; + else + { + auto const& [builtinName, previousDefinition] = *previousDefinitionIt; + m_errorReporter.typeError( + 9609_error, + _typeDefinition.location(), + SecondarySourceLocation{}.append("Previous definition:", previousDefinition->location()), + "Duplicate builtin type definition." + ); + } + } + else + annotation(_typeDefinition).typeConstructor = m_typeSystem.declareTypeConstructor( + _typeDefinition.name(), + "t_" + *_typeDefinition.annotation().canonicalName + "_" + util::toString(_typeDefinition.id()), + _typeDefinition.arguments() ? _typeDefinition.arguments()->parameters().size() : 0, + &_typeDefinition + ); +} + +TypeRegistration::Annotation& TypeRegistration::annotation(ASTNode const& _node) +{ + return m_analysis.annotation(_node); +} + +TypeRegistration::GlobalAnnotation& TypeRegistration::annotation() +{ + return m_analysis.annotation(); +} diff --git a/compiler/libsolidity/experimental/analysis/TypeRegistration.h b/compiler/libsolidity/experimental/analysis/TypeRegistration.h new file mode 100644 index 00000000..9bc49a29 --- /dev/null +++ b/compiler/libsolidity/experimental/analysis/TypeRegistration.h @@ -0,0 +1,67 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include +#include + +#include + +namespace solidity::frontend::experimental +{ + +class Analysis; + +class TypeRegistration: public ASTConstVisitor +{ +public: + using TypeClassInstantiations = std::map; + struct Annotation + { + // For type class definitions. + TypeClassInstantiations instantiations; + // For builtins, type definitions, type class definitions, type names and type name expressions. + std::optional typeConstructor; + }; + struct GlobalAnnotation + { + std::map primitiveClassInstantiations; + std::map builtinClassInstantiations; + std::map builtinTypeDefinitions; + }; + TypeRegistration(Analysis& _analysis); + + bool analyze(SourceUnit const& _sourceUnit); +private: + bool visit(TypeClassDefinition const& _typeClassDefinition) override; + bool visit(TypeClassInstantiation const& _typeClassInstantiation) override; + bool visit(TypeDefinition const& _typeDefinition) override; + void endVisit(TypeDefinition const& _typeDefinition) override; + bool visit(UserDefinedTypeName const& _typeName) override; + void endVisit(ElementaryTypeNameExpression const& _typeName) override; + bool visit(Builtin const& _builtin) override; + Annotation& annotation(ASTNode const& _node); + GlobalAnnotation& annotation(); + + Analysis& m_analysis; + langutil::ErrorReporter& m_errorReporter; + TypeSystem& m_typeSystem; + std::set m_visitedClasses; +}; + +} diff --git a/compiler/libsolidity/experimental/ast/FunctionCallGraph.h b/compiler/libsolidity/experimental/ast/FunctionCallGraph.h new file mode 100644 index 00000000..0a1db845 --- /dev/null +++ b/compiler/libsolidity/experimental/ast/FunctionCallGraph.h @@ -0,0 +1,40 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +/// Data structure representing a function dependency graph. + +#pragma once + +#include + +#include +#include +#include + +namespace solidity::frontend::experimental +{ + +struct FunctionDependencyGraph +{ + /// Graph edges. Edges are directed and lead from the caller to the callee. + /// The map contains a key for every function, even if does not actually perform + /// any calls. + std::map>, ASTCompareByID> edges; +}; + +} diff --git a/compiler/libsolidity/experimental/ast/Type.cpp b/compiler/libsolidity/experimental/ast/Type.cpp new file mode 100644 index 00000000..ec822dc6 --- /dev/null +++ b/compiler/libsolidity/experimental/ast/Type.cpp @@ -0,0 +1,62 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include +#include +#include + +#include +#include + +#include + +using namespace solidity; +using namespace solidity::frontend::experimental; + +bool Sort::operator==(Sort const& _rhs) const +{ + if (classes.size() != _rhs.classes.size()) + return false; + for (auto [lhs, rhs]: ranges::zip_view(classes, _rhs.classes)) + if (lhs != rhs) + return false; + return true; +} + +bool Sort::operator<=(Sort const& _rhs) const +{ + for (auto c: classes) + if (!_rhs.classes.count(c)) + return false; + return true; +} + +Sort Sort::operator+(Sort const& _rhs) const +{ + Sort result { classes }; + result.classes += _rhs.classes; + return result; +} + + +Sort Sort::operator-(Sort const& _rhs) const +{ + Sort result { classes }; + result.classes -= _rhs.classes; + return result; +} diff --git a/compiler/libsolidity/experimental/ast/Type.h b/compiler/libsolidity/experimental/ast/Type.h new file mode 100644 index 00000000..dde41b1d --- /dev/null +++ b/compiler/libsolidity/experimental/ast/Type.h @@ -0,0 +1,154 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include +#include +#include +#include + +namespace solidity::frontend::experimental +{ + +class TypeSystem; + +struct TypeConstant; +struct TypeVariable; + +using Type = std::variant; + +enum class PrimitiveType +{ + Void, + Function, + TypeFunction, + Itself, + Unit, + Pair, + Sum, + Word, + Bool, + Integer +}; + +enum class PrimitiveClass +{ + Type +}; + +// TODO: move elsewhere? +enum class BuiltinClass +{ + Integer, + Mul, + Add, + Equal, + Less, + LessOrEqual, + Greater, + GreaterOrEqual +}; + +struct TypeConstructor +{ +public: + TypeConstructor(TypeConstructor const& _typeConstructor): m_index(_typeConstructor.m_index) {} + TypeConstructor& operator=(TypeConstructor const& _typeConstructor) + { + m_index = _typeConstructor.m_index; + return *this; + } + bool operator<(TypeConstructor const& _rhs) const + { + return m_index < _rhs.m_index; + } + bool operator==(TypeConstructor const& _rhs) const + { + return m_index == _rhs.m_index; + } + bool operator!=(TypeConstructor const& _rhs) const + { + return m_index != _rhs.m_index; + } +private: + friend class TypeSystem; + TypeConstructor(std::size_t _index): m_index(_index) {} + std::size_t m_index = 0; +}; + +struct TypeConstant +{ + TypeConstructor constructor; + std::vector arguments; +}; + +struct TypeClass +{ +public: + TypeClass(TypeClass const& _typeClass): m_index(_typeClass.m_index) {} + TypeClass& operator=(TypeClass const& _typeConstructor) + { + m_index = _typeConstructor.m_index; + return *this; + } + bool operator<(TypeClass const& _rhs) const + { + return m_index < _rhs.m_index; + } + bool operator==(TypeClass const& _rhs) const + { + return m_index == _rhs.m_index; + } + bool operator!=(TypeClass const& _rhs) const + { + return m_index != _rhs.m_index; + } +private: + friend class TypeSystem; + TypeClass(std::size_t _index): m_index(_index) {} + std::size_t m_index = 0; +}; + +struct Sort +{ + std::set classes; + bool operator==(Sort const& _rhs) const; + bool operator!=(Sort const& _rhs) const { return !operator==(_rhs); } + bool operator<=(Sort const& _rhs) const; + Sort operator+(Sort const& _rhs) const; + Sort operator-(Sort const& _rhs) const; +}; + +struct Arity +{ + std::vector argumentSorts; + TypeClass typeClass; +}; + +struct TypeVariable +{ + std::size_t index() const { return m_index; } + Sort const& sort() const { return m_sort; } +private: + friend class TypeSystem; + std::size_t m_index = 0; + Sort m_sort; + TypeVariable(std::size_t _index, Sort _sort): m_index(_index), m_sort(std::move(_sort)) {} +}; + +} diff --git a/compiler/libsolidity/experimental/ast/TypeSystem.cpp b/compiler/libsolidity/experimental/ast/TypeSystem.cpp new file mode 100644 index 00000000..680edce8 --- /dev/null +++ b/compiler/libsolidity/experimental/ast/TypeSystem.cpp @@ -0,0 +1,347 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +using namespace solidity; +using namespace solidity::frontend; +using namespace solidity::frontend::experimental; + +std::vector TypeEnvironment::unify(Type _a, Type _b) +{ + std::vector failures; + auto unificationFailure = [&]() { + failures.emplace_back(UnificationFailure{TypeMismatch{_a, _b}}); + }; + _a = resolve(_a); + _b = resolve(_b); + std::visit(util::GenericVisitor{ + [&](TypeVariable _left, TypeVariable _right) { + if (_left.index() == _right.index()) + solAssert(_left.sort() == _right.sort()); + else + { + if (_left.sort() <= _right.sort()) + failures += instantiate(_left, _right); + else if (_right.sort() <= _left.sort()) + failures += instantiate(_right, _left); + else + { + Type newVar = m_typeSystem.freshVariable(_left.sort() + _right.sort()); + failures += instantiate(_left, newVar); + failures += instantiate(_right, newVar); + } + } + }, + [&](TypeVariable _var, auto) { + failures += instantiate(_var, _b); + }, + [&](auto, TypeVariable _var) { + failures += instantiate(_var, _a); + }, + [&](TypeConstant _left, TypeConstant _right) { + if (_left.constructor != _right.constructor) + return unificationFailure(); + if (_left.arguments.size() != _right.arguments.size()) + return unificationFailure(); + for (auto&& [left, right]: ranges::zip_view(_left.arguments, _right.arguments)) + failures += unify(left, right); + }, + [&](auto, auto) { + unificationFailure(); + } + }, _a, _b); + return failures; +} + +bool TypeEnvironment::typeEquals(Type _lhs, Type _rhs) const +{ + return std::visit(util::GenericVisitor{ + [&](TypeVariable _left, TypeVariable _right) { + if (_left.index() == _right.index()) + { + solAssert(_left.sort() == _right.sort()); + return true; + } + return false; + }, + [&](TypeConstant _left, TypeConstant _right) { + if (_left.constructor != _right.constructor) + return false; + if (_left.arguments.size() != _right.arguments.size()) + return false; + for (auto&& [left, right]: ranges::zip_view(_left.arguments, _right.arguments)) + if (!typeEquals(left, right)) + return false; + return true; + }, + [&](auto, auto) { + return false; + } + }, resolve(_lhs), resolve(_rhs)); +} + +TypeEnvironment TypeEnvironment::clone() const +{ + TypeEnvironment result{m_typeSystem}; + result.m_typeVariables = m_typeVariables; + return result; +} + +TypeSystem::TypeSystem() +{ + auto declarePrimitiveClass = [&](std::string _name) { + return std::visit(util::GenericVisitor{ + [](std::string _error) -> TypeClass { + solAssert(false, _error); + }, + [](TypeClass _class) -> TypeClass { return _class; } + }, declareTypeClass(_name, nullptr, true /* _primitive */)); + }; + + m_primitiveTypeClasses.emplace(PrimitiveClass::Type, declarePrimitiveClass("type")); + + for (auto [type, name, arity]: std::initializer_list>{ + {PrimitiveType::TypeFunction, "tfun", 2}, + {PrimitiveType::Function, "fun", 2}, + {PrimitiveType::Itself, "itself", 1}, + {PrimitiveType::Void, "void", 0}, + {PrimitiveType::Unit, "unit", 0}, + {PrimitiveType::Pair, "pair", 2}, + {PrimitiveType::Sum, "sum", 2}, + {PrimitiveType::Word, "word", 0}, + {PrimitiveType::Integer, "integer", 0}, + {PrimitiveType::Bool, "bool", 0}, + }) + m_primitiveTypeConstructors.emplace(type, declareTypeConstructor(name, name, arity, nullptr)); + + TypeClass classType = primitiveClass(PrimitiveClass::Type); + //TypeClass classKind = primitiveClass(PrimitiveClass::Kind); + Sort typeSort{{classType}}; + m_typeConstructors.at(m_primitiveTypeConstructors.at(PrimitiveType::TypeFunction).m_index).arities = {Arity{std::vector{{typeSort},{typeSort}}, classType}}; + m_typeConstructors.at(m_primitiveTypeConstructors.at(PrimitiveType::Function).m_index).arities = {Arity{std::vector{{typeSort, typeSort}}, classType}}; + m_typeConstructors.at(m_primitiveTypeConstructors.at(PrimitiveType::Itself).m_index).arities = {Arity{std::vector{{typeSort, typeSort}}, classType}}; +} + +experimental::Type TypeSystem::freshVariable(Sort _sort) +{ + size_t index = m_numTypeVariables++; + return TypeVariable(index, std::move(_sort)); +} + +experimental::Type TypeSystem::freshTypeVariable(Sort _sort) +{ + _sort.classes.emplace(primitiveClass(PrimitiveClass::Type)); + return freshVariable(_sort); +} + +std::vector TypeEnvironment::instantiate(TypeVariable _variable, Type _type) +{ + for (auto const& maybeTypeVar: TypeEnvironmentHelpers{*this}.typeVars(_type)) + if (auto const* typeVar = std::get_if(&maybeTypeVar)) + if (typeVar->index() == _variable.index()) + return {UnificationFailure{RecursiveUnification{_variable, _type}}}; + Sort typeSort = sort(_type); + if (!(_variable.sort() <= typeSort)) + { + return {UnificationFailure{SortMismatch{_type, _variable.sort() - typeSort}}}; + } + solAssert(m_typeVariables.emplace(_variable.index(), _type).second); + return {}; +} + +experimental::Type TypeEnvironment::resolve(Type _type) const +{ + Type result = _type; + while (auto const* var = std::get_if(&result)) + if (Type const* resolvedType = util::valueOrNullptr(m_typeVariables, var->index())) + result = *resolvedType; + else + break; + return result; +} + +experimental::Type TypeEnvironment::resolveRecursive(Type _type) const +{ + return std::visit(util::GenericVisitor{ + [&](TypeConstant const& _typeConstant) -> Type { + return TypeConstant{ + _typeConstant.constructor, + _typeConstant.arguments | ranges::views::transform([&](Type const& _argType) { + return resolveRecursive(_argType); + }) | ranges::to> + }; + }, + [](TypeVariable const& _typeVar) -> Type { + return _typeVar; + }, + [](std::monostate _nothing) -> Type { + return _nothing; + } + }, resolve(_type)); +} + +Sort TypeEnvironment::sort(Type _type) const +{ + return std::visit(util::GenericVisitor{ + [&](TypeConstant const& _expression) -> Sort + { + auto const& constructorInfo = m_typeSystem.constructorInfo(_expression.constructor); + auto argumentSorts = _expression.arguments | ranges::views::transform([&](Type _argumentType) { + return sort(resolve(_argumentType)); + }) | ranges::to>; + Sort sort; + for (auto const& arity: constructorInfo.arities) + { + solAssert(arity.argumentSorts.size() == argumentSorts.size()); + bool hasArity = true; + for (auto&& [argumentSort, arityArgumentSort]: ranges::zip_view(argumentSorts, arity.argumentSorts)) + { + if (!(arityArgumentSort <= argumentSort)) + { + hasArity = false; + break; + } + } + + if (hasArity) + sort.classes.insert(arity.typeClass); + } + return sort; + }, + [](TypeVariable const& _variable) -> Sort { return _variable.sort(); }, + [](std::monostate) -> Sort { solAssert(false); } + }, _type); +} + +TypeConstructor TypeSystem::declareTypeConstructor(std::string _name, std::string _canonicalName, size_t _arguments, Declaration const* _declaration) +{ + solAssert(m_canonicalTypeNames.insert(_canonicalName).second, "Duplicate canonical type name."); + Sort baseSort{{primitiveClass(PrimitiveClass::Type)}}; + size_t index = m_typeConstructors.size(); + m_typeConstructors.emplace_back(TypeConstructorInfo{ + _name, + _canonicalName, + {Arity{std::vector{_arguments, baseSort}, primitiveClass(PrimitiveClass::Type)}}, + _declaration + }); + TypeConstructor constructor{index}; + if (_arguments) + { + std::vector argumentSorts; + std::generate_n(std::back_inserter(argumentSorts), _arguments, [&](){ return Sort{{primitiveClass(PrimitiveClass::Type)}}; }); + std::vector argumentTypes; + std::generate_n(std::back_inserter(argumentTypes), _arguments, [&](){ return freshVariable({}); }); + auto error = instantiateClass(type(constructor, argumentTypes), Arity{argumentSorts, primitiveClass(PrimitiveClass::Type)}); + solAssert(!error, *error); + } + else + { + auto error = instantiateClass(type(constructor, {}), Arity{{}, primitiveClass(PrimitiveClass::Type)}); + solAssert(!error, *error); + } + return constructor; +} + +std::variant TypeSystem::declareTypeClass(std::string _name, Declaration const* _declaration, bool _primitive) +{ + TypeClass typeClass{m_typeClasses.size()}; + + Type typeVariable = (_primitive ? freshVariable({{typeClass}}) : freshTypeVariable({{typeClass}})); + solAssert(std::holds_alternative(typeVariable)); + + m_typeClasses.emplace_back(TypeClassInfo{ + typeVariable, + _name, + _declaration + }); + return typeClass; +} + +experimental::Type TypeSystem::type(TypeConstructor _constructor, std::vector _arguments) const +{ + // TODO: proper error handling + auto const& info = m_typeConstructors.at(_constructor.m_index); + solAssert( + info.arguments() == _arguments.size(), + fmt::format("Type constructor '{}' accepts {} type arguments (got {}).", constructorInfo(_constructor).name, info.arguments(), _arguments.size()) + ); + return TypeConstant{_constructor, _arguments}; +} + +experimental::Type TypeEnvironment::fresh(Type _type) +{ + std::unordered_map mapping; + auto freshImpl = [&](Type _type, auto _recurse) -> Type { + return std::visit(util::GenericVisitor{ + [&](TypeConstant const& _type) -> Type { + return TypeConstant{ + _type.constructor, + _type.arguments | ranges::views::transform([&](Type _argType) { + return _recurse(_argType, _recurse); + }) | ranges::to> + }; + }, + [&](TypeVariable const& _var) -> Type { + if (auto* mapped = util::valueOrNullptr(mapping, _var.index())) + { + auto* typeVariable = std::get_if(mapped); + solAssert(typeVariable); + // TODO: can there be a mismatch? + solAssert(typeVariable->sort() == _var.sort()); + return *mapped; + } + return mapping[_var.index()] = m_typeSystem.freshTypeVariable(_var.sort()); + }, + [](std::monostate) -> Type { solAssert(false); } + }, resolve(_type)); + }; + return freshImpl(_type, freshImpl); +} + +std::optional TypeSystem::instantiateClass(Type _instanceVariable, Arity _arity) +{ + if (!TypeSystemHelpers{*this}.isTypeConstant(_instanceVariable)) + return "Invalid instance variable."; + auto [typeConstructor, typeArguments] = TypeSystemHelpers{*this}.destTypeConstant(_instanceVariable); + auto& typeConstructorInfo = m_typeConstructors.at(typeConstructor.m_index); + if (_arity.argumentSorts.size() != typeConstructorInfo.arguments()) + return "Invalid arity."; + if (typeArguments.size() != typeConstructorInfo.arguments()) + return "Invalid arity."; + + typeConstructorInfo.arities.emplace_back(_arity); + + return std::nullopt; +} diff --git a/compiler/libsolidity/experimental/ast/TypeSystem.h b/compiler/libsolidity/experimental/ast/TypeSystem.h new file mode 100644 index 00000000..cac0feb8 --- /dev/null +++ b/compiler/libsolidity/experimental/ast/TypeSystem.h @@ -0,0 +1,184 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace solidity::frontend +{ +class Declaration; +} + +namespace solidity::frontend::experimental +{ + +class TypeEnvironment +{ +public: + struct TypeMismatch + { + Type a; + Type b; + }; + + struct SortMismatch { + Type type; + Sort sort; + }; + + struct RecursiveUnification + { + Type var; + Type type; + }; + + using UnificationFailure = std::variant< + TypeMismatch, + SortMismatch, + RecursiveUnification + >; + + TypeEnvironment(TypeSystem& _typeSystem): m_typeSystem(_typeSystem) {} + TypeEnvironment(TypeEnvironment const&) = delete; + TypeEnvironment& operator=(TypeEnvironment const&) = delete; + TypeEnvironment clone() const; + + Type resolve(Type _type) const; + Type resolveRecursive(Type _type) const; + Type fresh(Type _type); + [[nodiscard]] std::vector unify(Type _a, Type _b); + Sort sort(Type _type) const; + bool typeEquals(Type _lhs, Type _rhs) const; + + TypeSystem& typeSystem() { return m_typeSystem; } + TypeSystem const& typeSystem() const { return m_typeSystem; } + +private: + TypeEnvironment(TypeEnvironment&& _env): + m_typeSystem(_env.m_typeSystem), + m_typeVariables(std::move(_env.m_typeVariables)) + {} + + [[nodiscard]] std::vector instantiate(TypeVariable _variable, Type _type); + + TypeSystem& m_typeSystem; + std::map m_typeVariables; +}; + +class TypeSystem +{ +public: + struct TypeConstructorInfo + { + std::string name; + std::string canonicalName; + std::vector arities; + Declaration const* typeDeclaration = nullptr; + size_t arguments() const + { + solAssert(!arities.empty()); + return arities.front().argumentSorts.size(); + } + }; + struct TypeClassInfo + { + Type typeVariable; + std::string name; + Declaration const* classDeclaration = nullptr; + }; + TypeSystem(); + TypeSystem(TypeSystem const&) = delete; + TypeSystem const& operator=(TypeSystem const&) = delete; + Type type(PrimitiveType _typeConstructor, std::vector _arguments) const + { + return type(m_primitiveTypeConstructors.at(_typeConstructor), std::move(_arguments)); + } + Type type(TypeConstructor _typeConstructor, std::vector _arguments) const; + std::string typeName(TypeConstructor _typeConstructor) const + { + // TODO: proper error handling + return m_typeConstructors.at(_typeConstructor.m_index).name; + } + std::string canonicalName(TypeConstructor _typeConstructor) const + { + // TODO: proper error handling + return m_typeConstructors.at(_typeConstructor.m_index).canonicalName; + } + TypeConstructor declareTypeConstructor(std::string _name, std::string _canonicalName, size_t _arguments, Declaration const* _declaration); + TypeConstructor constructor(PrimitiveType _type) const + { + return m_primitiveTypeConstructors.at(_type); + } + TypeClass primitiveClass(PrimitiveClass _class) const + { + return m_primitiveTypeClasses.at(_class); + } + size_t constructorArguments(TypeConstructor _typeConstructor) const + { + // TODO: error handling + return m_typeConstructors.at(_typeConstructor.m_index).arguments(); + } + TypeConstructorInfo const& constructorInfo(TypeConstructor _typeConstructor) const + { + // TODO: error handling + return m_typeConstructors.at(_typeConstructor.m_index); + } + TypeConstructorInfo const& constructorInfo(PrimitiveType _typeConstructor) const + { + return constructorInfo(constructor(_typeConstructor)); + } + + std::variant declareTypeClass(std::string _name, Declaration const* _declaration, bool _primitive = false); + [[nodiscard]] std::optional instantiateClass(Type _instanceVariable, Arity _arity); + + Type freshTypeVariable(Sort _sort); + + TypeEnvironment const& env() const { return m_globalTypeEnvironment; } + TypeEnvironment& env() { return m_globalTypeEnvironment; } + + Type freshVariable(Sort _sort); + std::string typeClassName(TypeClass _class) const { return m_typeClasses.at(_class.m_index).name; } + Declaration const* typeClassDeclaration(TypeClass _class) const { return m_typeClasses.at(_class.m_index).classDeclaration; } + Type typeClassVariable(TypeClass _class) const + { + return m_typeClasses.at(_class.m_index).typeVariable; + } + + TypeClassInfo const& typeClassInfo(TypeClass _class) const + { + return m_typeClasses.at(_class.m_index); + } + +private: + friend class TypeEnvironment; + size_t m_numTypeVariables = 0; + std::map m_primitiveTypeConstructors; + std::map m_primitiveTypeClasses; + std::set m_canonicalTypeNames; + std::vector m_typeConstructors; + std::vector m_typeClasses; + TypeEnvironment m_globalTypeEnvironment{*this}; +}; + +} diff --git a/compiler/libsolidity/experimental/ast/TypeSystemHelper.cpp b/compiler/libsolidity/experimental/ast/TypeSystemHelper.cpp new file mode 100644 index 00000000..bc7dd4fc --- /dev/null +++ b/compiler/libsolidity/experimental/ast/TypeSystemHelper.cpp @@ -0,0 +1,404 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +using namespace solidity::langutil; +using namespace solidity::frontend; +using namespace solidity::frontend::experimental; + +/*std::optional experimental::typeConstructorFromTypeName(Analysis const& _analysis, TypeName const& _typeName) +{ + if (auto const* elementaryTypeName = dynamic_cast(&_typeName)) + { + if (auto constructor = typeConstructorFromToken(_analysis, elementaryTypeName->typeName().token())) + return *constructor; + } + else if (auto const* userDefinedType = dynamic_cast(&_typeName)) + { + if (auto const* referencedDeclaration = userDefinedType->pathNode().annotation().referencedDeclaration) + return _analysis.annotation(*referencedDeclaration).typeConstructor; + } + return nullopt; +}*/ +/* +std::optional experimental::typeConstructorFromToken(Analysis const& _analysis, langutil::Token _token) +{ + TypeSystem const& typeSystem = _analysis.typeSystem(); + switch (_token) + { + case Token::Void: + return typeSystem.builtinConstructor(BuiltinType::Void); + case Token::Fun: + return typeSystem.builtinConstructor(BuiltinType::Function); + case Token::Unit: + return typeSystem.builtinConstructor(BuiltinType::Unit); + case Token::Pair: + return typeSystem.builtinConstructor(BuiltinType::Pair); + case Token::Word: + return typeSystem.builtinConstructor(BuiltinType::Word); + case Token::IntegerType: + return typeSystem.builtinConstructor(BuiltinType::Integer); + case Token::Bool: + return typeSystem.builtinConstructor(BuiltinType::Bool); + default: + return nullopt; + } +}*/ + +std::optional experimental::builtinClassFromToken(langutil::Token _token) +{ + switch (_token) + { + case Token::Integer: + return BuiltinClass::Integer; + case Token::Mul: + return BuiltinClass::Mul; + case Token::Add: + return BuiltinClass::Add; + case Token::Equal: + return BuiltinClass::Equal; + case Token::LessThan: + return BuiltinClass::Less; + case Token::LessThanOrEqual: + return BuiltinClass::LessOrEqual; + case Token::GreaterThan: + return BuiltinClass::Greater; + case Token::GreaterThanOrEqual: + return BuiltinClass::GreaterOrEqual; + default: + return std::nullopt; + } +} +/* +std::optional experimental::typeClassFromTypeClassName(TypeClassName const& _typeClass) +{ + return std::visit(util::GenericVisitor{ + [&](ASTPointer _path) -> optional { + auto classDefinition = dynamic_cast(_path->annotation().referencedDeclaration); + if (!classDefinition) + return nullopt; + return TypeClass{classDefinition}; + }, + [&](Token _token) -> optional { + return typeClassFromToken(_token); + } + }, _typeClass.name()); +} +*/ +experimental::Type TypeSystemHelpers::tupleType(std::vector _elements) const +{ + if (_elements.empty()) + return typeSystem.type(PrimitiveType::Unit, {}); + if (_elements.size() == 1) + return _elements.front(); + Type result = _elements.back(); + for (Type type: _elements | ranges::views::reverse | ranges::views::drop_exactly(1)) + result = typeSystem.type(PrimitiveType::Pair, {type, result}); + return result; +} + +std::vector TypeSystemHelpers::destTupleType(Type _tupleType) const +{ + if (!isTypeConstant(_tupleType)) + return {_tupleType}; + TypeConstructor pairConstructor = typeSystem.constructor(PrimitiveType::Pair); + auto [constructor, arguments] = destTypeConstant(_tupleType); + if (constructor == typeSystem.constructor(PrimitiveType::Unit)) + return {}; + if (constructor != pairConstructor) + return {_tupleType}; + solAssert(arguments.size() == 2); + + std::vector result; + result.emplace_back(arguments.front()); + Type tail = arguments.back(); + while (true) + { + if (!isTypeConstant(tail)) + break; + auto [tailConstructor, tailArguments] = destTypeConstant(tail); + if (tailConstructor != pairConstructor) + break; + solAssert(tailArguments.size() == 2); + result.emplace_back(tailArguments.front()); + tail = tailArguments.back(); + } + result.emplace_back(tail); + return result; +} + +experimental::Type TypeSystemHelpers::sumType(std::vector _elements) const +{ + if (_elements.empty()) + return typeSystem.type(PrimitiveType::Void, {}); + if (_elements.size() == 1) + return _elements.front(); + Type result = _elements.back(); + for (Type type: _elements | ranges::views::reverse | ranges::views::drop_exactly(1)) + result = typeSystem.type(PrimitiveType::Sum, {type, result}); + return result; +} + +std::vector TypeSystemHelpers::destSumType(Type _tupleType) const +{ + if (!isTypeConstant(_tupleType)) + return {_tupleType}; + TypeConstructor sumConstructor = typeSystem.constructor(PrimitiveType::Sum); + auto [constructor, arguments] = destTypeConstant(_tupleType); + if (constructor == typeSystem.constructor(PrimitiveType::Void)) + return {}; + if (constructor != sumConstructor) + return {_tupleType}; + solAssert(arguments.size() == 2); + + std::vector result; + result.emplace_back(arguments.front()); + Type tail = arguments.back(); + while (true) + { + if (!isTypeConstant(tail)) + break; + auto [tailConstructor, tailArguments] = destTypeConstant(tail); + if (tailConstructor != sumConstructor) + break; + solAssert(tailArguments.size() == 2); + result.emplace_back(tailArguments.front()); + tail = tailArguments.back(); + } + result.emplace_back(tail); + return result; +} + +std::tuple> TypeSystemHelpers::destTypeConstant(Type _type) const +{ + using ResultType = std::tuple>; + return std::visit(util::GenericVisitor{ + [&](TypeConstant const& _type) -> ResultType { + return std::make_tuple(_type.constructor, _type.arguments); + }, + [](auto const&) -> ResultType { + solAssert(false); + } + }, _type); +} + +bool TypeSystemHelpers::isTypeConstant(Type _type) const +{ + return std::visit(util::GenericVisitor{ + [&](TypeConstant const&) -> bool { + return true; + }, + [](auto const&) -> bool { + return false; + } + }, _type); +} + +bool TypeSystemHelpers::isPrimitiveType(Type _type, PrimitiveType _primitiveType) const +{ + if (!isTypeConstant(_type)) + return false; + auto constructor = std::get<0>(destTypeConstant(_type)); + return constructor == typeSystem.constructor(_primitiveType); +} + +experimental::Type TypeSystemHelpers::functionType(experimental::Type _argType, experimental::Type _resultType) const +{ + return typeSystem.type(PrimitiveType::Function, {_argType, _resultType}); +} + +std::tuple TypeSystemHelpers::destFunctionType(Type _functionType) const +{ + auto [constructor, arguments] = destTypeConstant(_functionType); + solAssert(constructor == typeSystem.constructor(PrimitiveType::Function)); + solAssert(arguments.size() == 2); + return std::make_tuple(arguments.front(), arguments.back()); +} + +bool TypeSystemHelpers::isFunctionType(Type _type) const +{ + return isPrimitiveType(_type, PrimitiveType::Function); +} + +experimental::Type TypeSystemHelpers::typeFunctionType(experimental::Type _argType, experimental::Type _resultType) const +{ + return typeSystem.type(PrimitiveType::TypeFunction, {_argType, _resultType}); +} + +std::tuple TypeSystemHelpers::destTypeFunctionType(Type _functionType) const +{ + auto [constructor, arguments] = destTypeConstant(_functionType); + solAssert(constructor == typeSystem.constructor(PrimitiveType::TypeFunction)); + solAssert(arguments.size() == 2); + return std::make_tuple(arguments.front(), arguments.back()); +} + +bool TypeSystemHelpers::isTypeFunctionType(Type _type) const +{ + return isPrimitiveType(_type, PrimitiveType::TypeFunction); +} + +std::vector TypeEnvironmentHelpers::typeVars(Type _type) const +{ + std::set indices; + std::vector typeVars; + auto typeVarsImpl = [&](Type _type, auto _recurse) -> void { + std::visit(util::GenericVisitor{ + [&](TypeConstant const& _type) { + for (auto arg: _type.arguments) + _recurse(arg, _recurse); + }, + [&](TypeVariable const& _var) { + if (indices.emplace(_var.index()).second) + typeVars.emplace_back(_var); + }, + [](std::monostate) { solAssert(false); } + }, env.resolve(_type)); + }; + typeVarsImpl(_type, typeVarsImpl); + return typeVars; + +} + + +std::string TypeSystemHelpers::sortToString(Sort _sort) const +{ + switch (_sort.classes.size()) + { + case 0: + return "()"; + case 1: + return typeSystem.typeClassName(*_sort.classes.begin()); + default: + { + std::stringstream stream; + stream << "("; + for (auto typeClass: _sort.classes | ranges::views::drop_last(1)) + stream << typeSystem.typeClassName(typeClass) << ", "; + stream << typeSystem.typeClassName(*_sort.classes.rbegin()) << ")"; + return stream.str(); + } + } +} + +std::string TypeEnvironmentHelpers::canonicalTypeName(Type _type) const +{ + return visit(util::GenericVisitor{ + [&](TypeConstant _type) -> std::string { + std::stringstream stream; + stream << env.typeSystem().constructorInfo(_type.constructor).canonicalName; + if (!_type.arguments.empty()) + { + stream << "$"; + for (auto type: _type.arguments | ranges::views::drop_last(1)) + stream << canonicalTypeName(type) << "$"; + stream << canonicalTypeName(_type.arguments.back()); + stream << "$"; + } + return stream.str(); + }, + [](TypeVariable) -> std::string { + solAssert(false); + }, + [](std::monostate) -> std::string { + solAssert(false); + }, + }, env.resolve(_type)); +} + +std::string TypeEnvironmentHelpers::typeToString(Type const& _type) const +{ + std::map)>> formatters{ + {env.typeSystem().constructor(PrimitiveType::Function), [&](auto const& _args) { + solAssert(_args.size() == 2); + return fmt::format("{} -> {}", typeToString(_args.front()), typeToString(_args.back())); + }}, + {env.typeSystem().constructor(PrimitiveType::Unit), [&](auto const& _args) { + solAssert(_args.size() == 0); + return "()"; + }}, + {env.typeSystem().constructor(PrimitiveType::Pair), [&](auto const& _arguments) { + auto tupleTypes = TypeSystemHelpers{env.typeSystem()}.destTupleType(_arguments.back()); + std::string result = "("; + result += typeToString(_arguments.front()); + for (auto type: tupleTypes) + result += ", " + typeToString(type); + result += ")"; + return result; + }}, + }; + return std::visit(util::GenericVisitor{ + [&](TypeConstant const& _type) { + if (auto* formatter = util::valueOrNullptr(formatters, _type.constructor)) + return (*formatter)(_type.arguments); + std::stringstream stream; + stream << env.typeSystem().constructorInfo(_type.constructor).name; + if (!_type.arguments.empty()) + { + stream << "("; + for (auto type: _type.arguments | ranges::views::drop_last(1)) + stream << typeToString(type) << ", "; + stream << typeToString(_type.arguments.back()); + stream << ")"; + } + return stream.str(); + }, + [&](TypeVariable const& _type) { + std::stringstream stream; + std::string varName; + size_t index = _type.index(); + varName += static_cast('a' + (index%26)); + while (index /= 26) + varName += static_cast('a' + (index%26)); + reverse(varName.begin(), varName.end()); + stream << '\'' << varName; + switch (_type.sort().classes.size()) + { + case 0: + break; + case 1: + stream << ":" << env.typeSystem().typeClassName(*_type.sort().classes.begin()); + break; + default: + stream << ":("; + for (auto typeClass: _type.sort().classes | ranges::views::drop_last(1)) + stream << env.typeSystem().typeClassName(typeClass) << ", "; + stream << env.typeSystem().typeClassName(*_type.sort().classes.rbegin()); + stream << ")"; + break; + } + return stream.str(); + }, + [](std::monostate) -> std::string { solAssert(false); } + }, env.resolve(_type)); +} diff --git a/compiler/libsolidity/experimental/ast/TypeSystemHelper.h b/compiler/libsolidity/experimental/ast/TypeSystemHelper.h new file mode 100644 index 00000000..fd55c1bf --- /dev/null +++ b/compiler/libsolidity/experimental/ast/TypeSystemHelper.h @@ -0,0 +1,60 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#pragma once + +#include +#include +#include + +namespace solidity::frontend::experimental +{ +class Analysis; +enum class BuiltinClass; +//std::optional typeConstructorFromTypeName(Analysis const& _analysis, TypeName const& _typeName); +//std::optional typeConstructorFromToken(Analysis const& _analysis, langutil::Token _token); +//std::optional typeClassFromTypeClassName(TypeClassName const& _typeClass); +std::optional builtinClassFromToken(langutil::Token _token); + +struct TypeSystemHelpers +{ + TypeSystem const& typeSystem; + std::tuple> destTypeConstant(Type _type) const; + bool isTypeConstant(Type _type) const; + bool isPrimitiveType(Type _type, PrimitiveType _primitiveType) const; + Type tupleType(std::vector _elements) const; + std::vector destTupleType(Type _tupleType) const; + Type sumType(std::vector _elements) const; + std::vector destSumType(Type _tupleType) const; + Type functionType(Type _argType, Type _resultType) const; + std::tuple destFunctionType(Type _functionType) const; + bool isFunctionType(Type _type) const; + Type typeFunctionType(Type _argType, Type _resultType) const; + std::tuple destTypeFunctionType(Type _functionType) const; + bool isTypeFunctionType(Type _type) const; + std::string sortToString(Sort _sort) const; +}; + +struct TypeEnvironmentHelpers +{ + TypeEnvironment const& env; + std::string typeToString(Type const& _type) const; + std::string canonicalTypeName(Type _type) const; + std::vector typeVars(Type _type) const; +}; + +} diff --git a/compiler/libsolidity/formal/ArraySlicePredicate.cpp b/compiler/libsolidity/formal/ArraySlicePredicate.cpp new file mode 100644 index 00000000..21d5fb1a --- /dev/null +++ b/compiler/libsolidity/formal/ArraySlicePredicate.cpp @@ -0,0 +1,90 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include + +using namespace solidity; +using namespace solidity::smtutil; +using namespace solidity::frontend; +using namespace solidity::frontend::smt; + +std::map ArraySlicePredicate::m_slicePredicates; + +std::pair ArraySlicePredicate::create(SortPointer _sort, EncodingContext& _context) +{ + solAssert(_sort->kind == Kind::Tuple, ""); + auto tupleSort = std::dynamic_pointer_cast(_sort); + solAssert(tupleSort, ""); + + auto tupleName = tupleSort->name; + if (m_slicePredicates.count(tupleName)) + return {true, m_slicePredicates.at(tupleName)}; + + auto sort = tupleSort->components.at(0); + solAssert(sort->kind == Kind::Array, ""); + + smt::SymbolicArrayVariable aVar{sort, "a_" + tupleName, _context }; + smt::SymbolicArrayVariable bVar{sort, "b_" + tupleName, _context}; + smt::SymbolicIntVariable startVar{TypeProvider::uint256(), TypeProvider::uint256(), "start_" + tupleName, _context}; + smt::SymbolicIntVariable endVar{TypeProvider::uint256(), TypeProvider::uint256(), "end_" + tupleName, _context }; + smt::SymbolicIntVariable iVar{TypeProvider::uint256(), TypeProvider::uint256(), "i_" + tupleName, _context}; + + std::vector domain{sort, sort, startVar.sort(), endVar.sort()}; + auto sliceSort = std::make_shared(domain, SortProvider::boolSort); + Predicate const& slice = *Predicate::create(sliceSort, "array_slice_" + tupleName, PredicateType::Custom, _context); + + domain.emplace_back(iVar.sort()); + auto predSort = std::make_shared(domain, SortProvider::boolSort); + Predicate const& header = *Predicate::create(predSort, "array_slice_header_" + tupleName, PredicateType::Custom, _context); + Predicate const& loop = *Predicate::create(predSort, "array_slice_loop_" + tupleName, PredicateType::Custom, _context); + + auto a = aVar.elements(); + auto b = bVar.elements(); + auto start = startVar.currentValue(); + auto end = endVar.currentValue(); + auto i = iVar.currentValue(); + + auto rule1 = smtutil::Expression::implies( + end > start, + header({a, b, start, end, 0}) + ); + + auto rule2 = smtutil::Expression::implies( + header({a, b, start, end, i}) && i >= (end - start), + slice({a, b, start, end}) + ); + + auto rule3 = smtutil::Expression::implies( + header({a, b, start, end, i}) && i >= 0 && i < (end - start), + loop({a, b, start, end, i}) + ); + + auto b_i = smtutil::Expression::select(b, i); + auto a_start_i = smtutil::Expression::select(a, start + i); + auto rule4 = smtutil::Expression::implies( + loop({a, b, start, end, i}) && b_i == a_start_i, + header({a, b, start, end, i + 1}) + ); + + return {false, m_slicePredicates[tupleName] = { + {&slice, &header, &loop}, + {std::move(rule1), std::move(rule2), std::move(rule3), std::move(rule4)} + }}; +} diff --git a/compiler/libsolidity/formal/BMC.cpp b/compiler/libsolidity/formal/BMC.cpp new file mode 100644 index 00000000..b16182af --- /dev/null +++ b/compiler/libsolidity/formal/BMC.cpp @@ -0,0 +1,1329 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include + +#include + +#include +#include + +#include + +#ifdef HAVE_Z3_DLOPEN +#include +#endif + +using namespace solidity; +using namespace solidity::util; +using namespace solidity::langutil; +using namespace solidity::frontend; + +BMC::BMC( + smt::EncodingContext& _context, + UniqueErrorReporter& _errorReporter, + UniqueErrorReporter& _unsupportedErrorReporter, + std::map const& _smtlib2Responses, + ReadCallback::Callback const& _smtCallback, + ModelCheckerSettings _settings, + CharStreamProvider const& _charStreamProvider +): + SMTEncoder(_context, _settings, _errorReporter, _unsupportedErrorReporter, _charStreamProvider), + m_interface(std::make_unique( + _smtlib2Responses, _smtCallback, _settings.solvers, _settings.timeout, _settings.printQuery + )) +{ + solAssert(!_settings.printQuery || _settings.solvers == smtutil::SMTSolverChoice::SMTLIB2(), "Only SMTLib2 solver can be enabled to print queries"); +#if defined (HAVE_Z3) || defined (HAVE_CVC4) + if (m_settings.solvers.cvc4 || m_settings.solvers.z3) + if (!_smtlib2Responses.empty()) + m_errorReporter.warning( + 5622_error, + "SMT-LIB2 query responses were given in the auxiliary input, " + "but this Solidity binary uses an SMT solver (Z3/CVC4) directly." + "These responses will be ignored." + "Consider disabling Z3/CVC4 at compilation time in order to use SMT-LIB2 responses." + ); +#endif +} + +void BMC::analyze(SourceUnit const& _source, std::map, smt::EncodingContext::IdCompare> _solvedTargets) +{ + // At this point every enabled solver is available. + if (!m_settings.solvers.cvc4 && !m_settings.solvers.smtlib2 && !m_settings.solvers.z3) + { + m_errorReporter.warning( + 7710_error, + SourceLocation(), + "BMC analysis was not possible since no SMT solver was found and enabled." + " The accepted solvers for BMC are cvc4 and z3." + ); + return; + } + + SMTEncoder::resetSourceAnalysis(); + + state().prepareForSourceUnit(_source, false); + m_solvedTargets = std::move(_solvedTargets); + m_context.setSolver(m_interface.get()); + m_context.reset(); + m_context.setAssertionAccumulation(true); + m_variableUsage.setFunctionInlining(shouldInlineFunctionCall); + createFreeConstants(sourceDependencies(_source)); + m_unprovedAmt = 0; + + _source.accept(*this); + + if (m_unprovedAmt > 0 && !m_settings.showUnproved) + m_errorReporter.warning( + 2788_error, + {}, + "BMC: " + + std::to_string(m_unprovedAmt) + + " verification condition(s) could not be proved." + + " Enable the model checker option \"show unproved\" to see all of them." + + " Consider choosing a specific contract to be verified in order to reduce the solving problems." + + " Consider increasing the timeout per query." + ); + + if (!m_settings.showProvedSafe && !m_safeTargets.empty()) + m_errorReporter.info( + 6002_error, + "BMC: " + + std::to_string(m_safeTargets.size()) + + " verification condition(s) proved safe!" + + " Enable the model checker option \"show proved safe\" to see all of them." + ); + else if (m_settings.showProvedSafe) + for (auto const& [node, targets]: m_safeTargets) + for (auto const& target: targets) + m_errorReporter.info( + 2961_error, + node->location(), + "BMC: " + + targetDescription(target) + + " check is safe!" + ); + + + // If this check is true, Z3 and CVC4 are not available + // and the query answers were not provided, since SMTPortfolio + // guarantees that SmtLib2Interface is the first solver, if enabled. + if ( + !m_interface->unhandledQueries().empty() && + m_interface->solvers() == 1 && + m_settings.solvers.smtlib2 + ) + m_errorReporter.warning( + 8084_error, + SourceLocation(), + "BMC analysis was not possible. No SMT solver (Z3 or CVC4) was available." + " None of the installed solvers was enabled." +#ifdef HAVE_Z3_DLOPEN + " Install libz3.so." + std::to_string(Z3_MAJOR_VERSION) + "." + std::to_string(Z3_MINOR_VERSION) + " to enable Z3." +#endif + ); +} + +bool BMC::shouldInlineFunctionCall( + FunctionCall const& _funCall, + ContractDefinition const* _scopeContract, + ContractDefinition const* _contextContract +) +{ + auto funDef = functionCallToDefinition(_funCall, _scopeContract, _contextContract); + if (!funDef || !funDef->isImplemented()) + return false; + + FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); + if (funType.kind() == FunctionType::Kind::External) + return isExternalCallToThis(&_funCall.expression()); + else if (funType.kind() != FunctionType::Kind::Internal) + return false; + + return true; +} + +/// AST visitors. + +bool BMC::visit(ContractDefinition const& _contract) +{ + initContract(_contract); + + SMTEncoder::visit(_contract); + + return false; +} + +void BMC::endVisit(ContractDefinition const& _contract) +{ + if (auto constructor = _contract.constructor()) + constructor->accept(*this); + else + { + /// Visiting implicit constructor - we need a dummy callstack frame + pushCallStack({nullptr, nullptr}); + inlineConstructorHierarchy(_contract); + popCallStack(); + /// Check targets created by state variable initialization. + checkVerificationTargets(); + m_verificationTargets.clear(); + } + + SMTEncoder::endVisit(_contract); +} + +bool BMC::visit(FunctionDefinition const& _function) +{ + // Free functions need to be visited in the context of a contract. + if (!m_currentContract) + return false; + + auto contract = dynamic_cast(_function.scope()); + auto const& hierarchy = m_currentContract->annotation().linearizedBaseContracts; + if (contract && find(hierarchy.begin(), hierarchy.end(), contract) == hierarchy.end()) + createStateVariables(*contract); + + if (m_callStack.empty()) + { + reset(); + initFunction(_function); + if (_function.isConstructor() || _function.isPublic()) + m_context.addAssertion(state().txTypeConstraints() && state().txFunctionConstraints(_function)); + resetStateVariables(); + } + + if (_function.isConstructor()) + { + solAssert(contract, ""); + inlineConstructorHierarchy(*contract); + } + + /// Already visits the children. + SMTEncoder::visit(_function); + + return false; +} + +void BMC::endVisit(FunctionDefinition const& _function) +{ + // Free functions need to be visited in the context of a contract. + if (!m_currentContract) + return; + + if (isRootFunction()) + { + checkVerificationTargets(); + m_verificationTargets.clear(); + m_pathConditions.clear(); + } + + SMTEncoder::endVisit(_function); +} + +bool BMC::visit(IfStatement const& _node) +{ + auto indicesBeforePush = copyVariableIndices(); + // This check needs to be done in its own context otherwise + // constraints from the If body might influence it. + m_context.pushSolver(); + _node.condition().accept(*this); + + // We ignore called functions here because they have + // specific input values. + if (isRootFunction() && !isInsideLoop()) + addVerificationTarget( + VerificationTargetType::ConstantCondition, + expr(_node.condition()), + &_node.condition() + ); + m_context.popSolver(); + resetVariableIndices(std::move(indicesBeforePush)); + + _node.condition().accept(*this); + auto conditionExpr = expr(_node.condition()); + // visit true branch + auto [indicesEndTrue, trueEndPathCondition] = visitBranch(&_node.trueStatement(), conditionExpr); + + // visit false branch + decltype(indicesEndTrue) indicesEndFalse; + auto falseEndPathCondition = currentPathConditions() && !conditionExpr; + if (_node.falseStatement()) + std::tie(indicesEndFalse, falseEndPathCondition) = visitBranch(_node.falseStatement(), !conditionExpr); + else + indicesEndFalse = copyVariableIndices(); + + // merge the information from branches + setPathCondition(trueEndPathCondition || falseEndPathCondition); + mergeVariables(expr(_node.condition()), indicesEndTrue, indicesEndFalse); + + return false; +} + +bool BMC::visit(Conditional const& _op) +{ + auto indicesBeforePush = copyVariableIndices(); + m_context.pushSolver(); + _op.condition().accept(*this); + + if (isRootFunction() && !isInsideLoop()) + addVerificationTarget( + VerificationTargetType::ConstantCondition, + expr(_op.condition()), + &_op.condition() + ); + m_context.popSolver(); + resetVariableIndices(std::move(indicesBeforePush)); + + SMTEncoder::visit(_op); + + return false; +} + +// Unrolls while or do-while loop +bool BMC::visit(WhileStatement const& _node) +{ + unsigned int bmcLoopIterations = m_settings.bmcLoopIterations.value_or(1); + smtutil::Expression broke(false); + smtutil::Expression loopCondition(true); + if (_node.isDoWhile()) + { + for (unsigned int i = 0; i < bmcLoopIterations; ++i) + { + m_loopCheckpoints.emplace(); + + auto indicesBefore = copyVariableIndices(); + _node.body().accept(*this); + + auto brokeInCurrentIteration = mergeVariablesFromLoopCheckpoints(); + + auto indicesBreak = copyVariableIndices(); + _node.condition().accept(*this); + mergeVariables( + !brokeInCurrentIteration, + copyVariableIndices(), + indicesBreak + ); + + mergeVariables( + broke || !loopCondition, + indicesBefore, + copyVariableIndices() + ); + loopCondition = loopCondition && expr(_node.condition()); + broke = broke || brokeInCurrentIteration; + m_loopCheckpoints.pop(); + } + if (bmcLoopIterations > 0) + m_context.addAssertion(!loopCondition || broke); + } + else { + smtutil::Expression loopConditionOnPreviousIterations(true); + for (unsigned int i = 0; i < bmcLoopIterations; ++i) + { + m_loopCheckpoints.emplace(); + auto indicesBefore = copyVariableIndices(); + _node.condition().accept(*this); + loopCondition = expr(_node.condition()); + + auto indicesAfterCondition = copyVariableIndices(); + + pushPathCondition(loopCondition); + _node.body().accept(*this); + popPathCondition(); + + auto brokeInCurrentIteration = mergeVariablesFromLoopCheckpoints(); + + // merges indices modified when accepting loop condition that no longer holds + mergeVariables( + !loopCondition, + indicesAfterCondition, + copyVariableIndices() + ); + + // handles breaks in previous iterations + // breaks in current iterations are handled when traversing loop checkpoints + // handles case when the loop condition no longer holds but bmc loop iterations still unrolls the loop + mergeVariables( + broke || !loopConditionOnPreviousIterations, + indicesBefore, + copyVariableIndices() + ); + m_loopCheckpoints.pop(); + broke = broke || brokeInCurrentIteration; + loopConditionOnPreviousIterations = loopConditionOnPreviousIterations && loopCondition; + } + if (bmcLoopIterations > 0) + { + //after loop iterations are done, we check the loop condition last final time + auto indices = copyVariableIndices(); + _node.condition().accept(*this); + loopCondition = expr(_node.condition()); + // asseert that the loop is complete + m_context.addAssertion(!loopCondition || broke || !loopConditionOnPreviousIterations); + mergeVariables( + broke || !loopConditionOnPreviousIterations, + indices, + copyVariableIndices() + ); + } + } + m_loopExecutionHappened = true; + return false; +} + +// Unrolls for loop +bool BMC::visit(ForStatement const& _node) +{ + if (_node.initializationExpression()) + _node.initializationExpression()->accept(*this); + + smtutil::Expression broke(false); + smtutil::Expression forCondition(true); + smtutil::Expression forConditionOnPreviousIterations(true); + unsigned int bmcLoopIterations = m_settings.bmcLoopIterations.value_or(1); + for (unsigned int i = 0; i < bmcLoopIterations; ++i) + { + auto indicesBefore = copyVariableIndices(); + if (_node.condition()) + { + _node.condition()->accept(*this); + // values in loop condition might change during loop iteration + forCondition = expr(*_node.condition()); + } + m_loopCheckpoints.emplace(); + auto indicesAfterCondition = copyVariableIndices(); + + pushPathCondition(forCondition); + _node.body().accept(*this); + + auto brokeInCurrentIteration = mergeVariablesFromLoopCheckpoints(); + + // accept loop expression if there was no break + if (_node.loopExpression()) + { + auto indicesBreak = copyVariableIndices(); + _node.loopExpression()->accept(*this); + mergeVariables( + !brokeInCurrentIteration, + copyVariableIndices(), + indicesBreak + ); + } + popPathCondition(); + + // merges indices modified when accepting loop condition that does no longer hold + mergeVariables( + !forCondition, + indicesAfterCondition, + copyVariableIndices() + ); + + // handles breaks in previous iterations + // breaks in current iterations are handled when traversing loop checkpoints + // handles case when the loop condition no longer holds but bmc loop iterations still unrolls the loop + mergeVariables( + broke || !forConditionOnPreviousIterations, + indicesBefore, + copyVariableIndices() + ); + m_loopCheckpoints.pop(); + broke = broke || brokeInCurrentIteration; + forConditionOnPreviousIterations = forConditionOnPreviousIterations && forCondition; + } + if (bmcLoopIterations > 0) + { + //after loop iterations are done, we check the loop condition last final time + auto indices = copyVariableIndices(); + if (_node.condition()) + { + _node.condition()->accept(*this); + forCondition = expr(*_node.condition()); + } + // asseert that the loop is complete + m_context.addAssertion(!forCondition || broke || !forConditionOnPreviousIterations); + mergeVariables( + broke || !forConditionOnPreviousIterations, + indices, + copyVariableIndices() + ); + } + m_loopExecutionHappened = true; + return false; +} + +// merges variables based on loop control statements +// returns expression indicating whether there was a break in current loop unroll iteration +smtutil::Expression BMC::mergeVariablesFromLoopCheckpoints() +{ + smtutil::Expression continues(false); + smtutil::Expression brokeInCurrentIteration(false); + for (auto const& loopControl: m_loopCheckpoints.top()) + { + // use SSAs associated with this break statement only if + // loop didn't break or continue earlier in the iteration + // loop condition is included in break path conditions + mergeVariables( + !brokeInCurrentIteration && !continues && loopControl.pathConditions, + loopControl.variableIndices, + copyVariableIndices() + ); + if (loopControl.kind == LoopControlKind::Break) + brokeInCurrentIteration = + brokeInCurrentIteration || loopControl.pathConditions; + else if (loopControl.kind == LoopControlKind::Continue) + continues = continues || loopControl.pathConditions; + } + return brokeInCurrentIteration; +} + +bool BMC::visit(TryStatement const& _tryStatement) +{ + FunctionCall const* externalCall = dynamic_cast(&_tryStatement.externalCall()); + solAssert(externalCall && externalCall->annotation().tryCall, ""); + + externalCall->accept(*this); + if (_tryStatement.successClause()->parameters()) + expressionToTupleAssignment(_tryStatement.successClause()->parameters()->parameters(), *externalCall); + + smtutil::Expression clauseId = m_context.newVariable("clause_choice_" + std::to_string(m_context.newUniqueId()), smtutil::SortProvider::uintSort); + auto const& clauses = _tryStatement.clauses(); + m_context.addAssertion(clauseId >= 0 && clauseId < clauses.size()); + solAssert(clauses[0].get() == _tryStatement.successClause(), "First clause of TryStatement should be the success clause"); + std::vector> clausesVisitResults; + for (size_t i = 0; i < clauses.size(); ++i) + clausesVisitResults.push_back(visitBranch(clauses[i].get())); + + // merge the information from all clauses + smtutil::Expression pathCondition = clausesVisitResults.front().second; + auto currentIndices = clausesVisitResults[0].first; + for (size_t i = 1; i < clauses.size(); ++i) + { + mergeVariables(clauseId == i, clausesVisitResults[i].first, currentIndices); + currentIndices = copyVariableIndices(); + pathCondition = pathCondition || clausesVisitResults[i].second; + } + setPathCondition(pathCondition); + + return false; +} + +bool BMC::visit(Break const&) +{ + LoopControl control = { + LoopControlKind::Break, + currentPathConditions(), + copyVariableIndices() + }; + m_loopCheckpoints.top().emplace_back(control); + return false; +} + +bool BMC::visit(Continue const&) +{ + LoopControl control = { + LoopControlKind::Continue, + currentPathConditions(), + copyVariableIndices() + }; + m_loopCheckpoints.top().emplace_back(control); + return false; +} + +void BMC::endVisit(UnaryOperation const& _op) +{ + SMTEncoder::endVisit(_op); + + // User-defined operators are essentially function calls. + if (auto funDef = *_op.annotation().userDefinedFunction) + { + std::vector arguments; + arguments.push_back(&_op.subExpression()); + // pushCallStack and defineExpr inside createReturnedExpression should be called on the expression + // in case of a user-defined operator call + inlineFunctionCall(funDef, _op, std::nullopt, arguments); + return; + } + + if ( + _op.annotation().type->category() == Type::Category::RationalNumber || + _op.annotation().type->category() == Type::Category::FixedPoint + ) + return; + + if (_op.getOperator() == Token::Sub && smt::isInteger(*_op.annotation().type)) + addVerificationTarget( + VerificationTargetType::UnderOverflow, + expr(_op), + &_op + ); +} + +void BMC::endVisit(BinaryOperation const& _op) +{ + SMTEncoder::endVisit(_op); + + if (auto funDef = *_op.annotation().userDefinedFunction) + { + std::vector arguments; + arguments.push_back(&_op.leftExpression()); + arguments.push_back(&_op.rightExpression()); + + // pushCallStack and defineExpr inside createReturnedExpression should be called on the expression + // in case of a user-defined operator call + inlineFunctionCall(funDef, _op, std::nullopt, arguments); + } +} + +void BMC::endVisit(FunctionCall const& _funCall) +{ + auto functionCallKind = *_funCall.annotation().kind; + + if (functionCallKind != FunctionCallKind::FunctionCall) + { + SMTEncoder::endVisit(_funCall); + return; + } + + FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); + switch (funType.kind()) + { + case FunctionType::Kind::Assert: + visitAssert(_funCall); + SMTEncoder::endVisit(_funCall); + break; + case FunctionType::Kind::Require: + visitRequire(_funCall); + SMTEncoder::endVisit(_funCall); + break; + case FunctionType::Kind::Internal: + case FunctionType::Kind::External: + case FunctionType::Kind::DelegateCall: + case FunctionType::Kind::BareCall: + case FunctionType::Kind::BareCallCode: + case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: + case FunctionType::Kind::Creation: + SMTEncoder::endVisit(_funCall); + internalOrExternalFunctionCall(_funCall); + break; + case FunctionType::Kind::Send: + case FunctionType::Kind::Transfer: + { + auto value = _funCall.arguments().front(); + solAssert(value, ""); + smtutil::Expression thisBalance = state().balance(); + + addVerificationTarget( + VerificationTargetType::Balance, + thisBalance < expr(*value), + &_funCall + ); + + SMTEncoder::endVisit(_funCall); + break; + } + case FunctionType::Kind::KECCAK256: + case FunctionType::Kind::ECRecover: + case FunctionType::Kind::SHA256: + case FunctionType::Kind::RIPEMD160: + case FunctionType::Kind::BlockHash: + case FunctionType::Kind::AddMod: + case FunctionType::Kind::MulMod: + case FunctionType::Kind::Unwrap: + case FunctionType::Kind::Wrap: + [[fallthrough]]; + default: + SMTEncoder::endVisit(_funCall); + break; + } +} + +void BMC::endVisit(Return const& _return) +{ + SMTEncoder::endVisit(_return); + setPathCondition(smtutil::Expression(false)); +} + +/// Visitor helpers. + +void BMC::visitAssert(FunctionCall const& _funCall) +{ + auto const& args = _funCall.arguments(); + solAssert(args.size() == 1, ""); + solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); + addVerificationTarget( + VerificationTargetType::Assert, + expr(*args.front()), + &_funCall + ); +} + +void BMC::visitRequire(FunctionCall const& _funCall) +{ + auto const& args = _funCall.arguments(); + solAssert(args.size() >= 1, ""); + solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); + if (isRootFunction() && !isInsideLoop()) + addVerificationTarget( + VerificationTargetType::ConstantCondition, + expr(*args.front()), + args.front().get() + ); +} + +void BMC::visitAddMulMod(FunctionCall const& _funCall) +{ + solAssert(_funCall.arguments().at(2), ""); + addVerificationTarget( + VerificationTargetType::DivByZero, + expr(*_funCall.arguments().at(2)), + &_funCall + ); + + SMTEncoder::visitAddMulMod(_funCall); +} + +void BMC::inlineFunctionCall( + FunctionDefinition const* _funDef, + Expression const& _callStackExpr, + std::optional _boundArgumentCall, + std::vector const& _arguments +) +{ + solAssert(_funDef, ""); + + if (visitedFunction(_funDef)) + { + auto const& returnParams = _funDef->returnParameters(); + for (auto param: returnParams) + { + m_context.newValue(*param); + m_context.setUnknownValue(*param); + } + } + else + { + initializeFunctionCallParameters(*_funDef, symbolicArguments(_funDef->parameters(), _arguments, _boundArgumentCall)); + + // The reason why we need to pushCallStack here instead of visit(FunctionDefinition) + // is that there we don't have `_callStackExpr`. + pushCallStack({_funDef, &_callStackExpr}); + pushPathCondition(currentPathConditions()); + auto oldChecked = std::exchange(m_checked, true); + _funDef->accept(*this); + m_checked = oldChecked; + popPathCondition(); + } + + createReturnedExpressions(_funDef, _callStackExpr); +} + +void BMC::inlineFunctionCall(FunctionCall const& _funCall) +{ + solAssert(shouldInlineFunctionCall(_funCall, currentScopeContract(), m_currentContract), ""); + + auto funDef = functionCallToDefinition(_funCall, currentScopeContract(), m_currentContract); + Expression const* expr = &_funCall.expression(); + auto funType = dynamic_cast(expr->annotation().type); + std::optional boundArgumentCall = + funType->hasBoundFirstArgument() ? std::make_optional(expr) : std::nullopt; + + std::vector arguments; + for (auto& arg: _funCall.sortedArguments()) + arguments.push_back(&(*arg)); + + // pushCallStack and defineExpr inside createReturnedExpression should be called + // on the FunctionCall object for the normal function call case + inlineFunctionCall(funDef, _funCall, boundArgumentCall, arguments); +} + +void BMC::internalOrExternalFunctionCall(FunctionCall const& _funCall) +{ + auto const& funType = dynamic_cast(*_funCall.expression().annotation().type); + if (shouldInlineFunctionCall(_funCall, currentScopeContract(), m_currentContract)) + inlineFunctionCall(_funCall); + else if (publicGetter(_funCall.expression())) + { + // Do nothing here. + // The processing happens in SMT Encoder, but we need to prevent the resetting of the state variables. + } + else if (funType.kind() == FunctionType::Kind::Internal) + m_unsupportedErrors.warning( + 5729_error, + _funCall.location(), + "BMC does not yet implement this type of function call." + ); + else if (funType.kind() == FunctionType::Kind::BareStaticCall) + { + // Do nothing here. + // Neither storage nor balances should be modified. + } + else + { + m_externalFunctionCallHappened = true; + resetStorageVariables(); + resetBalances(); + } +} + +std::pair BMC::arithmeticOperation( + Token _op, + smtutil::Expression const& _left, + smtutil::Expression const& _right, + Type const* _commonType, + Expression const& _expression +) +{ + // Unchecked does not disable div by 0 checks. + if (_op == Token::Div || _op == Token::Mod) + addVerificationTarget( + VerificationTargetType::DivByZero, + _right, + &_expression + ); + + auto values = SMTEncoder::arithmeticOperation(_op, _left, _right, _commonType, _expression); + + if (!m_checked) + return values; + + auto const* intType = dynamic_cast(_commonType); + if (!intType) + intType = TypeProvider::uint256(); + + // Mod does not need underflow/overflow checks. + if (_op == Token::Mod) + return values; + + VerificationTargetType type; + // The order matters here: + // If _op is Div and intType is signed, we only care about overflow. + if (_op == Token::Div) + { + if (intType->isSigned()) + // Signed division can only overflow. + type = VerificationTargetType::Overflow; + else + // Unsigned division cannot underflow/overflow. + return values; + } + else if (intType->isSigned()) + type = VerificationTargetType::UnderOverflow; + else if (_op == Token::Sub) + type = VerificationTargetType::Underflow; + else if (_op == Token::Add || _op == Token::Mul) + type = VerificationTargetType::Overflow; + else + solAssert(false, ""); + + addVerificationTarget( + type, + values.second, + &_expression + ); + return values; +} + +void BMC::reset() +{ + m_externalFunctionCallHappened = false; + m_loopExecutionHappened = false; +} + +std::pair, std::vector> BMC::modelExpressions() +{ + std::vector expressionsToEvaluate; + std::vector expressionNames; + for (auto const& var: m_context.variables()) + if (var.first->type()->isValueType()) + { + expressionsToEvaluate.emplace_back(currentValue(*var.first)); + expressionNames.push_back(var.first->name()); + } + for (auto const& var: m_context.globalSymbols()) + { + auto const& type = var.second->type(); + if ( + type->isValueType() && + smt::smtKind(*type) != smtutil::Kind::Function + ) + { + expressionsToEvaluate.emplace_back(var.second->currentValue()); + expressionNames.push_back(var.first); + } + } + for (auto const& uf: m_uninterpretedTerms) + if (uf->annotation().type->isValueType()) + { + expressionsToEvaluate.emplace_back(expr(*uf)); + std::string expressionName; + if (uf->location().hasText()) + expressionName = m_charStreamProvider.charStream(*uf->location().sourceName).text( + uf->location() + ); + expressionNames.push_back(std::move(expressionName)); + } + + return {expressionsToEvaluate, expressionNames}; +} + +/// Verification targets. + +std::string BMC::targetDescription(BMCVerificationTarget const& _target) +{ + if ( + _target.type == VerificationTargetType::Underflow || + _target.type == VerificationTargetType::Overflow + ) + { + auto const* intType = dynamic_cast(_target.expression->annotation().type); + if (!intType) + intType = TypeProvider::uint256(); + + if (_target.type == VerificationTargetType::Underflow) + return "Underflow (resulting value less than " + formatNumberReadable(intType->minValue()) + ")"; + return "Overflow (resulting value larger than " + formatNumberReadable(intType->maxValue()) + ")"; + } + else if (_target.type == VerificationTargetType::DivByZero) + return "Division by zero"; + else if (_target.type == VerificationTargetType::Assert) + return "Assertion violation"; + else if (_target.type == VerificationTargetType::Balance) + return "Insufficient funds"; + solAssert(false); +} + +void BMC::checkVerificationTargets() +{ + for (auto& target: m_verificationTargets) + checkVerificationTarget(target); +} + +void BMC::checkVerificationTarget(BMCVerificationTarget& _target) +{ + switch (_target.type) + { + case VerificationTargetType::ConstantCondition: + checkConstantCondition(_target); + break; + case VerificationTargetType::Underflow: + checkUnderflow(_target); + break; + case VerificationTargetType::Overflow: + checkOverflow(_target); + break; + case VerificationTargetType::UnderOverflow: + checkUnderflow(_target); + checkOverflow(_target); + break; + case VerificationTargetType::DivByZero: + checkDivByZero(_target); + break; + case VerificationTargetType::Balance: + checkBalance(_target); + break; + case VerificationTargetType::Assert: + checkAssert(_target); + break; + default: + solAssert(false, ""); + } +} + +void BMC::checkConstantCondition(BMCVerificationTarget& _target) +{ + checkBooleanNotConstant( + *_target.expression, + _target.constraints, + _target.value, + _target.callStack + ); +} + +void BMC::checkUnderflow(BMCVerificationTarget& _target) +{ + solAssert( + _target.type == VerificationTargetType::Underflow || + _target.type == VerificationTargetType::UnderOverflow, + "" + ); + + if ( + m_solvedTargets.count(_target.expression) && ( + m_solvedTargets.at(_target.expression).count(VerificationTargetType::Underflow) || + m_solvedTargets.at(_target.expression).count(VerificationTargetType::UnderOverflow) + ) + ) + return; + + auto const* intType = dynamic_cast(_target.expression->annotation().type); + if (!intType) + intType = TypeProvider::uint256(); + + checkCondition( + _target, + _target.constraints && _target.value < smt::minValue(*intType), + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + 4144_error, + 8312_error, + "", + &_target.value + ); +} + +void BMC::checkOverflow(BMCVerificationTarget& _target) +{ + solAssert( + _target.type == VerificationTargetType::Overflow || + _target.type == VerificationTargetType::UnderOverflow, + "" + ); + + if ( + m_solvedTargets.count(_target.expression) && ( + m_solvedTargets.at(_target.expression).count(VerificationTargetType::Overflow) || + m_solvedTargets.at(_target.expression).count(VerificationTargetType::UnderOverflow) + ) + ) + return; + + auto const* intType = dynamic_cast(_target.expression->annotation().type); + if (!intType) + intType = TypeProvider::uint256(); + + checkCondition( + _target, + _target.constraints && _target.value > smt::maxValue(*intType), + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + 2661_error, + 8065_error, + "", + &_target.value + ); +} + +void BMC::checkDivByZero(BMCVerificationTarget& _target) +{ + solAssert(_target.type == VerificationTargetType::DivByZero, ""); + + if ( + m_solvedTargets.count(_target.expression) && + m_solvedTargets.at(_target.expression).count(VerificationTargetType::DivByZero) + ) + return; + + checkCondition( + _target, + _target.constraints && (_target.value == 0), + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + 3046_error, + 5272_error, + "", + &_target.value + ); +} + +void BMC::checkBalance(BMCVerificationTarget& _target) +{ + solAssert(_target.type == VerificationTargetType::Balance, ""); + checkCondition( + _target, + _target.constraints && _target.value, + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + 1236_error, + 4010_error, + "address(this).balance" + ); +} + +void BMC::checkAssert(BMCVerificationTarget& _target) +{ + solAssert(_target.type == VerificationTargetType::Assert, ""); + + if ( + m_solvedTargets.count(_target.expression) && + m_solvedTargets.at(_target.expression).count(_target.type) + ) + return; + + checkCondition( + _target, + _target.constraints && !_target.value, + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + 4661_error, + 7812_error + ); +} + +void BMC::addVerificationTarget( + VerificationTargetType _type, + smtutil::Expression const& _value, + Expression const* _expression +) +{ + if (!m_settings.targets.has(_type) || (m_currentContract && !shouldAnalyze(*m_currentContract))) + return; + + BMCVerificationTarget target{ + { + _type, + _value, + currentPathConditions() && m_context.assertions() + }, + _expression, + m_callStack, + modelExpressions() + }; + if (_type == VerificationTargetType::ConstantCondition) + checkVerificationTarget(target); + else + m_verificationTargets.emplace_back(std::move(target)); +} + +/// Solving. + +void BMC::checkCondition( + BMCVerificationTarget const& _target, + smtutil::Expression _condition, + std::vector const& _callStack, + std::pair, std::vector> const& _modelExpressions, + SourceLocation const& _location, + ErrorId _errorHappens, + ErrorId _errorMightHappen, + std::string const& _additionalValueName, + smtutil::Expression const* _additionalValue +) +{ + m_interface->push(); + m_interface->addAssertion(_condition); + + std::vector expressionsToEvaluate; + std::vector expressionNames; + tie(expressionsToEvaluate, expressionNames) = _modelExpressions; + if (!_callStack.empty()) + if (_additionalValue) + { + expressionsToEvaluate.emplace_back(*_additionalValue); + expressionNames.push_back(_additionalValueName); + } + smtutil::CheckResult result; + std::vector values; + tie(result, values) = checkSatisfiableAndGenerateModel(expressionsToEvaluate); + + std::string extraComment = SMTEncoder::extraComment(); + if (m_loopExecutionHappened) + extraComment += + "False negatives are possible when unrolling loops.\n" + "This is due to the possibility that the BMC loop iteration setting is" + " smaller than the actual number of iterations needed to complete a loop."; + if (m_externalFunctionCallHappened) + extraComment += + "\nNote that external function calls are not inlined," + " even if the source code of the function is available." + " This is due to the possibility that the actual called contract" + " has the same ABI but implements the function differently."; + + SecondarySourceLocation secondaryLocation{}; + secondaryLocation.append(extraComment, SourceLocation{}); + + switch (result) + { + case smtutil::CheckResult::SATISFIABLE: + { + solAssert(!_callStack.empty(), ""); + std::ostringstream message; + message << "BMC: " << targetDescription(_target) << " happens here."; + + std::ostringstream modelMessage; + // Sometimes models have complex smtlib2 expressions that SMTLib2Interface fails to parse. + if (values.size() == expressionNames.size()) + { + modelMessage << "Counterexample:\n"; + std::map sortedModel; + for (size_t i = 0; i < values.size(); ++i) + if (expressionsToEvaluate.at(i).name != values.at(i)) + sortedModel[expressionNames.at(i)] = values.at(i); + + for (auto const& eval: sortedModel) + modelMessage << " " << eval.first << " = " << eval.second << "\n"; + } + + m_errorReporter.warning( + _errorHappens, + _location, + message.str(), + SecondarySourceLocation().append(modelMessage.str(), SourceLocation{}) + .append(SMTEncoder::callStackMessage(_callStack)) + .append(std::move(secondaryLocation)) + ); + break; + } + case smtutil::CheckResult::UNSATISFIABLE: + { + m_safeTargets[_target.expression].insert(_target); + break; + } + case smtutil::CheckResult::UNKNOWN: + { + ++m_unprovedAmt; + if (m_settings.showUnproved) + m_errorReporter.warning(_errorMightHappen, _location, "BMC: " + targetDescription(_target) + " might happen here.", secondaryLocation); + break; + } + case smtutil::CheckResult::CONFLICTING: + m_errorReporter.warning(1584_error, _location, "BMC: At least two SMT solvers provided conflicting answers. Results might not be sound."); + break; + case smtutil::CheckResult::ERROR: + m_errorReporter.warning(1823_error, _location, "BMC: Error trying to invoke SMT solver."); + break; + } + + m_interface->pop(); +} + +void BMC::checkBooleanNotConstant( + Expression const& _condition, + smtutil::Expression const& _constraints, + smtutil::Expression const& _value, + std::vector const& _callStack +) +{ + // Do not check for const-ness if this is a constant. + if (dynamic_cast(&_condition)) + return; + + m_interface->push(); + m_interface->addAssertion(_constraints && _value); + auto positiveResult = checkSatisfiable(); + m_interface->pop(); + + m_interface->push(); + m_interface->addAssertion(_constraints && !_value); + auto negatedResult = checkSatisfiable(); + m_interface->pop(); + + if (positiveResult == smtutil::CheckResult::ERROR || negatedResult == smtutil::CheckResult::ERROR) + m_errorReporter.warning(8592_error, _condition.location(), "BMC: Error trying to invoke SMT solver."); + else if (positiveResult == smtutil::CheckResult::CONFLICTING || negatedResult == smtutil::CheckResult::CONFLICTING) + m_errorReporter.warning(3356_error, _condition.location(), "BMC: At least two SMT solvers provided conflicting answers. Results might not be sound."); + else if (positiveResult == smtutil::CheckResult::SATISFIABLE && negatedResult == smtutil::CheckResult::SATISFIABLE) + { + // everything fine. + } + else if (positiveResult == smtutil::CheckResult::UNKNOWN || negatedResult == smtutil::CheckResult::UNKNOWN) + { + // can't do anything. + } + else if (positiveResult == smtutil::CheckResult::UNSATISFIABLE && negatedResult == smtutil::CheckResult::UNSATISFIABLE) + m_errorReporter.warning(2512_error, _condition.location(), "BMC: Condition unreachable.", SMTEncoder::callStackMessage(_callStack)); + else + { + std::string description; + if (positiveResult == smtutil::CheckResult::SATISFIABLE) + { + solAssert(negatedResult == smtutil::CheckResult::UNSATISFIABLE, ""); + description = "BMC: Condition is always true."; + } + else + { + solAssert(positiveResult == smtutil::CheckResult::UNSATISFIABLE, ""); + solAssert(negatedResult == smtutil::CheckResult::SATISFIABLE, ""); + description = "BMC: Condition is always false."; + } + m_errorReporter.warning( + 6838_error, + _condition.location(), + description, + SMTEncoder::callStackMessage(_callStack) + ); + } +} + +std::pair> +BMC::checkSatisfiableAndGenerateModel(std::vector const& _expressionsToEvaluate) +{ + smtutil::CheckResult result; + std::vector values; + try + { + if (m_settings.printQuery) + { + auto portfolio = dynamic_cast(m_interface.get()); + std::string smtlibCode = portfolio->dumpQuery(_expressionsToEvaluate); + m_errorReporter.info( + 6240_error, + "BMC: Requested query:\n" + smtlibCode + ); + } + tie(result, values) = m_interface->check(_expressionsToEvaluate); + } + catch (smtutil::SolverError const& _e) + { + std::string description("BMC: Error querying SMT solver"); + if (_e.comment()) + description += ": " + *_e.comment(); + m_errorReporter.warning(8140_error, description); + result = smtutil::CheckResult::ERROR; + } + + for (std::string& value: values) + { + try + { + // Parse and re-format nicely + value = formatNumberReadable(bigint(value)); + } + catch (...) { } + } + + return make_pair(result, values); +} + +smtutil::CheckResult BMC::checkSatisfiable() +{ + return checkSatisfiableAndGenerateModel({}).first; +} + +void BMC::assignment(smt::SymbolicVariable& _symVar, smtutil::Expression const& _value) +{ + auto oldVar = _symVar.currentValue(); + auto newVar = _symVar.increaseIndex(); + m_context.addAssertion(smtutil::Expression::ite( + currentPathConditions(), + newVar == _value, + newVar == oldVar + )); +} + +bool BMC::isInsideLoop() const +{ + return !m_loopCheckpoints.empty(); +} diff --git a/compiler/libsolidity/formal/BMC.h b/compiler/libsolidity/formal/BMC.h new file mode 100644 index 00000000..9daa1683 --- /dev/null +++ b/compiler/libsolidity/formal/BMC.h @@ -0,0 +1,236 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Class that implements an SMT-based Bounded Model Checker (BMC). + * Traverses the AST such that: + * - Loops are unrolled + * - Internal function calls are inlined + * Creates verification targets for: + * - Underflow/Overflow + * - Constant conditions + * - Assertions + */ + +#pragma once + + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +using solidity::util::h256; + +namespace solidity::langutil +{ +class ErrorReporter; +struct ErrorId; +struct SourceLocation; +} + +namespace solidity::frontend +{ + +class BMC: public SMTEncoder +{ +public: + BMC( + smt::EncodingContext& _context, + langutil::UniqueErrorReporter& _errorReporter, + langutil::UniqueErrorReporter& _unsupportedErrorReporter, + std::map const& _smtlib2Responses, + ReadCallback::Callback const& _smtCallback, + ModelCheckerSettings _settings, + langutil::CharStreamProvider const& _charStreamProvider + ); + + void analyze(SourceUnit const& _sources, std::map, smt::EncodingContext::IdCompare> _solvedTargets); + + /// This is used if the SMT solver is not directly linked into this binary. + /// @returns a list of inputs to the SMT solver that were not part of the argument to + /// the constructor. + std::vector unhandledQueries() { return m_interface->unhandledQueries(); } + + /// @returns true if _funCall should be inlined, otherwise false. + /// @param _scopeContract The contract that contains the current function being analyzed. + /// @param _contextContract The most derived contract, currently being analyzed. + static bool shouldInlineFunctionCall( + FunctionCall const& _funCall, + ContractDefinition const* _scopeContract, + ContractDefinition const* _contextContract + ); + +private: + /// AST visitors. + /// Only nodes that lead to verification targets being built + /// or checked are visited. + //@{ + bool visit(ContractDefinition const& _node) override; + void endVisit(ContractDefinition const& _node) override; + bool visit(FunctionDefinition const& _node) override; + void endVisit(FunctionDefinition const& _node) override; + bool visit(IfStatement const& _node) override; + bool visit(Conditional const& _node) override; + bool visit(WhileStatement const& _node) override; + bool visit(ForStatement const& _node) override; + void endVisit(UnaryOperation const& _node) override; + void endVisit(BinaryOperation const& _node) override; + void endVisit(FunctionCall const& _node) override; + void endVisit(Return const& _node) override; + bool visit(TryStatement const& _node) override; + bool visit(Break const& _node) override; + bool visit(Continue const& _node) override; + //@} + + /// Visitor helpers. + //@{ + void visitAssert(FunctionCall const& _funCall); + void visitRequire(FunctionCall const& _funCall); + void visitAddMulMod(FunctionCall const& _funCall) override; + void assignment(smt::SymbolicVariable& _symVar, smtutil::Expression const& _value) override; + /// Visits the FunctionDefinition of the called function + /// if available and inlines the return value. + void inlineFunctionCall(FunctionCall const& _funCall); + void inlineFunctionCall( + FunctionDefinition const* _funDef, + Expression const& _callStackExpr, + std::optional _calledExpr, + std::vector const& _arguments + ); + /// Inlines if the function call is internal or external to `this`. + /// Erases knowledge about state variables if external. + void internalOrExternalFunctionCall(FunctionCall const& _funCall); + + /// Creates underflow/overflow verification targets. + std::pair arithmeticOperation( + Token _op, + smtutil::Expression const& _left, + smtutil::Expression const& _right, + Type const* _commonType, + Expression const& _expression + ) override; + + void reset(); + + std::pair, std::vector> modelExpressions(); + //@} + + /// Verification targets. + //@{ + struct BMCVerificationTarget: VerificationTarget + { + Expression const* expression; + std::vector callStack; + std::pair, std::vector> modelExpressions; + + friend bool operator<(BMCVerificationTarget const& _a, BMCVerificationTarget const& _b) + { + return _a.expression->id() < _b.expression->id(); + } + }; + + std::string targetDescription(BMCVerificationTarget const& _target); + + void checkVerificationTargets(); + void checkVerificationTarget(BMCVerificationTarget& _target); + void checkConstantCondition(BMCVerificationTarget& _target); + void checkUnderflow(BMCVerificationTarget& _target); + void checkOverflow(BMCVerificationTarget& _target); + void checkDivByZero(BMCVerificationTarget& _target); + void checkBalance(BMCVerificationTarget& _target); + void checkAssert(BMCVerificationTarget& _target); + void addVerificationTarget( + VerificationTargetType _type, + smtutil::Expression const& _value, + Expression const* _expression + ); + //@} + + /// Solver related. + //@{ + /// Check that a condition can be satisfied. + void checkCondition( + BMCVerificationTarget const& _target, + smtutil::Expression _condition, + std::vector const& _callStack, + std::pair, std::vector> const& _modelExpressions, + langutil::SourceLocation const& _location, + langutil::ErrorId _errorHappens, + langutil::ErrorId _errorMightHappen, + std::string const& _additionalValueName = "", + smtutil::Expression const* _additionalValue = nullptr + ); + /// Checks that a boolean condition is not constant. Do not warn if the expression + /// is a literal constant. + void checkBooleanNotConstant( + Expression const& _condition, + smtutil::Expression const& _constraints, + smtutil::Expression const& _value, + std::vector const& _callStack + ); + std::pair> + checkSatisfiableAndGenerateModel(std::vector const& _expressionsToEvaluate); + + smtutil::CheckResult checkSatisfiable(); + //@} + + smtutil::Expression mergeVariablesFromLoopCheckpoints(); + bool isInsideLoop() const; + + std::unique_ptr m_interface; + + /// Flags used for better warning messages. + bool m_loopExecutionHappened = false; + bool m_externalFunctionCallHappened = false; + + std::vector m_verificationTargets; + + /// Targets proved safe by this engine. + std::map, smt::EncodingContext::IdCompare> m_safeTargets; + + /// Targets that were already proven before this engine started. + std::map, smt::EncodingContext::IdCompare> m_solvedTargets; + + /// Number of verification conditions that could not be proved. + size_t m_unprovedAmt = 0; + + enum class LoopControlKind + { + Continue, + Break + }; + + // Current path conditions and SSA indices for break or continue statement + struct LoopControl { + LoopControlKind kind; + smtutil::Expression pathConditions; + VariableIndices variableIndices; + }; + + // Loop control statements for every loop + std::stack> m_loopCheckpoints; +}; +} diff --git a/compiler/libsolidity/formal/CHC.cpp b/compiler/libsolidity/formal/CHC.cpp new file mode 100644 index 00000000..2c9075ab --- /dev/null +++ b/compiler/libsolidity/formal/CHC.cpp @@ -0,0 +1,2494 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include + +#ifdef HAVE_Z3 +#include +#endif + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#ifdef HAVE_Z3_DLOPEN +#include +#endif + +#include + +#include +#include +#include +#include + +#include +#include + +using namespace solidity; +using namespace solidity::util; +using namespace solidity::langutil; +using namespace solidity::smtutil; +using namespace solidity::frontend; +using namespace solidity::frontend::smt; + +CHC::CHC( + EncodingContext& _context, + UniqueErrorReporter& _errorReporter, + UniqueErrorReporter& _unsupportedErrorReporter, + std::map const& _smtlib2Responses, + ReadCallback::Callback const& _smtCallback, + ModelCheckerSettings _settings, + CharStreamProvider const& _charStreamProvider +): + SMTEncoder(_context, _settings, _errorReporter, _unsupportedErrorReporter, _charStreamProvider), + m_smtlib2Responses(_smtlib2Responses), + m_smtCallback(_smtCallback) +{ + solAssert(!_settings.printQuery || _settings.solvers == smtutil::SMTSolverChoice::SMTLIB2(), "Only SMTLib2 solver can be enabled to print queries"); +} + +void CHC::analyze(SourceUnit const& _source) +{ + // At this point every enabled solver is available. + if (!m_settings.solvers.eld && !m_settings.solvers.smtlib2 && !m_settings.solvers.z3) + { + m_errorReporter.warning( + 7649_error, + SourceLocation(), + "CHC analysis was not possible since no Horn solver was found and enabled." + " The accepted solvers for CHC are Eldarica and z3." + ); + return; + } + + if (m_settings.solvers.eld && m_settings.solvers.z3) + m_errorReporter.warning( + 5798_error, + SourceLocation(), + "Multiple Horn solvers were selected for CHC." + " CHC only supports one solver at a time, therefore only z3 will be used." + " If you wish to use Eldarica, please enable Eldarica only." + ); + + if (!shouldAnalyze(_source)) + return; + + resetSourceAnalysis(); + + auto sources = sourceDependencies(_source); + collectFreeFunctions(sources); + createFreeConstants(sources); + state().prepareForSourceUnit(_source, encodeExternalCallsAsTrusted()); + + for (auto const* source: sources) + defineInterfacesAndSummaries(*source); + for (auto const* source: sources) + source->accept(*this); + + checkVerificationTargets(); + + bool ranSolver = true; + // If ranSolver is true here it's because an SMT solver callback was + // actually given and the queries were solved, + // or Eldarica was chosen and was present in the system. + if (auto const* smtLibInterface = dynamic_cast(m_interface.get())) + ranSolver = smtLibInterface->unhandledQueries().empty(); + if (!ranSolver) + m_errorReporter.warning( + 3996_error, + SourceLocation(), + "CHC analysis was not possible. No Horn solver was available." + " None of the installed solvers was enabled." + ); +} + +std::vector CHC::unhandledQueries() const +{ + if (auto smtlib2 = dynamic_cast(m_interface.get())) + return smtlib2->unhandledQueries(); + + return {}; +} + +bool CHC::visit(ContractDefinition const& _contract) +{ + if (!shouldAnalyze(_contract)) + return false; + + resetContractAnalysis(); + initContract(_contract); + clearIndices(&_contract); + + m_scopes.push_back(&_contract); + + m_stateVariables = SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract); + solAssert(m_currentContract, ""); + + SMTEncoder::visit(_contract); + return false; +} + +void CHC::endVisit(ContractDefinition const& _contract) +{ + if (!shouldAnalyze(_contract)) + return; + + for (auto base: _contract.annotation().linearizedBaseContracts) + { + if (auto constructor = base->constructor()) + constructor->accept(*this); + defineContractInitializer(*base, _contract); + } + + auto const& entry = *createConstructorBlock(_contract, "implicit_constructor_entry"); + + // In case constructors use uninitialized state variables, + // they need to be zeroed. + // This is not part of `initialConstraints` because it's only true here, + // at the beginning of the deployment routine. + smtutil::Expression zeroes(true); + for (auto var: stateVariablesIncludingInheritedAndPrivate(_contract)) + zeroes = zeroes && currentValue(*var) == smt::zeroValue(var->type()); + + smtutil::Expression newAddress = encodeExternalCallsAsTrusted() ? + !state().addressActive(state().thisAddress()) : + smtutil::Expression(true); + + // The contract's address might already have funds before deployment, + // so the balance must be at least `msg.value`, but not equals. + auto initialBalanceConstraint = state().balance(state().thisAddress()) >= state().txMember("msg.value"); + addRule(smtutil::Expression::implies( + initialConstraints(_contract) && zeroes && newAddress && initialBalanceConstraint, + predicate(entry) + ), entry.functor().name); + + setCurrentBlock(entry); + + if (encodeExternalCallsAsTrusted()) + { + auto const& entryAfterAddress = *createConstructorBlock(_contract, "implicit_constructor_entry_after_address"); + state().setAddressActive(state().thisAddress(), true); + + connectBlocks(m_currentBlock, predicate(entryAfterAddress)); + setCurrentBlock(entryAfterAddress); + } + + solAssert(!m_errorDest, ""); + m_errorDest = m_constructorSummaries.at(&_contract); + // We need to evaluate the base constructor calls (arguments) from derived -> base + auto baseArgs = baseArguments(_contract); + for (auto base: _contract.annotation().linearizedBaseContracts) + if (base != &_contract) + { + m_callGraph[&_contract].insert(base); + + auto baseConstructor = base->constructor(); + if (baseConstructor && baseArgs.count(base)) + { + std::vector> const& args = baseArgs.at(base); + auto const& params = baseConstructor->parameters(); + solAssert(params.size() == args.size(), ""); + for (unsigned i = 0; i < params.size(); ++i) + { + args.at(i)->accept(*this); + if (params.at(i)) + { + solAssert(m_context.knownVariable(*params.at(i)), ""); + m_context.addAssertion(currentValue(*params.at(i)) == expr(*args.at(i), params.at(i)->type())); + } + } + } + } + m_errorDest = nullptr; + // Then call initializer_Base from base -> derived + for (auto base: _contract.annotation().linearizedBaseContracts | ranges::views::reverse) + { + errorFlag().increaseIndex(); + m_context.addAssertion(smt::constructorCall(*m_contractInitializers.at(&_contract).at(base), m_context)); + connectBlocks(m_currentBlock, summary(_contract), errorFlag().currentValue() > 0); + m_context.addAssertion(errorFlag().currentValue() == 0); + } + + if (encodeExternalCallsAsTrusted()) + state().writeStateVars(_contract, state().thisAddress()); + + connectBlocks(m_currentBlock, summary(_contract)); + + setCurrentBlock(*m_constructorSummaries.at(&_contract)); + + solAssert(&_contract == m_currentContract, ""); + if (shouldAnalyze(_contract)) + { + auto constructor = _contract.constructor(); + auto txConstraints = state().txTypeConstraints(); + if (!constructor || !constructor->isPayable()) + txConstraints = txConstraints && state().txNonPayableConstraint(); + m_queryPlaceholders[&_contract].push_back({txConstraints, errorFlag().currentValue(), m_currentBlock}); + connectBlocks(m_currentBlock, interface(), txConstraints && errorFlag().currentValue() == 0); + } + + solAssert(m_scopes.back() == &_contract, ""); + m_scopes.pop_back(); + + SMTEncoder::endVisit(_contract); +} + +bool CHC::visit(FunctionDefinition const& _function) +{ + // Free functions need to be visited in the context of a contract. + if (!m_currentContract) + return false; + + if ( + !_function.isImplemented() || + abstractAsNondet(_function) + ) + { + smtutil::Expression conj(true); + if ( + _function.stateMutability() == StateMutability::Pure || + _function.stateMutability() == StateMutability::View + ) + conj = conj && currentEqualInitialVarsConstraints(stateVariablesIncludingInheritedAndPrivate(_function)); + + conj = conj && errorFlag().currentValue() == 0; + addRule(smtutil::Expression::implies(conj, summary(_function)), "summary_function_" + std::to_string(_function.id())); + return false; + } + + // No inlining. + solAssert(!m_currentFunction, "Function inlining should not happen in CHC."); + m_currentFunction = &_function; + + m_scopes.push_back(&_function); + + initFunction(_function); + + auto functionEntryBlock = createBlock(m_currentFunction, PredicateType::FunctionBlock); + auto bodyBlock = createBlock(&m_currentFunction->body(), PredicateType::FunctionBlock); + + auto functionPred = predicate(*functionEntryBlock); + auto bodyPred = predicate(*bodyBlock); + + addRule(functionPred, functionPred.name); + + solAssert(m_currentContract, ""); + m_context.addAssertion(initialConstraints(*m_currentContract, &_function)); + + connectBlocks(functionPred, bodyPred); + + setCurrentBlock(*bodyBlock); + + solAssert(!m_errorDest, ""); + m_errorDest = m_summaries.at(m_currentContract).at(&_function); + SMTEncoder::visit(*m_currentFunction); + m_errorDest = nullptr; + + return false; +} + +void CHC::endVisit(FunctionDefinition const& _function) +{ + // Free functions need to be visited in the context of a contract. + if (!m_currentContract) + return; + + if ( + !_function.isImplemented() || + abstractAsNondet(_function) + ) + return; + + solAssert(m_currentFunction && m_currentContract, ""); + // No inlining. + solAssert(m_currentFunction == &_function, ""); + + solAssert(m_scopes.back() == &_function, ""); + m_scopes.pop_back(); + + connectBlocks(m_currentBlock, summary(_function)); + setCurrentBlock(*m_summaries.at(m_currentContract).at(&_function)); + + // Query placeholders for constructors are not created here because + // of contracts without constructors. + // Instead, those are created in endVisit(ContractDefinition). + if ( + !_function.isConstructor() && + _function.isPublic() && + contractFunctions(*m_currentContract).count(&_function) && + shouldAnalyze(*m_currentContract) + ) + { + defineExternalFunctionInterface(_function, *m_currentContract); + setCurrentBlock(*m_interfaces.at(m_currentContract)); + + // Create the rule + // interface \land externalFunctionEntry => interface' + auto ifacePre = smt::interfacePre(*m_interfaces.at(m_currentContract), *m_currentContract, m_context); + auto sum = externalSummary(_function); + + m_queryPlaceholders[&_function].push_back({sum, errorFlag().currentValue(), ifacePre}); + connectBlocks(ifacePre, interface(), sum && errorFlag().currentValue() == 0); + } + + m_currentFunction = nullptr; + + SMTEncoder::endVisit(_function); +} + +bool CHC::visit(Block const& _block) +{ + m_scopes.push_back(&_block); + return SMTEncoder::visit(_block); +} + +void CHC::endVisit(Block const& _block) +{ + solAssert(m_scopes.back() == &_block, ""); + m_scopes.pop_back(); + SMTEncoder::endVisit(_block); +} + +bool CHC::visit(IfStatement const& _if) +{ + solAssert(m_currentFunction, ""); + + bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen; + m_unknownFunctionCallSeen = false; + + solAssert(m_currentFunction, ""); + auto const& functionBody = m_currentFunction->body(); + + auto ifHeaderBlock = createBlock(&_if, PredicateType::FunctionBlock, "if_header_"); + auto trueBlock = createBlock(&_if.trueStatement(), PredicateType::FunctionBlock, "if_true_"); + auto falseBlock = _if.falseStatement() ? createBlock(_if.falseStatement(), PredicateType::FunctionBlock, "if_false_") : nullptr; + auto afterIfBlock = createBlock(&functionBody, PredicateType::FunctionBlock); + + connectBlocks(m_currentBlock, predicate(*ifHeaderBlock)); + + setCurrentBlock(*ifHeaderBlock); + _if.condition().accept(*this); + auto condition = expr(_if.condition()); + + connectBlocks(m_currentBlock, predicate(*trueBlock), condition); + if (_if.falseStatement()) + connectBlocks(m_currentBlock, predicate(*falseBlock), !condition); + else + connectBlocks(m_currentBlock, predicate(*afterIfBlock), !condition); + + setCurrentBlock(*trueBlock); + _if.trueStatement().accept(*this); + connectBlocks(m_currentBlock, predicate(*afterIfBlock)); + + if (_if.falseStatement()) + { + setCurrentBlock(*falseBlock); + _if.falseStatement()->accept(*this); + connectBlocks(m_currentBlock, predicate(*afterIfBlock)); + } + + setCurrentBlock(*afterIfBlock); + + if (m_unknownFunctionCallSeen) + eraseKnowledge(); + + m_unknownFunctionCallSeen = unknownFunctionCallWasSeen; + + return false; +} + +bool CHC::visit(WhileStatement const& _while) +{ + bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen; + m_unknownFunctionCallSeen = false; + + solAssert(m_currentFunction, ""); + auto const& functionBody = m_currentFunction->body(); + + auto namePrefix = std::string(_while.isDoWhile() ? "do_" : "") + "while"; + auto loopHeaderBlock = createBlock(&_while, PredicateType::FunctionBlock, namePrefix + "_header_"); + auto loopBodyBlock = createBlock(&_while.body(), PredicateType::FunctionBlock, namePrefix + "_body_"); + auto afterLoopBlock = createBlock(&functionBody, PredicateType::FunctionBlock); + + auto outerBreakDest = m_breakDest; + auto outerContinueDest = m_continueDest; + m_breakDest = afterLoopBlock; + m_continueDest = loopHeaderBlock; + + if (_while.isDoWhile()) + _while.body().accept(*this); + + connectBlocks(m_currentBlock, predicate(*loopHeaderBlock)); + + setCurrentBlock(*loopHeaderBlock); + + _while.condition().accept(*this); + auto condition = expr(_while.condition()); + + connectBlocks(m_currentBlock, predicate(*loopBodyBlock), condition); + connectBlocks(m_currentBlock, predicate(*afterLoopBlock), !condition); + + // Loop body visit. + setCurrentBlock(*loopBodyBlock); + _while.body().accept(*this); + + m_breakDest = outerBreakDest; + m_continueDest = outerContinueDest; + + // Back edge. + connectBlocks(m_currentBlock, predicate(*loopHeaderBlock)); + setCurrentBlock(*afterLoopBlock); + + if (m_unknownFunctionCallSeen) + eraseKnowledge(); + + m_unknownFunctionCallSeen = unknownFunctionCallWasSeen; + + return false; +} + +bool CHC::visit(ForStatement const& _for) +{ + m_scopes.push_back(&_for); + + bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen; + m_unknownFunctionCallSeen = false; + + solAssert(m_currentFunction, ""); + auto const& functionBody = m_currentFunction->body(); + + auto loopHeaderBlock = createBlock(&_for, PredicateType::FunctionBlock, "for_header_"); + auto loopBodyBlock = createBlock(&_for.body(), PredicateType::FunctionBlock, "for_body_"); + auto afterLoopBlock = createBlock(&functionBody, PredicateType::FunctionBlock); + auto postLoop = _for.loopExpression(); + auto postLoopBlock = postLoop ? createBlock(postLoop, PredicateType::FunctionBlock, "for_post_") : nullptr; + + auto outerBreakDest = m_breakDest; + auto outerContinueDest = m_continueDest; + m_breakDest = afterLoopBlock; + m_continueDest = postLoop ? postLoopBlock : loopHeaderBlock; + + if (auto init = _for.initializationExpression()) + init->accept(*this); + + connectBlocks(m_currentBlock, predicate(*loopHeaderBlock)); + setCurrentBlock(*loopHeaderBlock); + + auto condition = smtutil::Expression(true); + if (auto forCondition = _for.condition()) + { + forCondition->accept(*this); + condition = expr(*forCondition); + } + + connectBlocks(m_currentBlock, predicate(*loopBodyBlock), condition); + connectBlocks(m_currentBlock, predicate(*afterLoopBlock), !condition); + + // Loop body visit. + setCurrentBlock(*loopBodyBlock); + _for.body().accept(*this); + + if (postLoop) + { + connectBlocks(m_currentBlock, predicate(*postLoopBlock)); + setCurrentBlock(*postLoopBlock); + postLoop->accept(*this); + } + + m_breakDest = outerBreakDest; + m_continueDest = outerContinueDest; + + // Back edge. + connectBlocks(m_currentBlock, predicate(*loopHeaderBlock)); + setCurrentBlock(*afterLoopBlock); + + if (m_unknownFunctionCallSeen) + eraseKnowledge(); + + m_unknownFunctionCallSeen = unknownFunctionCallWasSeen; + + return false; +} + +void CHC::endVisit(ForStatement const& _for) +{ + solAssert(m_scopes.back() == &_for, ""); + m_scopes.pop_back(); +} + +void CHC::endVisit(UnaryOperation const& _op) +{ + SMTEncoder::endVisit(_op); + + if (auto funDef = *_op.annotation().userDefinedFunction) + { + std::vector arguments; + arguments.push_back(&_op.subExpression()); + internalFunctionCall(funDef, std::nullopt, _op.userDefinedFunctionType(), arguments, state().thisAddress()); + + createReturnedExpressions(funDef, _op); + } +} + +void CHC::endVisit(BinaryOperation const& _op) +{ + SMTEncoder::endVisit(_op); + + if (auto funDef = *_op.annotation().userDefinedFunction) + { + std::vector arguments; + arguments.push_back(&_op.leftExpression()); + arguments.push_back(&_op.rightExpression()); + internalFunctionCall(funDef, std::nullopt, _op.userDefinedFunctionType(), arguments, state().thisAddress()); + + createReturnedExpressions(funDef, _op); + } +} + +void CHC::endVisit(FunctionCall const& _funCall) +{ + auto functionCallKind = *_funCall.annotation().kind; + + if (functionCallKind != FunctionCallKind::FunctionCall) + { + SMTEncoder::endVisit(_funCall); + return; + } + + FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); + switch (funType.kind()) + { + case FunctionType::Kind::Assert: + visitAssert(_funCall); + SMTEncoder::endVisit(_funCall); + break; + case FunctionType::Kind::Internal: + internalFunctionCall(_funCall); + break; + case FunctionType::Kind::External: + case FunctionType::Kind::BareStaticCall: + case FunctionType::Kind::BareCall: + externalFunctionCall(_funCall); + SMTEncoder::endVisit(_funCall); + break; + case FunctionType::Kind::Creation: + visitDeployment(_funCall); + break; + case FunctionType::Kind::DelegateCall: + case FunctionType::Kind::BareCallCode: + case FunctionType::Kind::BareDelegateCall: + SMTEncoder::endVisit(_funCall); + unknownFunctionCall(_funCall); + break; + case FunctionType::Kind::KECCAK256: + case FunctionType::Kind::ECRecover: + case FunctionType::Kind::SHA256: + case FunctionType::Kind::RIPEMD160: + case FunctionType::Kind::BlockHash: + case FunctionType::Kind::AddMod: + case FunctionType::Kind::MulMod: + case FunctionType::Kind::Unwrap: + case FunctionType::Kind::Wrap: + [[fallthrough]]; + default: + SMTEncoder::endVisit(_funCall); + break; + } + + auto funDef = functionCallToDefinition(_funCall, currentScopeContract(), m_currentContract); + createReturnedExpressions(funDef, _funCall); +} + +void CHC::endVisit(Break const& _break) +{ + solAssert(m_breakDest, ""); + connectBlocks(m_currentBlock, predicate(*m_breakDest)); + + // Add an unreachable ghost node to collect unreachable statements after a break. + auto breakGhost = createBlock(&_break, PredicateType::FunctionBlock, "break_ghost_"); + m_currentBlock = predicate(*breakGhost); +} + +void CHC::endVisit(Continue const& _continue) +{ + solAssert(m_continueDest, ""); + connectBlocks(m_currentBlock, predicate(*m_continueDest)); + + // Add an unreachable ghost node to collect unreachable statements after a continue. + auto continueGhost = createBlock(&_continue, PredicateType::FunctionBlock, "continue_ghost_"); + m_currentBlock = predicate(*continueGhost); +} + +void CHC::endVisit(IndexRangeAccess const& _range) +{ + createExpr(_range); + + auto baseArray = std::dynamic_pointer_cast(m_context.expression(_range.baseExpression())); + auto sliceArray = std::dynamic_pointer_cast(m_context.expression(_range)); + solAssert(baseArray && sliceArray, ""); + + auto const& sliceData = ArraySlicePredicate::create(sliceArray->sort(), m_context); + if (!sliceData.first) + { + for (auto pred: sliceData.second.predicates) + m_interface->registerRelation(pred->functor()); + for (auto const& rule: sliceData.second.rules) + addRule(rule, ""); + } + + auto start = _range.startExpression() ? expr(*_range.startExpression()) : 0; + auto end = _range.endExpression() ? expr(*_range.endExpression()) : baseArray->length(); + auto slicePred = (*sliceData.second.predicates.at(0))({ + baseArray->elements(), + sliceArray->elements(), + start, + end + }); + + m_context.addAssertion(slicePred); + m_context.addAssertion(sliceArray->length() == end - start); +} + +void CHC::endVisit(Return const& _return) +{ + SMTEncoder::endVisit(_return); + + connectBlocks(m_currentBlock, predicate(*m_returnDests.back())); + + // Add an unreachable ghost node to collect unreachable statements after a return. + auto returnGhost = createBlock(&_return, PredicateType::FunctionBlock, "return_ghost_"); + m_currentBlock = predicate(*returnGhost); +} + +bool CHC::visit(TryCatchClause const& _tryStatement) +{ + m_scopes.push_back(&_tryStatement); + return SMTEncoder::visit(_tryStatement); +} + +void CHC::endVisit(TryCatchClause const& _tryStatement) +{ + solAssert(m_scopes.back() == &_tryStatement, ""); + m_scopes.pop_back(); +} + +bool CHC::visit(TryStatement const& _tryStatement) +{ + FunctionCall const* externalCall = dynamic_cast(&_tryStatement.externalCall()); + solAssert(externalCall && externalCall->annotation().tryCall, ""); + solAssert(m_currentFunction, ""); + + auto tryHeaderBlock = createBlock(&_tryStatement, PredicateType::FunctionBlock, "try_header_"); + auto afterTryBlock = createBlock(&m_currentFunction->body(), PredicateType::FunctionBlock); + + auto const& clauses = _tryStatement.clauses(); + solAssert(clauses[0].get() == _tryStatement.successClause(), "First clause of TryStatement should be the success clause"); + auto clauseBlocks = applyMap(clauses, [this](ASTPointer clause) { + return createBlock(clause.get(), PredicateType::FunctionBlock, "try_clause_" + std::to_string(clause->id())); + }); + + connectBlocks(m_currentBlock, predicate(*tryHeaderBlock)); + setCurrentBlock(*tryHeaderBlock); + // Visit everything, except the actual external call. + externalCall->expression().accept(*this); + ASTNode::listAccept(externalCall->arguments(), *this); + // Branch directly to all catch clauses, since in these cases, any effects of the external call are reverted. + for (size_t i = 1; i < clauseBlocks.size(); ++i) + connectBlocks(m_currentBlock, predicate(*clauseBlocks[i])); + // Only now visit the actual call to record its effects and connect to the success clause. + endVisit(*externalCall); + if (_tryStatement.successClause()->parameters()) + expressionToTupleAssignment(_tryStatement.successClause()->parameters()->parameters(), *externalCall); + + connectBlocks(m_currentBlock, predicate(*clauseBlocks[0])); + + for (size_t i = 0; i < clauses.size(); ++i) + { + setCurrentBlock(*clauseBlocks[i]); + clauses[i]->accept(*this); + connectBlocks(m_currentBlock, predicate(*afterTryBlock)); + } + setCurrentBlock(*afterTryBlock); + + return false; +} + +void CHC::pushInlineFrame(CallableDeclaration const& _callable) +{ + m_returnDests.push_back(createBlock(&_callable, PredicateType::FunctionBlock, "return_")); +} + +void CHC::popInlineFrame(CallableDeclaration const& _callable) +{ + solAssert(!m_returnDests.empty(), ""); + auto const& ret = *m_returnDests.back(); + solAssert(ret.programNode() == &_callable, ""); + connectBlocks(m_currentBlock, predicate(ret)); + setCurrentBlock(ret); + m_returnDests.pop_back(); +} + +void CHC::visitAssert(FunctionCall const& _funCall) +{ + auto const& args = _funCall.arguments(); + solAssert(args.size() == 1, ""); + solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); + + solAssert(m_currentContract, ""); + solAssert(m_currentFunction, ""); + auto errorCondition = !m_context.expression(*args.front())->currentValue(); + verificationTargetEncountered(&_funCall, VerificationTargetType::Assert, errorCondition); +} + +void CHC::visitPublicGetter(FunctionCall const& _funCall) +{ + createExpr(_funCall); + if (encodeExternalCallsAsTrusted()) + { + auto const& access = dynamic_cast(_funCall.expression()); + auto const& contractType = dynamic_cast(*access.expression().annotation().type); + state().writeStateVars(*m_currentContract, state().thisAddress()); + state().readStateVars(contractType.contractDefinition(), expr(access.expression())); + } + SMTEncoder::visitPublicGetter(_funCall); +} + +void CHC::visitAddMulMod(FunctionCall const& _funCall) +{ + solAssert(_funCall.arguments().at(2), ""); + + verificationTargetEncountered(&_funCall, VerificationTargetType::DivByZero, expr(*_funCall.arguments().at(2)) == 0); + + SMTEncoder::visitAddMulMod(_funCall); +} + +void CHC::visitDeployment(FunctionCall const& _funCall) +{ + if (!encodeExternalCallsAsTrusted()) + { + SMTEncoder::endVisit(_funCall); + unknownFunctionCall(_funCall); + return; + } + + auto [callExpr, callOptions] = functionCallExpression(_funCall); + auto funType = dynamic_cast(callExpr->annotation().type); + ContractDefinition const* contract = + &dynamic_cast(*funType->returnParameterTypes().front()).contractDefinition(); + + // copy state variables from m_currentContract to state.storage. + state().writeStateVars(*m_currentContract, state().thisAddress()); + errorFlag().increaseIndex(); + + Expression const* value = valueOption(callOptions); + if (value) + decreaseBalanceFromOptionsValue(*value); + + auto originalTx = state().tx(); + newTxConstraints(value); + + auto prevThisAddr = state().thisAddress(); + auto newAddr = state().newThisAddress(); + + if (auto constructor = contract->constructor()) + { + auto const& args = _funCall.sortedArguments(); + auto const& params = constructor->parameters(); + solAssert(args.size() == params.size(), ""); + for (auto [arg, param]: ranges::zip_view(args, params)) + m_context.addAssertion(expr(*arg) == m_context.variable(*param)->currentValue()); + } + for (auto var: stateVariablesIncludingInheritedAndPrivate(*contract)) + m_context.variable(*var)->increaseIndex(); + Predicate const& constructorSummary = *m_constructorSummaries.at(contract); + m_context.addAssertion(smt::constructorCall(constructorSummary, m_context, false)); + + solAssert(m_errorDest, ""); + connectBlocks( + m_currentBlock, + predicate(*m_errorDest), + errorFlag().currentValue() > 0 + ); + m_context.addAssertion(errorFlag().currentValue() == 0); + + m_context.addAssertion(state().newThisAddress() == prevThisAddr); + + // copy state variables from state.storage to m_currentContract. + state().readStateVars(*m_currentContract, state().thisAddress()); + + state().newTx(); + m_context.addAssertion(originalTx == state().tx()); + + defineExpr(_funCall, newAddr); +} + +void CHC::internalFunctionCall( + FunctionDefinition const* _funDef, + std::optional _boundArgumentCall, + FunctionType const* _funType, + std::vector const& _arguments, + smtutil::Expression _contractAddressValue +) +{ + solAssert(m_currentContract, ""); + solAssert(_funType, ""); + + if (_funDef) + { + if (m_currentFunction && !m_currentFunction->isConstructor()) + m_callGraph[m_currentFunction].insert(_funDef); + else + m_callGraph[m_currentContract].insert(_funDef); + } + + m_context.addAssertion(predicate(_funDef, _boundArgumentCall, _funType, _arguments, _contractAddressValue)); + + solAssert(m_errorDest, ""); + connectBlocks( + m_currentBlock, + predicate(*m_errorDest), + errorFlag().currentValue() > 0 && currentPathConditions() + ); + m_context.addAssertion(smtutil::Expression::implies(currentPathConditions(), errorFlag().currentValue() == 0)); + m_context.addAssertion(errorFlag().increaseIndex() == 0); +} + +void CHC::internalFunctionCall(FunctionCall const& _funCall) +{ + solAssert(m_currentContract, ""); + + auto funDef = functionCallToDefinition(_funCall, currentScopeContract(), m_currentContract); + if (funDef) + { + if (m_currentFunction && !m_currentFunction->isConstructor()) + m_callGraph[m_currentFunction].insert(funDef); + else + m_callGraph[m_currentContract].insert(funDef); + } + + Expression const* calledExpr = &_funCall.expression(); + auto funType = dynamic_cast(calledExpr->annotation().type); + + auto contractAddressValue = [this](FunctionCall const& _f) { + auto [callExpr, callOptions] = functionCallExpression(_f); + + FunctionType const& funType = dynamic_cast(*callExpr->annotation().type); + if (funType.kind() == FunctionType::Kind::Internal) + return state().thisAddress(); + if (MemberAccess const* callBase = dynamic_cast(callExpr)) + return expr(callBase->expression()); + solAssert(false, "Unreachable!"); + }; + + std::vector arguments; + for (auto& arg: _funCall.sortedArguments()) + arguments.push_back(&(*arg)); + + std::optional boundArgumentCall = + funType->hasBoundFirstArgument() ? std::make_optional(calledExpr) : std::nullopt; + internalFunctionCall(funDef, boundArgumentCall, funType, arguments, contractAddressValue(_funCall)); +} + +void CHC::addNondetCalls(ContractDefinition const& _contract) +{ + for (auto var: _contract.stateVariables()) + if (auto contractType = dynamic_cast(var->type())) + { + auto const& symbVar = m_context.variable(*var); + m_context.addAssertion(symbVar->currentValue() == symbVar->valueAtIndex(0)); + nondetCall(contractType->contractDefinition(), *var); + } +} + +void CHC::nondetCall(ContractDefinition const& _contract, VariableDeclaration const& _var) +{ + auto address = m_context.variable(_var)->currentValue(); + // Load the called contract's state variables from the global state. + state().readStateVars(_contract, address); + + m_context.addAssertion(state().state() == state().state(0)); + auto preCallState = std::vector{state().state()} + currentStateVariables(_contract); + + state().newState(); + for (auto const* var: _contract.stateVariables()) + m_context.variable(*var)->increaseIndex(); + + Predicate const& callPredicate = *createSymbolicBlock( + nondetInterfaceSort(_contract, state()), + "nondet_call_" + uniquePrefix(), + PredicateType::FunctionSummary, + &_var, + m_currentContract + ); + auto postCallState = std::vector{state().state()} + currentStateVariables(_contract); + std::vector stateExprs = commonStateExpressions(errorFlag().increaseIndex(), address); + + auto nondet = (*m_nondetInterfaces.at(&_contract))(stateExprs + preCallState + postCallState); + auto nondetCall = callPredicate(stateExprs + preCallState + postCallState); + + addRule(smtutil::Expression::implies(nondet, nondetCall), nondetCall.name); + + m_context.addAssertion(nondetCall); + + // Load the called contract's state variables into the global state. + state().writeStateVars(_contract, address); +} + +void CHC::externalFunctionCall(FunctionCall const& _funCall) +{ + /// In external function calls we do not add a "predicate call" + /// because we do not trust their function body anyway, + /// so we just add the nondet_interface predicate. + + solAssert(m_currentContract, ""); + + auto [callExpr, callOptions] = functionCallExpression(_funCall); + FunctionType const& funType = dynamic_cast(*callExpr->annotation().type); + + auto kind = funType.kind(); + solAssert( + kind == FunctionType::Kind::External || + kind == FunctionType::Kind::BareCall || + kind == FunctionType::Kind::BareStaticCall, + "" + ); + + + // Only consider high level external calls in trusted mode. + if ( + kind == FunctionType::Kind::External && + (encodeExternalCallsAsTrusted() || isExternalCallToThis(callExpr)) + ) + { + externalFunctionCallToTrustedCode(_funCall); + return; + } + + // Low level calls are still encoded nondeterministically. + + auto function = functionCallToDefinition(_funCall, currentScopeContract(), m_currentContract); + if (function) + for (auto var: function->returnParameters()) + m_context.variable(*var)->increaseIndex(); + + // If we see a low level call in trusted mode, + // we need to havoc the global state. + if ( + kind == FunctionType::Kind::BareCall && + encodeExternalCallsAsTrusted() + ) + state().newStorage(); + + // No reentrancy from constructor calls. + if (!m_currentFunction || m_currentFunction->isConstructor()) + return; + + if (Expression const* value = valueOption(callOptions)) + decreaseBalanceFromOptionsValue(*value); + + auto preCallState = std::vector{state().state()} + currentStateVariables(); + + if (!usesStaticCall(_funCall)) + { + state().newState(); + for (auto const* var: m_stateVariables) + m_context.variable(*var)->increaseIndex(); + } + + Predicate const& callPredicate = *createSymbolicBlock( + nondetInterfaceSort(*m_currentContract, state()), + "nondet_call_" + uniquePrefix(), + PredicateType::ExternalCallUntrusted, + &_funCall + ); + auto postCallState = std::vector{state().state()} + currentStateVariables(); + std::vector stateExprs = commonStateExpressions(errorFlag().increaseIndex(), state().thisAddress()); + + auto nondet = (*m_nondetInterfaces.at(m_currentContract))(stateExprs + preCallState + postCallState); + auto nondetCall = callPredicate(stateExprs + preCallState + postCallState); + + addRule(smtutil::Expression::implies(nondet, nondetCall), nondetCall.name); + + m_context.addAssertion(nondetCall); + solAssert(m_errorDest, ""); + connectBlocks(m_currentBlock, predicate(*m_errorDest), errorFlag().currentValue() > 0 && currentPathConditions()); + + // To capture the possibility of a reentrant call, we record in the call graph that the current function + // can call any of the external methods of the current contract. + if (m_currentFunction) + for (auto const* definedFunction: contractFunctions(*m_currentContract)) + if (!definedFunction->isConstructor() && definedFunction->isPublic()) + m_callGraph[m_currentFunction].insert(definedFunction); + + m_context.addAssertion(errorFlag().currentValue() == 0); +} + +void CHC::externalFunctionCallToTrustedCode(FunctionCall const& _funCall) +{ + if (publicGetter(_funCall.expression())) + visitPublicGetter(_funCall); + + solAssert(m_currentContract, ""); + + auto [callExpr, callOptions] = functionCallExpression(_funCall); + FunctionType const& funType = dynamic_cast(*callExpr->annotation().type); + + auto kind = funType.kind(); + solAssert(kind == FunctionType::Kind::External || kind == FunctionType::Kind::BareStaticCall, ""); + + auto function = functionCallToDefinition(_funCall, currentScopeContract(), m_currentContract); + if (!function) + return; + + // Remember the external call in the call graph to properly detect verification targets for the current function + if (m_currentFunction && !m_currentFunction->isConstructor()) + m_callGraph[m_currentFunction].insert(function); + else + m_callGraph[m_currentContract].insert(function); + + // External call creates a new transaction. + auto originalTx = state().tx(); + Expression const* value = valueOption(callOptions); + newTxConstraints(value); + + auto calledAddress = contractAddressValue(_funCall); + if (value) + { + decreaseBalanceFromOptionsValue(*value); + state().addBalance(calledAddress, expr(*value)); + } + + if (encodeExternalCallsAsTrusted()) + { + // The order here is important!! Write should go first. + + // Load the caller contract's state variables into the global state. + state().writeStateVars(*m_currentContract, state().thisAddress()); + // Load the called contract's state variables from the global state. + state().readStateVars(*function->annotation().contract, contractAddressValue(_funCall)); + } + + std::vector arguments; + for (auto& arg: _funCall.sortedArguments()) + arguments.push_back(&(*arg)); + smtutil::Expression pred = predicate(function, std::nullopt, &funType, arguments, calledAddress); + + auto txConstraints = state().txTypeConstraints() && state().txFunctionConstraints(*function); + m_context.addAssertion(pred && txConstraints); + // restore the original transaction data + state().newTx(); + m_context.addAssertion(originalTx == state().tx()); + + solAssert(m_errorDest, ""); + connectBlocks( + m_currentBlock, + predicate(*m_errorDest), + (errorFlag().currentValue() > 0) + ); + m_context.addAssertion(errorFlag().currentValue() == 0); + + if (!usesStaticCall(_funCall)) + if (encodeExternalCallsAsTrusted()) + { + // The order here is important!! Write should go first. + + // Load the called contract's state variables into the global state. + state().writeStateVars(*function->annotation().contract, contractAddressValue(_funCall)); + // Load the caller contract's state variables from the global state. + state().readStateVars(*m_currentContract, state().thisAddress()); + } +} + +void CHC::unknownFunctionCall(FunctionCall const&) +{ + /// Function calls are not handled at the moment, + /// so always erase knowledge. + /// TODO remove when function calls get predicates/blocks. + eraseKnowledge(); + + /// Used to erase outer scope knowledge in loops and ifs. + /// TODO remove when function calls get predicates/blocks. + m_unknownFunctionCallSeen = true; +} + +void CHC::makeArrayPopVerificationTarget(FunctionCall const& _arrayPop) +{ + FunctionType const& funType = dynamic_cast(*_arrayPop.expression().annotation().type); + solAssert(funType.kind() == FunctionType::Kind::ArrayPop, ""); + + auto memberAccess = dynamic_cast(cleanExpression(_arrayPop.expression())); + solAssert(memberAccess, ""); + auto symbArray = std::dynamic_pointer_cast(m_context.expression(memberAccess->expression())); + solAssert(symbArray, ""); + + verificationTargetEncountered(&_arrayPop, VerificationTargetType::PopEmptyArray, symbArray->length() <= 0); +} + +void CHC::makeOutOfBoundsVerificationTarget(IndexAccess const& _indexAccess) +{ + if (_indexAccess.annotation().type->category() == Type::Category::TypeType) + return; + + auto baseType = _indexAccess.baseExpression().annotation().type; + + std::optional length; + if (smt::isArray(*baseType)) + length = dynamic_cast( + *m_context.expression(_indexAccess.baseExpression()) + ).length(); + else if (auto const* type = dynamic_cast(baseType)) + length = smtutil::Expression(static_cast(type->numBytes())); + + std::optional target; + if ( + auto index = _indexAccess.indexExpression(); + index && length + ) + target = expr(*index) < 0 || expr(*index) >= *length; + + if (target) + verificationTargetEncountered(&_indexAccess, VerificationTargetType::OutOfBounds, *target); +} + +std::pair CHC::arithmeticOperation( + Token _op, + smtutil::Expression const& _left, + smtutil::Expression const& _right, + Type const* _commonType, + frontend::Expression const& _expression +) +{ + // Unchecked does not disable div by 0 checks. + if (_op == Token::Mod || _op == Token::Div) + verificationTargetEncountered(&_expression, VerificationTargetType::DivByZero, _right == 0); + + auto values = SMTEncoder::arithmeticOperation(_op, _left, _right, _commonType, _expression); + + if (!m_checked) + return values; + + IntegerType const* intType = nullptr; + if (auto const* type = dynamic_cast(_commonType)) + intType = type; + else + intType = TypeProvider::uint256(); + + // Mod does not need underflow/overflow checks. + // Div only needs overflow check for signed types. + if (_op == Token::Mod || (_op == Token::Div && !intType->isSigned())) + return values; + + if (_op == Token::Div) + verificationTargetEncountered(&_expression, VerificationTargetType::Overflow, values.second > intType->maxValue()); + else if (intType->isSigned()) + { + verificationTargetEncountered(&_expression, VerificationTargetType::Underflow, values.second < intType->minValue()); + verificationTargetEncountered(&_expression, VerificationTargetType::Overflow, values.second > intType->maxValue()); + } + else if (_op == Token::Sub) + verificationTargetEncountered(&_expression, VerificationTargetType::Underflow, values.second < intType->minValue()); + else if (_op == Token::Add || _op == Token::Mul) + verificationTargetEncountered(&_expression, VerificationTargetType::Overflow, values.second > intType->maxValue()); + else + solAssert(false, ""); + return values; +} + +void CHC::resetSourceAnalysis() +{ + SMTEncoder::resetSourceAnalysis(); + + m_unprovedTargets.clear(); + m_invariants.clear(); + m_functionTargetIds.clear(); + m_verificationTargets.clear(); + m_queryPlaceholders.clear(); + m_callGraph.clear(); + m_summaries.clear(); + m_externalSummaries.clear(); + m_interfaces.clear(); + m_nondetInterfaces.clear(); + m_constructorSummaries.clear(); + m_contractInitializers.clear(); + Predicate::reset(); + ArraySlicePredicate::reset(); + m_blockCounter = 0; + + // At this point every enabled solver is available. + // If more than one Horn solver is selected we go with z3. + // We still need the ifdef because of Z3CHCInterface. + if (m_settings.solvers.z3) + { +#ifdef HAVE_Z3 + // z3::fixedpoint does not have a reset mechanism, so we need to create another. + m_interface = std::make_unique(m_settings.timeout); + auto z3Interface = dynamic_cast(m_interface.get()); + solAssert(z3Interface, ""); + m_context.setSolver(z3Interface->z3Interface()); +#else + solAssert(false); +#endif + } + if (!m_settings.solvers.z3) + { + solAssert(m_settings.solvers.smtlib2 || m_settings.solvers.eld); + + if (!m_interface) + m_interface = std::make_unique(m_smtlib2Responses, m_smtCallback, m_settings.solvers, m_settings.timeout); + + auto smtlib2Interface = dynamic_cast(m_interface.get()); + solAssert(smtlib2Interface, ""); + smtlib2Interface->reset(); + m_context.setSolver(smtlib2Interface->smtlib2Interface()); + } + + m_context.reset(); + m_context.resetUniqueId(); + m_context.setAssertionAccumulation(false); +} + +void CHC::resetContractAnalysis() +{ + m_stateVariables.clear(); + m_unknownFunctionCallSeen = false; + m_breakDest = nullptr; + m_continueDest = nullptr; + m_returnDests.clear(); + errorFlag().resetIndex(); +} + +void CHC::eraseKnowledge() +{ + resetStorageVariables(); + resetBalances(); +} + +void CHC::clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function) +{ + SMTEncoder::clearIndices(_contract, _function); + for (auto const* var: m_stateVariables) + /// SSA index 0 is reserved for state variables at the beginning + /// of the current transaction. + m_context.variable(*var)->increaseIndex(); + if (_function) + { + for (auto const& var: _function->parameters() + _function->returnParameters()) + m_context.variable(*var)->increaseIndex(); + for (auto const& var: localVariablesIncludingModifiers(*_function, _contract)) + m_context.variable(*var)->increaseIndex(); + } + + state().newState(); +} + +void CHC::setCurrentBlock(Predicate const& _block) +{ + if (m_context.solverStackHeigh() > 0) + m_context.popSolver(); + solAssert(m_currentContract, ""); + clearIndices(m_currentContract, m_currentFunction); + m_context.pushSolver(); + m_currentBlock = predicate(_block); +} + +std::set CHC::transactionVerificationTargetsIds(ASTNode const* _txRoot) +{ + std::set verificationTargetsIds; + struct ASTNodeCompare: EncodingContext::IdCompare + { + bool operator<(ASTNodeCompare _other) const { return operator()(node, _other.node); } + ASTNode const* node; + }; + solidity::util::BreadthFirstSearch{{{{}, _txRoot}}}.run([&](auto _node, auto&& _addChild) { + verificationTargetsIds.insert(m_functionTargetIds[_node.node].begin(), m_functionTargetIds[_node.node].end()); + for (ASTNode const* called: m_callGraph[_node.node]) + _addChild({{}, called}); + }); + return verificationTargetsIds; +} + +bool CHC::usesStaticCall(FunctionDefinition const* _funDef, FunctionType const* _funType) +{ + auto kind = _funType->kind(); + return (_funDef && (_funDef->stateMutability() == StateMutability::Pure || _funDef->stateMutability() == StateMutability::View)) || kind == FunctionType::Kind::BareStaticCall; +} + +bool CHC::usesStaticCall(FunctionCall const& _funCall) +{ + FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); + auto kind = funType.kind(); + auto function = functionCallToDefinition(_funCall, currentScopeContract(), m_currentContract); + return (function && (function->stateMutability() == StateMutability::Pure || function->stateMutability() == StateMutability::View)) || kind == FunctionType::Kind::BareStaticCall; +} + +std::optional CHC::natspecOptionFromString(std::string const& _option) +{ + static std::map options{ + {"abstract-function-nondet", CHCNatspecOption::AbstractFunctionNondet} + }; + if (options.count(_option)) + return options.at(_option); + return {}; +} + +std::set CHC::smtNatspecTags(FunctionDefinition const& _function) +{ + std::set options; + std::string smtStr = "custom:smtchecker"; + bool errorSeen = false; + for (auto const& [tag, value]: _function.annotation().docTags) + if (tag == smtStr) + { + std::string const& content = value.content; + if (auto option = natspecOptionFromString(content)) + options.insert(*option); + else if (!errorSeen) + { + errorSeen = true; + m_errorReporter.warning(3130_error, _function.location(), "Unknown option for \"" + smtStr + "\": \"" + content + "\""); + } + } + return options; +} + +bool CHC::abstractAsNondet(FunctionDefinition const& _function) +{ + return smtNatspecTags(_function).count(CHCNatspecOption::AbstractFunctionNondet); +} + +SortPointer CHC::sort(FunctionDefinition const& _function) +{ + return functionBodySort(_function, m_currentContract, state()); +} + +bool CHC::encodeExternalCallsAsTrusted() +{ + return m_settings.externalCalls.isTrusted(); +} + +SortPointer CHC::sort(ASTNode const* _node) +{ + if (auto funDef = dynamic_cast(_node)) + return sort(*funDef); + + solAssert(m_currentFunction, ""); + return functionBodySort(*m_currentFunction, m_currentContract, state()); +} + +Predicate const* CHC::createSymbolicBlock(SortPointer _sort, std::string const& _name, PredicateType _predType, ASTNode const* _node, ContractDefinition const* _contractContext) +{ + auto const* block = Predicate::create(_sort, _name, _predType, m_context, _node, _contractContext, m_scopes); + m_interface->registerRelation(block->functor()); + return block; +} + +void CHC::defineInterfacesAndSummaries(SourceUnit const& _source) +{ + for (auto const& node: _source.nodes()) + if (auto const* contract = dynamic_cast(node.get())) + { + std::string suffix = contract->name() + "_" + std::to_string(contract->id()); + m_interfaces[contract] = createSymbolicBlock(interfaceSort(*contract, state()), "interface_" + uniquePrefix() + "_" + suffix, PredicateType::Interface, contract, contract); + m_nondetInterfaces[contract] = createSymbolicBlock(nondetInterfaceSort(*contract, state()), "nondet_interface_" + uniquePrefix() + "_" + suffix, PredicateType::NondetInterface, contract, contract); + m_constructorSummaries[contract] = createConstructorBlock(*contract, "summary_constructor"); + + for (auto const* var: stateVariablesIncludingInheritedAndPrivate(*contract)) + if (!m_context.knownVariable(*var)) + createVariable(*var); + + /// Base nondeterministic interface that allows + /// 0 steps to be taken, used as base for the inductive + /// rule for each function. + auto const& iface = *m_nondetInterfaces.at(contract); + addRule(smtutil::Expression::implies(errorFlag().currentValue() == 0, smt::nondetInterface(iface, *contract, m_context, 0, 0)), "base_nondet"); + + auto const& resolved = contractFunctions(*contract); + for (auto const* function: contractFunctionsWithoutVirtual(*contract) + allFreeFunctions()) + { + for (auto var: function->parameters()) + createVariable(*var); + for (auto var: function->returnParameters()) + createVariable(*var); + for (auto const* var: localVariablesIncludingModifiers(*function, contract)) + createVariable(*var); + + m_summaries[contract].emplace(function, createSummaryBlock(*function, *contract)); + + if ( + !function->isConstructor() && + function->isPublic() && + // Public library functions should have interfaces only for the libraries + // they're declared in. + (!function->libraryFunction() || (function->scope() == contract)) && + resolved.count(function) + ) + { + m_externalSummaries[contract].emplace(function, createSummaryBlock(*function, *contract)); + + auto state1 = stateVariablesAtIndex(1, *contract); + auto state2 = stateVariablesAtIndex(2, *contract); + + auto errorPre = errorFlag().currentValue(); + auto nondetPre = smt::nondetInterface(iface, *contract, m_context, 0, 1); + auto errorPost = errorFlag().increaseIndex(); + auto nondetPost = smt::nondetInterface(iface, *contract, m_context, 0, 2); + + std::vector args = + commonStateExpressions(errorPost, state().thisAddress()) + + std::vector{state().tx(), state().state(1)}; + args += state1 + + applyMap(function->parameters(), [this](auto _var) { return valueAtIndex(*_var, 0); }) + + std::vector{state().state(2)} + + state2 + + applyMap(function->parameters(), [this](auto _var) { return valueAtIndex(*_var, 1); }) + + applyMap(function->returnParameters(), [this](auto _var) { return valueAtIndex(*_var, 1); }); + + connectBlocks(nondetPre, nondetPost, errorPre == 0 && (*m_externalSummaries.at(contract).at(function))(args)); + } + } + } +} + +void CHC::defineExternalFunctionInterface(FunctionDefinition const& _function, ContractDefinition const& _contract) +{ + // Create a rule that represents an external call to this function. + // This contains more things than the function body itself, + // such as balance updates because of ``msg.value``. + auto functionEntryBlock = createBlock(&_function, PredicateType::FunctionBlock); + auto functionPred = predicate(*functionEntryBlock); + addRule(functionPred, functionPred.name); + setCurrentBlock(*functionEntryBlock); + + m_context.addAssertion(initialConstraints(_contract, &_function)); + m_context.addAssertion(state().txTypeConstraints() && state().txFunctionConstraints(_function)); + + // The contract may have received funds through a selfdestruct or + // block.coinbase, which do not trigger calls into the contract. + // So the only constraint we can add here is that the balance of + // the contract grows by at least `msg.value`. + SymbolicIntVariable k{TypeProvider::uint256(), TypeProvider::uint256(), "funds_" + std::to_string(m_context.newUniqueId()), m_context}; + m_context.addAssertion(k.currentValue() >= state().txMember("msg.value")); + // Assume that address(this).balance cannot overflow. + m_context.addAssertion(smt::symbolicUnknownConstraints(state().balance(state().thisAddress()) + k.currentValue(), TypeProvider::uint256())); + state().addBalance(state().thisAddress(), k.currentValue()); + + if (encodeExternalCallsAsTrusted()) + { + // If the contract has state variables that are addresses to other contracts, + // we need to encode the fact that those contracts may have been called in between + // transactions to _contract. + // + // We do that by adding nondet_interface constraints for those contracts, + // in the last line of this if block. + // + // If there are state variables of container types like structs or arrays + // that indirectly contain contract types, we havoc the state for simplicity, + // in the first part of this block. + // TODO: This could actually be supported. + // For structs: simply collect the SMT expressions of all the indirect contract type members. + // For arrays: more involved, needs to traverse the array symbolically and do the same for each contract. + // For mappings: way more complicated if the element type is a contract. + auto hasContractOrAddressSubType = [&](VariableDeclaration const* _var) -> bool { + bool foundContract = false; + solidity::util::BreadthFirstSearch bfs{{_var->type()}}; + bfs.run([&](auto _type, auto&& _addChild) { + if ( + _type->category() == Type::Category::Address || + _type->category() == Type::Category::Contract + ) + { + foundContract = true; + bfs.abort(); + } + if (auto const* mapType = dynamic_cast(_type)) + _addChild(mapType->valueType()); + else if (auto const* arrayType = dynamic_cast(_type)) + _addChild(arrayType->baseType()); + else if (auto const* structType = dynamic_cast(_type)) + for (auto const& member: structType->nativeMembers(nullptr)) + _addChild(member.type); + }); + return foundContract; + }; + bool found = false; + for (auto var: m_currentContract->stateVariables()) + if ( + var->type()->category() != Type::Category::Address && + var->type()->category() != Type::Category::Contract && + hasContractOrAddressSubType(var) + ) + { + found = true; + break; + } + + if (found) + state().newStorage(); + else + addNondetCalls(*m_currentContract); + } + + errorFlag().increaseIndex(); + m_context.addAssertion(summaryCall(_function)); + + connectBlocks(functionPred, externalSummary(_function)); +} + +void CHC::defineContractInitializer(ContractDefinition const& _contract, ContractDefinition const& _contextContract) +{ + m_contractInitializers[&_contextContract][&_contract] = createConstructorBlock(_contract, "contract_initializer"); + auto const& implicitConstructorPredicate = *createConstructorBlock(_contract, "contract_initializer_entry"); + + auto implicitFact = smt::constructor(implicitConstructorPredicate, m_context); + addRule(smtutil::Expression::implies(initialConstraints(_contract), implicitFact), implicitFact.name); + setCurrentBlock(implicitConstructorPredicate); + + auto prevErrorDest = m_errorDest; + m_errorDest = m_contractInitializers.at(&_contextContract).at(&_contract); + for (auto var: _contract.stateVariables()) + if (var->value()) + { + var->value()->accept(*this); + assignment(*var, *var->value()); + } + m_errorDest = prevErrorDest; + + auto const& afterInit = *createConstructorBlock(_contract, "contract_initializer_after_init"); + connectBlocks(m_currentBlock, predicate(afterInit)); + setCurrentBlock(afterInit); + + if (auto constructor = _contract.constructor()) + { + errorFlag().increaseIndex(); + m_context.addAssertion(smt::functionCall(*m_summaries.at(&_contextContract).at(constructor), &_contextContract, m_context)); + connectBlocks(m_currentBlock, initializer(_contract, _contextContract), errorFlag().currentValue() > 0); + m_context.addAssertion(errorFlag().currentValue() == 0); + } + + connectBlocks(m_currentBlock, initializer(_contract, _contextContract)); +} + +smtutil::Expression CHC::interface() +{ + solAssert(m_currentContract, ""); + return interface(*m_currentContract); +} + +smtutil::Expression CHC::interface(ContractDefinition const& _contract) +{ + return ::interface(*m_interfaces.at(&_contract), _contract, m_context); +} + +smtutil::Expression CHC::error() +{ + return (*m_errorPredicate)({}); +} + +smtutil::Expression CHC::error(unsigned _idx) +{ + return m_errorPredicate->functor(_idx)({}); +} + +smtutil::Expression CHC::initializer(ContractDefinition const& _contract, ContractDefinition const& _contractContext) +{ + return predicate(*m_contractInitializers.at(&_contractContext).at(&_contract)); +} + +smtutil::Expression CHC::summary(ContractDefinition const& _contract) +{ + return predicate(*m_constructorSummaries.at(&_contract)); +} + +smtutil::Expression CHC::summary(FunctionDefinition const& _function, ContractDefinition const& _contract) +{ + return smt::function(*m_summaries.at(&_contract).at(&_function), &_contract, m_context); +} + +smtutil::Expression CHC::summary(FunctionDefinition const& _function) +{ + solAssert(m_currentContract, ""); + return summary(_function, *m_currentContract); +} + +smtutil::Expression CHC::summaryCall(FunctionDefinition const& _function, ContractDefinition const& _contract) +{ + return smt::functionCall(*m_summaries.at(&_contract).at(&_function), &_contract, m_context); +} + +smtutil::Expression CHC::summaryCall(FunctionDefinition const& _function) +{ + solAssert(m_currentContract, ""); + return summaryCall(_function, *m_currentContract); +} + +smtutil::Expression CHC::externalSummary(FunctionDefinition const& _function, ContractDefinition const& _contract) +{ + return smt::function(*m_externalSummaries.at(&_contract).at(&_function), &_contract, m_context); +} + +smtutil::Expression CHC::externalSummary(FunctionDefinition const& _function) +{ + solAssert(m_currentContract, ""); + return externalSummary(_function, *m_currentContract); +} + +Predicate const* CHC::createBlock(ASTNode const* _node, PredicateType _predType, std::string const& _prefix) +{ + auto block = createSymbolicBlock( + sort(_node), + "block_" + uniquePrefix() + "_" + _prefix + predicateName(_node), + _predType, + _node, + m_currentContract + ); + + solAssert(m_currentFunction, ""); + return block; +} + +Predicate const* CHC::createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract, PredicateType _type) +{ + return createSymbolicBlock( + functionSort(_function, &_contract, state()), + "summary_" + uniquePrefix() + "_" + predicateName(&_function, &_contract), + _type, + &_function, + &_contract + ); +} + +Predicate const* CHC::createConstructorBlock(ContractDefinition const& _contract, std::string const& _prefix) +{ + return createSymbolicBlock( + constructorSort(_contract, state()), + _prefix + "_" + uniquePrefix() + "_" + contractSuffix(_contract), + PredicateType::ConstructorSummary, + &_contract, + &_contract + ); +} + +void CHC::createErrorBlock() +{ + m_errorPredicate = createSymbolicBlock( + arity0FunctionSort(), + "error_target_" + std::to_string(m_context.newUniqueId()), + PredicateType::Error + ); + m_interface->registerRelation(m_errorPredicate->functor()); +} + +void CHC::connectBlocks(smtutil::Expression const& _from, smtutil::Expression const& _to, smtutil::Expression const& _constraints) +{ + smtutil::Expression edge = smtutil::Expression::implies( + _from && m_context.assertions() && _constraints, + _to + ); + addRule(edge, _from.name + "_to_" + _to.name); +} + +smtutil::Expression CHC::initialConstraints(ContractDefinition const& _contract, FunctionDefinition const* _function) +{ + smtutil::Expression conj = state().state() == state().state(0); + conj = conj && errorFlag().currentValue() == 0; + conj = conj && currentEqualInitialVarsConstraints(stateVariablesIncludingInheritedAndPrivate(_contract)); + + FunctionDefinition const* function = _function ? _function : _contract.constructor(); + if (function) + conj = conj && currentEqualInitialVarsConstraints(applyMap(function->parameters(), [](auto&& _var) -> VariableDeclaration const* { return _var.get(); })); + + return conj; +} + +std::vector CHC::initialStateVariables() +{ + return stateVariablesAtIndex(0); +} + +std::vector CHC::stateVariablesAtIndex(unsigned _index) +{ + solAssert(m_currentContract, ""); + return stateVariablesAtIndex(_index, *m_currentContract); +} + +std::vector CHC::stateVariablesAtIndex(unsigned _index, ContractDefinition const& _contract) +{ + return applyMap( + SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract), + [&](auto _var) { return valueAtIndex(*_var, _index); } + ); +} + +std::vector CHC::currentStateVariables() +{ + solAssert(m_currentContract, ""); + return currentStateVariables(*m_currentContract); +} + +std::vector CHC::currentStateVariables(ContractDefinition const& _contract) +{ + return applyMap(SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract), [this](auto _var) { return currentValue(*_var); }); +} + +smtutil::Expression CHC::currentEqualInitialVarsConstraints(std::vector const& _vars) const +{ + return fold(_vars, smtutil::Expression(true), [this](auto&& _conj, auto _var) { + return std::move(_conj) && currentValue(*_var) == m_context.variable(*_var)->valueAtIndex(0); + }); +} + +std::string CHC::predicateName(ASTNode const* _node, ContractDefinition const* _contract) +{ + std::string prefix; + if (auto funDef = dynamic_cast(_node)) + { + prefix += TokenTraits::toString(funDef->kind()); + if (!funDef->name().empty()) + prefix += "_" + funDef->name() + "_"; + } + else if (m_currentFunction && !m_currentFunction->name().empty()) + prefix += m_currentFunction->name(); + + auto contract = _contract ? _contract : m_currentContract; + solAssert(contract, ""); + return prefix + "_" + std::to_string(_node->id()) + "_" + std::to_string(contract->id()); +} + +smtutil::Expression CHC::predicate(Predicate const& _block) +{ + switch (_block.type()) + { + case PredicateType::Interface: + solAssert(m_currentContract, ""); + return ::interface(_block, *m_currentContract, m_context); + case PredicateType::ConstructorSummary: + return constructor(_block, m_context); + case PredicateType::FunctionSummary: + case PredicateType::InternalCall: + case PredicateType::ExternalCallTrusted: + case PredicateType::ExternalCallUntrusted: + return smt::function(_block, m_currentContract, m_context); + case PredicateType::FunctionBlock: + case PredicateType::FunctionErrorBlock: + solAssert(m_currentFunction, ""); + return functionBlock(_block, *m_currentFunction, m_currentContract, m_context); + case PredicateType::Error: + return _block({}); + case PredicateType::NondetInterface: + // Nondeterministic interface predicates are handled differently. + solAssert(false, ""); + case PredicateType::Custom: + // Custom rules are handled separately. + solAssert(false, ""); + } + solAssert(false, ""); +} + +smtutil::Expression CHC::predicate( + FunctionDefinition const* _funDef, + std::optional _boundArgumentCall, + FunctionType const* _funType, + std::vector _arguments, + smtutil::Expression _contractAddressValue +) +{ + solAssert(_funType, ""); + auto kind = _funType->kind(); + solAssert(kind == FunctionType::Kind::Internal || kind == FunctionType::Kind::External || kind == FunctionType::Kind::BareStaticCall, ""); + if (!_funDef) + return smtutil::Expression(true); + + errorFlag().increaseIndex(); + + std::vector args = + commonStateExpressions(errorFlag().currentValue(), _contractAddressValue) + + std::vector{state().tx(), state().state()}; + + auto const* contract = _funDef->annotation().contract; + auto const& hierarchy = m_currentContract->annotation().linearizedBaseContracts; + solAssert(kind != FunctionType::Kind::Internal || _funDef->isFree() || (contract && contract->isLibrary()) || util::contains(hierarchy, contract), ""); + + if (kind == FunctionType::Kind::Internal) + contract = m_currentContract; + + args += currentStateVariables(*contract); + args += symbolicArguments(_funDef->parameters(), _arguments, _boundArgumentCall); + if (!usesStaticCall(_funDef, _funType)) + { + state().newState(); + for (auto const& var: stateVariablesIncludingInheritedAndPrivate(*contract)) + m_context.variable(*var)->increaseIndex(); + } + args += std::vector{state().state()}; + args += currentStateVariables(*contract); + + for (auto var: _funDef->parameters() + _funDef->returnParameters()) + { + if (m_context.knownVariable(*var)) + m_context.variable(*var)->increaseIndex(); + else + createVariable(*var); + args.push_back(currentValue(*var)); + } + + Predicate const& summary = *m_summaries.at(contract).at(_funDef); + auto from = smt::function(summary, contract, m_context); + Predicate const& callPredicate = *createSummaryBlock( + *_funDef, + *contract, + kind == FunctionType::Kind::Internal ? PredicateType::InternalCall : PredicateType::ExternalCallTrusted + ); + auto to = smt::function(callPredicate, contract, m_context); + addRule(smtutil::Expression::implies(from, to), to.name); + + return callPredicate(args); +} + +void CHC::addRule(smtutil::Expression const& _rule, std::string const& _ruleName) +{ + m_interface->addRule(_rule, _ruleName); +} + +std::tuple CHC::query(smtutil::Expression const& _query, langutil::SourceLocation const& _location) +{ + CheckResult result; + smtutil::Expression invariant(true); + CHCSolverInterface::CexGraph cex; + if (m_settings.printQuery) + { + auto smtLibInterface = dynamic_cast(m_interface.get()); + solAssert(smtLibInterface, "Requested to print queries but CHCSmtLib2Interface not available"); + std::string smtLibCode = smtLibInterface->dumpQuery(_query); + m_errorReporter.info( + 2339_error, + "CHC: Requested query:\n" + smtLibCode + ); + } + std::tie(result, invariant, cex) = m_interface->query(_query); + switch (result) + { + case CheckResult::SATISFIABLE: + { + // We still need the ifdef because of Z3CHCInterface. + if (m_settings.solvers.z3) + { +#ifdef HAVE_Z3 + // Even though the problem is SAT, Spacer's pre processing makes counterexamples incomplete. + // We now disable those optimizations and check whether we can still solve the problem. + auto* spacer = dynamic_cast(m_interface.get()); + solAssert(spacer, ""); + spacer->setSpacerOptions(false); + + CheckResult resultNoOpt; + smtutil::Expression invariantNoOpt(true); + CHCSolverInterface::CexGraph cexNoOpt; + std::tie(resultNoOpt, invariantNoOpt, cexNoOpt) = m_interface->query(_query); + + if (resultNoOpt == CheckResult::SATISFIABLE) + cex = std::move(cexNoOpt); + + spacer->setSpacerOptions(true); +#else + solAssert(false); +#endif + } + break; + } + case CheckResult::UNSATISFIABLE: + break; + case CheckResult::UNKNOWN: + break; + case CheckResult::CONFLICTING: + m_errorReporter.warning(1988_error, _location, "CHC: At least two SMT solvers provided conflicting answers. Results might not be sound."); + break; + case CheckResult::ERROR: + m_errorReporter.warning(1218_error, _location, "CHC: Error trying to invoke SMT solver."); + break; + } + return {result, invariant, cex}; +} + +void CHC::verificationTargetEncountered( + ASTNode const* const _errorNode, + VerificationTargetType _type, + smtutil::Expression const& _errorCondition +) +{ + if (!m_settings.targets.has(_type)) + return; + + if (!(m_currentContract || m_currentFunction)) + return; + + bool scopeIsFunction = m_currentFunction && !m_currentFunction->isConstructor(); + auto errorId = newErrorId(); + solAssert(m_verificationTargets.count(errorId) == 0, "Error ID is not unique!"); + m_verificationTargets.emplace(errorId, CHCVerificationTarget{{_type, _errorCondition, smtutil::Expression(true)}, errorId, _errorNode}); + if (scopeIsFunction) + m_functionTargetIds[m_currentFunction].push_back(errorId); + else + m_functionTargetIds[m_currentContract].push_back(errorId); + auto previousError = errorFlag().currentValue(); + errorFlag().increaseIndex(); + auto extendedErrorCondition = currentPathConditions() && _errorCondition; + + Predicate const* localBlock = m_currentFunction ? + createBlock(m_currentFunction, PredicateType::FunctionErrorBlock) : + createConstructorBlock(*m_currentContract, "local_error"); + + auto pred = predicate(*localBlock); + connectBlocks( + m_currentBlock, + pred, + extendedErrorCondition && errorFlag().currentValue() == errorId + ); + solAssert(m_errorDest, ""); + addRule(smtutil::Expression::implies(pred, predicate(*m_errorDest)), pred.name); + + m_context.addAssertion(errorFlag().currentValue() == previousError); +} + +std::pair CHC::targetDescription(CHCVerificationTarget const& _target) +{ + if (_target.type == VerificationTargetType::PopEmptyArray) + { + solAssert(dynamic_cast(_target.errorNode), ""); + return {"Empty array \"pop\"", 2529_error}; + } + else if (_target.type == VerificationTargetType::OutOfBounds) + { + solAssert(dynamic_cast(_target.errorNode), ""); + return {"Out of bounds access", 6368_error}; + } + else if ( + _target.type == VerificationTargetType::Underflow || + _target.type == VerificationTargetType::Overflow + ) + { + auto const* expr = dynamic_cast(_target.errorNode); + solAssert(expr, ""); + auto const* intType = dynamic_cast(expr->annotation().type); + if (!intType) + intType = TypeProvider::uint256(); + + if (_target.type == VerificationTargetType::Underflow) + return { + "Underflow (resulting value less than " + formatNumberReadable(intType->minValue()) + ")", + 3944_error + }; + + return { + "Overflow (resulting value larger than " + formatNumberReadable(intType->maxValue()) + ")", + 4984_error + }; + } + else if (_target.type == VerificationTargetType::DivByZero) + return {"Division by zero", 4281_error}; + else if (_target.type == VerificationTargetType::Assert) + return {"Assertion violation", 6328_error}; + else + solAssert(false); +} + +void CHC::checkVerificationTargets() +{ + // The verification conditions have been collected per function where they have been encountered (m_verificationTargets). + // Also, all possible contexts in which an external function can be called has been recorded (m_queryPlaceholders). + // Here we combine every context in which an external function can be called with all possible verification conditions + // in its call graph. Each such combination forms a unique verification target. + std::map> targetEntryPoints; + for (auto const& [function, placeholders]: m_queryPlaceholders) + { + auto functionTargets = transactionVerificationTargetsIds(function); + for (auto const& placeholder: placeholders) + for (unsigned id: functionTargets) + targetEntryPoints[id].push_back(placeholder); + } + + std::set checkedErrorIds; + for (auto const& [targetId, placeholders]: targetEntryPoints) + { + auto const& target = m_verificationTargets.at(targetId); + auto [errorType, errorReporterId] = targetDescription(target); + + checkAndReportTarget(target, placeholders, errorReporterId, errorType + " happens here.", errorType + " might happen here."); + checkedErrorIds.insert(target.errorId); + } + + auto toReport = m_unsafeTargets; + if (m_settings.showUnproved) + for (auto const& [node, targets]: m_unprovedTargets) + for (auto const& [target, info]: targets) + toReport[node].emplace(target, info); + + for (auto const& [node, targets]: toReport) + for (auto const& [target, info]: targets) + m_errorReporter.warning( + info.error, + info.location, + info.message + ); + + if (!m_settings.showUnproved && !m_unprovedTargets.empty()) + m_errorReporter.warning( + 5840_error, + {}, + "CHC: " + + std::to_string(m_unprovedTargets.size()) + + " verification condition(s) could not be proved." + + " Enable the model checker option \"show unproved\" to see all of them." + + " Consider choosing a specific contract to be verified in order to reduce the solving problems." + + " Consider increasing the timeout per query." + ); + + if (!m_settings.showProvedSafe && !m_safeTargets.empty()) + m_errorReporter.info( + 1391_error, + "CHC: " + + std::to_string(m_safeTargets.size()) + + " verification condition(s) proved safe!" + + " Enable the model checker option \"show proved safe\" to see all of them." + ); + else if (m_settings.showProvedSafe) + for (auto const& [node, targets]: m_safeTargets) + for (auto const& target: targets) + m_errorReporter.info( + 9576_error, + node->location(), + "CHC: " + + targetDescription(target).first + + " check is safe!" + ); + + if (!m_settings.invariants.invariants.empty()) + { + std::string msg; + for (auto pred: m_invariants | ranges::views::keys) + { + ASTNode const* node = pred->programNode(); + std::string what; + if (auto contract = dynamic_cast(node)) + what = contract->fullyQualifiedName(); + else + solAssert(false, ""); + + std::string invType; + if (pred->type() == PredicateType::Interface) + invType = "Contract invariant(s)"; + else if (pred->type() == PredicateType::NondetInterface) + invType = "Reentrancy property(ies)"; + else + solAssert(false, ""); + + msg += invType + " for " + what + ":\n"; + for (auto const& inv: m_invariants.at(pred)) + msg += inv + "\n"; + } + if (msg.find("") != std::string::npos) + { + std::set seenErrors; + msg += " = 0 -> no errors\n"; + for (auto const& [id, target]: m_verificationTargets) + if (!seenErrors.count(target.errorId)) + { + seenErrors.insert(target.errorId); + std::string loc = std::string(m_charStreamProvider.charStream(*target.errorNode->location().sourceName).text(target.errorNode->location())); + msg += " = " + std::to_string(target.errorId) + " -> " + ModelCheckerTargets::targetTypeToString.at(target.type) + " at " + loc + "\n"; + + } + } + if (!msg.empty()) + m_errorReporter.info(1180_error, msg); + } + + // There can be targets in internal functions that are not reachable from the external interface. + // These are safe by definition and are not even checked by the CHC engine, but this information + // must still be reported safe by the BMC engine. + std::set allErrorIds; + for (auto const& entry: m_functionTargetIds) + for (unsigned id: entry.second) + allErrorIds.insert(id); + + std::set unreachableErrorIds; + set_difference( + allErrorIds.begin(), + allErrorIds.end(), + checkedErrorIds.begin(), + checkedErrorIds.end(), + inserter(unreachableErrorIds, unreachableErrorIds.begin()) + ); + for (auto id: unreachableErrorIds) + m_safeTargets[m_verificationTargets.at(id).errorNode].insert(m_verificationTargets.at(id)); +} + +void CHC::checkAndReportTarget( + CHCVerificationTarget const& _target, + std::vector const& _placeholders, + ErrorId _errorReporterId, + std::string _satMsg, + std::string _unknownMsg +) +{ + if (m_unsafeTargets.count(_target.errorNode) && m_unsafeTargets.at(_target.errorNode).count(_target.type)) + return; + + createErrorBlock(); + for (auto const& placeholder: _placeholders) + connectBlocks( + placeholder.fromPredicate, + error(), + placeholder.constraints && placeholder.errorExpression == _target.errorId + ); + auto const& location = _target.errorNode->location(); + auto [result, invariant, model] = query(error(), location); + if (result == CheckResult::UNSATISFIABLE) + { + m_safeTargets[_target.errorNode].insert(_target); + std::set predicates; + for (auto const* pred: m_interfaces | ranges::views::values) + predicates.insert(pred); + for (auto const* pred: m_nondetInterfaces | ranges::views::values) + predicates.insert(pred); + std::map> invariants = collectInvariants(invariant, predicates, m_settings.invariants); + for (auto pred: invariants | ranges::views::keys) + m_invariants[pred] += std::move(invariants.at(pred)); + } + else if (result == CheckResult::SATISFIABLE) + { + solAssert(!_satMsg.empty(), ""); + auto cex = generateCounterexample(model, error().name); + if (cex) + m_unsafeTargets[_target.errorNode][_target.type] = { + _errorReporterId, + location, + "CHC: " + _satMsg + "\nCounterexample:\n" + *cex + }; + else + m_unsafeTargets[_target.errorNode][_target.type] = { + _errorReporterId, + location, + "CHC: " + _satMsg + }; + } + else if (!_unknownMsg.empty()) + m_unprovedTargets[_target.errorNode][_target.type] = { + _errorReporterId, + location, + "CHC: " + _unknownMsg + }; +} + +/** +The counterexample DAG has the following properties: +1) The root node represents the reachable error predicate. +2) The root node has 1 or 2 children: + - One of them is the summary of the function that was called and led to that node. + If this is the only child, this function must be the constructor. + - If it has 2 children, the function is not the constructor and the other child is the interface node, + that is, it represents the state of the contract before the function described above was called. +3) Interface nodes also have property 2. + +We run a BFS on the DAG from the root node collecting the reachable function summaries from the given node. +When a function summary is seen, the search continues with that summary as the new root for its subgraph. +The result of the search is a callgraph containing: +- Functions calls needed to reach the root node, that is, transaction entry points. +- Functions called by other functions (internal calls or external calls/internal transactions). +The BFS visit order and the shape of the DAG described in the previous paragraph guarantee that the order of +the function summaries in the callgraph of the error node is the reverse transaction trace. + +The first function summary seen contains the values for the state, input and output variables at the +error point. +*/ +std::optional CHC::generateCounterexample(CHCSolverInterface::CexGraph const& _graph, std::string const& _root) +{ + std::optional rootId; + for (auto const& [id, node]: _graph.nodes) + if (node.name == _root) + { + rootId = id; + break; + } + if (!rootId) + return {}; + + std::vector path; + std::string localState; + + auto callGraph = summaryCalls(_graph, *rootId); + + auto nodePred = [&](auto _node) { return Predicate::predicate(_graph.nodes.at(_node).name); }; + auto nodeArgs = [&](auto _node) { return _graph.nodes.at(_node).arguments; }; + + bool first = true; + for (auto summaryId: callGraph.at(*rootId)) + { + CHCSolverInterface::CexNode const& summaryNode = _graph.nodes.at(summaryId); + Predicate const* summaryPredicate = Predicate::predicate(summaryNode.name); + auto const& summaryArgs = summaryNode.arguments; + + if (!summaryPredicate->programVariable()) + { + auto stateVars = summaryPredicate->stateVariables(); + solAssert(stateVars.has_value(), ""); + auto stateValues = summaryPredicate->summaryStateValues(summaryArgs); + solAssert(stateValues.size() == stateVars->size(), ""); + + if (first) + { + first = false; + /// Generate counterexample message local to the failed target. + localState = formatVariableModel(*stateVars, stateValues, ", ") + "\n"; + + if (auto calledFun = summaryPredicate->programFunction()) + { + auto inValues = summaryPredicate->summaryPostInputValues(summaryArgs); + auto const& inParams = calledFun->parameters(); + if (auto inStr = formatVariableModel(inParams, inValues, "\n"); !inStr.empty()) + localState += inStr + "\n"; + auto outValues = summaryPredicate->summaryPostOutputValues(summaryArgs); + auto const& outParams = calledFun->returnParameters(); + if (auto outStr = formatVariableModel(outParams, outValues, "\n"); !outStr.empty()) + localState += outStr + "\n"; + + std::optional localErrorId; + solidity::util::BreadthFirstSearch bfs{{summaryId}}; + bfs.run([&](auto _nodeId, auto&& _addChild) { + auto const& children = _graph.edges.at(_nodeId); + if ( + children.size() == 1 && + nodePred(children.front())->isFunctionErrorBlock() + ) + { + localErrorId = children.front(); + bfs.abort(); + } + ranges::for_each(children, _addChild); + }); + + if (localErrorId.has_value()) + { + auto const* localError = nodePred(*localErrorId); + solAssert(localError && localError->isFunctionErrorBlock(), ""); + auto const [localValues, localVars] = localError->localVariableValues(nodeArgs(*localErrorId)); + if (auto localStr = formatVariableModel(localVars, localValues, "\n"); !localStr.empty()) + localState += localStr + "\n"; + } + } + } + else + { + auto modelMsg = formatVariableModel(*stateVars, stateValues, ", "); + /// We report the state after every tx in the trace except for the last, which is reported + /// first in the code above. + if (!modelMsg.empty()) + path.emplace_back("State: " + modelMsg); + } + } + std::string txCex = summaryPredicate->formatSummaryCall(summaryArgs, m_charStreamProvider); + + std::list calls; + auto dfs = [&](unsigned parent, unsigned node, unsigned depth, auto&& _dfs) -> void { + auto pred = nodePred(node); + auto parentPred = nodePred(parent); + solAssert(pred && pred->isSummary(), ""); + solAssert(parentPred && parentPred->isSummary(), ""); + auto callTraceSize = calls.size(); + if (!pred->isConstructorSummary()) + for (unsigned v: callGraph[node]) + _dfs(node, v, depth + 1, _dfs); + + bool appendTxVars = pred->isConstructorSummary() || pred->isFunctionSummary() || pred->isExternalCallUntrusted(); + + calls.push_front(std::string(depth * 4, ' ') + pred->formatSummaryCall(nodeArgs(node), m_charStreamProvider, appendTxVars)); + if (pred->isInternalCall()) + calls.front() += " -- internal call"; + else if (pred->isExternalCallTrusted()) + calls.front() += " -- trusted external call"; + else if (pred->isExternalCallUntrusted()) + { + calls.front() += " -- untrusted external call"; + if (calls.size() > callTraceSize + 1) + calls.front() += ", synthesized as:"; + } + else if (pred->programVariable()) + { + calls.front() += "-- action on external contract in state variable \"" + pred->programVariable()->name() + "\""; + if (calls.size() > callTraceSize + 1) + calls.front() += ", synthesized as:"; + } + else if (pred->isFunctionSummary() && parentPred->isExternalCallUntrusted()) + calls.front() += " -- reentrant call"; + }; + dfs(summaryId, summaryId, 0, dfs); + path.emplace_back(boost::algorithm::join(calls, "\n")); + } + + return localState + "\nTransaction trace:\n" + boost::algorithm::join(path | ranges::views::reverse, "\n"); +} + +std::map> CHC::summaryCalls(CHCSolverInterface::CexGraph const& _graph, unsigned _root) +{ + std::map> calls; + + auto compare = [&](unsigned _a, unsigned _b) { + auto extract = [&](std::string const& _s) { + // We want to sort sibling predicates in the counterexample graph by their unique predicate id. + // For most predicates, this actually doesn't matter. + // The cases where this matters are internal and external function calls which have the form: + // summary__ + // nondet_call__ + // Those have the extra unique numbers based on the traversal order, and are necessary + // to infer the call order so that's shown property in the counterexample trace. + // Predicates that do not have a CALLID have a predicate id at the end of , + // so the assertion below should still hold. + auto beg = _s.data(); + while (beg != _s.data() + _s.size() && !isDigit(*beg)) ++beg; + auto end = beg; + while (end != _s.data() + _s.size() && isDigit(*end)) ++end; + + solAssert(beg != end, "Expected to find numerical call or predicate id."); + + int result; + auto [p, ec] = std::from_chars(beg, end, result); + solAssert(ec == std::errc(), "Id should be a number."); + + return result; + }; + return extract(_graph.nodes.at(_a).name) > extract(_graph.nodes.at(_b).name); + }; + + std::queue> q; + q.push({_root, _root}); + while (!q.empty()) + { + auto [node, root] = q.front(); + q.pop(); + + Predicate const* nodePred = Predicate::predicate(_graph.nodes.at(node).name); + Predicate const* rootPred = Predicate::predicate(_graph.nodes.at(root).name); + if (nodePred->isSummary() && ( + _root == root || + nodePred->isInternalCall() || + nodePred->isExternalCallTrusted() || + nodePred->isExternalCallUntrusted() || + rootPred->isExternalCallUntrusted() || + rootPred->programVariable() + )) + { + calls[root].push_back(node); + root = node; + } + auto const& edges = _graph.edges.at(node); + for (unsigned v: std::set(begin(edges), end(edges), compare)) + q.push({v, root}); + } + + return calls; +} + +std::string CHC::cex2dot(CHCSolverInterface::CexGraph const& _cex) +{ + std::string dot = "digraph {\n"; + + auto pred = [&](CHCSolverInterface::CexNode const& _node) { + std::vector args = applyMap( + _node.arguments, + [&](auto const& arg) { return arg.name; } + ); + return "\"" + _node.name + "(" + boost::algorithm::join(args, ", ") + ")\""; + }; + + for (auto const& [u, vs]: _cex.edges) + for (auto v: vs) + dot += pred(_cex.nodes.at(v)) + " -> " + pred(_cex.nodes.at(u)) + "\n"; + + dot += "}"; + return dot; +} + +std::string CHC::uniquePrefix() +{ + return std::to_string(m_blockCounter++); +} + +std::string CHC::contractSuffix(ContractDefinition const& _contract) +{ + return _contract.name() + "_" + std::to_string(_contract.id()); +} + +unsigned CHC::newErrorId() +{ + unsigned errorId = m_context.newUniqueId(); + // We need to make sure the error id is not zero, + // because error id zero actually means no error in the CHC encoding. + if (errorId == 0) + errorId = m_context.newUniqueId(); + return errorId; +} + +SymbolicIntVariable& CHC::errorFlag() +{ + return state().errorFlag(); +} + +void CHC::newTxConstraints(Expression const* _value) +{ + auto txOrigin = state().txMember("tx.origin"); + state().newTx(); + // set the transaction sender as this contract + m_context.addAssertion(state().txMember("msg.sender") == state().thisAddress()); + // set the origin to be the current transaction origin + m_context.addAssertion(state().txMember("tx.origin") == txOrigin); + + if (_value) + // set the msg value + m_context.addAssertion(state().txMember("msg.value") == expr(*_value)); +} + +frontend::Expression const* CHC::valueOption(FunctionCallOptions const* _options) +{ + if (_options) + for (auto&& [i, name]: _options->names() | ranges::views::enumerate) + if (name && *name == "value") + return _options->options().at(i).get(); + return nullptr; +} + +void CHC::decreaseBalanceFromOptionsValue(Expression const& _value) +{ + state().addBalance(state().thisAddress(), 0 - expr(_value)); +} + +std::vector CHC::commonStateExpressions(smtutil::Expression const& error, smtutil::Expression const& address) +{ + if (state().hasBytesConcatFunction()) + return {error, address, state().abi(), state().bytesConcat(), state().crypto()}; + return {error, address, state().abi(), state().crypto()}; +} diff --git a/compiler/libsolidity/formal/CHC.h b/compiler/libsolidity/formal/CHC.h new file mode 100644 index 00000000..be150baf --- /dev/null +++ b/compiler/libsolidity/formal/CHC.h @@ -0,0 +1,477 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +/** + * Model checker based on Constrained Horn Clauses. + * + * A Solidity contract's CFG is encoded into a system of Horn clauses where + * each block has a predicate and edges are rules. + * + * The entry block is the constructor which has no in-edges. + * The constructor has one out-edge to an artificial block named _Interface_ + * which has in/out-edges from/to all public functions. + * + * Loop invariants for Interface -> Interface' are state invariants. + */ + +#pragma once + +#include +#include +#include + +#include + +#include + +#include +#include + +#include + +#include +#include +#include + +namespace solidity::frontend +{ + +class CHC: public SMTEncoder +{ +public: + CHC( + smt::EncodingContext& _context, + langutil::UniqueErrorReporter& _errorReporter, + langutil::UniqueErrorReporter& _unsupportedErrorReporter, + std::map const& _smtlib2Responses, + ReadCallback::Callback const& _smtCallback, + ModelCheckerSettings _settings, + langutil::CharStreamProvider const& _charStreamProvider + ); + + void analyze(SourceUnit const& _sources); + + struct CHCVerificationTarget: VerificationTarget + { + unsigned const errorId; + ASTNode const* const errorNode; + + friend bool operator<(CHCVerificationTarget const& _a, CHCVerificationTarget const& _b) + { + return _a.errorId < _b.errorId; + } + }; + + struct ReportTargetInfo + { + langutil::ErrorId error; + langutil::SourceLocation location; + std::string message; + }; + + std::map, smt::EncodingContext::IdCompare> const& safeTargets() const { return m_safeTargets; } + std::map, smt::EncodingContext::IdCompare> const& unsafeTargets() const { return m_unsafeTargets; } + + /// This is used if the Horn solver is not directly linked into this binary. + /// @returns a list of inputs to the Horn solver that were not part of the argument to + /// the constructor. + std::vector unhandledQueries() const; + + enum class CHCNatspecOption + { + AbstractFunctionNondet + }; + +private: + /// Visitor functions. + //@{ + bool visit(ContractDefinition const& _node) override; + void endVisit(ContractDefinition const& _node) override; + bool visit(FunctionDefinition const& _node) override; + void endVisit(FunctionDefinition const& _node) override; + bool visit(Block const& _block) override; + void endVisit(Block const& _block) override; + bool visit(IfStatement const& _node) override; + bool visit(WhileStatement const&) override; + bool visit(ForStatement const&) override; + void endVisit(ForStatement const&) override; + void endVisit(FunctionCall const& _node) override; + void endVisit(BinaryOperation const& _op) override; + void endVisit(UnaryOperation const& _op) override; + void endVisit(Break const& _node) override; + void endVisit(Continue const& _node) override; + void endVisit(IndexRangeAccess const& _node) override; + void endVisit(Return const& _node) override; + bool visit(TryCatchClause const&) override; + void endVisit(TryCatchClause const&) override; + bool visit(TryStatement const& _node) override; + + void pushInlineFrame(CallableDeclaration const& _callable) override; + void popInlineFrame(CallableDeclaration const& _callable) override; + + void visitAssert(FunctionCall const& _funCall); + void visitPublicGetter(FunctionCall const& _funCall) override; + void visitAddMulMod(FunctionCall const& _funCall) override; + void visitDeployment(FunctionCall const& _funCall); + void internalFunctionCall(FunctionCall const& _funCall); + void internalFunctionCall( + FunctionDefinition const* _funDef, + std::optional _boundArgumentCall, + FunctionType const* _funType, + std::vector const& _arguments, + smtutil::Expression _contractAddressValue + ); + void externalFunctionCall(FunctionCall const& _funCall); + void externalFunctionCallToTrustedCode(FunctionCall const& _funCall); + void addNondetCalls(ContractDefinition const& _contract); + void nondetCall(ContractDefinition const& _contract, VariableDeclaration const& _var); + void unknownFunctionCall(FunctionCall const& _funCall); + void makeArrayPopVerificationTarget(FunctionCall const& _arrayPop) override; + void makeOutOfBoundsVerificationTarget(IndexAccess const& _access) override; + /// Creates underflow/overflow verification targets. + std::pair arithmeticOperation( + Token _op, + smtutil::Expression const& _left, + smtutil::Expression const& _right, + Type const* _commonType, + Expression const& _expression + ) override; + //@} + + /// Helpers. + //@{ + void resetSourceAnalysis(); + void resetContractAnalysis(); + void eraseKnowledge(); + void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override; + void setCurrentBlock(Predicate const& _block); + std::set transactionVerificationTargetsIds(ASTNode const* _txRoot); + bool usesStaticCall(FunctionDefinition const* _funDef, FunctionType const* _funType); + bool usesStaticCall(FunctionCall const& _funCall); + //@} + + /// SMT Natspec and abstraction helpers. + //@{ + /// @returns a CHCNatspecOption enum if _option is a valid SMTChecker Natspec value + /// or nullopt otherwise. + static std::optional natspecOptionFromString(std::string const& _option); + /// @returns which SMTChecker options are enabled by @a _function's Natspec via + /// `@custom:smtchecker ${JOB} failed on ${BRANCH}. Please see build ${BUILD_NUM} for details." +} diff --git a/compiler/scripts/ci/notification/templates/build_release.json b/compiler/scripts/ci/notification/templates/build_release.json new file mode 100644 index 00000000..b7c538f8 --- /dev/null +++ b/compiler/scripts/ci/notification/templates/build_release.json @@ -0,0 +1,6 @@ +{ + "msgtype": "m.text", + "body": " 📦 Release binaries for version ${TAG} are ready and attached as artifacts to ${BUILD_URL}. Please make sure the whole workflow succeeded before using them.", + "format": "org.matrix.custom.html", + "formatted_body": " 📦 Release binaries for version ${TAG} are ready and attached as artifacts to build ${BUILD_NUM}. Please make sure the whole workflow succeeded before using them." +} diff --git a/compiler/scripts/ci/notification/templates/build_success.json b/compiler/scripts/ci/notification/templates/build_success.json new file mode 100644 index 00000000..170360fb --- /dev/null +++ b/compiler/scripts/ci/notification/templates/build_success.json @@ -0,0 +1,6 @@ +{ + "msgtype": "m.text", + "body": " ✅ [${WORKFLOW_NAME}] Job ${JOB} succeeded on ${BRANCH}. Please see ${BUILD_URL} for details.", + "format": "org.matrix.custom.html", + "formatted_body":" ✅ [${WORKFLOW_NAME}] Job ${JOB} succeeded on ${BRANCH}. Please see build ${BUILD_NUM} for details." +} diff --git a/compiler/scripts/common.sh b/compiler/scripts/common.sh index 98f63ecb..d149253b 100644 --- a/compiler/scripts/common.sh +++ b/compiler/scripts/common.sh @@ -26,18 +26,18 @@ set -e # changes directory. The paths returned by `caller` are relative to it. _initial_work_dir=$(pwd) -if [ "$CIRCLECI" ] +if [[ -n ${CIRCLECI:-} ]] then export TERM="${TERM:-xterm}" - function printTask { echo "$(tput bold)$(tput setaf 2)$1$(tput setaf 7)"; } - function printError { >&2 echo "$(tput setaf 1)$1$(tput setaf 7)"; } - function printWarning { >&2 echo "$(tput setaf 11)$1$(tput setaf 7)"; } - function printLog { echo "$(tput setaf 3)$1$(tput setaf 7)"; } + function printTask { echo -e "$(tput bold)$(tput setaf 2)$1$(tput setaf 7)"; } + function printError { >&2 echo -e "$(tput setaf 1)$1$(tput setaf 7)"; } + function printWarning { >&2 echo -e "$(tput setaf 11)$1$(tput setaf 7)"; } + function printLog { echo -e "$(tput setaf 3)$1$(tput setaf 7)"; } else - function printTask { echo "$(tput bold)$(tput setaf 2)$1$(tput sgr0)"; } - function printError { >&2 echo "$(tput setaf 1)$1$(tput sgr0)"; } - function printWarning { >&2 echo "$(tput setaf 11)$1$(tput sgr0)"; } - function printLog { echo "$(tput setaf 3)$1$(tput sgr0)"; } + function printTask { echo -e "$(tput bold)$(tput setaf 2)$1$(tput sgr0)"; } + function printError { >&2 echo -e "$(tput setaf 1)$1$(tput sgr0)"; } + function printWarning { >&2 echo -e "$(tput setaf 11)$1$(tput sgr0)"; } + function printLog { echo -e "$(tput setaf 3)$1$(tput sgr0)"; } fi function checkDputEntries @@ -75,9 +75,9 @@ function printStackTrace # `caller` returns something that could already be printed as a stacktrace but we can make # it more readable by rearranging the components. # NOTE: This assumes that paths do not contain spaces. - lineNumber=$(caller "$frame" | cut --delimiter " " --field 1) - function=$(caller "$frame" | cut --delimiter " " --field 2) - file=$(caller "$frame" | cut --delimiter " " --field 3) + lineNumber=$(caller "$frame" | cut -d " " -f 1) + function=$(caller "$frame" | cut -d " " -f 2) + file=$(caller "$frame" | cut -d " " -f 3) # Paths in the output from `caller` can be relative or absolute (depends on how the path # with which the script was invoked) and if they're relative, they're not necessarily @@ -121,7 +121,7 @@ function assertFail function msg_on_error { - local error_message + local error_message="" local no_stdout=false local no_stderr=false @@ -193,16 +193,42 @@ function msg_on_error fi } + function diff_values { (( $# >= 2 )) || fail "diff_values requires at least 2 arguments." local value1="$1" local value2="$2" - shift - shift + shift 2 - diff --unified=0 <(echo "$value1") <(echo "$value2") "$@" + if ! diff --unified=0 <(echo "$value1") <(echo "$value2") "$@" + then + if [[ -n ${DIFFVIEW:-} ]] + then + # Use user supplied diff view binary + "$DIFFVIEW" <(echo "$value1") <(echo "$value2") + fi + return 1 + fi +} + +function diff_files +{ + (( $# >= 2 )) || fail "diff_files requires at least 2 arguments." + + local file1="$1" + local file2="$2" + + if ! diff "${file1}" "${file2}" + then + if [[ -n ${DIFFVIEW:-} ]] + then + # Use user supplied diff view binary + "$DIFFVIEW" "${file1}" "${file2}" + fi + return 1 + fi } function safe_kill @@ -277,3 +303,24 @@ function split_on_empty_lines_into_numbered_files awk -v RS= "{print > (\"${path_prefix}_\"NR \"${path_suffix}\")}" } + +function command_available +{ + local program="$1" + local parameters=( "${@:2}" ) + if ! "${program}" "${parameters[@]}" > /dev/null 2>&1 + then + fail "'${program}' not found or not executed successfully with parameter(s) '${parameters[*]}'. aborting." + fi +} + +function gnu_grep +{ + if [[ "$OSTYPE" == "darwin"* ]]; then + command_available ggrep --version + ggrep "$@" + else + command_available grep --version + grep "$@" + fi +} diff --git a/compiler/scripts/common/cmdline_helpers.py b/compiler/scripts/common/cmdline_helpers.py new file mode 100644 index 00000000..ede9f938 --- /dev/null +++ b/compiler/scripts/common/cmdline_helpers.py @@ -0,0 +1,67 @@ +import os +import subprocess +from pathlib import Path +from shutil import rmtree +from tempfile import mkdtemp +from textwrap import dedent +from typing import List +from typing import Optional + +from bytecodecompare.prepare_report import FileReport +from bytecodecompare.prepare_report import parse_cli_output + + +DEFAULT_PREAMBLE = dedent(""" + // SPDX-License-Identifier: UNLICENSED + pragma solidity >=0.0; +""") + + +def inside_temporary_dir(prefix): + """ + Creates a temporary directory, enters the directory and executes the function inside it. + Restores the previous working directory after executing the function. + """ + def tmp_dir_decorator(fn): + previous_dir = os.getcwd() + def f(*args, **kwargs): + try: + tmp_dir = mkdtemp(prefix=prefix) + os.chdir(tmp_dir) + result = fn(*args, **kwargs) + rmtree(tmp_dir) + return result + finally: + os.chdir(previous_dir) + return f + return tmp_dir_decorator + + +def solc_bin_report(solc_binary: str, input_files: List[Path], via_ir: bool) -> FileReport: + """ + Runs the solidity compiler binary + """ + + output = subprocess.check_output( + [solc_binary, '--bin'] + + input_files + + (['--via-ir'] if via_ir else []), + encoding='utf8', + ) + return parse_cli_output('', output, 0) + + +def save_bytecode(bytecode_path: Path, reports: FileReport, contract: Optional[str] = None): + with open(bytecode_path, 'w', encoding='utf8') as f: + for report in reports.contract_reports: + if contract is None or report.contract_name == contract: + bytecode = report.bytecode if report.bytecode is not None else '' + f.write(f'{report.contract_name}: {bytecode}\n') + + +def add_preamble(source_path: Path, preamble: str = DEFAULT_PREAMBLE): + for source in source_path.glob('**/*.sol'): + with open(source, 'r+', encoding='utf8') as f: + content = f.read() + f.seek(0, 0) + f.write(preamble + content) diff --git a/compiler/scripts/common/git_helpers.py b/compiler/scripts/common/git_helpers.py index bb5b1f61..f43a8619 100644 --- a/compiler/scripts/common/git_helpers.py +++ b/compiler/scripts/common/git_helpers.py @@ -1,4 +1,6 @@ import subprocess +from pathlib import Path +from shutil import which def run_git_command(command): @@ -17,3 +19,21 @@ def git_current_branch(): def git_commit_hash(ref: str = 'HEAD'): return run_git_command(['git', 'rev-parse', '--verify', ref]) + + +def git_diff(file_a: Path, file_b: Path) -> int: + if which('git') is None: + raise RuntimeError('git not found.') + + return subprocess.run([ + 'git', + 'diff', + '--color', + '--word-diff=plain', + '--word-diff-regex=.', + '--ignore-space-change', + '--ignore-blank-lines', + '--exit-code', + file_a, + file_b, + ], encoding='utf8', check=False).returncode diff --git a/compiler/scripts/common_cmdline.sh b/compiler/scripts/common_cmdline.sh index d364f644..5e32a76d 100644 --- a/compiler/scripts/common_cmdline.sh +++ b/compiler/scripts/common_cmdline.sh @@ -103,3 +103,69 @@ function compileFull false fi } + +function singleContractOutputViaStandardJSON +{ + (( $# == 4 )) || assertFail + local language="$1" + local selected_output="$2" + local extra_settings="$3" + local input_file="$4" + [[ $selected_output != "*" ]] || assertFail + + json_output=$( + "$SOLC" --standard-json --allow-paths "$(basename "$input_file")" - <= 9.0.0), python3 Standards-Version: 3.9.6 Homepage: https://github.com/Z3Prover/z3 -Vcs-Git: git://github.com/Z3Prover/z3.git +Vcs-Git: https://github.com/Z3Prover/z3.git Vcs-Browser: https://github.com/Z3Prover/z3 Package: z3-static diff --git a/compiler/scripts/docker/buildpack-deps/Dockerfile.emscripten b/compiler/scripts/docker/buildpack-deps/Dockerfile.emscripten index ba36541e..c29fc699 100644 --- a/compiler/scripts/docker/buildpack-deps/Dockerfile.emscripten +++ b/compiler/scripts/docker/buildpack-deps/Dockerfile.emscripten @@ -33,16 +33,16 @@ # Using $(em-config CACHE)/sysroot/usr seems to work, though, and still has cmake find the # dependencies automatically. FROM emscripten/emsdk:3.1.19 AS base -LABEL version="13" +LABEL version="16" ADD emscripten.jam /usr/src RUN set -ex && \ \ apt-get update && \ - apt-get install lz4 --no-install-recommends && \ + apt-get install lz4 sudo --no-install-recommends && \ \ cd /usr/src && \ - git clone https://github.com/Z3Prover/z3.git -b z3-4.11.0 --depth 1 && \ + git clone https://github.com/Z3Prover/z3.git -b z3-4.12.1 --depth 1 && \ cd z3 && \ mkdir build && \ cd build && \ diff --git a/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu.clang.ossfuzz b/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu.clang.ossfuzz new file mode 100644 index 00000000..fd98de73 --- /dev/null +++ b/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu.clang.ossfuzz @@ -0,0 +1,143 @@ +# vim:syntax=dockerfile +#------------------------------------------------------------------------------ +# Dockerfile for building and testing Solidity Compiler on CI +# Target: Ubuntu ossfuzz Clang variant +# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps +# +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2016-2021 solidity contributors. +#------------------------------------------------------------------------------ +FROM gcr.io/oss-fuzz-base/base-clang:latest as base +LABEL version="4" + +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt-get update; \ + apt-get -qqy install --no-install-recommends \ + automake libtool bison texinfo \ + build-essential sudo \ + software-properties-common \ + ninja-build git wget \ + libbz2-dev zlib1g-dev git curl uuid-dev \ + pkg-config openjdk-8-jdk liblzma-dev unzip mlton m4 jq; \ + apt-get install -qy python3-pip; + +# Install cmake 3.21.2 (minimum requirement is cmake 3.10) +RUN wget https://github.com/Kitware/CMake/releases/download/v3.21.2/cmake-3.21.2-Linux-x86_64.sh; \ + test "$(sha256sum cmake-3.21.2-Linux-x86_64.sh)" = "3310362c6fe4d4b2dc00823835f3d4a7171bbd73deb7d059738494761f1c908c cmake-3.21.2-Linux-x86_64.sh"; \ + chmod +x cmake-3.21.2-Linux-x86_64.sh; \ + ./cmake-3.21.2-Linux-x86_64.sh --skip-license --prefix="/usr" + +FROM base AS libraries + +# Boost +RUN set -ex; \ + cd /usr/src; \ + wget -q 'https://boostorg.jfrog.io/artifactory/main/release/1.74.0/source/boost_1_74_0.tar.bz2' -O boost.tar.bz2; \ + test "$(sha256sum boost.tar.bz2)" = "83bfc1507731a0906e387fc28b7ef5417d591429e51e788417fe9ff025e116b1 boost.tar.bz2" && \ + tar -xf boost.tar.bz2; \ + rm boost.tar.bz2; \ + cd boost_1_74_0; \ + CXXFLAGS="-stdlib=libc++ -pthread" LDFLAGS="-stdlib=libc++" ./bootstrap.sh --with-toolset=clang --prefix=/usr; \ + ./b2 toolset=clang cxxflags="-stdlib=libc++ -pthread" linkflags="-stdlib=libc++ -pthread" headers; \ + ./b2 toolset=clang cxxflags="-stdlib=libc++ -pthread" linkflags="-stdlib=libc++ -pthread" \ + link=static variant=release runtime-link=static \ + system filesystem unit_test_framework program_options \ + install -j $(($(nproc)/2)); \ + rm -rf /usr/src/boost_1_74_0 + +# Z3 +RUN set -ex; \ + git clone --depth 1 -b z3-4.12.1 https://github.com/Z3Prover/z3.git \ + /usr/src/z3; \ + cd /usr/src/z3; \ + mkdir build; \ + cd build; \ + LDFLAGS=$CXXFLAGS cmake -DZ3_BUILD_LIBZ3_SHARED=OFF -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_BUILD_TYPE=Release ..; \ + make libz3 -j; \ + make install; \ + rm -rf /usr/src/z3 + +# OSSFUZZ: libprotobuf-mutator +RUN set -ex; \ + git clone https://github.com/google/libprotobuf-mutator.git \ + /usr/src/libprotobuf-mutator; \ + cd /usr/src/libprotobuf-mutator; \ + git checkout 3521f47a2828da9ace403e4ecc4aece1a84feb36; \ + mkdir build; \ + cd build; \ + cmake .. -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON \ + -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="/usr"; \ + ninja; \ + cp -vpr external.protobuf/bin/* /usr/bin/; \ + cp -vpr external.protobuf/include/* /usr/include/; \ + cp -vpr external.protobuf/lib/* /usr/lib/; \ + ninja install/strip; \ + rm -rf /usr/src/libprotobuf-mutator + +# EVMONE +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.11.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ + cd evmone; \ + mkdir build; \ + cd build; \ + CXX=clang++ cmake -G Ninja -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX="/usr" ..; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/evmone + +# gmp +RUN set -ex; \ + # Replace system installed libgmp static library + # with sanitized version. Do not perform apt + # remove because it removes mlton as well that + # we need for building libabicoder + cd /usr/src/; \ + git clone --depth 1 --branch gmp-6.2.1 https://github.com/gmp-mirror/gmp-6.2 gmp/; \ + rm -r gmp/.git/; \ + test \ + "$(tar --create gmp/ --sort=name --mtime=1970-01-01Z --owner=0 --group=0 --numeric-owner | sha256sum)" = \ + "d606ff6a4ce98692f9920031e85ea8fcf4a65ce1426f6f0048b8794aefed174b -"; \ + # NOTE: This removes also libgmp.so, which git depends on + rm -f /usr/lib/x86_64-linux-gnu/libgmp.*; \ + rm -f /usr/include/x86_64-linux-gnu/gmp.h; \ + cd gmp/; \ + autoreconf -i; \ + ./configure --prefix=/usr --enable-static=yes --enable-maintainer-mode; \ + make -j; \ + make check; \ + make install; \ + rm -rf /usr/src/gmp/ + +# libabicoder +RUN set -ex; \ + cd /usr/src; \ + git clone https://github.com/ekpyron/Yul-Isabelle; \ + cd Yul-Isabelle; \ + cd libabicoder; \ + CXX=clang++ CXXFLAGS="-stdlib=libc++ -pthread" make; \ + cp libabicoder.a /usr/lib; \ + cp abicoder.hpp /usr/include; \ + rm -rf /usr/src/Yul-Isabelle + +FROM base +COPY --from=libraries /usr/lib /usr/lib +COPY --from=libraries /usr/bin /usr/bin +COPY --from=libraries /usr/include /usr/include diff --git a/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu1604.clang.ossfuzz b/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu1604.clang.ossfuzz deleted file mode 100644 index d5481604..00000000 --- a/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu1604.clang.ossfuzz +++ /dev/null @@ -1,138 +0,0 @@ -# vim:syntax=dockerfile -#------------------------------------------------------------------------------ -# Dockerfile for building and testing Solidity Compiler on CI -# Target: Ubuntu 16.04 (Xenial Xerus) ossfuzz Clang variant -# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps -# -# This file is part of solidity. -# -# solidity is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# solidity is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with solidity. If not, see -# -# (c) 2016-2021 solidity contributors. -#------------------------------------------------------------------------------ -FROM gcr.io/oss-fuzz-base/base-clang:latest as base -LABEL version="19" - -ARG DEBIAN_FRONTEND=noninteractive - -RUN apt-get update; \ - apt-get -qqy install --no-install-recommends \ - build-essential \ - software-properties-common \ - ninja-build git wget \ - libbz2-dev zlib1g-dev git curl uuid-dev \ - pkg-config openjdk-8-jdk liblzma-dev unzip mlton m4 jq; \ - apt-get install -qy python3-pip; - -# Install cmake 3.21.2 (minimum requirement is cmake 3.10) -RUN wget https://github.com/Kitware/CMake/releases/download/v3.21.2/cmake-3.21.2-Linux-x86_64.sh; \ - test "$(sha256sum cmake-3.21.2-Linux-x86_64.sh)" = "3310362c6fe4d4b2dc00823835f3d4a7171bbd73deb7d059738494761f1c908c cmake-3.21.2-Linux-x86_64.sh"; \ - chmod +x cmake-3.21.2-Linux-x86_64.sh; \ - ./cmake-3.21.2-Linux-x86_64.sh --skip-license --prefix="/usr" - -FROM base AS libraries - -# Boost -RUN set -ex; \ - cd /usr/src; \ - wget -q 'https://boostorg.jfrog.io/artifactory/main/release/1.73.0/source/boost_1_73_0.tar.bz2' -O boost.tar.bz2; \ - test "$(sha256sum boost.tar.bz2)" = "4eb3b8d442b426dc35346235c8733b5ae35ba431690e38c6a8263dce9fcbb402 boost.tar.bz2"; \ - tar -xf boost.tar.bz2; \ - rm boost.tar.bz2; \ - cd boost_1_73_0; \ - CXXFLAGS="-stdlib=libc++ -pthread" LDFLAGS="-stdlib=libc++" ./bootstrap.sh --with-toolset=clang --prefix=/usr; \ - ./b2 toolset=clang cxxflags="-stdlib=libc++ -pthread" linkflags="-stdlib=libc++ -pthread" headers; \ - ./b2 toolset=clang cxxflags="-stdlib=libc++ -pthread" linkflags="-stdlib=libc++ -pthread" \ - link=static variant=release runtime-link=static \ - system filesystem unit_test_framework program_options \ - install -j $(($(nproc)/2)); \ - rm -rf /usr/src/boost_1_73_0 - -# Z3 -RUN set -ex; \ - git clone --depth 1 -b z3-4.11.0 https://github.com/Z3Prover/z3.git \ - /usr/src/z3; \ - cd /usr/src/z3; \ - mkdir build; \ - cd build; \ - LDFLAGS=$CXXFLAGS cmake -DZ3_BUILD_LIBZ3_SHARED=OFF -DCMAKE_INSTALL_PREFIX=/usr \ - -DCMAKE_BUILD_TYPE=Release ..; \ - make libz3 -j; \ - make install; \ - rm -rf /usr/src/z3 - -# OSSFUZZ: libprotobuf-mutator -RUN set -ex; \ - git clone https://github.com/google/libprotobuf-mutator.git \ - /usr/src/libprotobuf-mutator; \ - cd /usr/src/libprotobuf-mutator; \ - git checkout 3521f47a2828da9ace403e4ecc4aece1a84feb36; \ - mkdir build; \ - cd build; \ - cmake .. -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON \ - -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX="/usr"; \ - ninja; \ - cp -vpr external.protobuf/bin/* /usr/bin/; \ - cp -vpr external.protobuf/include/* /usr/include/; \ - cp -vpr external.protobuf/lib/* /usr/lib/; \ - ninja install/strip; \ - rm -rf /usr/src/libprotobuf-mutator - -# EVMONE -RUN set -ex; \ - cd /usr/src; \ - git clone --branch="v0.8.2" --recurse-submodules https://github.com/ethereum/evmone.git; \ - cd evmone; \ - mkdir build; \ - cd build; \ - cmake -G Ninja -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX="/usr" ..; \ - ninja; \ - ninja install/strip; \ - rm -rf /usr/src/evmone - -# gmp -RUN set -ex; \ - # Replace system installed libgmp static library - # with sanitized version. Do not perform apt - # remove because it removes mlton as well that - # we need for building libabicoder - rm -f /usr/lib/x86_64-linux-gnu/libgmp.*; \ - rm -f /usr/include/x86_64-linux-gnu/gmp.h; \ - cd /usr/src; \ - wget -q 'https://gmplib.org/download/gmp/gmp-6.2.1.tar.xz' -O gmp.tar.xz; \ - test "$(sha256sum gmp.tar.xz)" = "fd4829912cddd12f84181c3451cc752be224643e87fac497b69edddadc49b4f2 gmp.tar.xz"; \ - tar -xf gmp.tar.xz; \ - cd gmp-6.2.1; \ - ./configure --prefix=/usr --enable-static=yes; \ - make -j; \ - make install; \ - rm -rf /usr/src/gmp-6.2.1; \ - rm -f /usr/src/gmp.tar.xz - -# libabicoder -RUN set -ex; \ - cd /usr/src; \ - git clone https://github.com/ekpyron/Yul-Isabelle; \ - cd Yul-Isabelle; \ - cd libabicoder; \ - CXX=clang++ CXXFLAGS="-stdlib=libc++ -pthread" make; \ - cp libabicoder.a /usr/lib; \ - cp abicoder.hpp /usr/include; \ - rm -rf /usr/src/Yul-Isabelle - -FROM base -COPY --from=libraries /usr/lib /usr/lib -COPY --from=libraries /usr/bin /usr/bin -COPY --from=libraries /usr/include /usr/include diff --git a/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004 b/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004 index 6cac5cc8..6ff8d1bd 100644 --- a/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004 +++ b/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004 @@ -22,7 +22,7 @@ # (c) 2016-2019 solidity contributors. #------------------------------------------------------------------------------ FROM buildpack-deps:focal AS base -LABEL version="14" +LABEL version="21" ARG DEBIAN_FRONTEND=noninteractive @@ -32,7 +32,7 @@ RUN set -ex; \ apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1c52189c923f6ca9 ; \ apt-get update; \ apt-get install -qqy --no-install-recommends \ - build-essential \ + build-essential sudo \ software-properties-common \ cmake ninja-build \ libboost-filesystem-dev libboost-test-dev libboost-system-dev \ @@ -47,27 +47,11 @@ FROM base AS libraries # EVMONE RUN set -ex; \ - cd /usr/src; \ - git clone --branch="v0.8.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ - cd evmone; \ - mkdir build; \ - cd build; \ - cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX="/usr" ..; \ - ninja; \ - ninja install/strip; \ - rm -rf /usr/src/evmone - -# HERA -RUN set -ex; \ - cd /usr/src; \ - git clone --branch="v0.5.0" --depth 1 --recurse-submodules https://github.com/ewasm/hera.git; \ - cd hera; \ - mkdir build; \ - cd build; \ - cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX="/usr" ..; \ - ninja; \ - ninja install/strip; \ - rm -rf /usr/src/hera + wget -O /usr/src/evmone.tar.gz https://github.com/ethereum/evmone/releases/download/v0.11.0/evmone-0.11.0-linux-x86_64.tar.gz; \ + test "$(sha256sum /usr/src/evmone.tar.gz)" = "051dbe523da165658ced63619ea2c05925516aac3061951da96d3f0962560719 /usr/src/evmone.tar.gz"; \ + cd /usr; \ + tar -xf /usr/src/evmone.tar.gz; \ + rm -rf /usr/src/evmone.tar.gz FROM base COPY --from=libraries /usr/lib /usr/lib diff --git a/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004.clang b/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004.clang deleted file mode 100644 index dc5acbcd..00000000 --- a/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu2004.clang +++ /dev/null @@ -1,77 +0,0 @@ -# vim:syntax=dockerfile -#------------------------------------------------------------------------------ -# Dockerfile for building and testing Solidity Compiler on CI -# Target: Ubuntu 19.04 (Disco Dingo) Clang variant -# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps -# -# This file is part of solidity. -# -# solidity is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# solidity is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with solidity. If not, see -# -# (c) 2016-2019 solidity contributors. -#------------------------------------------------------------------------------ -FROM buildpack-deps:focal AS base -LABEL version="14" - -ARG DEBIAN_FRONTEND=noninteractive - -RUN set -ex; \ - dist=$(grep DISTRIB_CODENAME /etc/lsb-release | cut -d= -f2); \ - echo "deb http://ppa.launchpad.net/ethereum/cpp-build-deps/ubuntu $dist main" >> /etc/apt/sources.list ; \ - apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1c52189c923f6ca9 ; \ - apt-get update; \ - apt-get install -qqy --no-install-recommends \ - build-essential \ - software-properties-common \ - cmake ninja-build \ - libboost-filesystem-dev libboost-test-dev libboost-system-dev \ - libboost-program-options-dev \ - clang \ - libz3-static-dev z3-static jq \ - ; \ - rm -rf /var/lib/apt/lists/* - -FROM base AS libraries - -ENV CC clang -ENV CXX clang++ - -# EVMONE -RUN set -ex; \ - cd /usr/src; \ - git clone --branch="v0.8.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ - cd evmone; \ - mkdir build; \ - cd build; \ - cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX="/usr" ..; \ - ninja; \ - ninja install/strip; \ - rm -rf /usr/src/evmone - -# HERA -RUN set -ex; \ - cd /usr/src; \ - git clone --branch="v0.5.0" --depth 1 --recurse-submodules https://github.com/ewasm/hera.git; \ - cd hera; \ - mkdir build; \ - cd build; \ - cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX="/usr" ..; \ - ninja; \ - ninja install/strip; \ - rm -rf /usr/src/hera - -FROM base -COPY --from=libraries /usr/lib /usr/lib -COPY --from=libraries /usr/bin /usr/bin -COPY --from=libraries /usr/include /usr/include diff --git a/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu2204 b/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu2204 new file mode 100644 index 00000000..6908811f --- /dev/null +++ b/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu2204 @@ -0,0 +1,63 @@ +# vim:syntax=dockerfile +#------------------------------------------------------------------------------ +# Dockerfile for building and testing Solidity Compiler on CI +# Target: Ubuntu 19.04 (Disco Dingo) +# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps +# +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2016-2019 solidity contributors. +#------------------------------------------------------------------------------ +FROM buildpack-deps:jammy AS base +LABEL version="6" + +ARG DEBIAN_FRONTEND=noninteractive + +RUN set -ex; \ + dist=$(grep DISTRIB_CODENAME /etc/lsb-release | cut -d= -f2); \ + echo "deb http://ppa.launchpad.net/ethereum/cpp-build-deps/ubuntu $dist main" >> /etc/apt/sources.list ; \ + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1c52189c923f6ca9 ; \ + apt-get update; \ + apt-get install -qqy --no-install-recommends \ + build-essential sudo \ + software-properties-common \ + cmake ninja-build \ + libboost-filesystem-dev libboost-test-dev libboost-system-dev \ + libboost-program-options-dev \ + libcvc4-dev libz3-static-dev z3-static jq \ + libcln-dev zip locales-all; \ + apt-get install -qy python3-pip python3-sphinx; \ + pip3 install codecov; \ + rm -rf /var/lib/apt/lists/* + +FROM base AS libraries + +# EVMONE +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.11.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ + cd evmone; \ + mkdir build; \ + cd build; \ + cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX="/usr" ..; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/evmone + +FROM base +COPY --from=libraries /usr/lib /usr/lib +COPY --from=libraries /usr/bin /usr/bin +COPY --from=libraries /usr/include /usr/include diff --git a/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu2204.clang b/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu2204.clang new file mode 100644 index 00000000..a14bdc11 --- /dev/null +++ b/compiler/scripts/docker/buildpack-deps/Dockerfile.ubuntu2204.clang @@ -0,0 +1,65 @@ +# vim:syntax=dockerfile +#------------------------------------------------------------------------------ +# Dockerfile for building and testing Solidity Compiler on CI +# Target: Ubuntu 19.04 (Disco Dingo) Clang variant +# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps +# +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2016-2019 solidity contributors. +#------------------------------------------------------------------------------ +FROM buildpack-deps:jammy AS base +LABEL version="5" + +ARG DEBIAN_FRONTEND=noninteractive + +RUN set -ex; \ + dist=$(grep DISTRIB_CODENAME /etc/lsb-release | cut -d= -f2); \ + echo "deb http://ppa.launchpad.net/ethereum/cpp-build-deps/ubuntu $dist main" >> /etc/apt/sources.list ; \ + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1c52189c923f6ca9 ; \ + apt-get update; \ + apt-get install -qqy --no-install-recommends \ + build-essential sudo \ + software-properties-common \ + cmake ninja-build \ + libboost-filesystem-dev libboost-test-dev libboost-system-dev \ + libboost-program-options-dev \ + clang \ + libz3-static-dev z3-static jq \ + libcln-dev; \ + rm -rf /var/lib/apt/lists/* + +FROM base AS libraries + +ENV CC clang +ENV CXX clang++ + +# EVMONE +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.11.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ + cd evmone; \ + mkdir build; \ + cd build; \ + cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX="/usr" ..; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/evmone + +FROM base +COPY --from=libraries /usr/lib /usr/lib +COPY --from=libraries /usr/bin /usr/bin +COPY --from=libraries /usr/include /usr/include diff --git a/compiler/scripts/docker/buildpack-deps/README.md b/compiler/scripts/docker/buildpack-deps/README.md index 7f635258..15cb71f5 100644 --- a/compiler/scripts/docker/buildpack-deps/README.md +++ b/compiler/scripts/docker/buildpack-deps/README.md @@ -27,7 +27,7 @@ If the version check was successful, the docker image will be built using the Do The resulting docker image will be tested by executing the corresponding `scripts/ci/buildpack-deps_test_*` scripts. Some of these scripts are symlinked to `scripts/ci/build.sh`, except the following two: - * `buildpack-deps-ubuntu1604.clang.ossfuzz` => `scripts/ci/build_ossfuzz.sh` + * `buildpack-deps-ubuntu.clang.ossfuzz` => `scripts/ci/build_ossfuzz.sh` * `buildpack-deps_test_emscripten.sh` => `scripts/ci/build_emscripten.sh` These scripts `scripts/ci/build.sh` and `scripts/ci/build_ossfuzz.sh` are also used by CircleCI, see `.circleci/config.yml`. diff --git a/compiler/scripts/endToEndExtraction/create_traces.sh b/compiler/scripts/endToEndExtraction/create_traces.sh deleted file mode 100755 index 3e1de467..00000000 --- a/compiler/scripts/endToEndExtraction/create_traces.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -set -eu - -BASE_PATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 && pwd -P )" - -mkdir -p build -cd build -cmake ../../../ -make soltest -cd test/ -echo "running soltest on 'semanticTests/extracted'..." -./soltest --color_output=false --log_level=test_suite -t semanticTests/extracted/ -- --testpath "${BASE_PATH}/../../test" --no-smt --evmonepath /Users/alex/evmone/lib/libevmone.dylib --show-messages --show-metadata > "${BASE_PATH}/extracted-tests.trace" -echo "running soltest on 'semanticTests/extracted'... done" - -cd "$BASE_PATH" -git clone git@github.com:ethereum/solidity.git solidity-develop -cd solidity-develop -mkdir -p build -cd build -cmake .. -make soltest -cd test/ -echo "running soltest on 'SolidityEndToEndTest'..." -./soltest --color_output=false --log_level=test_suite -t SolidityEndToEndTest/ -- --testpath "${BASE_PATH}/solidity-develop/test" --no-smt --evmonepath /Users/alex/evmone/lib/libevmone.dylib --show-messages --show-metadata > "${BASE_PATH}/endToEndExtraction-tests.trace" -echo "running soltest on 'SolidityEndToEndTest'... done" diff --git a/compiler/scripts/endToEndExtraction/remove-testcases.py b/compiler/scripts/endToEndExtraction/remove-testcases.py deleted file mode 100755 index ca9a6359..00000000 --- a/compiler/scripts/endToEndExtraction/remove-testcases.py +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env python3 -# pylint: disable=consider-using-enumerate, import-error - -import re -import os -import sys -import getopt -import tempfile -from getkey import getkey - - -def parse_call(call): - function = '' - arguments = "" - results = "" - search = re.search(r'// (.*):(.*)\s->\s(.*)', call, re.MULTILINE | re.DOTALL) - if search: - function = search.group(1) - arguments = search.group(2) - results = search.group(3) - if results.find("#") != -1: - results = results[:results.find("#")] - else: - search = re.search(r'// (.*)(.*)\s->\s(.*)', call, re.MULTILINE | re.DOTALL) - if search: - function = search.group(1) - arguments = search.group(2) - results = search.group(3) - if results.find("#") != -1: - results = results[:results.find("#")] - if function.find("wei") >= 0: - function = function[:function.find(",")] - return function.strip(), arguments.strip(), results.strip() - - -def colorize(left, right, index): - red = "\x1b[31m" - yellow = "\x1b[33m" - reset = "\x1b[0m" - colors = [red, yellow] - color = colors[index % len(colors)] - function, _arguments, _results = parse_call(right) - left = left.replace("compileAndRun", color + "compileAndRun" + reset) - right = right.replace("constructor", color + "constructor" + reset) - if function: - left = left.replace(function, color + function + reset) - right = right.replace(function, color + function + reset) - if left.find(function): - bottom = " " * (left.find(function) - 4) + right - else: - bottom = " " + right - return " " + left + "\n" + bottom # " {:<90} {:<90}\n{}".format(left, right, bottom) - - -def get_checks(content, sol_file_path): - constructors = [] - checks = [] - for line in content.split("\n"): - line = line.strip() - if line.startswith("compileAndRun"): - constructors.append(line) - if line.startswith("ABI_CHECK") or line.startswith("BOOST_REQUIRE"): - checks.append(line) - with open(sol_file_path, "r", encoding='utf8') as sol_file: - sol_constructors = [] - sol_checks = [] - inside_expectations = False - for line in sol_file.readlines(): - if line.startswith("// constructor()"): - sol_constructors.append(line) - elif inside_expectations and line.startswith("// "): - sol_checks.append(line) - if line.startswith("// ----"): - inside_expectations = True - sol_file.close() - if len(constructors) == len(sol_constructors) == 1: - checks.insert(0, constructors[0]) - sol_checks.insert(0, sol_constructors[0]) - return checks, sol_checks - - -def show_test(name, content, sol_file_path, current_test, test_count): - with tempfile.NamedTemporaryFile(delete=False) as cpp_file: - cpp_file.write(content.encode()) - cpp_file.close() - - os.system("clear") - print(str(current_test) + " / " + str(test_count) + " - " + name + "\n") - diff_env = os.getenv('DIFF', "/usr/local/bin/colordiff -a -d -w -y -W 200 ") - os.system(diff_env + " " + cpp_file.name + " " + sol_file_path) - os.unlink(cpp_file.name) - print("\n") - - checks, sol_checks = get_checks(content, sol_file_path) - - if len(checks) == len(sol_checks): - for i in range(0, len(checks)): - print(colorize(checks[i].strip(), sol_checks[i].strip(), i)) - else: - print("warning: check count not matching. this should not happen!") - - what = "" - print("\nContinue? (ENTER) Abort? (ANY OTHER KEY)") - while what != '\n': - what = getkey() - if what != '\n': - sys.exit(0) - print() - - -def get_tests(e2e_path): - tests = [] - for f in os.listdir(e2e_path): - if f.endswith(".sol"): - tests.append(f.replace(".sol", "")) - return tests - - -def process_input_file(e2e_path, input_file, interactive): - tests = get_tests(e2e_path) - with open(input_file, "r", encoding='utf8') as cpp_file: - inside_test = False - test_name = "" - inside_extracted_test = False - new_lines = 0 - count = 0 - test_content = "" - for line in cpp_file.readlines(): - test = re.search(r'BOOST_AUTO_TEST_CASE\((.*)\)', line, re.M | re.I) - if test: - test_name = test.group(1) - inside_test = True - inside_extracted_test = inside_test & (test_name in tests) - if inside_extracted_test: - count = count + 1 - - if interactive and inside_extracted_test: - test_content = test_content + line - - if not inside_extracted_test: - if line == "\n": - new_lines = new_lines + 1 - else: - new_lines = 0 - if not interactive and new_lines <= 1: - sys.stdout.write(line) - - if line == "}\n": - if interactive and inside_extracted_test: - show_test(test_name, test_content.strip(), e2e_path + "/" + test_name + ".sol", count, len(tests)) - test_content = "" - inside_test = False - cpp_file.close() - sys.stdout.flush() - - -def main(argv): - interactive = False - input_file = None - try: - opts, _args = getopt.getopt(argv, "if:") - except getopt.GetoptError: - print("./remove-testcases.py [-i] [-f ]") - sys.exit(1) - - for opt, arg in opts: - if opt == '-i': - interactive = True - elif opt in '-f': - input_file = arg - - base_path = os.path.dirname(__file__) - - if not input_file: - input_file = base_path + "/../../test/libsolidity/SolidityEndToEndTest.cpp" - - e2e_path = base_path + "/../../test/libsolidity/semanticTests/extracted" - - process_input_file(e2e_path, input_file, interactive) - - -if __name__ == "__main__": - main(sys.argv[1:]) diff --git a/compiler/scripts/endToEndExtraction/verify-testcases.py b/compiler/scripts/endToEndExtraction/verify-testcases.py deleted file mode 100755 index b0118704..00000000 --- a/compiler/scripts/endToEndExtraction/verify-testcases.py +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/env python3 -# -# - SolidityEndToEndTest.trace was created with soltest with the following command on -# ./soltest --color_output=false --log_level=test_suite -t SolidityEndToEndTest/ -- --no-smt -# --evmonepath /Users/alex/evmone/lib/libevmone.dylib --show-messages > SolidityEndToEndTest.trace -# - a trace of the semantic tests can be created by using -# ./soltest --color_output=false --log_level=test_suite -t semanticTests/extracted/ -- --no-smt -# --evmonepath /Users/alex/evmone/lib/libevmone.dylib --show-messages > semanticTests.trace -# -# verify-testcases.py will compare both traces. If these traces are identical, the extracted tests were -# identical with the tests specified in SolidityEndToEndTest.cpp. - -import re -import os -import sys -import getopt -import json - - -class Trace: - def __init__(self, kind, parameter): - self.kind = kind - self.parameter = parameter - self._input = "" - self._output = "" - self.value = "" - self.result = "" - self.gas = "" - - def get_input(self): - return self._input - - def set_input(self, bytecode): - if self.kind == "create": - # remove cbor encoded metadata from bytecode - length = int(bytecode[-4:], 16) * 2 - self._input = bytecode[:len(bytecode) - length - 4] - - def get_output(self): - return self._output - - def set_output(self, output): - if self.kind == "create": - # remove cbor encoded metadata from bytecode - length = int(output[-4:], 16) * 2 - self._output = output[:len(output) - length - 4] - - def __str__(self): - # we ignore the used gas - result = str( - "kind='" + self.kind + "' parameter='" + self.parameter + "' input='" + self._input + - "' output='" + self._output + "' value='" + self.value + "' result='" + self.result + "'" - ) - return result - - -class TestCase: - def __init__(self, name): - self.name = name - self.metadata = None - self.traces = [] - - def add_trace(self, kind, parameter): - trace = Trace(kind, parameter) - self.traces.append(trace) - return trace - - -class TraceAnalyser: - def __init__(self, file): - self.file = file - self.tests = {} - self.ready = False - - def analyse(self): - with open(self.file, "r", encoding='utf8') as trace_file: - trace = None - test_case = None - for line in trace_file.readlines(): - test = re.search(r'Entering test case "(.*)"', line, re.M | re.I) - if test: - test_name = test.group(1) - test_case = TestCase(test_name) - self.tests[test_name] = test_case - - metadata = re.search(r'\s*metadata:\s*(.*)$', line, re.M | re.I) - if metadata: - test_case.metadata = json.loads(metadata.group(1)) - del test_case.metadata["sources"] - del test_case.metadata["compiler"]["version"] - - create = re.search(r'CREATE\s*([a-fA-F0-9]*):', line, re.M | re.I) - if create: - trace = test_case.add_trace("create", create.group(1)) - - call = re.search(r'CALL\s*([a-fA-F0-9]*)\s*->\s*([a-fA-F0-9]*):', line, re.M | re.I) - if call: - trace = test_case.add_trace("call", call.group(1)) # + "->" + call.group(2)) - - if not create and not call: - self.parse_parameters(line, trace) - - trace_file.close() - - print(self.file + ":", len(self.tests), "test-cases.") - - self.ready = True - - @staticmethod - def parse_parameters(line, trace): - input_match = re.search(r'\s*in:\s*([a-fA-F0-9]*)', line, re.M | re.I) - if input_match: - trace.input = input_match.group(1) - output_match = re.search(r'\s*out:\s*([a-fA-F0-9]*)', line, re.M | re.I) - if output_match: - trace.output = output_match.group(1) - result_match = re.search(r'\s*result:\s*([a-fA-F0-9]*)', line, re.M | re.I) - if result_match: - trace.result = result_match.group(1) - gas_used_match = re.search(r'\s*gas\sused:\s*([a-fA-F0-9]*)', line, re.M | re.I) - if gas_used_match: - trace.gas = gas_used_match.group(1) - value_match = re.search(r'\s*value:\s*([a-fA-F0-9]*)', line, re.M | re.I) - if value_match: - trace.value = value_match.group(1) - - def diff(self, analyser): - if not self.ready: - self.analyse() - if not analyser.ready: - analyser.analyse() - - intersection = set(self.tests.keys()) & set(analyser.tests.keys()) - mismatches = set() - - for test_name in intersection: - left = self.tests[test_name] - right = analyser.tests[test_name] - if json.dumps(left.metadata) != json.dumps(right.metadata): - mismatches.add( - (test_name, "metadata where different: " + json.dumps(left.metadata) + " != " + json.dumps( - right.metadata))) - if len(left.traces) != len(right.traces): - mismatches.add((test_name, "trace count are different: " + str(len(left.traces)) + - " != " + str(len(right.traces)))) - else: - self.check_traces(test_name, left, right, mismatches) - - for mismatch in mismatches: - print(mismatch[0]) - print(mismatch[1]) - - print(len(intersection), "test-cases - ", len(mismatches), " mismatche(s)") - - @classmethod - def check_traces(cls, test_name, left, right, mismatches): - for trace_id, trace in enumerate(left.traces): - left_trace = trace - right_trace = right.traces[trace_id] - assert left_trace.kind == right_trace.kind - if str(left_trace) != str(right_trace): - mismatch_info = " " + str(left_trace) + "\n" - mismatch_info += " " + str(right_trace) + "\n" - mismatch_info += " " - for ch in range(0, len(str(left_trace))): - if ch < len(str(left_trace)) and ch < len(str(right_trace)): - if str(left_trace)[ch] != str(right_trace)[ch]: - mismatch_info += "|" - else: - mismatch_info += " " - else: - mismatch_info += "|" - mismatch_info += "\n" - mismatches.add((test_name, mismatch_info)) - - -def main(argv): - extracted_tests_trace_file = None - end_to_end_trace_file = None - try: - opts, _args = getopt.getopt(argv, "s:e:") - except getopt.GetoptError: - print("verify-testcases.py [-s ] [-e ]") - sys.exit(2) - - for opt, arg in opts: - if opt in '-s': - extracted_tests_trace_file = arg - elif opt in '-e': - end_to_end_trace_file = arg - - base_path = os.path.dirname(__file__) - if not extracted_tests_trace_file: - extracted_tests_trace_file = base_path + "/extracted-tests.trace" - if not end_to_end_trace_file: - end_to_end_trace_file = base_path + "/endToEndExtraction-tests.trace" - - for f in [extracted_tests_trace_file, end_to_end_trace_file]: - if not os.path.isfile(f): - print("trace file '" + f + "' not found. aborting.") - sys.exit(1) - - if not os.path.isfile(extracted_tests_trace_file): - print("semantic trace file '" + extracted_tests_trace_file + "' not found. aborting.") - sys.exit(1) - - semantic_trace = TraceAnalyser(extracted_tests_trace_file) - end_to_end_trace = TraceAnalyser(end_to_end_trace_file) - - semantic_trace.diff(end_to_end_trace) - - -if __name__ == "__main__": - main(sys.argv[1:]) diff --git a/compiler/scripts/error_codes.py b/compiler/scripts/error_codes.py index 84ba08bc..d4e9e548 100755 --- a/compiler/scripts/error_codes.py +++ b/compiler/scripts/error_codes.py @@ -171,7 +171,7 @@ def print_ids_per_file(ids, id_to_file_names, top_dir): def examine_id_coverage(top_dir, source_id_to_file_names, new_ids_only=False): test_sub_dirs = [ - path.join("test", "libsolidity", "errorRecoveryTests"), + path.join("test", "libsolidity", "natspecJSON"), path.join("test", "libsolidity", "smtCheckerTests"), path.join("test", "libsolidity", "syntaxTests"), path.join("test", "libyul", "yulSyntaxTests") @@ -202,7 +202,12 @@ def examine_id_coverage(top_dir, source_id_to_file_names, new_ids_only=False): "4591", # "There are more than 256 warnings. Ignoring the rest." # Due to 3805, the warning lists look different for different compiler builds. "1834", # Unimplemented feature error, as we do not test it anymore via cmdLineTests - "5430" # basefee being used in inline assembly for EVMVersion < london + "6679", # blobbasefee being used in inline assembly for EVMVersion < cancun + "1180", # SMTChecker, covered by CL tests + "2339", # SMTChecker, covered by CL tests + "2961", # SMTChecker, covered by CL tests + "6240", # SMTChecker, covered by CL tests + "9576", # SMTChecker, covered by CL tests } assert len(test_ids & white_ids) == 0, "The sets are not supposed to intersect" test_ids |= white_ids @@ -227,16 +232,109 @@ def examine_id_coverage(top_dir, source_id_to_file_names, new_ids_only=False): return False old_source_only_ids = { - "1584", "1823", - "1988", "2066", "2833", "3356", - "3893", "3996", "4010", "4802", - "5272", "5622", "7128", "7400", - "7589", "7593", "7649", "7710", - "8065", "8084", "8140", "8158", - "8312", "8592", "9134", "9609", + "1218", + "1584", + "1823", + "1988", + "2066", + "2833", + "3356", + "3893", + "3996", + "4010", + "4458", + "4802", + "4902", + "5272", + "5622", + "5798", + "5840", + "7128", + "7400", + "7589", + "7593", + "7649", + "7710", + "8065", + "8084", + "8140", + "8158", + "8312", + "8592", + "9134", + "9609", } - new_source_only_ids = source_only_ids - old_source_only_ids + # TODO Cover these with tests and remove from this list as the development of experimental + # TODO Solidity progresses. The aim should be to completely get rid of `experimental_source_only_ids`. + experimental_source_only_ids = { + "1017", + "1439", + "1723", + "1741", + "1801", + "1807", + "2015", + "2138", + "2345", + "2399", + "2599", + "2655", + "2658", + "2934", + "3101", + "3111", + "3195", + "3520", + "3570", + "3573", + "3654", + "4316", + "4337", + "4496", + "4504", + "4686", + "4767", + "4873", + "4955", + "5044", + "5094", + "5096", + "5104", + "5195", + "5262", + "5348", + "5360", + "5387", + "5577", + "5714", + "5731", + "5755", + "5904", + "6175", + "6387", + "6388", + "6460", + "6620", + "6739", + "6948", + "7341", + "7428", + "7531", + "8379", + "8809", + "8953", + "9159", + "9173", + "9282", + "9603", + "9658", + "9817", + "9831", + "9988", + } + + new_source_only_ids = source_only_ids - old_source_only_ids - experimental_source_only_ids if len(new_source_only_ids) != 0: print("The following new error code(s), not covered by tests, found:") print_ids(new_source_only_ids) diff --git a/compiler/scripts/externalTests/common.sh b/compiler/scripts/externalTests/common.sh new file mode 100644 index 00000000..3bca968a --- /dev/null +++ b/compiler/scripts/externalTests/common.sh @@ -0,0 +1,677 @@ +#!/usr/bin/env bash + +# ------------------------------------------------------------------------------ +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2019 solidity contributors. +#------------------------------------------------------------------------------ +set -e + +# Requires $REPO_ROOT to be defined and "${REPO_ROOT}/scripts/common.sh" to be included before. + +CURRENT_EVM_VERSION=shanghai + +AVAILABLE_PRESETS=( + legacy-no-optimize + ir-no-optimize + legacy-optimize-evm-only + ir-optimize-evm-only + legacy-optimize-evm+yul + ir-optimize-evm+yul +) + +function print_presets_or_exit +{ + local selected_presets="$1" + + [[ $selected_presets != "" ]] || { printWarning "No presets to run. Exiting."; exit 0; } + + printLog "Selected settings presets: ${selected_presets}" +} + +function verify_input +{ + local binary_type="$1" + local binary_path="$2" + local selected_presets="$3" + + (( $# >= 2 && $# <= 3 )) || fail "Usage: $0 native|solcjs [preset]" + [[ $binary_type == native || $binary_type == solcjs ]] || fail "Invalid binary type: '${binary_type}'. Must be either 'native' or 'solcjs'." + [[ -f "$binary_path" ]] || fail "The compiler binary does not exist at '${binary_path}'" + + if [[ $selected_presets != "" ]] + then + for preset in $selected_presets + do + if [[ " ${AVAILABLE_PRESETS[*]} " != *" $preset "* ]] + then + fail "Preset '${preset}' does not exist. Available presets: ${AVAILABLE_PRESETS[*]}." + fi + done + fi +} + +function setup_solc +{ + local test_dir="$1" + local binary_type="$2" + local binary_path="$3" + local solcjs_branch="${4:-master}" + local install_dir="${5:-solc/}" + local solcjs_dir="$6" + + [[ $binary_type == native || $binary_type == solcjs ]] || assertFail + [[ $binary_type == solcjs || $solcjs_dir == "" ]] || assertFail + + cd "$test_dir" + + if [[ $binary_type == solcjs ]] + then + printLog "Setting up solc-js..." + if [[ $solcjs_dir == "" ]]; then + printLog "Cloning branch ${solcjs_branch}..." + git clone --depth 1 -b "$solcjs_branch" https://github.com/ethereum/solc-js.git "$install_dir" + else + printLog "Using local solc-js from ${solcjs_dir}..." + cp -ra "$solcjs_dir" solc + fi + + pushd "$install_dir" + npm install + cp "$binary_path" soljson.js + npm run build + SOLCVERSION=$(dist/solc.js --version) + popd + else + printLog "Setting up solc..." + SOLCVERSION=$("$binary_path" --version | tail -n 1 | sed -n -E 's/^Version: (.*)$/\1/p') + fi + + SOLCVERSION_SHORT=$(echo "$SOLCVERSION" | sed -En 's/^([0-9.]+).*\+commit\.[0-9a-f]+.*$/\1/p') + printLog "Using compiler version $SOLCVERSION" +} + +function download_project +{ + local repo="$1" + local ref_type="$2" + local solcjs_ref="$3" + local test_dir="$4" + + [[ $ref_type == commit || $ref_type == branch || $ref_type == tag ]] || assertFail + + printLog "Cloning ${ref_type} ${solcjs_ref} of ${repo}..." + if [[ $ref_type == commit ]]; then + mkdir ext + cd ext + git init + git remote add origin "$repo" + git fetch --depth 1 origin "$solcjs_ref" + git reset --hard FETCH_HEAD + else + git clone --depth 1 "$repo" -b "$solcjs_ref" "$test_dir/ext" + cd ext + fi + echo "Current commit hash: $(git rev-parse HEAD)" +} + +function force_truffle_version +{ + local version="$1" + + sed -i 's/"truffle":\s*".*"/"truffle": "'"$version"'"/g' package.json +} + +function replace_version_pragmas +{ + # Replace fixed-version pragmas (part of Consensys best practice). + # Include all directories to also cover node dependencies. + printLog "Replacing fixed-version pragmas..." + find . test -name '*.sol' -type f -print0 | xargs -0 sed -i -E -e 's/pragma solidity [^;]+;/pragma solidity >=0.0;/' +} + +function neutralize_package_lock +{ + # Remove lock files (if they exist) to prevent them from overriding our changes in package.json + printLog "Removing package lock files..." + rm --force --verbose yarn.lock + rm --force --verbose package-lock.json +} + +function neutralize_package_json_hooks +{ + printLog "Disabling package.json hooks..." + [[ -f package.json ]] || fail "package.json not found" + sed -i 's|"prepublish": *".*"|"prepublish": ""|g' package.json + sed -i 's|"prepare": *".*"|"prepare": ""|g' package.json +} + +function neutralize_packaged_contracts +{ + # Frameworks will build contracts from any package that contains a configuration file. + # This is both unnecessary (any files imported from these packages will get compiled again as a + # part of the main project anyway) and trips up our version check because it won't use our + # custom compiler binary. + printLog "Removing framework config and artifacts from npm packages..." + find node_modules/ -type f '(' -name 'hardhat.config.*' -o -name 'truffle-config.*' ')' -delete + + # Some npm packages also come packaged with pre-built artifacts. + find node_modules/ -path '*artifacts/build-info/*.json' -delete +} + +function force_solc_modules +{ + local custom_solcjs_path="${1:-solc/}" + + [[ -d node_modules/ ]] || assertFail + + printLog "Replacing all installed solc-js with a link to the latest version..." + soljson_binaries=$(find node_modules -type f -path "*/solc/soljson.js") + for soljson_binary in $soljson_binaries + do + local solc_module_path + solc_module_path=$(dirname "$soljson_binary") + + printLog "Found and replaced solc-js in $solc_module_path" + rm -r "$solc_module_path" + ln -s "$custom_solcjs_path" "$solc_module_path" + done +} + +function force_truffle_compiler_settings +{ + local config_file="$1" + local binary_type="$2" + local solc_path="$3" + local preset="$4" + local evm_version="${5:-"$CURRENT_EVM_VERSION"}" + local extra_settings="$6" + local extra_optimizer_settings="$7" + + [[ $binary_type == native || $binary_type == solcjs ]] || assertFail + + [[ $binary_type == native ]] && local solc_path="native" + + printLog "Forcing Truffle compiler settings..." + echo "-------------------------------------" + echo "Config file: $config_file" + echo "Binary type: $binary_type" + echo "Compiler path: $solc_path" + echo "Settings preset: ${preset}" + echo "Settings: $(settings_from_preset "$preset" "$evm_version" "$extra_settings" "$extra_optimizer_settings")" + echo "EVM version: $evm_version" + echo "Compiler version: ${SOLCVERSION_SHORT}" + echo "Compiler version (full): ${SOLCVERSION}" + echo "-------------------------------------" + + local compiler_settings gas_reporter_settings + compiler_settings=$(truffle_compiler_settings "$solc_path" "$preset" "$evm_version" "$extra_settings" "$extra_optimizer_settings") + gas_reporter_settings=$(eth_gas_reporter_settings "$preset") + + { + echo "require('eth-gas-reporter');" + echo "module.exports['mocha'] = {" + echo " reporter: 'eth-gas-reporter'," + echo " reporterOptions: ${gas_reporter_settings}" + echo "};" + + echo "module.exports['compilers'] = ${compiler_settings};" + } >> "$config_file" +} + +function name_hardhat_default_export +{ + local config_file="$1" + local import="import {HardhatUserConfig} from 'hardhat/types/config';" + local config="const config: HardhatUserConfig = {" + sed -i "s|^\s*export\s*default\s*{|${import}\n${config}|g" "$config_file" + echo "export default config;" >> "$config_file" +} + +function force_hardhat_timeout +{ + local config_file="$1" + local config_var_name="$2" + local new_timeout="$3" + + printLog "Configuring Hardhat..." + echo "-------------------------------------" + echo "Timeout: ${new_timeout}" + echo "-------------------------------------" + + if [[ $config_file == *\.js ]]; then + [[ $config_var_name == "" ]] || assertFail + echo "module.exports.mocha = module.exports.mocha || {timeout: ${new_timeout}}" + echo "module.exports.mocha.timeout = ${new_timeout}" + else + [[ $config_file == *\.ts ]] || assertFail + [[ $config_var_name != "" ]] || assertFail + echo "${config_var_name}.mocha = ${config_var_name}.mocha ?? {timeout: ${new_timeout}};" + echo "${config_var_name}.mocha!.timeout = ${new_timeout}" + fi >> "$config_file" +} + +function force_hardhat_compiler_binary +{ + local config_file="$1" + local binary_type="$2" + local solc_path="$3" + + printLog "Configuring Hardhat..." + echo "-------------------------------------" + echo "Config file: ${config_file}" + echo "Binary type: ${binary_type}" + echo "Compiler path: ${solc_path}" + + local language="${config_file##*.}" + hardhat_solc_build_subtask "$SOLCVERSION_SHORT" "$SOLCVERSION" "$binary_type" "$solc_path" "$language" >> "$config_file" +} + +function force_hardhat_unlimited_contract_size +{ + local config_file="$1" + local config_var_name="$2" + + printLog "Configuring Hardhat..." + echo "-------------------------------------" + echo "Allow unlimited contract size: true" + echo "-------------------------------------" + + if [[ $config_file == *\.js ]]; then + [[ $config_var_name == "" ]] || assertFail + echo "module.exports.networks.hardhat = module.exports.networks.hardhat || {allowUnlimitedContractSize: undefined}" + echo "module.exports.networks.hardhat.allowUnlimitedContractSize = true" + else + [[ $config_file == *\.ts ]] || assertFail + [[ $config_var_name != "" ]] || assertFail + echo "${config_var_name}.networks!.hardhat = ${config_var_name}.networks!.hardhat ?? {allowUnlimitedContractSize: undefined};" + echo "${config_var_name}.networks!.hardhat!.allowUnlimitedContractSize = true" + fi >> "$config_file" +} + +function force_hardhat_compiler_settings +{ + local config_file="$1" + local preset="$2" + local config_var_name="$3" + local evm_version="${4:-"$CURRENT_EVM_VERSION"}" + local extra_settings="$5" + local extra_optimizer_settings="$6" + + printLog "Configuring Hardhat..." + echo "-------------------------------------" + echo "Config file: ${config_file}" + echo "Settings preset: ${preset}" + echo "Settings: $(settings_from_preset "$preset" "$evm_version" "$extra_settings" "$extra_optimizer_settings")" + echo "EVM version: ${evm_version}" + echo "Compiler version: ${SOLCVERSION_SHORT}" + echo "Compiler version (full): ${SOLCVERSION}" + echo "-------------------------------------" + + local compiler_settings gas_reporter_settings + compiler_settings=$(hardhat_compiler_settings "$SOLCVERSION_SHORT" "$preset" "$evm_version" "$extra_settings" "$extra_optimizer_settings") + gas_reporter_settings=$(eth_gas_reporter_settings "$preset") + if [[ $config_file == *\.js ]]; then + [[ $config_var_name == "" ]] || assertFail + echo "require('hardhat-gas-reporter');" + echo "module.exports.gasReporter = ${gas_reporter_settings};" + echo "module.exports.solidity = ${compiler_settings};" + else + [[ $config_file == *\.ts ]] || assertFail + [[ $config_var_name != "" ]] || assertFail + echo 'import "hardhat-gas-reporter";' + echo "${config_var_name}.gasReporter = ${gas_reporter_settings};" + echo "${config_var_name}.solidity = {compilers: [${compiler_settings}]};" + fi >> "$config_file" +} + +function truffle_verify_compiler_version +{ + local solc_version="$1" + local full_solc_version="$2" + + printLog "Verify that the correct version (${solc_version}/${full_solc_version}) of the compiler was used to compile the contracts..." + grep "$full_solc_version" --recursive --quiet build/contracts || fail "Wrong compiler version detected." +} + +function hardhat_verify_compiler_version +{ + local solc_version="$1" + local full_solc_version="$2" + + printLog "Verify that the correct version (${solc_version}/${full_solc_version}) of the compiler was used to compile the contracts..." + local build_info_files + build_info_files=$(find . -path '*artifacts/build-info/*.json') + for build_info_file in $build_info_files; do + grep '"solcVersion":[[:blank:]]*"'"${solc_version}"'"' --quiet "$build_info_file" || fail "Wrong compiler version detected in ${build_info_file}." + grep '"solcLongVersion":[[:blank:]]*"'"${full_solc_version}"'"' --quiet "$build_info_file" || fail "Wrong compiler version detected in ${build_info_file}." + done +} + +function truffle_clean +{ + rm -rf build/ +} + +function hardhat_clean +{ + rm -rf build/ artifacts/ cache/ +} + +function settings_from_preset +{ + local preset="$1" + local evm_version="$2" + local extra_settings="$3" + local extra_optimizer_settings="$4" + + [[ " ${AVAILABLE_PRESETS[*]} " == *" $preset "* ]] || assertFail + + [[ $extra_settings == "" ]] || extra_settings="${extra_settings}, " + [[ $extra_optimizer_settings == "" ]] || extra_optimizer_settings="${extra_optimizer_settings}, " + + case "$preset" in + # NOTE: Remember to update `parallelism` of `t_ems_ext` job in CI config if you add/remove presets + legacy-no-optimize) echo "{${extra_settings}evmVersion: '${evm_version}', viaIR: false, optimizer: {${extra_optimizer_settings}enabled: false}}" ;; + ir-no-optimize) echo "{${extra_settings}evmVersion: '${evm_version}', viaIR: true, optimizer: {${extra_optimizer_settings}enabled: false}}" ;; + legacy-optimize-evm-only) echo "{${extra_settings}evmVersion: '${evm_version}', viaIR: false, optimizer: {${extra_optimizer_settings}enabled: true, details: {yul: false}}}" ;; + ir-optimize-evm-only) echo "{${extra_settings}evmVersion: '${evm_version}', viaIR: true, optimizer: {${extra_optimizer_settings}enabled: true, details: {yul: false}}}" ;; + legacy-optimize-evm+yul) echo "{${extra_settings}evmVersion: '${evm_version}', viaIR: false, optimizer: {${extra_optimizer_settings}enabled: true, details: {yul: true}}}" ;; + ir-optimize-evm+yul) echo "{${extra_settings}evmVersion: '${evm_version}', viaIR: true, optimizer: {${extra_optimizer_settings}enabled: true, details: {yul: true}}}" ;; + *) + fail "Unknown settings preset: '${preset}'." + ;; + esac +} + +function replace_global_solc +{ + local solc_path="$1" + + [[ ! -e solc ]] || fail "A file named 'solc' already exists in '${PWD}'." + + ln -s "$solc_path" solc + export PATH="$PWD:$PATH" +} + +function eth_gas_reporter_settings +{ + local preset="$1" + + echo "{" + echo " enabled: true," + echo " gasPrice: 1," # Gas price does not matter to us at all. Set to whatever to avoid API call. + echo " noColors: true," + echo " showTimeSpent: false," # We're not interested in test timing + echo " onlyCalledMethods: true," # Exclude entries with no gas for shorter report + echo " showMethodSig: true," # Should make diffs more stable if there are overloaded functions + echo " outputFile: \"$(gas_report_path "$preset")\"" + echo "}" +} + +function truffle_compiler_settings +{ + local solc_path="$1" + local preset="$2" + local evm_version="$3" + local extra_settings="$4" + local extra_optimizer_settings="$5" + + echo "{" + echo " solc: {" + echo " version: \"${solc_path}\"," + echo " settings: $(settings_from_preset "$preset" "$evm_version" "$extra_settings" "$extra_optimizer_settings")" + echo " }" + echo "}" +} + +function hardhat_solc_build_subtask { + local solc_version="$1" + local full_solc_version="$2" + local binary_type="$3" + local solc_path="$4" + local language="$5" + + [[ $binary_type == native || $binary_type == solcjs ]] || assertFail + + [[ $binary_type == native ]] && local is_solcjs=false + [[ $binary_type == solcjs ]] && local is_solcjs=true + + if [[ $language == js ]]; then + echo "const {TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD} = require('hardhat/builtin-tasks/task-names');" + echo "const assert = require('assert');" + echo + echo "subtask(TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, async (args, hre, runSuper) => {" + else + [[ $language == ts ]] || assertFail + echo "import {TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD} from 'hardhat/builtin-tasks/task-names';" + echo "import assert = require('assert');" + echo "import {subtask} from 'hardhat/config';" + echo + echo "subtask(TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, async (args: any, _hre: any, _runSuper: any) => {" + fi + + echo " assert(args.solcVersion == '${solc_version}', 'Unexpected solc version: ' + args.solcVersion)" + echo " return {" + echo " compilerPath: '$(realpath "$solc_path")'," + echo " isSolcJs: ${is_solcjs}," + echo " version: args.solcVersion," + echo " longVersion: '${full_solc_version}'" + echo " }" + echo "})" +} + +function hardhat_compiler_settings { + local solc_version="$1" + local preset="$2" + local evm_version="$3" + local extra_settings="$4" + local extra_optimizer_settings="$5" + + echo "{" + echo " version: '${solc_version}'," + echo " settings: $(settings_from_preset "$preset" "$evm_version" "$extra_settings" "$extra_optimizer_settings")" + echo "}" +} + +function compile_and_run_test +{ + local compile_fn="$1" + local test_fn="$2" + local verify_fn="$3" + local preset="$4" + local compile_only_presets="$5" + + [[ $preset != *" "* ]] || assertFail "Preset names must not contain spaces." + + printLog "Running compile function..." + time $compile_fn + $verify_fn "$SOLCVERSION_SHORT" "$SOLCVERSION" + + if [[ "$COMPILE_ONLY" == 1 || " $compile_only_presets " == *" $preset "* ]]; then + printLog "Skipping test function..." + else + printLog "Running test function..." + $test_fn + fi +} + +function truffle_run_test +{ + local config_file="$1" + local binary_type="$2" + local solc_path="$3" + local preset="$4" + local compile_only_presets="$5" + local compile_fn="$6" + local test_fn="$7" + local extra_settings="$8" + local extra_optimizer_settings="$9" + + truffle_clean + force_truffle_compiler_settings "$config_file" "$binary_type" "$solc_path" "$preset" "$CURRENT_EVM_VERSION" "$extra_settings" "$extra_optimizer_settings" + compile_and_run_test compile_fn test_fn truffle_verify_compiler_version "$preset" "$compile_only_presets" +} + +function hardhat_run_test +{ + local config_file="$1" + local preset="$2" + local compile_only_presets="$3" + local compile_fn="$4" + local test_fn="$5" + local config_var_name="$6" + local extra_settings="$7" + local extra_optimizer_settings="$8" + + hardhat_clean + force_hardhat_compiler_settings "$config_file" "$preset" "$config_var_name" "$CURRENT_EVM_VERSION" "$extra_settings" "$extra_optimizer_settings" + compile_and_run_test compile_fn test_fn hardhat_verify_compiler_version "$preset" "$compile_only_presets" +} + +function external_test +{ + local name="$1" + local main_fn="$2" + + printTask "Testing $name..." + echo "===========================" + DIR=$(mktemp -d -t "ext-test-${name}-XXXXXX") + ( + [[ "$main_fn" != "" ]] || fail "Test main function not defined." + $main_fn + ) + rm -rf "$DIR" + echo "Done." +} + +function gas_report_path +{ + local preset="$1" + + echo "${DIR}/gas-report-${preset}.rst" +} + +function gas_report_to_json +{ + cat - | "${REPO_ROOT}/scripts/externalTests/parse_eth_gas_report.py" | jq '{gas: .}' +} + +function detect_hardhat_artifact_dir +{ + if [[ -e build/ && -e artifacts/ ]]; then + fail "Cannot determine Hardhat artifact location. Both build/ and artifacts/ exist" + elif [[ -e build/ ]]; then + echo -n build/artifacts + elif [[ -e artifacts/ ]]; then + echo -n artifacts + else + fail "Hardhat build artifacts not found." + fi +} + +function bytecode_size_json_from_truffle_artifacts +{ + # NOTE: The output of this function is a series of concatenated JSON dicts rather than a list. + + for artifact in build/contracts/*.json; do + if [[ $(jq '. | has("unlinked_binary")' "$artifact") == false ]]; then + # Each artifact represents compilation output for a single contract. Some top-level keys contain + # bits of Standard JSON output while others are generated by Truffle. Process it into a dict + # of the form `{"": {"": }}`. + # NOTE: The `bytecode` field starts with 0x, which is why we subtract 1 from size. + jq '{ + (.ast.absolutePath): { + (.contractName): (.bytecode | length / 2 - 1) + } + }' "$artifact" + fi + done +} + +function bytecode_size_json_from_hardhat_artifacts +{ + # NOTE: The output of this function is a series of concatenated JSON dicts rather than a list. + + for artifact in "$(detect_hardhat_artifact_dir)"/build-info/*.json; do + # Each artifact contains Standard JSON output under the `output` key. + # Process it into a dict of the form `{"": {"": }}`, + # Note that one Hardhat artifact often represents multiple input files. + jq '.output.contracts | to_entries[] | { + "\(.key)": .value | to_entries[] | { + "\(.key)": (.value.evm.bytecode.object | length / 2) + } + }' "$artifact" + done +} + +function combine_artifact_json +{ + # Combine all dicts into a list with `jq --slurp` and then use `reduce` to merge them into one + # big dict with keys of the form `":"`. Then run jq again to filter out items + # with zero size and put the rest under under a top-level `bytecode_size` key. Also add another + # key with total bytecode size. + # NOTE: The extra inner `bytecode_size` key is there only to make diffs more readable. + cat - | + jq --slurp 'reduce (.[] | to_entries[]) as {$key, $value} ({}; . + { + ($key + ":" + ($value | to_entries[].key)): { + bytecode_size: $value | to_entries[].value + } + })' | + jq --indent 4 --sort-keys '{ + bytecode_size: [. | to_entries[] | select(.value.bytecode_size > 0)] | from_entries, + total_bytecode_size: (reduce (. | to_entries[]) as {$key, $value} (0; . + $value.bytecode_size)) + }' +} + +function project_info_json +{ + local project_url="$1" + + echo "{" + echo " \"project\": {" + # NOTE: Given that we clone with `--depth 1`, we'll only get useful output out of `git describe` + # if we directly check out a tag. Still better than nothing. + echo " \"version\": \"$(git describe --always)\"," + echo " \"commit\": \"$(git rev-parse HEAD)\"," + echo " \"url\": \"${project_url}\"" + echo " }" + echo "}" +} + +function store_benchmark_report +{ + local framework="$1" + local project_name="$2" + local project_url="$3" + local preset="$4" + + [[ $framework == truffle || $framework == hardhat ]] || assertFail + [[ " ${AVAILABLE_PRESETS[*]} " == *" $preset "* ]] || assertFail + + local report_dir="${REPO_ROOT}/reports/externalTests" + local output_file="${report_dir}/benchmark-${project_name}-${preset}.json" + mkdir -p "$report_dir" + + { + if [[ -e $(gas_report_path "$preset") ]]; then + gas_report_to_json < "$(gas_report_path "$preset")" + fi + + "bytecode_size_json_from_${framework}_artifacts" | combine_artifact_json + project_info_json "$project_url" + } | jq --slurp "{\"${project_name}\": {\"${preset}\": add}}" --indent 4 --sort-keys > "$output_file" +} diff --git a/compiler/scripts/externalTests/runners/base.py b/compiler/scripts/externalTests/runners/base.py new file mode 100644 index 00000000..a79ed534 --- /dev/null +++ b/compiler/scripts/externalTests/runners/base.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + +# ------------------------------------------------------------------------------ +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2023 solidity contributors. +# ------------------------------------------------------------------------------ + +import os +import subprocess +from abc import ABCMeta +from abc import abstractmethod +from dataclasses import dataclass +from dataclasses import field +from pathlib import Path +from shutil import rmtree +from tempfile import mkdtemp +from textwrap import dedent +from typing import List +from typing import Set + +from test_helpers import download_project +from test_helpers import get_solc_short_version +from test_helpers import parse_command_line +from test_helpers import parse_custom_presets +from test_helpers import parse_solc_version +from test_helpers import replace_version_pragmas +from test_helpers import settings_from_preset +from test_helpers import SettingsPreset + +CURRENT_EVM_VERSION: str = "shanghai" + +@dataclass +class TestConfig: + name: str + repo_url: str + ref_type: str + ref: str + compile_only_presets: List[SettingsPreset] = field(default_factory=list) + settings_presets: List[SettingsPreset] = field(default_factory=lambda: list(SettingsPreset)) + evm_version: str = field(default=CURRENT_EVM_VERSION) + + def selected_presets(self) -> Set[SettingsPreset]: + return set(self.compile_only_presets + self.settings_presets) + + +class BaseRunner(metaclass=ABCMeta): + config: TestConfig + solc_binary_type: str + solc_binary_path: Path + presets: Set[SettingsPreset] + + def __init__(self, argv, config: TestConfig): + args = parse_command_line(f"{config.name} external tests", argv) + self.config = config + self.solc_binary_type = args.solc_binary_type + self.solc_binary_path = args.solc_binary_path + self.presets = parse_custom_presets(args.selected_presets) if args.selected_presets else config.selected_presets() + self.env = os.environ.copy() + self.tmp_dir = mkdtemp(prefix=f"ext-test-{config.name}-") + self.test_dir = Path(self.tmp_dir) / "ext" + + def setup_solc(self) -> str: + if self.solc_binary_type == "solcjs": + # TODO: add support to solc-js + raise NotImplementedError() + print("Setting up solc...") + solc_version_output = subprocess.check_output( + [self.solc_binary_path, "--version"], + shell=False, + encoding="utf-8" + ).split(":")[1] + return parse_solc_version(solc_version_output) + + @staticmethod + def enter_test_dir(fn): + """Run a function inside the test directory""" + + previous_dir = os.getcwd() + def f(self, *args, **kwargs): + try: + assert self.test_dir is not None + os.chdir(self.test_dir) + return fn(self, *args, **kwargs) + finally: + # Restore the previous directory after execute fn + os.chdir(previous_dir) + return f + + def setup_environment(self): + """Configure the project build environment""" + print("Configuring Runner building environment...") + replace_version_pragmas(self.test_dir) + + @enter_test_dir + def clean(self): + """Clean temporary directories""" + rmtree(self.tmp_dir) + + @enter_test_dir + @abstractmethod + def configure(self): + raise NotImplementedError() + + @enter_test_dir + @abstractmethod + def compile(self, preset: SettingsPreset): + raise NotImplementedError() + + @enter_test_dir + @abstractmethod + def run_test(self): + raise NotImplementedError() + +def run_test(runner: BaseRunner): + print(f"Testing {runner.config.name}...\n===========================") + print(f"Selected settings presets: {' '.join(p.value for p in runner.presets)}") + + # Configure solc compiler + solc_version = runner.setup_solc() + print(f"Using compiler version {solc_version}") + + # Download project + download_project(runner.test_dir, runner.config.repo_url, runner.config.ref_type, runner.config.ref) + + # Configure run environment + runner.setup_environment() + + # Configure TestRunner instance + print(dedent(f"""\ + Configuring runner's profiles with: + ------------------------------------- + Binary type: {runner.solc_binary_type} + Compiler path: {runner.solc_binary_path} + ------------------------------------- + """)) + runner.configure() + for preset in runner.presets: + print("Running compile function...") + settings = settings_from_preset(preset, runner.config.evm_version) + print(dedent(f"""\ + ------------------------------------- + Settings preset: {preset.value} + Settings: {settings} + EVM version: {runner.config.evm_version} + Compiler version: {get_solc_short_version(solc_version)} + Compiler version (full): {solc_version} + ------------------------------------- + """)) + runner.compile(preset) + # TODO: COMPILE_ONLY should be a command-line option + if os.environ.get("COMPILE_ONLY") == "1" or preset in runner.config.compile_only_presets: + print("Skipping test function...") + else: + print("Running test function...") + runner.run_test() + # TODO: store_benchmark_report + runner.clean() + print("Done.") diff --git a/compiler/scripts/externalTests/runners/foundry.py b/compiler/scripts/externalTests/runners/foundry.py new file mode 100644 index 00000000..3d35f1ac --- /dev/null +++ b/compiler/scripts/externalTests/runners/foundry.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +# ------------------------------------------------------------------------------ +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2023 solidity contributors. +# ------------------------------------------------------------------------------ + +import os +import re +import subprocess +from shutil import which +from textwrap import dedent +from typing import Optional + +from runners.base import BaseRunner +from test_helpers import SettingsPreset +from test_helpers import settings_from_preset + +def run_forge_command(command: str, env: Optional[dict] = None): + subprocess.run( + command.split(), + env=env if env is not None else os.environ.copy(), + check=True + ) + + +class FoundryRunner(BaseRunner): + """Configure and run Foundry-based projects""" + + FOUNDRY_CONFIG_FILE = "foundry.toml" + + def setup_environment(self): + super().setup_environment() + if which("forge") is None: + raise RuntimeError("Forge not found.") + + @staticmethod + def profile_name(preset: SettingsPreset): + """Returns foundry profile name""" + # Replace - or + by underscore to avoid invalid toml syntax + return re.sub(r"(\-|\+)+", "_", preset.value) + + @staticmethod + def profile_section(profile_fields: dict) -> str: + return dedent("""\ + [profile.{name}] + gas_reports = ["*"] + auto_detect_solc = false + solc = "{solc}" + evm_version = "{evm_version}" + optimizer = {optimizer} + via_ir = {via_ir} + + [profile.{name}.optimizer_details] + yul = {yul} + """).format(**profile_fields) + + def setup_presets_profiles(self): + """Configure forge tests profiles""" + + profiles = [] + for preset in self.presets: + settings = settings_from_preset(preset, self.config.evm_version) + profiles.append(self.profile_section({ + "name": self.profile_name(preset), + "solc": self.solc_binary_path, + "evm_version": self.config.evm_version, + "optimizer": str(settings["optimizer"]["enabled"]).lower(), + "via_ir": str(settings["viaIR"]).lower(), + "yul": str(settings["optimizer"]["details"]["yul"]).lower(), + })) + + with open( + file=self.test_dir / self.FOUNDRY_CONFIG_FILE, + mode="a", + encoding="utf-8", + ) as f: + for profile in profiles: + f.write(profile) + + @BaseRunner.enter_test_dir + def configure(self): + """Install project dependencies""" + self.setup_presets_profiles() + run_forge_command("forge install", self.env) + + @BaseRunner.enter_test_dir + def compile(self, preset: SettingsPreset): + """Compile project""" + + # Set the Foundry profile environment variable + self.env.update({"FOUNDRY_PROFILE": self.profile_name(preset)}) + run_forge_command("forge build", self.env) + + @BaseRunner.enter_test_dir + def run_test(self): + """Run project tests""" + + run_forge_command("forge test --gas-report", self.env) diff --git a/compiler/scripts/externalTests/test_helpers.py b/compiler/scripts/externalTests/test_helpers.py new file mode 100644 index 00000000..9a573d50 --- /dev/null +++ b/compiler/scripts/externalTests/test_helpers.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 + +# ------------------------------------------------------------------------------ +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2023 solidity contributors. +# ------------------------------------------------------------------------------ + +import os +import re +import subprocess +import sys +from argparse import ArgumentParser +from enum import Enum +from pathlib import Path +from typing import List +from typing import Set + +# Our scripts/ is not a proper Python package so we need to modify PYTHONPATH to import from it +# pragma pylint: disable=import-error,wrong-import-position +PROJECT_ROOT = Path(__file__).parents[2] +sys.path.insert(0, f"{PROJECT_ROOT}/scripts/common") + +from git_helpers import git_commit_hash + +SOLC_FULL_VERSION_REGEX = re.compile(r"^[a-zA-Z: ]*(.*)$") +SOLC_SHORT_VERSION_REGEX = re.compile(r"^([0-9.]+).*\+|\-$") + + +class SettingsPreset(Enum): + LEGACY_NO_OPTIMIZE = 'legacy-no-optimize' + IR_NO_OPTIMIZE = 'ir-no-optimize' + LEGACY_OPTIMIZE_EVM_ONLY = 'legacy-optimize-evm-only' + IR_OPTIMIZE_EVM_ONLY = 'ir-optimize-evm-only' + LEGACY_OPTIMIZE_EVM_YUL = 'legacy-optimize-evm+yul' + IR_OPTIMIZE_EVM_YUL = 'ir-optimize-evm+yul' + + +def compiler_settings(evm_version: str, via_ir: bool = False, optimizer: bool = False, yul: bool = False) -> dict: + return { + "optimizer": {"enabled": optimizer, "details": {"yul": yul}}, + "evmVersion": evm_version, + "viaIR": via_ir, + } + + +def settings_from_preset(preset: SettingsPreset, evm_version: str) -> dict: + return { + SettingsPreset.LEGACY_NO_OPTIMIZE: compiler_settings(evm_version), + SettingsPreset.IR_NO_OPTIMIZE: compiler_settings(evm_version, via_ir=True), + SettingsPreset.LEGACY_OPTIMIZE_EVM_ONLY: compiler_settings(evm_version, optimizer=True), + SettingsPreset.IR_OPTIMIZE_EVM_ONLY: compiler_settings(evm_version, via_ir=True, optimizer=True), + SettingsPreset.LEGACY_OPTIMIZE_EVM_YUL: compiler_settings(evm_version, optimizer=True, yul=True), + SettingsPreset.IR_OPTIMIZE_EVM_YUL: compiler_settings(evm_version, via_ir=True, optimizer=True, yul=True), + }[preset] + + +def parse_custom_presets(presets: List[str]) -> Set[SettingsPreset]: + return {SettingsPreset(p) for p in presets} + +def parse_command_line(description: str, args: List[str]): + arg_parser = ArgumentParser(description) + arg_parser.add_argument( + "solc_binary_type", + metavar="solc-binary-type", + type=str, + default="native", + choices=["native", "solcjs"], + help="""Solidity compiler binary type""", + ) + arg_parser.add_argument( + "solc_binary_path", + metavar="solc-binary-path", + type=Path, + help="""Path to solc binary""", + ) + arg_parser.add_argument( + "selected_presets", + metavar="selected-presets", + help="""List of compiler settings presets""", + nargs='*', + ) + return arg_parser.parse_args(args) + + +def download_project(test_dir: Path, repo_url: str, ref_type: str = "branch", ref: str = "master"): + assert ref_type in ("commit", "branch", "tag") + + print(f"Cloning {ref_type} {ref} of {repo_url}...") + if ref_type == "commit": + os.mkdir(test_dir) + os.chdir(test_dir) + subprocess.run(["git", "init"], check=True) + subprocess.run(["git", "remote", "add", "origin", repo_url], check=True) + subprocess.run(["git", "fetch", "--depth", "1", "origin", ref], check=True) + subprocess.run(["git", "reset", "--hard", "FETCH_HEAD"], check=True) + else: + os.chdir(test_dir.parent) + subprocess.run(["git", "clone", "--no-progress", "--depth", "1", repo_url, "-b", ref, test_dir.resolve()], check=True) + if not test_dir.exists(): + raise RuntimeError("Failed to clone the project.") + os.chdir(test_dir) + + if (test_dir / ".gitmodules").exists(): + subprocess.run(["git", "submodule", "update", "--init"], check=True) + + print(f"Current commit hash: {git_commit_hash()}") + + +def parse_solc_version(solc_version_string: str) -> str: + solc_version_match = re.search(SOLC_FULL_VERSION_REGEX, solc_version_string) + if solc_version_match is None: + raise RuntimeError(f"Solc version could not be found in: {solc_version_string}.") + return solc_version_match.group(1) + + +def get_solc_short_version(solc_full_version: str) -> str: + solc_short_version_match = re.search(SOLC_SHORT_VERSION_REGEX, solc_full_version) + if solc_short_version_match is None: + raise RuntimeError(f"Error extracting short version string from: {solc_full_version}.") + return solc_short_version_match.group(1) + + +def store_benchmark_report(self): + raise NotImplementedError() + + +def replace_version_pragmas(test_dir: Path): + """ + Replace fixed-version pragmas (part of Consensys best practice). + Include all directories to also cover node dependencies. + """ + print("Replacing fixed-version pragmas...") + for source in test_dir.glob("**/*.sol"): + content = source.read_text(encoding="utf-8") + content = re.sub(r"pragma solidity [^;]+;", r"pragma solidity >=0.0;", content) + with open(source, "w", encoding="utf-8") as f: + f.write(content) diff --git a/compiler/scripts/externalTests/update_external_repos.sh b/compiler/scripts/externalTests/update_external_repos.sh index 2270691e..8a25f379 100755 --- a/compiler/scripts/externalTests/update_external_repos.sh +++ b/compiler/scripts/externalTests/update_external_repos.sh @@ -80,10 +80,9 @@ cd "$target_dir" clone_repo brinktrade brink-core clone_repo dapphub dappsys-monolithic clone_repo element-fi elf-contracts -clone_repo ensdomains ens clone_repo ensdomains ens-contracts clone_repo euler-xyz euler-contracts -clone_repo gnosis gp-v2-contracts +clone_repo cowprotocol contracts gp2-contracts clone_repo gnosis mock-contract clone_repo gnosis util-contracts clone_repo JoinColony colonyNetwork @@ -102,10 +101,9 @@ clone_repo yieldprotocol yield-liquidator-v2 sync_branch brink-core master sync_branch dappsys-monolithic master sync_branch elf-contracts main -sync_branch ens master sync_branch ens-contracts master sync_branch euler-contracts master -sync_branch gp-v2-contracts main +sync_branch gp2-contracts main sync_branch mock-contract master sync_branch util-contracts main sync_branch colonyNetwork develop diff --git a/compiler/scripts/gas_diff_stats.py b/compiler/scripts/gas_diff_stats.py index 56cd0cd4..234d42df 100755 --- a/compiler/scripts/gas_diff_stats.py +++ b/compiler/scripts/gas_diff_stats.py @@ -75,7 +75,7 @@ def collect_statistics(lines) -> (int, int, int, int, int, int): """ if not lines: - raise Exception("Empty list") + raise RuntimeError("Empty list") def try_parse(line): try: diff --git a/compiler/scripts/install_deps.ps1 b/compiler/scripts/install_deps.ps1 index e7726993..26a9e116 100644 --- a/compiler/scripts/install_deps.ps1 +++ b/compiler/scripts/install_deps.ps1 @@ -6,23 +6,26 @@ $progressPreference = "silentlyContinue" if ( -not (Test-Path "$PSScriptRoot\..\deps\boost") ) { New-Item -ItemType Directory -Force -Path "$PSScriptRoot\..\deps" - Invoke-WebRequest -URI "https://github.com/Kitware/CMake/releases/download/v3.18.2/cmake-3.18.2-win64-x64.zip" -OutFile cmake.zip - if ((Get-FileHash cmake.zip).Hash -ne "5f4ec834fbd9b62fbf73bc48ed459fa2ea6a86c403106c90fedc2ac76d51612d") { + Invoke-WebRequest -URI "https://github.com/Kitware/CMake/releases/download/v3.27.4/cmake-3.27.4-windows-x86_64.zip" -OutFile cmake.zip + if ((Get-FileHash cmake.zip).Hash -ne "e5e060756444d0b2070328a8821c1ceb62bd6d267aae61bfff06f96c7ec943a6") { throw 'Downloaded CMake source package has wrong checksum.' } tar -xf cmake.zip - mv cmake-3.18.2-win64-x64 "$PSScriptRoot\..\deps\cmake" + mv cmake-3.27.4-windows-x86_64 "$PSScriptRoot\..\deps\cmake" + Remove-Item cmake.zip # FIXME: The default user agent results in Artifactory treating Invoke-WebRequest as a browser # and serving it a page that requires JavaScript. - Invoke-WebRequest -URI "https://boostorg.jfrog.io/artifactory/main/release/1.77.0/source/boost_1_77_0.zip" -OutFile boost.zip -UserAgent "" - if ((Get-FileHash boost.zip).Hash -ne "d2886ceff60c35fc6dc9120e8faa960c1e9535f2d7ce447469eae9836110ea77") { + Invoke-WebRequest -URI "https://boostorg.jfrog.io/artifactory/main/release/1.83.0/source/boost_1_83_0.zip" -OutFile boost.zip -UserAgent "" + if ((Get-FileHash boost.zip).Hash -ne "c86bd9d9eef795b4b0d3802279419fde5221922805b073b9bd822edecb1ca28e") { throw 'Downloaded Boost source package has wrong checksum.' } tar -xf boost.zip - cd boost_1_77_0 + Remove-Item boost.zip + cd boost_1_83_0 .\bootstrap.bat .\b2 -j4 -d0 link=static runtime-link=static variant=release threading=multi address-model=64 --with-filesystem --with-system --with-program_options --with-test --prefix="$PSScriptRoot\..\deps\boost" install if ( -not $? ) { throw "Error building boost." } cd .. + Remove-Item -LiteralPath .\boost_1_83_0 -Force -Recurse } diff --git a/compiler/scripts/install_evmone.ps1 b/compiler/scripts/install_evmone.ps1 index ace3e575..acbacdaf 100644 --- a/compiler/scripts/install_evmone.ps1 +++ b/compiler/scripts/install_evmone.ps1 @@ -3,7 +3,7 @@ $ErrorActionPreference = "Stop" # Needed for Invoke-WebRequest to work via CI. $progressPreference = "silentlyContinue" -Invoke-WebRequest -URI "https://github.com/ethereum/evmone/releases/download/v0.8.0/evmone-0.8.0-windows-amd64.zip" -OutFile "evmone.zip" +Invoke-WebRequest -URI "https://github.com/ethereum/evmone/releases/download/v0.11.0/evmone-0.11.0-windows-amd64.zip" -OutFile "evmone.zip" tar -xf evmone.zip "bin/evmone.dll" mkdir deps mv bin/evmone.dll deps diff --git a/compiler/scripts/install_lib_variable.sh b/compiler/scripts/install_lib_variable.sh deleted file mode 100755 index 08dec8d8..00000000 --- a/compiler/scripts/install_lib_variable.sh +++ /dev/null @@ -1,7 +0,0 @@ - -if [ -z "${TVM_LINKER_LIB_PATH}" ]; then - SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" - LIBPATH=$(dirname $(dirname $SCRIPTPATH))'/lib/stdlib_sol.tvm' - SET_LIBPATH='export TVM_LINKER_LIB_PATH='$LIBPATH - echo $SET_LIBPATH >> ~/.bashrc -fi \ No newline at end of file diff --git a/compiler/scripts/install_obsolete_jsoncpp_1_7_4.sh b/compiler/scripts/install_obsolete_jsoncpp_1_7_4.sh index 825d1a58..f9de0245 100755 --- a/compiler/scripts/install_obsolete_jsoncpp_1_7_4.sh +++ b/compiler/scripts/install_obsolete_jsoncpp_1_7_4.sh @@ -17,7 +17,7 @@ TEMPDIR=$(mktemp -d) cd "jsoncpp-${jsoncpp_version}" mkdir -p build cd build - cmake -DARCHIVE_INSTALL_DIR=. -G "Unix Makefiles" .. + cmake -DCMAKE_OSX_ARCHITECTURES:STRING="x86_64;arm64" -DARCHIVE_INSTALL_DIR=. -G "Unix Makefiles" .. make make install ) diff --git a/compiler/scripts/list_contributors.sh b/compiler/scripts/list_contributors.sh new file mode 100755 index 00000000..4b8f06d7 --- /dev/null +++ b/compiler/scripts/list_contributors.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +# ------------------------------------------------------------------------------ +# Creates a list containing names of people who contributed to the project, for use +# in release notes. The names come from the author field on the commits between +# the current revision and the one specified as argument. +# +# Note that the output often requires extra manual processing to remove entries +# that refer to the same person (diacritics vs no diacritics, name vs nickname, etc.). +# +# Usage: +#