From 18ae0c3d784cb587d50bdfc1f33e200fd97f2676 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 4 Oct 2017 14:23:27 +0100 Subject: SMT enforce variable types --- libsolidity/formal/SMTLib2Interface.cpp | 9 ++++++++- libsolidity/formal/SMTLib2Interface.h | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/libsolidity/formal/SMTLib2Interface.cpp b/libsolidity/formal/SMTLib2Interface.cpp index cbd766fb..c627057a 100644 --- a/libsolidity/formal/SMTLib2Interface.cpp +++ b/libsolidity/formal/SMTLib2Interface.cpp @@ -64,6 +64,8 @@ void SMTLib2Interface::pop() Expression SMTLib2Interface::newFunction(string _name, Sort _domain, Sort _codomain) { + solAssert(!m_variables.count(_name), ""); + m_variables[_name] = SMTVariableType::Function; write( "(declare-fun |" + _name + @@ -78,12 +80,16 @@ Expression SMTLib2Interface::newFunction(string _name, Sort _domain, Sort _codom Expression SMTLib2Interface::newInteger(string _name) { + solAssert(!m_variables.count(_name), ""); + m_variables[_name] = SMTVariableType::Integer; write("(declare-const |" + _name + "| Int)"); return SolverInterface::newInteger(move(_name)); } Expression SMTLib2Interface::newBool(string _name) { + solAssert(!m_variables.count(_name), ""); + m_variables[_name] = SMTVariableType::Bool; write("(declare-const |" + _name + "| Bool)"); return SolverInterface::newBool(std::move(_name)); } @@ -145,7 +151,8 @@ string SMTLib2Interface::checkSatAndGetValuesCommand(vector const& _ for (size_t i = 0; i < _expressionsToEvaluate.size(); i++) { auto const& e = _expressionsToEvaluate.at(i); - // TODO they don't have to be ints... + solAssert(m_variables.count(e.name), ""); + solAssert(m_variables[e.name] == SMTVariableType::Integer, ""); command += "(declare-const |EVALEXPR_" + to_string(i) + "| Int)\n"; command += "(assert (= |EVALEXPR_" + to_string(i) + "| " + toSExpr(e) + "))\n"; } diff --git a/libsolidity/formal/SMTLib2Interface.h b/libsolidity/formal/SMTLib2Interface.h index 63188acd..e827449f 100644 --- a/libsolidity/formal/SMTLib2Interface.h +++ b/libsolidity/formal/SMTLib2Interface.h @@ -68,6 +68,14 @@ private: ReadCallback::Callback m_queryCallback; std::vector m_accumulatedOutput; + + enum class SMTVariableType { + Function, + Integer, + Bool + }; + + std::map m_variables; }; } -- cgit v1.2.3 From cd420dc0bcf35117c34c287e7d258ff1ffd59714 Mon Sep 17 00:00:00 2001 From: Federico Bond Date: Wed, 19 Jul 2017 16:12:49 -0300 Subject: cmdlineTests: print solc output instead of contract code on error --- test/cmdlineTests.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index f12a6686..67018941 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -32,6 +32,8 @@ REPO_ROOT=$(cd $(dirname "$0")/.. && pwd) echo $REPO_ROOT SOLC="$REPO_ROOT/build/solc/solc" +FULLARGS="--optimize --combined-json abi,asm,ast,bin,bin-runtime,clone-bin,compact-format,devdoc,hashes,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc" + echo "Checking that the bug list is up to date..." "$REPO_ROOT"/scripts/update_bugs_by_version.py @@ -43,15 +45,13 @@ function compileFull() { files="$*" set +e - "$SOLC" --optimize \ - --combined-json abi,asm,ast,bin,bin-runtime,clone-bin,compact-format,devdoc,hashes,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc \ - $files >/dev/null 2>&1 + output=$( ("$SOLC" $FULLARGS $files) 2>&1 ) failed=$? set -e if [ $failed -ne 0 ] then echo "Compilation failed on:" - cat $files + echo "$output" false fi } -- cgit v1.2.3 From eea88f33aac58e358e3b95ffcf59fd3e89967b81 Mon Sep 17 00:00:00 2001 From: Federico Bond Date: Wed, 19 Jul 2017 16:27:05 -0300 Subject: Use local variables inside bash functions --- test/cmdlineTests.sh | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index 67018941..d50360b2 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -43,11 +43,13 @@ test "${output//[[:blank:]]/}" = "3" function compileFull() { - files="$*" + local files="$*" + set +e - output=$( ("$SOLC" $FULLARGS $files) 2>&1 ) - failed=$? + local output=$( ("$SOLC" $FULLARGS $files) 2>&1 ) + local failed=$? set -e + if [ $failed -ne 0 ] then echo "Compilation failed on:" @@ -58,14 +60,16 @@ function compileFull() function compileWithoutWarning() { - files="$*" + local files="$*" + set +e - output=$("$SOLC" $files 2>&1) - failed=$? + local output=$("$SOLC" $files 2>&1) + local failed=$? # Remove the pre-release warning from the compiler output output=$(echo "$output" | grep -v 'pre-release') echo "$output" set -e + test -z "$output" -a "$failed" -eq 0 } -- cgit v1.2.3 From ae91510765c4f4e8caf67765bd466562b63498ff Mon Sep 17 00:00:00 2001 From: Federico Bond Date: Thu, 5 Oct 2017 15:46:38 -0300 Subject: Improve cmdline error output --- test/cmdlineTests.sh | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index d50360b2..a249b601 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -41,19 +41,28 @@ echo "Checking that StandardToken.sol, owned.sol and mortal.sol produce bytecode output=$("$REPO_ROOT"/build/solc/solc --bin "$REPO_ROOT"/std/*.sol 2>/dev/null | grep "ffff" | wc -l) test "${output//[[:blank:]]/}" = "3" +function printTask() { echo "$(tput bold)$(tput setaf 2)$1$(tput sgr0)"; } + +function printError() { echo "$(tput setaf 1)$1$(tput sgr0)"; } + function compileFull() { local files="$*" + local output failed set +e - local output=$( ("$SOLC" $FULLARGS $files) 2>&1 ) - local failed=$? + output=$( ("$SOLC" $FULLARGS $files) 2>&1 ) + failed=$? set -e if [ $failed -ne 0 ] then - echo "Compilation failed on:" + printError "Compilation failed on:" echo "$output" + printError "While calling:" + echo "\"$SOLC\" $FULLARGS $files" + printError "Inside directory:" + pwd false fi } @@ -61,10 +70,11 @@ function compileFull() function compileWithoutWarning() { local files="$*" + local output failed set +e - local output=$("$SOLC" $files 2>&1) - local failed=$? + output=$("$SOLC" $files 2>&1) + failed=$? # Remove the pre-release warning from the compiler output output=$(echo "$output" | grep -v 'pre-release') echo "$output" @@ -73,7 +83,7 @@ function compileWithoutWarning() test -z "$output" -a "$failed" -eq 0 } -echo "Compiling various other contracts and libraries..." +printTask "Compiling various other contracts and libraries..." ( cd "$REPO_ROOT"/test/compilationTests/ for dir in * @@ -88,7 +98,7 @@ do done ) -echo "Compiling all files in std and examples..." +printTask "Compiling all files in std and examples..." for f in "$REPO_ROOT"/std/*.sol do @@ -96,7 +106,7 @@ do compileWithoutWarning "$f" done -echo "Compiling all examples from the documentation..." +printTask "Compiling all examples from the documentation..." TMPDIR=$(mktemp -d) ( set -e @@ -113,14 +123,14 @@ TMPDIR=$(mktemp -d) rm -rf "$TMPDIR" echo "Done." -echo "Testing library checksum..." +printTask "Testing library checksum..." echo '' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222 ! echo '' | "$SOLC" --link --libraries a:0x80f20564390eAe531E810af625A22f51385Cd222 2>/dev/null -echo "Testing long library names..." +printTask "Testing long library names..." echo '' | "$SOLC" --link --libraries aveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylonglibraryname:0x90f20564390eAe531E810af625A22f51385Cd222 -echo "Testing overwriting files" +printTask "Testing overwriting files" TMPDIR=$(mktemp -d) ( set -e @@ -133,7 +143,7 @@ TMPDIR=$(mktemp -d) ) rm -rf "$TMPDIR" -echo "Testing soljson via the fuzzer..." +printTask "Testing soljson via the fuzzer..." TMPDIR=$(mktemp -d) ( set -e @@ -147,14 +157,14 @@ TMPDIR=$(mktemp -d) set +e "$REPO_ROOT"/build/test/solfuzzer --quiet < "$f" if [ $? -ne 0 ]; then - echo "Fuzzer failed on:" + printError "Fuzzer failed on:" cat "$f" exit 1 fi "$REPO_ROOT"/build/test/solfuzzer --without-optimizer --quiet < "$f" if [ $? -ne 0 ]; then - echo "Fuzzer (without optimizer) failed on:" + printError "Fuzzer (without optimizer) failed on:" cat "$f" exit 1 fi -- cgit v1.2.3 From c3ec0beba553ccb5653c4d38c32ef9d70e48471d Mon Sep 17 00:00:00 2001 From: William Morriss Date: Sun, 8 Oct 2017 13:24:27 -0700 Subject: remove auctionStart --- docs/solidity-by-example.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index 139c8a42..7b28296f 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -221,7 +221,7 @@ activate themselves. // absolute unix timestamps (seconds since 1970-01-01) // or time periods in seconds. address public beneficiary; - uint public auctionStart; + uint public auctionEnd; uint public biddingTime; // Current state of the auction. @@ -251,7 +251,7 @@ activate themselves. address _beneficiary ) { beneficiary = _beneficiary; - auctionStart = now; + auctionEnd = now + biddingTime; biddingTime = _biddingTime; } @@ -268,7 +268,7 @@ activate themselves. // Revert the call if the bidding // period is over. - require(now <= (auctionStart + biddingTime)); + require(now <= auctionEnd); // If the bid is not higher, send the // money back. @@ -322,7 +322,7 @@ activate themselves. // external contracts. // 1. Conditions - require(now >= (auctionStart + biddingTime)); // auction did not yet end + require(now >= auctionEnd); // auction did not yet end require(!ended); // this function has already been called // 2. Effects @@ -382,7 +382,6 @@ high or low invalid bids. } address public beneficiary; - uint public auctionStart; uint public biddingEnd; uint public revealEnd; bool public ended; @@ -410,7 +409,6 @@ high or low invalid bids. address _beneficiary ) { beneficiary = _beneficiary; - auctionStart = now; biddingEnd = now + _biddingTime; revealEnd = biddingEnd + _revealTime; } -- cgit v1.2.3 From d05b24162f8d88d15601af8f5b3fdb713f3755b4 Mon Sep 17 00:00:00 2001 From: William Morriss Date: Sun, 8 Oct 2017 13:40:18 -0700 Subject: also rm biddingTime --- docs/solidity-by-example.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index 7b28296f..59ab7962 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -222,7 +222,6 @@ activate themselves. // or time periods in seconds. address public beneficiary; uint public auctionEnd; - uint public biddingTime; // Current state of the auction. address public highestBidder; @@ -251,8 +250,7 @@ activate themselves. address _beneficiary ) { beneficiary = _beneficiary; - auctionEnd = now + biddingTime; - biddingTime = _biddingTime; + auctionEnd = now + _biddingTime; } /// Bid on the auction with the value sent -- cgit v1.2.3 From b93a5980eddd6f61417fb9714110fda20c49698b Mon Sep 17 00:00:00 2001 From: rivenhk Date: Wed, 18 Oct 2017 01:59:15 +0800 Subject: added formatting when source snippets is too long --- libsolidity/interface/SourceReferenceFormatter.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/libsolidity/interface/SourceReferenceFormatter.cpp b/libsolidity/interface/SourceReferenceFormatter.cpp index 62d22999..fc55d328 100644 --- a/libsolidity/interface/SourceReferenceFormatter.cpp +++ b/libsolidity/interface/SourceReferenceFormatter.cpp @@ -49,6 +49,21 @@ void SourceReferenceFormatter::printSourceLocation( if (startLine == endLine) { string line = scanner.lineAtPosition(_location->start); + + int locationLength = endColumn - startColumn; + if (locationLength > 150) + { + line = line.substr(0, startColumn) + line.substr(startColumn, 15) + "..." + line.substr(endColumn - 15, 15) + line.substr(endColumn, line.length() - endColumn); + endColumn = startColumn + 33; + locationLength = 33; + } + if (line.length() > 150) + { + line = "..." + line.substr(startColumn, locationLength) + "..."; + startColumn = 3; + endColumn = startColumn + locationLength; + } + _stream << line << endl; for_each( line.cbegin(), -- cgit v1.2.3 From 9acc2ea711060f69e213bbe5af4f1e6b817d77cb Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Thu, 5 Oct 2017 07:20:53 +0000 Subject: Add test for each assembly opcode/instruction in LLL --- test/liblll/Compiler.cpp | 515 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 513 insertions(+), 2 deletions(-) diff --git a/test/liblll/Compiler.cpp b/test/liblll/Compiler.cpp index 77d263b8..ace97e15 100644 --- a/test/liblll/Compiler.cpp +++ b/test/liblll/Compiler.cpp @@ -24,6 +24,7 @@ #include #include #include +#include using namespace std; @@ -37,9 +38,9 @@ namespace test namespace { -bool successCompile(std::string const& _sourceCode) +bool successCompile(string const& _sourceCode) { - std::vector errors; + vector errors; bytes bytecode = eth::compileLLL(_sourceCode, false, &errors); if (!errors.empty()) return false; @@ -138,6 +139,516 @@ BOOST_AUTO_TEST_CASE(disallowed_functional_asm_instructions) BOOST_CHECK(!successCompile("(JUMPDEST)")); } +BOOST_AUTO_TEST_CASE(valid_opcodes_functional) +{ + vector opcodes_bytecode { + "00", + "6000600001", + "6000600002", + "6000600003", + "6000600004", + "6000600005", + "6000600006", + "6000600007", + "60006000600008", + "60006000600009", + "600060000a", + "600060000b", + "6000600010", + "6000600011", + "6000600012", + "6000600013", + "6000600014", + "600015", + "6000600016", + "6000600017", + "6000600018", + "600019", + "600060001a", + "6000600020", + "30", + "600031", + "32", + "33", + "34", + "600035", + "36", + "60006000600037", + "38", + "60006000600039", + "3a", + "60003b", + "60006000600060003c", + "3d", + "6000600060003e", + "600040", + "41", + "42", + "43", + "44", + "45", + "600050", + "600051", + "6000600052", + "6000600053", + "600054", + "6000600055", + "600056", + "6000600057", + "58", + "59", + "5a", + "60ff", + "61ffff", + "62ffffff", + "63ffffffff", + "64ffffffffff", + "65ffffffffffff", + "66ffffffffffffff", + "67ffffffffffffffff", + "68ffffffffffffffffff", + "69ffffffffffffffffffff", + "6affffffffffffffffffffff", + "6bffffffffffffffffffffffff", + "6cffffffffffffffffffffffffff", + "6dffffffffffffffffffffffffffff", + "6effffffffffffffffffffffffffffff", + "6fffffffffffffffffffffffffffffffff", + "70ffffffffffffffffffffffffffffffffff", + "71ffffffffffffffffffffffffffffffffffff", + "72ffffffffffffffffffffffffffffffffffffff", + "73ffffffffffffffffffffffffffffffffffffffff", + "74ffffffffffffffffffffffffffffffffffffffffff", + "75ffffffffffffffffffffffffffffffffffffffffffff", + "76ffffffffffffffffffffffffffffffffffffffffffffff", + "77ffffffffffffffffffffffffffffffffffffffffffffffff", + "78ffffffffffffffffffffffffffffffffffffffffffffffffff", + "79ffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7affffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7cffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "60006000a0", + "600060006000a1", + "6000600060006000a2", + "60006000600060006000a3", + "600060006000600060006000a4", + "600060006000f0", + "6000600060006000600060006000f1", + "6000600060006000600060006000f2", + "60006000f3", + "600060006000600060006000f4", + "600060006000600060006000fa", + "60006000fd", + "fe", + "6000ff" + }; + + vector opcodes_lll { + "{ (STOP) }", + "{ (ADD 0 0) }", + "{ (MUL 0 0) }", + "{ (SUB 0 0) }", + "{ (DIV 0 0) }", + "{ (SDIV 0 0) }", + "{ (MOD 0 0) }", + "{ (SMOD 0 0) }", + "{ (ADDMOD 0 0 0) }", + "{ (MULMOD 0 0 0) }", + "{ (EXP 0 0) }", + "{ (SIGNEXTEND 0 0) }", + "{ (LT 0 0) }", + "{ (GT 0 0) }", + "{ (SLT 0 0) }", + "{ (SGT 0 0) }", + "{ (EQ 0 0) }", + "{ (ISZERO 0) }", + "{ (AND 0 0) }", + "{ (OR 0 0) }", + "{ (XOR 0 0) }", + "{ (NOT 0) }", + "{ (BYTE 0 0) }", + "{ (KECCAK256 0 0) }", + "{ (ADDRESS) }", + "{ (BALANCE 0) }", + "{ (ORIGIN) }", + "{ (CALLER) }", + "{ (CALLVALUE) }", + "{ (CALLDATALOAD 0) }", + "{ (CALLDATASIZE) }", + "{ (CALLDATACOPY 0 0 0) }", + "{ (CODESIZE) }", + "{ (CODECOPY 0 0 0) }", + "{ (GASPRICE) }", + "{ (EXTCODESIZE 0) }", + "{ (EXTCODECOPY 0 0 0 0) }", + "{ (RETURNDATASIZE) }", + "{ (RETURNDATACOPY 0 0 0) }", + "{ (BLOCKHASH 0) }", + "{ (COINBASE) }", + "{ (TIMESTAMP) }", + "{ (NUMBER) }", + "{ (DIFFICULTY) }", + "{ (GASLIMIT) }", + "{ (POP 0) }", + "{ (MLOAD 0) }", + "{ (MSTORE 0 0) }", + "{ (MSTORE8 0 0) }", + "{ (SLOAD 0) }", + "{ (SSTORE 0 0) }", + "{ (JUMP 0) }", + "{ (JUMPI 0 0) }", + "{ (PC) }", + "{ (MSIZE) }", + "{ (GAS) }", + "{ 0xff }", + "{ 0xffff }", + "{ 0xffffff }", + "{ 0xffffffff }", + "{ 0xffffffffff }", + "{ 0xffffffffffff }", + "{ 0xffffffffffffff }", + "{ 0xffffffffffffffff }", + "{ 0xffffffffffffffffff }", + "{ 0xffffffffffffffffffff }", + "{ 0xffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ (LOG0 0 0) }", + "{ (LOG1 0 0 0) }", + "{ (LOG2 0 0 0 0) }", + "{ (LOG3 0 0 0 0 0) }", + "{ (LOG4 0 0 0 0 0 0) }", + "{ (CREATE 0 0 0) }", + "{ (CALL 0 0 0 0 0 0 0) }", + "{ (CALLCODE 0 0 0 0 0 0 0) }", + "{ (RETURN 0 0) }", + "{ (DELEGATECALL 0 0 0 0 0 0) }", + "{ (STATICCALL 0 0 0 0 0 0) }", + "{ (REVERT 0 0) }", + "{ (INVALID) }", + "{ (SELFDESTRUCT 0) }" + }; + + for (size_t i = 0; i < opcodes_bytecode.size(); i++) { + vector errors; + bytes code = eth::compileLLL(opcodes_lll[i], false, &errors); + + BOOST_REQUIRE_MESSAGE(errors.empty(), opcodes_lll[i]); + + BOOST_CHECK_EQUAL(toHex(code), opcodes_bytecode[i]); + } +} + +BOOST_AUTO_TEST_CASE(valid_opcodes_asm) +{ + vector opcodes_bytecode { + "00", + "01", + "02", + "03", + "04", + "05", + "06", + "07", + "08", + "09", + "0a", + "0b", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19", + "1a", + "20", + "30", + "31", + "32", + "33", + "34", + "35", + "36", + "37", + "38", + "39", + "3a", + "3b", + "3c", + "3d", + "3e", + "40", + "41", + "42", + "43", + "44", + "45", + "50", + "51", + "52", + "53", + "54", + "55", + "56", + "57", + "58", + "59", + "5a", + "5b", + "60ff", + "61ffff", + "62ffffff", + "63ffffffff", + "64ffffffffff", + "65ffffffffffff", + "66ffffffffffffff", + "67ffffffffffffffff", + "68ffffffffffffffffff", + "69ffffffffffffffffffff", + "6affffffffffffffffffffff", + "6bffffffffffffffffffffffff", + "6cffffffffffffffffffffffffff", + "6dffffffffffffffffffffffffffff", + "6effffffffffffffffffffffffffffff", + "6fffffffffffffffffffffffffffffffff", + "70ffffffffffffffffffffffffffffffffff", + "71ffffffffffffffffffffffffffffffffffff", + "72ffffffffffffffffffffffffffffffffffffff", + "73ffffffffffffffffffffffffffffffffffffffff", + "74ffffffffffffffffffffffffffffffffffffffffff", + "75ffffffffffffffffffffffffffffffffffffffffffff", + "76ffffffffffffffffffffffffffffffffffffffffffffff", + "77ffffffffffffffffffffffffffffffffffffffffffffffff", + "78ffffffffffffffffffffffffffffffffffffffffffffffffff", + "79ffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7affffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7cffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "80", + "81", + "82", + "83", + "84", + "85", + "86", + "87", + "88", + "89", + "8a", + "8b", + "8c", + "8d", + "8e", + "8f", + "90", + "91", + "92", + "93", + "94", + "95", + "96", + "97", + "98", + "99", + "9a", + "9b", + "9c", + "9d", + "9e", + "9f", + "a0", + "a1", + "a2", + "a3", + "a4", + "f0", + "f1", + "f2", + "f3", + "f4", + "fa", + "fd", + "fe", + "ff" + }; + + vector opcodes_lll { + "{ (asm STOP) }", + "{ (asm ADD) }", + "{ (asm MUL) }", + "{ (asm SUB) }", + "{ (asm DIV) }", + "{ (asm SDIV ) }", + "{ (asm MOD) }", + "{ (asm SMOD) }", + "{ (asm ADDMOD) }", + "{ (asm MULMOD) }", + "{ (asm EXP) }", + "{ (asm SIGNEXTEND) }", + "{ (asm LT) }", + "{ (asm GT) }", + "{ (asm SLT) }", + "{ (asm SGT) }", + "{ (asm EQ) }", + "{ (asm ISZERO) }", + "{ (asm AND) }", + "{ (asm OR) }", + "{ (asm XOR) }", + "{ (asm NOT) }", + "{ (asm BYTE) }", + "{ (asm KECCAK256) }", + "{ (asm ADDRESS) }", + "{ (asm BALANCE) }", + "{ (asm ORIGIN) }", + "{ (asm CALLER) }", + "{ (asm CALLVALUE) }", + "{ (asm CALLDATALOAD) }", + "{ (asm CALLDATASIZE) }", + "{ (asm CALLDATACOPY) }", + "{ (asm CODESIZE) }", + "{ (asm CODECOPY) }", + "{ (asm GASPRICE) }", + "{ (asm EXTCODESIZE)}", + "{ (asm EXTCODECOPY) }", + "{ (asm RETURNDATASIZE) }", + "{ (asm RETURNDATACOPY) }", + "{ (asm BLOCKHASH) }", + "{ (asm COINBASE) }", + "{ (asm TIMESTAMP) }", + "{ (asm NUMBER) }", + "{ (asm DIFFICULTY) }", + "{ (asm GASLIMIT) }", + "{ (asm POP) }", + "{ (asm MLOAD) }", + "{ (asm MSTORE) }", + "{ (asm MSTORE8) }", + "{ (asm SLOAD) }", + "{ (asm SSTORE) }", + "{ (asm JUMP ) }", + "{ (asm JUMPI ) }", + "{ (asm PC) }", + "{ (asm MSIZE) }", + "{ (asm GAS) }", + "{ (asm JUMPDEST) }", + "{ (asm 0xff) }", + "{ (asm 0xffff) }", + "{ (asm 0xffffff) }", + "{ (asm 0xffffffff) }", + "{ (asm 0xffffffffff) }", + "{ (asm 0xffffffffffff) }", + "{ (asm 0xffffffffffffff) }", + "{ (asm 0xffffffffffffffff) }", + "{ (asm 0xffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm DUP1) }", + "{ (asm DUP2) }", + "{ (asm DUP3) }", + "{ (asm DUP4) }", + "{ (asm DUP5) }", + "{ (asm DUP6) }", + "{ (asm DUP7) }", + "{ (asm DUP8) }", + "{ (asm DUP9) }", + "{ (asm DUP10) }", + "{ (asm DUP11) }", + "{ (asm DUP12) }", + "{ (asm DUP13) }", + "{ (asm DUP14) }", + "{ (asm DUP15) }", + "{ (asm DUP16) }", + "{ (asm SWAP1) }", + "{ (asm SWAP2) }", + "{ (asm SWAP3) }", + "{ (asm SWAP4) }", + "{ (asm SWAP5) }", + "{ (asm SWAP6) }", + "{ (asm SWAP7) }", + "{ (asm SWAP8) }", + "{ (asm SWAP9) }", + "{ (asm SWAP10) }", + "{ (asm SWAP11) }", + "{ (asm SWAP12) }", + "{ (asm SWAP13) }", + "{ (asm SWAP14) }", + "{ (asm SWAP15) }", + "{ (asm SWAP16) }", + "{ (asm LOG0) }", + "{ (asm LOG1) }", + "{ (asm LOG2) }", + "{ (asm LOG3) }", + "{ (asm LOG4) }", + "{ (asm CREATE) }", + "{ (asm CALL) }", + "{ (asm CALLCODE) }", + "{ (asm RETURN) }", + "{ (asm DELEGATECALL) }", + "{ (asm STATICCALL) }", + "{ (asm REVERT) }", + "{ (asm INVALID) }", + "{ (asm SELFDESTRUCT) }" + }; + + for (size_t i = 0; i < opcodes_bytecode.size(); i++) { + vector errors; + bytes code = eth::compileLLL(opcodes_lll[i], false, &errors); + + BOOST_REQUIRE_MESSAGE(errors.empty(), opcodes_lll[i]); + + BOOST_CHECK_EQUAL(toHex(code), opcodes_bytecode[i]); + } +} + BOOST_AUTO_TEST_SUITE_END() } -- cgit v1.2.3 From f67d34e4ec379aeed3ee2c05227a228c748b4d9d Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 18 Oct 2017 12:32:34 +0100 Subject: Indentation fixes in lllc --- lllc/main.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/lllc/main.cpp b/lllc/main.cpp index 912ce16a..8b990eec 100644 --- a/lllc/main.cpp +++ b/lllc/main.cpp @@ -35,15 +35,15 @@ using namespace dev::solidity; using namespace dev::eth; static string const VersionString = - string(ETH_PROJECT_VERSION) + - (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) + - (string(SOL_VERSION_BUILDINFO).empty() ? "" : "+" + string(SOL_VERSION_BUILDINFO)); + string(ETH_PROJECT_VERSION) + + (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) + + (string(SOL_VERSION_BUILDINFO).empty() ? "" : "+" + string(SOL_VERSION_BUILDINFO)); static void help() { cout << "Usage lllc [OPTIONS] " << endl - << "Options:" << endl + << "Options:" << endl << " -b,--binary Parse, compile and assemble; output byte code in binary." << endl << " -x,--hex Parse, compile and assemble; output byte code in hex." << endl << " -a,--assembly Only parse and compile; show assembly." << endl @@ -51,12 +51,12 @@ static void help() << " -o,--optimise Turn on/off the optimiser; off by default." << endl << " -h,--help Show this help message and exit." << endl << " -V,--version Show the version and exit." << endl; - exit(0); + exit(0); } static void version() { - cout << "LLLC, the Lovely Little Language Compiler " << endl; + cout << "LLLC, the Lovely Little Language Compiler" << endl; cout << "Version: " << VersionString << endl; exit(0); } @@ -131,7 +131,9 @@ int main(int argc, char** argv) vector errors; if (src.empty()) + { errors.push_back("Empty file."); + } else if (mode == Disassemble) { cout << disassemble(fromHex(src)) << endl; @@ -145,12 +147,17 @@ int main(int argc, char** argv) cout.write((char const*)bs.data(), bs.size()); } else if (mode == ParseTree) + { cout << parseLLL(src) << endl; + } else if (mode == Assembly) + { cout << compileLLLToAsm(src, optimise ? true : false, &errors, contentsString) << endl; + } + for (auto const& i: errors) cerr << i << endl; - if ( errors.size() ) + if (errors.size()) return 1; return 0; } -- cgit v1.2.3 From 7186e142b8ea546d98dc8ddb630da47362be8b0a Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 18 Oct 2017 12:34:29 +0100 Subject: Rename contentsString to readFileAsString --- libdevcore/CommonIO.cpp | 6 +++--- libdevcore/CommonIO.h | 2 +- lllc/main.cpp | 6 +++--- solc/CommandLineInterface.cpp | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libdevcore/CommonIO.cpp b/libdevcore/CommonIO.cpp index 5d47937b..eb7af83e 100644 --- a/libdevcore/CommonIO.cpp +++ b/libdevcore/CommonIO.cpp @@ -39,7 +39,7 @@ namespace { template -inline _T contentsGeneric(std::string const& _file) +inline _T readFile(std::string const& _file) { _T ret; size_t const c_elementSize = sizeof(typename _T::value_type); @@ -61,9 +61,9 @@ inline _T contentsGeneric(std::string const& _file) } -string dev::contentsString(string const& _file) +string dev::readFileAsString(string const& _file) { - return contentsGeneric(_file); + return readFile(_file); } void dev::writeFile(std::string const& _file, bytesConstRef _data, bool _writeDeleteRename) diff --git a/libdevcore/CommonIO.h b/libdevcore/CommonIO.h index d84362cf..0c702818 100644 --- a/libdevcore/CommonIO.h +++ b/libdevcore/CommonIO.h @@ -32,7 +32,7 @@ namespace dev /// Retrieve and returns the contents of the given file as a std::string. /// If the file doesn't exist or isn't readable, returns an empty container / bytes. -std::string contentsString(std::string const& _file); +std::string readFileAsString(std::string const& _file); /// Write the given binary data into the given file, replacing the file if it pre-exists. /// Throws exception on error. diff --git a/lllc/main.cpp b/lllc/main.cpp index 8b990eec..4ff204ba 100644 --- a/lllc/main.cpp +++ b/lllc/main.cpp @@ -127,7 +127,7 @@ int main(int argc, char** argv) } } else - src = contentsString(infile); + src = readFileAsString(infile); vector errors; if (src.empty()) @@ -140,7 +140,7 @@ int main(int argc, char** argv) } else if (mode == Binary || mode == Hex) { - auto bs = compileLLL(src, optimise ? true : false, &errors, contentsString); + auto bs = compileLLL(src, optimise ? true : false, &errors, readFileAsString); if (mode == Hex) cout << toHex(bs) << endl; else if (mode == Binary) @@ -152,7 +152,7 @@ int main(int argc, char** argv) } else if (mode == Assembly) { - cout << compileLLLToAsm(src, optimise ? true : false, &errors, contentsString) << endl; + cout << compileLLLToAsm(src, optimise ? true : false, &errors, readFileAsString) << endl; } for (auto const& i: errors) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 1686dc2e..b4c9fe82 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -424,7 +424,7 @@ void CommandLineInterface::readInputFilesAndConfigureRemappings() continue; } - m_sourceCodes[infile.string()] = dev::contentsString(infile.string()); + m_sourceCodes[infile.string()] = dev::readFileAsString(infile.string()); path = boost::filesystem::canonical(infile).string(); } m_allowedDirectories.push_back(boost::filesystem::path(path).remove_filename()); @@ -447,7 +447,7 @@ bool CommandLineInterface::parseLibraryOption(string const& _input) try { if (fs::is_regular_file(_input)) - data = contentsString(_input); + data = readFileAsString(_input); } catch (fs::filesystem_error const&) { @@ -698,7 +698,7 @@ bool CommandLineInterface::processInput() return ReadCallback::Result{false, "Not a valid file."}; else { - auto contents = dev::contentsString(canonicalPath.string()); + auto contents = dev::readFileAsString(canonicalPath.string()); m_sourceCodes[path.string()] = contents; return ReadCallback::Result{true, contents}; } -- cgit v1.2.3 From 6f2865228cb02f0ba0b58990a9d3006dbe2692c6 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 18 Oct 2017 12:54:47 +0100 Subject: Add readStandardInput helper --- libdevcore/CommonIO.cpp | 14 ++++++++++++++ libdevcore/CommonIO.h | 3 +++ lllc/main.cpp | 9 +-------- solc/CommandLineInterface.cpp | 17 ++--------------- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/libdevcore/CommonIO.cpp b/libdevcore/CommonIO.cpp index eb7af83e..8c7e08f6 100644 --- a/libdevcore/CommonIO.cpp +++ b/libdevcore/CommonIO.cpp @@ -66,6 +66,20 @@ string dev::readFileAsString(string const& _file) return readFile(_file); } +string dev::readStandardInput() +{ + string ret; + while (!cin.eof()) + { + string tmp; + // NOTE: this will read until EOF or NL + getline(cin, tmp); + ret.append(tmp); + ret.append("\n"); + } + return ret; +} + void dev::writeFile(std::string const& _file, bytesConstRef _data, bool _writeDeleteRename) { namespace fs = boost::filesystem; diff --git a/libdevcore/CommonIO.h b/libdevcore/CommonIO.h index 0c702818..33769ec3 100644 --- a/libdevcore/CommonIO.h +++ b/libdevcore/CommonIO.h @@ -34,6 +34,9 @@ namespace dev /// If the file doesn't exist or isn't readable, returns an empty container / bytes. std::string readFileAsString(std::string const& _file); +/// Retrieve and returns the contents of standard input (until EOF). +std::string readStandardInput(); + /// Write the given binary data into the given file, replacing the file if it pre-exists. /// Throws exception on error. /// @param _writeDeleteRename useful not to lose any data: If set, first writes to another file in diff --git a/lllc/main.cpp b/lllc/main.cpp index 4ff204ba..5679bc2b 100644 --- a/lllc/main.cpp +++ b/lllc/main.cpp @@ -118,14 +118,7 @@ int main(int argc, char** argv) string src; if (infile.empty()) - { - string s; - while (!cin.eof()) - { - getline(cin, s); - src.append(s); - } - } + src = readStandardInput(); else src = readFileAsString(infile); diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index b4c9fe82..fe1461b4 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -430,14 +430,7 @@ void CommandLineInterface::readInputFilesAndConfigureRemappings() m_allowedDirectories.push_back(boost::filesystem::path(path).remove_filename()); } if (addStdin) - { - string s; - while (!cin.eof()) - { - getline(cin, s); - m_sourceCodes[g_stdinFileName].append(s + '\n'); - } - } + m_sourceCodes[g_stdinFileName] = dev::readStandardInput(); } bool CommandLineInterface::parseLibraryOption(string const& _input) @@ -731,13 +724,7 @@ bool CommandLineInterface::processInput() if (m_args.count(g_argStandardJSON)) { - string input; - while (!cin.eof()) - { - string tmp; - getline(cin, tmp); - input.append(tmp + "\n"); - } + string input = dev::readStandardInput(); StandardCompiler compiler(fileReader); cout << compiler.compile(input) << endl; return true; -- cgit v1.2.3 From 38ad5bfac5ef9dd6094cabe1cb0d38631c31da1a Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 18 Oct 2017 15:43:37 +0200 Subject: Increment version to 0.4.19. --- CMakeLists.txt | 2 +- Changelog.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 537a9521..24bea3b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.4.18") +set(PROJECT_VERSION "0.4.19") project(solidity VERSION ${PROJECT_VERSION}) option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF) diff --git a/Changelog.md b/Changelog.md index a8a61363..cb81d975 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,9 @@ +### 0.4.19 (unreleased) + +Features: + +Bugfixes: + ### 0.4.18 (2017-10-18) Features: -- cgit v1.2.3 From d53c44a066ad70a34a5c7d858e1cb6afa1cbda3a Mon Sep 17 00:00:00 2001 From: rivenhk Date: Wed, 18 Oct 2017 22:03:33 +0800 Subject: updating formatting when source snippets is too long --- libsolidity/interface/SourceReferenceFormatter.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libsolidity/interface/SourceReferenceFormatter.cpp b/libsolidity/interface/SourceReferenceFormatter.cpp index fc55d328..256adccc 100644 --- a/libsolidity/interface/SourceReferenceFormatter.cpp +++ b/libsolidity/interface/SourceReferenceFormatter.cpp @@ -53,14 +53,14 @@ void SourceReferenceFormatter::printSourceLocation( int locationLength = endColumn - startColumn; if (locationLength > 150) { - line = line.substr(0, startColumn) + line.substr(startColumn, 15) + "..." + line.substr(endColumn - 15, 15) + line.substr(endColumn, line.length() - endColumn); - endColumn = startColumn + 33; - locationLength = 33; + line = line.substr(0, startColumn + 75) + " ... " + line.substr(endColumn); + endColumn = startColumn + 80; + locationLength = 80; } if (line.length() > 150) { - line = "..." + line.substr(startColumn, locationLength) + "..."; - startColumn = 3; + line = " ... " + line.substr(startColumn, locationLength) + " ... "; + startColumn = 5; endColumn = startColumn + locationLength; } -- cgit v1.2.3 From 55a7828848a9473ddce4698431adaa536fb709f1 Mon Sep 17 00:00:00 2001 From: Boris Kostenko Date: Thu, 19 Oct 2017 01:10:12 +0300 Subject: Accent on ext.deps in install docs (issue #3048) --- docs/installing-solidity.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 71607745..57c65911 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -230,6 +230,7 @@ Or, on Windows: Command-Line Build ------------------ +**Be sure to install External Dependencies before build.** Solidity project uses CMake to configure the build. Building Solidity is quite similar on Linux, macOS and other Unices: -- cgit v1.2.3 From 7a4c165518ec85577ebf0e231dd9cdbe0519a237 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 18 Oct 2017 23:18:11 +0100 Subject: Remove unused variable in Z3 --- libsolidity/formal/Z3Interface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index ab28baa3..6111b2c8 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -98,7 +98,7 @@ pair> Z3Interface::check(vector const& _ values.push_back(toString(m.eval(toZ3Expr(e)))); } } - catch (z3::exception const& _e) + catch (z3::exception const&) { result = CheckResult::ERROR; values.clear(); -- cgit v1.2.3 From 039cc25b1fd875e9f4cfd0f0649f2b4ed67640e1 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 21 Aug 2017 20:35:27 +0100 Subject: Always require a contract/sourceName in CompilerStack --- libsolidity/interface/CompilerStack.h | 28 ++++++++++++++-------------- solc/CommandLineInterface.cpp | 5 +++-- test/libsolidity/AnalysisFramework.cpp | 2 +- test/libsolidity/GasMeter.cpp | 12 ++++++------ 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index c567ac2c..f992ebc8 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -155,10 +155,10 @@ public: std::map sourceIndices() const; /// @returns the previously used scanner, useful for counting lines during error reporting. - Scanner const& scanner(std::string const& _sourceName = "") const; + Scanner const& scanner(std::string const& _sourceName) const; /// @returns the parsed source unit with the supplied name. - SourceUnit const& ast(std::string const& _sourceName = "") const; + SourceUnit const& ast(std::string const& _sourceName) const; /// Helper function for logs printing. Do only use in error cases, it's quite expensive. /// line and columns are numbered starting from 1 with following order: @@ -172,44 +172,44 @@ public: std::string const filesystemFriendlyName(std::string const& _contractName) const; /// @returns the assembled object for a contract. - eth::LinkerObject const& object(std::string const& _contractName = "") const; + eth::LinkerObject const& object(std::string const& _contractName) const; /// @returns the runtime object for the contract. - eth::LinkerObject const& runtimeObject(std::string const& _contractName = "") const; + eth::LinkerObject const& runtimeObject(std::string const& _contractName) const; /// @returns the bytecode of a contract that uses an already deployed contract via DELEGATECALL. /// The returned bytes will contain a sequence of 20 bytes of the format "XXX...XXX" which have to /// substituted by the actual address. Note that this sequence starts end ends in three X /// characters but can contain anything in between. - eth::LinkerObject const& cloneObject(std::string const& _contractName = "") const; + eth::LinkerObject const& cloneObject(std::string const& _contractName) const; /// @returns normal contract assembly items - eth::AssemblyItems const* assemblyItems(std::string const& _contractName = "") const; + eth::AssemblyItems const* assemblyItems(std::string const& _contractName) const; /// @returns runtime contract assembly items - eth::AssemblyItems const* runtimeAssemblyItems(std::string const& _contractName = "") const; + eth::AssemblyItems const* runtimeAssemblyItems(std::string const& _contractName) const; /// @returns the string that provides a mapping between bytecode and sourcecode or a nullptr /// if the contract does not (yet) have bytecode. - std::string const* sourceMapping(std::string const& _contractName = "") const; + std::string const* sourceMapping(std::string const& _contractName) const; /// @returns the string that provides a mapping between runtime bytecode and sourcecode. /// if the contract does not (yet) have bytecode. - std::string const* runtimeSourceMapping(std::string const& _contractName = "") const; + std::string const* runtimeSourceMapping(std::string const& _contractName) const; /// @return a verbose text representation of the assembly. /// @arg _sourceCodes is the map of input files to source code strings /// Prerequisite: Successful compilation. - std::string assemblyString(std::string const& _contractName = "", StringMap _sourceCodes = StringMap()) const; + std::string assemblyString(std::string const& _contractName, StringMap _sourceCodes = StringMap()) const; /// @returns a JSON representation of the assembly. /// @arg _sourceCodes is the map of input files to source code strings /// Prerequisite: Successful compilation. - Json::Value assemblyJSON(std::string const& _contractName = "", StringMap _sourceCodes = StringMap()) const; + Json::Value assemblyJSON(std::string const& _contractName, StringMap _sourceCodes = StringMap()) const; /// @returns a JSON representing the contract ABI. /// Prerequisite: Successful call to parse or compile. - Json::Value const& contractABI(std::string const& _contractName = "") const; + Json::Value const& contractABI(std::string const& _contractName) const; /// @returns a JSON representing the contract's user documentation. /// Prerequisite: Successful call to parse or compile. @@ -276,8 +276,8 @@ private: ); void link(); - Contract const& contract(std::string const& _contractName = "") const; - Source const& source(std::string const& _sourceName = "") const; + Contract const& contract(std::string const& _contractName) const; + Source const& source(std::string const& _sourceName) const; /// @returns the parsed contract with the supplied name. Throws an exception if the contract /// does not exist. diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 1686dc2e..3ab564c3 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -951,9 +951,10 @@ void CommandLineInterface::handleAst(string const& _argStr) for (auto const& sourceCode: m_sourceCodes) asts.push_back(&m_compiler->ast(sourceCode.first)); map gasCosts; - if (m_compiler->runtimeAssemblyItems()) + // FIXME: shouldn't this be done for every contract? + if (m_compiler->runtimeAssemblyItems("")) gasCosts = GasEstimator::breakToStatementLevel( - GasEstimator::structuralEstimation(*m_compiler->runtimeAssemblyItems(), asts), + GasEstimator::structuralEstimation(*m_compiler->runtimeAssemblyItems(""), asts), asts ); diff --git a/test/libsolidity/AnalysisFramework.cpp b/test/libsolidity/AnalysisFramework.cpp index 3bdc40a0..ea9703ea 100644 --- a/test/libsolidity/AnalysisFramework.cpp +++ b/test/libsolidity/AnalysisFramework.cpp @@ -81,7 +81,7 @@ AnalysisFramework::parseAnalyseAndReturnError( } } - return make_pair(&m_compiler.ast(), firstError); + return make_pair(&m_compiler.ast(""), firstError); } SourceUnit const* AnalysisFramework::parseAndAnalyse(string const& _source) diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index c2886f5b..83d24f6c 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -52,7 +52,7 @@ public: BOOST_REQUIRE_MESSAGE(m_compiler.compile(), "Compiling contract failed"); AssemblyItems const* items = m_compiler.runtimeAssemblyItems(""); - ASTNode const& sourceUnit = m_compiler.ast(); + ASTNode const& sourceUnit = m_compiler.ast(""); BOOST_REQUIRE(items != nullptr); m_gasCosts = GasEstimator::breakToStatementLevel( GasEstimator::structuralEstimation(*items, vector({&sourceUnit})), @@ -64,13 +64,13 @@ public: { compileAndRun(_sourceCode); auto state = make_shared(); - PathGasMeter meter(*m_compiler.assemblyItems()); + PathGasMeter meter(*m_compiler.assemblyItems("")); GasMeter::GasConsumption gas = meter.estimateMax(0, state); - u256 bytecodeSize(m_compiler.runtimeObject().bytecode.size()); + u256 bytecodeSize(m_compiler.runtimeObject("").bytecode.size()); // costs for deployment gas += bytecodeSize * GasCosts::createDataGas; // costs for transaction - gas += gasForTransaction(m_compiler.object().bytecode, true); + gas += gasForTransaction(m_compiler.object("").bytecode, true); BOOST_REQUIRE(!gas.isInfinite); BOOST_CHECK(gas.value == m_gasUsed); @@ -91,7 +91,7 @@ public: } gas += GasEstimator::functionalEstimation( - *m_compiler.runtimeAssemblyItems(), + *m_compiler.runtimeAssemblyItems(""), _sig ); BOOST_REQUIRE(!gas.isInfinite); @@ -135,7 +135,7 @@ BOOST_AUTO_TEST_CASE(non_overlapping_filtered_costs) if (first->first->location().intersects(second->first->location())) { BOOST_CHECK_MESSAGE(false, "Source locations should not overlap!"); - auto scannerFromSource = [&](string const&) -> Scanner const& { return m_compiler.scanner(); }; + auto scannerFromSource = [&](string const& _sourceName) -> Scanner const& { return m_compiler.scanner(_sourceName); }; SourceReferenceFormatter::printSourceLocation(cout, &first->first->location(), scannerFromSource); SourceReferenceFormatter::printSourceLocation(cout, &second->first->location(), scannerFromSource); } -- cgit v1.2.3 From 2ce35b77becab2395dce218b106a7ce904a1b141 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 21 Aug 2017 21:49:58 +0100 Subject: Implement CompilerStack.lastContractName() --- libsolidity/interface/CompilerStack.cpp | 26 ++++++++++++++++---------- libsolidity/interface/CompilerStack.h | 3 +++ solc/CommandLineInterface.cpp | 4 ++-- test/libsolidity/GasMeter.cpp | 10 +++++----- test/libsolidity/SolidityABIJSON.cpp | 2 +- test/libsolidity/SolidityExecutionFramework.h | 2 +- test/libsolidity/SolidityNatspecJSON.cpp | 4 ++-- 7 files changed, 30 insertions(+), 21 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index b99fe4ee..8edce879 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -747,22 +747,28 @@ void CompilerStack::compileContract( } } +string const CompilerStack::lastContractName() const +{ + if (m_contracts.empty()) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found.")); + // try to find some user-supplied contract + string contractName; + for (auto const& it: m_sources) + for (ASTPointer const& node: it.second.ast->nodes()) + if (auto contract = dynamic_cast(node.get())) + contractName = contract->fullyQualifiedName(); + return contractName; +} + CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const { if (m_contracts.empty()) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found.")); - string contractName = _contractName; - if (_contractName.empty()) - // try to find some user-supplied contract - for (auto const& it: m_sources) - for (ASTPointer const& node: it.second.ast->nodes()) - if (auto contract = dynamic_cast(node.get())) - contractName = contract->fullyQualifiedName(); - auto it = m_contracts.find(contractName); + auto it = m_contracts.find(_contractName); // To provide a measure of backward-compatibility, if a contract is not located by its // fully-qualified name, a lookup will be attempted purely on the contract's name to see // if anything will satisfy. - if (it == m_contracts.end() && contractName.find(":") == string::npos) + if (it == m_contracts.end() && _contractName.find(":") == string::npos) { for (auto const& contractEntry: m_contracts) { @@ -773,7 +779,7 @@ CompilerStack::Contract const& CompilerStack::contract(string const& _contractNa string foundName; getline(ss, source, ':'); getline(ss, foundName, ':'); - if (foundName == contractName) return contractEntry.second; + if (foundName == _contractName) return contractEntry.second; } // If we get here, both lookup methods failed. BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract " + _contractName + " not found.")); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index f992ebc8..b377b3aa 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -168,6 +168,9 @@ public: /// @returns a list of the contract names in the sources. std::vector contractNames() const; + /// @returns the name of the last contract. + std::string const lastContractName() const; + /// @returns either the contract's name or a mixture of its name and source file, sanitized for filesystem use std::string const filesystemFriendlyName(std::string const& _contractName) const; diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 3ab564c3..c4241a9e 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -952,9 +952,9 @@ void CommandLineInterface::handleAst(string const& _argStr) asts.push_back(&m_compiler->ast(sourceCode.first)); map gasCosts; // FIXME: shouldn't this be done for every contract? - if (m_compiler->runtimeAssemblyItems("")) + if (m_compiler->runtimeAssemblyItems(m_compiler->lastContractName())) gasCosts = GasEstimator::breakToStatementLevel( - GasEstimator::structuralEstimation(*m_compiler->runtimeAssemblyItems(""), asts), + GasEstimator::structuralEstimation(*m_compiler->runtimeAssemblyItems(m_compiler->lastContractName()), asts), asts ); diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 83d24f6c..86e8201b 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -51,7 +51,7 @@ public: m_compiler.setOptimiserSettings(dev::test::Options::get().optimize); BOOST_REQUIRE_MESSAGE(m_compiler.compile(), "Compiling contract failed"); - AssemblyItems const* items = m_compiler.runtimeAssemblyItems(""); + AssemblyItems const* items = m_compiler.runtimeAssemblyItems(m_compiler.lastContractName()); ASTNode const& sourceUnit = m_compiler.ast(""); BOOST_REQUIRE(items != nullptr); m_gasCosts = GasEstimator::breakToStatementLevel( @@ -64,13 +64,13 @@ public: { compileAndRun(_sourceCode); auto state = make_shared(); - PathGasMeter meter(*m_compiler.assemblyItems("")); + PathGasMeter meter(*m_compiler.assemblyItems(m_compiler.lastContractName())); GasMeter::GasConsumption gas = meter.estimateMax(0, state); - u256 bytecodeSize(m_compiler.runtimeObject("").bytecode.size()); + u256 bytecodeSize(m_compiler.runtimeObject(m_compiler.lastContractName()).bytecode.size()); // costs for deployment gas += bytecodeSize * GasCosts::createDataGas; // costs for transaction - gas += gasForTransaction(m_compiler.object("").bytecode, true); + gas += gasForTransaction(m_compiler.object(m_compiler.lastContractName()).bytecode, true); BOOST_REQUIRE(!gas.isInfinite); BOOST_CHECK(gas.value == m_gasUsed); @@ -91,7 +91,7 @@ public: } gas += GasEstimator::functionalEstimation( - *m_compiler.runtimeAssemblyItems(""), + *m_compiler.runtimeAssemblyItems(m_compiler.lastContractName()), _sig ); BOOST_REQUIRE(!gas.isInfinite); diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 42f7525f..33962730 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -46,7 +46,7 @@ public: m_compilerStack.addSource("", "pragma solidity >=0.0;\n" + _code); BOOST_REQUIRE_MESSAGE(m_compilerStack.parseAndAnalyze(), "Parsing contract failed"); - Json::Value generatedInterface = m_compilerStack.contractABI(""); + Json::Value generatedInterface = m_compilerStack.contractABI(m_compilerStack.lastContractName()); Json::Value expectedInterface; BOOST_REQUIRE(m_reader.parse(_expectedInterfaceString, expectedInterface)); BOOST_CHECK_MESSAGE( diff --git a/test/libsolidity/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index 342d0875..b0daaba9 100644 --- a/test/libsolidity/SolidityExecutionFramework.h +++ b/test/libsolidity/SolidityExecutionFramework.h @@ -69,7 +69,7 @@ public: ); BOOST_ERROR("Compiling contract failed"); } - eth::LinkerObject obj = m_compiler.object(_contractName); + eth::LinkerObject obj = m_compiler.object(_contractName.empty() ? m_compiler.lastContractName() : _contractName); BOOST_REQUIRE(obj.linkReferences.empty()); sendMessage(obj.bytecode + _arguments, true, _value); return m_output; diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index d83773bc..fb09451f 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -51,9 +51,9 @@ public: Json::Value generatedDocumentation; if (_userDocumentation) - generatedDocumentation = m_compilerStack.natspecUser(""); + generatedDocumentation = m_compilerStack.natspecUser(m_compilerStack.lastContractName()); else - generatedDocumentation = m_compilerStack.natspecDev(""); + generatedDocumentation = m_compilerStack.natspecDev(m_compilerStack.lastContractName()); Json::Value expectedDocumentation; m_reader.parse(_expectedDocumentationString, expectedDocumentation); BOOST_CHECK_MESSAGE( -- cgit v1.2.3 From 8d3cfa8cff85b1aed7c1b77a0886a58b09144a36 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 18 Oct 2017 23:17:06 +0100 Subject: Simplify contract lookup in CompileStack --- libsolidity/interface/CompilerStack.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 8edce879..5713256a 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -764,11 +764,15 @@ CompilerStack::Contract const& CompilerStack::contract(string const& _contractNa { if (m_contracts.empty()) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found.")); + auto it = m_contracts.find(_contractName); + if (it != m_contracts.end()) + return it->second; + // To provide a measure of backward-compatibility, if a contract is not located by its // fully-qualified name, a lookup will be attempted purely on the contract's name to see // if anything will satisfy. - if (it == m_contracts.end() && _contractName.find(":") == string::npos) + if (_contractName.find(":") == string::npos) { for (auto const& contractEntry: m_contracts) { @@ -779,12 +783,13 @@ CompilerStack::Contract const& CompilerStack::contract(string const& _contractNa string foundName; getline(ss, source, ':'); getline(ss, foundName, ':'); - if (foundName == _contractName) return contractEntry.second; + if (foundName == _contractName) + return contractEntry.second; } - // If we get here, both lookup methods failed. - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract " + _contractName + " not found.")); } - return it->second; + + // If we get here, both lookup methods failed. + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract \"" + _contractName + "\" not found.")); } CompilerStack::Source const& CompilerStack::source(string const& _sourceName) const -- cgit v1.2.3 From 4ca160d9fb94920b6708d691976154e1ddfb76d2 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 19 Oct 2017 00:12:32 +0100 Subject: Move contract related code to test/contracts --- test/ExecutionFramework.h | 67 +------------------------ test/contracts/AuctionRegistrar.cpp | 2 +- test/contracts/ContractInterface.h | 99 +++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 67 deletions(-) create mode 100644 test/contracts/ContractInterface.h diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index cc25bea7..2c61c0a6 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -172,78 +172,13 @@ public: { return bytes(); } + //@todo might be extended in the future template static bytes encodeDyn(Arg const& _arg) { return encodeArgs(u256(0x20), u256(_arg.size()), _arg); } - class ContractInterface - { - public: - ContractInterface(ExecutionFramework& _framework): m_framework(_framework) {} - - void setNextValue(u256 const& _value) { m_nextValue = _value; } - - protected: - template - bytes const& call(std::string const& _sig, Args const&... _arguments) - { - auto const& ret = m_framework.callContractFunctionWithValue(_sig, m_nextValue, _arguments...); - m_nextValue = 0; - return ret; - } - - void callString(std::string const& _name, std::string const& _arg) - { - BOOST_CHECK(call(_name + "(string)", u256(0x20), _arg.length(), _arg).empty()); - } - - void callStringAddress(std::string const& _name, std::string const& _arg1, u160 const& _arg2) - { - BOOST_CHECK(call(_name + "(string,address)", u256(0x40), _arg2, _arg1.length(), _arg1).empty()); - } - - void callStringAddressBool(std::string const& _name, std::string const& _arg1, u160 const& _arg2, bool _arg3) - { - BOOST_CHECK(call(_name + "(string,address,bool)", u256(0x60), _arg2, _arg3, _arg1.length(), _arg1).empty()); - } - - void callStringBytes32(std::string const& _name, std::string const& _arg1, h256 const& _arg2) - { - BOOST_CHECK(call(_name + "(string,bytes32)", u256(0x40), _arg2, _arg1.length(), _arg1).empty()); - } - - u160 callStringReturnsAddress(std::string const& _name, std::string const& _arg) - { - bytes const& ret = call(_name + "(string)", u256(0x20), _arg.length(), _arg); - BOOST_REQUIRE(ret.size() == 0x20); - BOOST_CHECK(std::count(ret.begin(), ret.begin() + 12, 0) == 12); - return u160(u256(h256(ret))); - } - - std::string callAddressReturnsString(std::string const& _name, u160 const& _arg) - { - bytesConstRef const ret(&call(_name + "(address)", _arg)); - BOOST_REQUIRE(ret.size() >= 0x40); - u256 offset(h256(ret.cropped(0, 0x20))); - BOOST_REQUIRE_EQUAL(offset, 0x20); - u256 len(h256(ret.cropped(0x20, 0x20))); - BOOST_REQUIRE_EQUAL(ret.size(), 0x40 + ((len + 0x1f) / 0x20) * 0x20); - return ret.cropped(0x40, size_t(len)).toString(); - } - - h256 callStringReturnsBytes32(std::string const& _name, std::string const& _arg) - { - bytes const& ret = call(_name + "(string)", u256(0x20), _arg.length(), _arg); - BOOST_REQUIRE(ret.size() == 0x20); - return h256(ret); - } - - private: - u256 m_nextValue; - ExecutionFramework& m_framework; - }; private: template diff --git a/test/contracts/AuctionRegistrar.cpp b/test/contracts/AuctionRegistrar.cpp index 73a5d1ed..c9c744af 100644 --- a/test/contracts/AuctionRegistrar.cpp +++ b/test/contracts/AuctionRegistrar.cpp @@ -24,6 +24,7 @@ #include #include #include +#include using namespace std; using namespace dev::test; @@ -230,7 +231,6 @@ protected: BOOST_REQUIRE(!m_output.empty()); } - using ContractInterface = SolidityExecutionFramework::ContractInterface; class RegistrarInterface: public ContractInterface { public: diff --git a/test/contracts/ContractInterface.h b/test/contracts/ContractInterface.h new file mode 100644 index 00000000..052b0db2 --- /dev/null +++ b/test/contracts/ContractInterface.h @@ -0,0 +1,99 @@ +/* + 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 . +*/ + +#pragma once + +#include +#include + +#include + +namespace dev +{ +namespace test +{ + +class ContractInterface +{ +public: + ContractInterface(ExecutionFramework& _framework): m_framework(_framework) {} + + void setNextValue(u256 const& _value) { m_nextValue = _value; } + +protected: + template + bytes const& call(std::string const& _sig, Args const&... _arguments) + { + auto const& ret = m_framework.callContractFunctionWithValue(_sig, m_nextValue, _arguments...); + m_nextValue = 0; + return ret; + } + + void callString(std::string const& _name, std::string const& _arg) + { + BOOST_CHECK(call(_name + "(string)", u256(0x20), _arg.length(), _arg).empty()); + } + + void callStringAddress(std::string const& _name, std::string const& _arg1, u160 const& _arg2) + { + BOOST_CHECK(call(_name + "(string,address)", u256(0x40), _arg2, _arg1.length(), _arg1).empty()); + } + + void callStringAddressBool(std::string const& _name, std::string const& _arg1, u160 const& _arg2, bool _arg3) + { + BOOST_CHECK(call(_name + "(string,address,bool)", u256(0x60), _arg2, _arg3, _arg1.length(), _arg1).empty()); + } + + void callStringBytes32(std::string const& _name, std::string const& _arg1, h256 const& _arg2) + { + BOOST_CHECK(call(_name + "(string,bytes32)", u256(0x40), _arg2, _arg1.length(), _arg1).empty()); + } + + u160 callStringReturnsAddress(std::string const& _name, std::string const& _arg) + { + bytes const& ret = call(_name + "(string)", u256(0x20), _arg.length(), _arg); + BOOST_REQUIRE(ret.size() == 0x20); + BOOST_CHECK(std::count(ret.begin(), ret.begin() + 12, 0) == 12); + return u160(u256(h256(ret))); + } + + std::string callAddressReturnsString(std::string const& _name, u160 const& _arg) + { + bytesConstRef const ret(&call(_name + "(address)", _arg)); + BOOST_REQUIRE(ret.size() >= 0x40); + u256 offset(h256(ret.cropped(0, 0x20))); + BOOST_REQUIRE_EQUAL(offset, 0x20); + u256 len(h256(ret.cropped(0x20, 0x20))); + BOOST_REQUIRE_EQUAL(ret.size(), 0x40 + ((len + 0x1f) / 0x20) * 0x20); + return ret.cropped(0x40, size_t(len)).toString(); + } + + h256 callStringReturnsBytes32(std::string const& _name, std::string const& _arg) + { + bytes const& ret = call(_name + "(string)", u256(0x20), _arg.length(), _arg); + BOOST_REQUIRE(ret.size() == 0x20); + return h256(ret); + } + +private: + u256 m_nextValue; + ExecutionFramework& m_framework; +}; + +} +} // end namespaces + -- cgit v1.2.3 From 10677125ae5b2c211d22add52022ebd5a7cc8260 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 11 Jul 2017 19:08:11 +0200 Subject: Turn usage of callcode into an error as experimental 0.5.0 feature --- Changelog.md | 1 + libsolidity/analysis/StaticAnalyzer.cpp | 16 ++++++++++++---- test/libsolidity/SolidityNameAndTypeResolution.cpp | 10 ++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Changelog.md b/Changelog.md index cb81d975..68b9973f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.4.19 (unreleased) Features: + * Syntax Checker: Turn the usage of ``callcode`` into an error as experimental 0.5.0 feature. Bugfixes: diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index ffa538b6..bd8ee597 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -150,10 +150,18 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) if (_memberAccess.memberName() == "callcode") if (auto const* type = dynamic_cast(_memberAccess.annotation().type.get())) if (type->kind() == FunctionType::Kind::BareCallCode) - m_errorReporter.warning( - _memberAccess.location(), - "\"callcode\" has been deprecated in favour of \"delegatecall\"." - ); + { + if (m_currentContract->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050)) + m_errorReporter.typeError( + _memberAccess.location(), + "\"callcode\" has been deprecated in favour of \"delegatecall\"." + ); + else + m_errorReporter.warning( + _memberAccess.location(), + "\"callcode\" has been deprecated in favour of \"delegatecall\"." + ); + } if (m_constructor && m_currentContract) if (ContractType const* type = dynamic_cast(_memberAccess.expression().annotation().type.get())) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 9b0647bf..9b5ea349 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4783,6 +4783,16 @@ BOOST_AUTO_TEST_CASE(warn_about_callcode) } )"; CHECK_WARNING(text, "\"callcode\" has been deprecated in favour of \"delegatecall\""); + text = R"( + pragma experimental "v0.5.0"; + contract test { + function f() pure public { + var x = address(0x12).callcode; + x; + } + } + )"; + CHECK_ERROR(text, TypeError, "\"callcode\" has been deprecated in favour of \"delegatecall\""); } BOOST_AUTO_TEST_CASE(no_warn_about_callcode_as_function) -- cgit v1.2.3 From 36bf0a8a86fec775e9e61adfc1ebef488733a895 Mon Sep 17 00:00:00 2001 From: Boris Kostenko Date: Thu, 19 Oct 2017 08:25:27 +0300 Subject: Accent on ext.deps in install docs (issue #3048) --- docs/installing-solidity.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 57c65911..b660cf02 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -230,7 +230,8 @@ Or, on Windows: Command-Line Build ------------------ -**Be sure to install External Dependencies before build.** +**Be sure to install External Dependencies (see above) before build.** + Solidity project uses CMake to configure the build. Building Solidity is quite similar on Linux, macOS and other Unices: -- cgit v1.2.3 From 362615c9c4301877de9d543b470ef5994c237a4b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 19 Oct 2017 11:00:24 +0100 Subject: Close IPC socket on connect failure --- test/RPCSession.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 72b26453..634954a3 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -74,7 +74,10 @@ IPCSocket::IPCSocket(string const& _path): m_path(_path) BOOST_FAIL("Error creating IPC socket object"); if (connect(m_socket, reinterpret_cast(&saun), sizeof(struct sockaddr_un)) < 0) + { + close(m_socket); BOOST_FAIL("Error connecting to IPC socket: " << _path); + } #endif } -- cgit v1.2.3 From 950f5ae7d762f5878b69043da16e9e27485d4c57 Mon Sep 17 00:00:00 2001 From: rivenhk Date: Thu, 19 Oct 2017 20:20:07 +0800 Subject: updated formatting when source snippets is too long --- libsolidity/interface/SourceReferenceFormatter.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libsolidity/interface/SourceReferenceFormatter.cpp b/libsolidity/interface/SourceReferenceFormatter.cpp index 256adccc..aeafaf2d 100644 --- a/libsolidity/interface/SourceReferenceFormatter.cpp +++ b/libsolidity/interface/SourceReferenceFormatter.cpp @@ -53,9 +53,9 @@ void SourceReferenceFormatter::printSourceLocation( int locationLength = endColumn - startColumn; if (locationLength > 150) { - line = line.substr(0, startColumn + 75) + " ... " + line.substr(endColumn); - endColumn = startColumn + 80; - locationLength = 80; + line = line.substr(0, startColumn + 35) + " ... " + line.substr(endColumn - 35); + endColumn = startColumn + 75; + locationLength = 75; } if (line.length() > 150) { -- cgit v1.2.3 From f0af4019efb35982823feaadea22198016ca0100 Mon Sep 17 00:00:00 2001 From: Boris Kostenko Date: Fri, 20 Oct 2017 00:41:56 +0300 Subject: No bytecodecompare if tests failed (issue #2300) --- .travis.yml | 4 ++-- appveyor.yml | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 708d3620..cf40a74a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -183,9 +183,9 @@ before_script: && scripts/create_source_tarball.sh) script: - - test $SOLC_EMSCRIPTEN != On || (scripts/test_emscripten.sh) + - test $SOLC_EMSCRIPTEN != On || (scripts/test_emscripten.sh) || SOLC_STOREBYTECODE=Off - test $SOLC_DOCS != On || (scripts/docs.sh) - - test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh) + - test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh) || SOLC_STOREBYTECODE=Off - test $SOLC_STOREBYTECODE != On || (cd $TRAVIS_BUILD_DIR && scripts/bytecodecompare/storebytecode.sh) deploy: diff --git a/appveyor.yml b/appveyor.yml index c63414b3..c4584228 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -68,15 +68,14 @@ build_script: - cd %APPVEYOR_BUILD_FOLDER% - scripts\release.bat %CONFIGURATION% - ps: $bytecodedir = git show -s --format="%cd-%H" --date=short -# Skip bytecode compare if private key is not available - - ps: if ($env:priv_key) { - scripts\bytecodecompare\storebytecode.bat $Env:CONFIGURATION $bytecodedir - } test_script: - - cd %APPVEYOR_BUILD_FOLDER% - cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION% - soltest.exe --show-progress -- --no-ipc --no-smt +# Skip bytecode compare if private key is not available + - ps: if ($env:priv_key) { + scripts\bytecodecompare\storebytecode.bat $Env:CONFIGURATION $bytecodedir + } artifacts: - path: solidity-windows.zip -- cgit v1.2.3 From 3041def259f106d8f2c235ab4b387b9e9de8e317 Mon Sep 17 00:00:00 2001 From: Boris Kostenko Date: Fri, 20 Oct 2017 13:32:01 +0300 Subject: No bytecodecompare if tests failed (issue #2300) --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cf40a74a..4fc76c49 100644 --- a/.travis.yml +++ b/.travis.yml @@ -183,9 +183,11 @@ before_script: && scripts/create_source_tarball.sh) script: - - test $SOLC_EMSCRIPTEN != On || (scripts/test_emscripten.sh) || SOLC_STOREBYTECODE=Off + - test $SOLC_EMSCRIPTEN != On || (scripts/test_emscripten.sh) + - [[ $? == 0 ]] || SOLC_STOREBYTECODE=Off - test $SOLC_DOCS != On || (scripts/docs.sh) - - test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh) || SOLC_STOREBYTECODE=Off + - test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh) + - [[ $? == 0 ]] || SOLC_STOREBYTECODE=Off - test $SOLC_STOREBYTECODE != On || (cd $TRAVIS_BUILD_DIR && scripts/bytecodecompare/storebytecode.sh) deploy: -- cgit v1.2.3 From ca236d4225643d33e747099276a2aaf2bb08d329 Mon Sep 17 00:00:00 2001 From: Boris Kostenko Date: Fri, 20 Oct 2017 13:44:28 +0300 Subject: No bytecodecompare if tests failed (issue #2300) --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4fc76c49..a2301c42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -184,10 +184,10 @@ before_script: script: - test $SOLC_EMSCRIPTEN != On || (scripts/test_emscripten.sh) - - [[ $? == 0 ]] || SOLC_STOREBYTECODE=Off + - test $? == 0 || SOLC_STOREBYTECODE=Off - test $SOLC_DOCS != On || (scripts/docs.sh) - test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh) - - [[ $? == 0 ]] || SOLC_STOREBYTECODE=Off + - test $? == 0 || SOLC_STOREBYTECODE=Off - test $SOLC_STOREBYTECODE != On || (cd $TRAVIS_BUILD_DIR && scripts/bytecodecompare/storebytecode.sh) deploy: -- cgit v1.2.3 From 3a8324266f470b18763054ed5caa791e8e8410a7 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 4 Oct 2017 10:59:21 +0100 Subject: More detailed errors for invalid array lengths (such as division by zero). --- Changelog.md | 1 + libsolidity/analysis/ConstantEvaluator.cpp | 23 +++++++++++++++++++--- test/libsolidity/SolidityNameAndTypeResolution.cpp | 6 ++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Changelog.md b/Changelog.md index 68b9973f..e691711b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Features: * Syntax Checker: Turn the usage of ``callcode`` into an error as experimental 0.5.0 feature. + * Type Checker: More detailed errors for invalid array lengths (such as division by zero). Bugfixes: diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index 6636ad97..bc3b7cf1 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -28,6 +28,7 @@ using namespace std; using namespace dev; using namespace dev::solidity; +/// FIXME: this is pretty much a copy of TypeChecker::endVisit(BinaryOperation) void ConstantEvaluator::endVisit(UnaryOperation const& _operation) { TypePointer const& subType = _operation.subExpression().annotation().type; @@ -37,6 +38,7 @@ void ConstantEvaluator::endVisit(UnaryOperation const& _operation) _operation.annotation().type = t; } +/// FIXME: this is pretty much a copy of TypeChecker::endVisit(BinaryOperation) void ConstantEvaluator::endVisit(BinaryOperation const& _operation) { TypePointer const& leftType = _operation.leftExpression().annotation().type; @@ -46,9 +48,24 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation) if (!dynamic_cast(rightType.get())) m_errorReporter.fatalTypeError(_operation.rightExpression().location(), "Invalid constant expression."); TypePointer commonType = leftType->binaryOperatorResult(_operation.getOperator(), rightType); - if (Token::isCompareOp(_operation.getOperator())) - commonType = make_shared(); - _operation.annotation().type = commonType; + if (!commonType) + { + m_errorReporter.typeError( + _operation.location(), + "Operator " + + string(Token::toString(_operation.getOperator())) + + " not compatible with types " + + leftType->toString() + + " and " + + rightType->toString() + ); + commonType = leftType; + } + _operation.annotation().commonType = commonType; + _operation.annotation().type = + Token::isCompareOp(_operation.getOperator()) ? + make_shared() : + commonType; } void ConstantEvaluator::endVisit(Literal const& _literal) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 9b5ea349..e5990e9b 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -7247,6 +7247,12 @@ BOOST_AUTO_TEST_CASE(array_length_invalid_expression) } )"; CHECK_ERROR(text, TypeError, "Invalid literal value."); + text = R"( + contract C { + uint[3/0] ids; + } + )"; + CHECK_ERROR(text, TypeError, "Operator / not compatible with types int_const 3 and int_const 0"); } BOOST_AUTO_TEST_CASE(no_address_members_on_contract) -- cgit v1.2.3 From 81e9c679c4d2d10a2ac2a7d82e25d5e9691c2b45 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 20 Oct 2017 12:41:50 +0100 Subject: Add helper RPCSession::createAccount --- test/RPCSession.cpp | 17 ++++++++++------- test/RPCSession.h | 1 + 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 634954a3..b99c1c06 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -339,22 +339,25 @@ Json::Value RPCSession::rpcCall(string const& _methodName, vector const& return result["result"]; } +string const& RPCSession::accountCreate() +{ + m_accounts.push_back(personal_newAccount("")); + personal_unlockAccount(m_accounts.back(), "", 100000); + return m_accounts.back(); +} + string const& RPCSession::accountCreateIfNotExists(size_t _id) { if (_id >= m_accounts.size()) - { - m_accounts.push_back(personal_newAccount("")); - personal_unlockAccount(m_accounts.back(), "", 100000); - } + accountCreate(); return m_accounts[_id]; } RPCSession::RPCSession(const string& _path): m_ipcSocket(_path) { - string account = personal_newAccount(""); - personal_unlockAccount(account, "", 100000); - m_accounts.push_back(account); + accountCreate(); + // This will pre-fund the accounts create prior. test_setChainParams(m_accounts); } diff --git a/test/RPCSession.h b/test/RPCSession.h index eae6a09c..63f1dd21 100644 --- a/test/RPCSession.h +++ b/test/RPCSession.h @@ -121,6 +121,7 @@ public: Json::Value rpcCall(std::string const& _methodName, std::vector const& _args = std::vector(), bool _canFail = false); std::string const& account(size_t _id) const { return m_accounts.at(_id); } + std::string const& accountCreate(); std::string const& accountCreateIfNotExists(size_t _id); private: -- cgit v1.2.3 From faa0a662c740a0d0d26d58e0e19bb4b887d642a5 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 20 Oct 2017 12:44:52 +0100 Subject: Always create the correct account even if gaps are needed --- test/RPCSession.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index b99c1c06..c06c3997 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -348,7 +348,7 @@ string const& RPCSession::accountCreate() string const& RPCSession::accountCreateIfNotExists(size_t _id) { - if (_id >= m_accounts.size()) + while ((_id + 1) > m_accounts.size()) accountCreate(); return m_accounts[_id]; } -- cgit v1.2.3 From ccc54c84f3b4df7be615edb10ea1052be9fdd5b9 Mon Sep 17 00:00:00 2001 From: rivenhk Date: Sun, 22 Oct 2017 00:26:46 +0800 Subject: updated Changelog.md --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index ed3cdfea..acd9ee5e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -11,6 +11,7 @@ Features: * Type Checker: Do not add members of ``address`` to contracts as experimental 0.5.0 feature. * Type Checker: Force interface functions to be external as experimental 0.5.0 feature. * Type Checker: Require ``storage`` or ``memory`` keyword for local variables as experimental 0.5.0 feature. + * Compiler Interface: Better formatted error message for long source snippets Bugfixes: * Code Generator: Allocate one byte per memory byte array element instead of 32. -- cgit v1.2.3 From dcbcd36be79df439d08c7ed3441eb498225dd68d Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 20 Oct 2017 16:23:53 +0100 Subject: Do not use the precompile address 0x05 in the wallet tests (but a randomized address) --- test/contracts/Wallet.cpp | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp index bbe603d4..90334ad6 100644 --- a/test/contracts/Wallet.cpp +++ b/test/contracts/Wallet.cpp @@ -559,24 +559,24 @@ BOOST_AUTO_TEST_CASE(multisig_value_transfer) BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(account(3), h256::AlignRight)) == encodeArgs()); // 4 owners, set required to 3 BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs()); - // check that balance is and stays zero at destination address - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + Address destination = Address("0x5c6d6026d3fb35cd7175fd0054ae8df50d8f8b41"); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); m_sender = account(0); sendEther(account(1), 10 * ether); m_sender = account(1); - auto ophash = callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + auto ophash = callContractFunction("execute(address,uint256,bytes)", h256(destination, h256::AlignRight), 100, 0x60, 0x00); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); m_sender = account(0); sendEther(account(2), 10 * ether); m_sender = account(2); callContractFunction("confirm(bytes32)", ophash); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); m_sender = account(0); sendEther(account(3), 10 * ether); m_sender = account(3); callContractFunction("confirm(bytes32)", ophash); // now it should go through - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 100); + BOOST_CHECK_EQUAL(balanceAt(destination), 100); } BOOST_AUTO_TEST_CASE(revoke_addOwner) @@ -622,30 +622,31 @@ BOOST_AUTO_TEST_CASE(revoke_transaction) BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs()); // create a transaction Address deployer = m_sender; - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + Address destination = Address("0x5c6d6026d3fb35cd7175fd0054ae8df50d8f8b41"); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); m_sender = account(0); sendEther(account(1), 10 * ether); m_sender = account(1); - auto opHash = callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + auto opHash = callContractFunction("execute(address,uint256,bytes)", h256(destination, h256::AlignRight), 100, 0x60, 0x00); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); m_sender = account(0); sendEther(account(2), 10 * ether); m_sender = account(2); callContractFunction("confirm(bytes32)", opHash); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); m_sender = account(0); sendEther(account(1), 10 * ether); m_sender = account(1); BOOST_REQUIRE(callContractFunction("revoke(bytes32)", opHash) == encodeArgs()); m_sender = deployer; callContractFunction("confirm(bytes32)", opHash); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); m_sender = account(0); sendEther(account(3), 10 * ether); m_sender = account(3); callContractFunction("confirm(bytes32)", opHash); // now it should go through - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 100); + BOOST_CHECK_EQUAL(balanceAt(destination), 100); } BOOST_AUTO_TEST_CASE(daylimit) @@ -661,31 +662,32 @@ BOOST_AUTO_TEST_CASE(daylimit) BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs()); // try to send tx over daylimit - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + Address destination = Address("0x5c6d6026d3fb35cd7175fd0054ae8df50d8f8b41"); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); sendEther(account(1), 10 * ether); m_sender = account(1); BOOST_REQUIRE( - callContractFunction("execute(address,uint256,bytes)", h256(0x05), 150, 0x60, 0x00) != + callContractFunction("execute(address,uint256,bytes)", h256(destination, h256::AlignRight), 150, 0x60, 0x00) != encodeArgs(u256(0)) ); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); // try to send tx under daylimit by stranger m_sender = account(0); sendEther(account(4), 10 * ether); m_sender = account(4); BOOST_REQUIRE( - callContractFunction("execute(address,uint256,bytes)", h256(0x05), 90, 0x60, 0x00) == + callContractFunction("execute(address,uint256,bytes)", h256(destination, h256::AlignRight), 90, 0x60, 0x00) == encodeArgs(u256(0)) ); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); // now send below limit by owner m_sender = account(0); sendEther(account(1), 10 * ether); BOOST_REQUIRE( - callContractFunction("execute(address,uint256,bytes)", h256(0x05), 90, 0x60, 0x00) == + callContractFunction("execute(address,uint256,bytes)", h256(destination, h256::AlignRight), 90, 0x60, 0x00) == encodeArgs(u256(0)) ); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 90); + BOOST_CHECK_EQUAL(balanceAt(destination), 90); } BOOST_AUTO_TEST_CASE(daylimit_constructor) -- cgit v1.2.3 From baaa032a75c5b9b2942fb1ea46e25a425444f580 Mon Sep 17 00:00:00 2001 From: Boris Kostenko Date: Mon, 23 Oct 2017 23:02:42 +0300 Subject: No bytecodecompare if tests failed (issue #2300) Fix PR #3113 --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index c4584228..f9578c43 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -73,6 +73,7 @@ test_script: - cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION% - soltest.exe --show-progress -- --no-ipc --no-smt # Skip bytecode compare if private key is not available + - cd %APPVEYOR_BUILD_FOLDER% - ps: if ($env:priv_key) { scripts\bytecodecompare\storebytecode.bat $Env:CONFIGURATION $bytecodedir } -- cgit v1.2.3 From c06fc97f3cb4283e438ba4520db4f0ba08ed46c8 Mon Sep 17 00:00:00 2001 From: Boris Kostenko Date: Mon, 23 Oct 2017 23:08:07 +0300 Subject: Fix folder changes --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index f9578c43..ef5f6907 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -77,6 +77,7 @@ test_script: - ps: if ($env:priv_key) { scripts\bytecodecompare\storebytecode.bat $Env:CONFIGURATION $bytecodedir } + - cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION% artifacts: - path: solidity-windows.zip -- cgit v1.2.3 From 3c91564dd9b8d2e32ecd060c850c8eb85f97712a Mon Sep 17 00:00:00 2001 From: Boris Kostenko Date: Tue, 24 Oct 2017 12:42:48 +0300 Subject: Include modexp precompile in genesis for testing (#3114) --- test/RPCSession.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index c06c3997..60aafc85 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -240,9 +240,10 @@ void RPCSession::test_setChainParams(vector const& _accounts) "0000000000000000000000000000000000000002": { "wei": "1", "precompiled": { "name": "sha256", "linear": { "base": 60, "word": 12 } } }, "0000000000000000000000000000000000000003": { "wei": "1", "precompiled": { "name": "ripemd160", "linear": { "base": 600, "word": 120 } } }, "0000000000000000000000000000000000000004": { "wei": "1", "precompiled": { "name": "identity", "linear": { "base": 15, "word": 3 } } }, - "0000000000000000000000000000000000000006": { "wei": "1", "precompiled": { "name": "alt_bn128_G1_add", "linear": { "base": 15, "word": 3 } } }, - "0000000000000000000000000000000000000007": { "wei": "1", "precompiled": { "name": "alt_bn128_G1_mul", "linear": { "base": 15, "word": 3 } } }, - "0000000000000000000000000000000000000008": { "wei": "1", "precompiled": { "name": "alt_bn128_pairing_product", "linear": { "base": 15, "word": 3 } } } + "0000000000000000000000000000000000000005": { "wei": "1", "precompiled": { "name": "modexp" } }, + "0000000000000000000000000000000000000006": { "wei": "1", "precompiled": { "name": "alt_bn128_G1_add", "linear": { "base": 500, "word": 0 } } }, + "0000000000000000000000000000000000000007": { "wei": "1", "precompiled": { "name": "alt_bn128_G1_mul", "linear": { "base": 40000, "word": 0 } } }, + "0000000000000000000000000000000000000008": { "wei": "1", "precompiled": { "name": "alt_bn128_pairing_product" } } } } )"; -- cgit v1.2.3 From f39cf7be41a4c781fdcc71b5bc86b16764241d4f Mon Sep 17 00:00:00 2001 From: Svetlin Nakov Date: Wed, 25 Oct 2017 10:33:26 +0300 Subject: Improved the "Naming" section Improved the "Naming" section. Added naming examples. Improved consistency. --- docs/style-guide.rst | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/docs/style-guide.rst b/docs/style-guide.rst index 0742d2e9..ee2be23e 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -696,49 +696,51 @@ indistinguishable from the numerals one and zero. Contract and Library Names ========================== -Contracts and libraries should be named using the CapWords style. +Contracts and libraries should be named using the CapWords style. Examples: ``SimpleToken``, ``SmartBank``, ``CertificateHashRepository``, ``Player``. -Events -====== +Event Names +=========== -Events should be named using the CapWords style. +Events should be named using the CapWords style. Examples: ``Deposit``, ``Transfer``, ``Approval``, ``BeforeTransfer``, ``AfterTransfer``. Function Names ============== -Functions should use mixedCase. +Functions should use mixedCase. Examples: ``getBalance``, ``transfer``, ``verifyOwner``, ``addMember``, ``changeOwner``. -Function Arguments -================== +Function Arguments Names +======================== + +Function arguments should use mixedCase. Examples: ``initialSupply``, ``account``, ``recipientAddress``, ``senderAddress``, ``newOwner``. When writing library functions that operate on a custom struct, the struct should be the first argument and should always be named ``self``. -Local and State Variables -========================= +Local and State Variables Names +=============================== -Use mixedCase. +Use mixedCase. Examples: ``totalSupply``, ``remainingSupply``, ``balancesOf``, ``creatorAddress``, ``isPreSale``, ``tokenExchangeRate``. Constants ========= Constants should be named with all capital letters with underscores separating -words. (for example:``MAX_BLOCKS``) +words. Examples: ``MAX_BLOCKS``, `TOKEN_NAME`, ``TOKEN_TICKER``, ``CONTRACT_VERSION``. -Modifiers -========= +Modifier Names +============== -Use mixedCase. +Use mixedCase. Examples: ``onlyBy``, ``onlyAfter``, ``onlyDuringThePreSale``. -Avoiding Collisions -=================== +Avoiding Naming Collisions +========================== * ``single_trailing_underscore_`` -- cgit v1.2.3 From 58778be567620a550210150f8616440b9a5dbd55 Mon Sep 17 00:00:00 2001 From: Svetlin Nakov Date: Wed, 25 Oct 2017 22:58:18 +0300 Subject: Fixed typos --- docs/style-guide.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/style-guide.rst b/docs/style-guide.rst index ee2be23e..66fae0c4 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -711,8 +711,8 @@ Function Names Functions should use mixedCase. Examples: ``getBalance``, ``transfer``, ``verifyOwner``, ``addMember``, ``changeOwner``. -Function Arguments Names -======================== +Function Argument Names +======================= Function arguments should use mixedCase. Examples: ``initialSupply``, ``account``, ``recipientAddress``, ``senderAddress``, ``newOwner``. @@ -720,8 +720,8 @@ When writing library functions that operate on a custom struct, the struct should be the first argument and should always be named ``self``. -Local and State Variables Names -=============================== +Local and State Variable Names +============================== Use mixedCase. Examples: ``totalSupply``, ``remainingSupply``, ``balancesOf``, ``creatorAddress``, ``isPreSale``, ``tokenExchangeRate``. -- cgit v1.2.3 From 3cd6ac418a8b7d9e4d97984166455b6bab7fe2b1 Mon Sep 17 00:00:00 2001 From: Federico Bond Date: Thu, 26 Oct 2017 14:13:33 -0300 Subject: Add missing name resolver tests --- test/libsolidity/SolidityNameAndTypeResolution.cpp | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index e5990e9b..30624260 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4036,6 +4036,35 @@ BOOST_AUTO_TEST_CASE(varM_disqualified_as_keyword) BOOST_CHECK(!success(text)); } +BOOST_AUTO_TEST_CASE(modifier_is_not_a_valid_typename) +{ + char const* text = R"( + contract test { + modifier mod() { _; } + + function f() public { + mod g; + } + } + )"; + CHECK_ERROR(text, TypeError, "Name has to refer to a struct, enum or contract."); +} + +BOOST_AUTO_TEST_CASE(function_is_not_a_valid_typename) +{ + char const* text = R"( + contract test { + function foo() public { + } + + function f() public { + foo g; + } + } + )"; + CHECK_ERROR(text, TypeError, "Name has to refer to a struct, enum or contract."); +} + BOOST_AUTO_TEST_CASE(long_uint_variable_fails) { char const* text = R"( -- cgit v1.2.3 From f9b240096f1c92de88ad10fa7c6c9101ecd9eba5 Mon Sep 17 00:00:00 2001 From: Christian Pamidov Date: Sat, 28 Oct 2017 14:48:57 +0300 Subject: Fixed typos (#3139) --- docs/style-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/style-guide.rst b/docs/style-guide.rst index 66fae0c4..a438b3d0 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -25,7 +25,7 @@ solidity code. The goal of this guide is *consistency*. A quote from python's captures this concept well. 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 judgement. Look at other examples and decide what looks best. And don't hesitate to ask! *********** @@ -223,7 +223,7 @@ Whitespace in Expressions Avoid extraneous whitespace in the following situations: -Immediately inside parenthesis, brackets or braces, with the exception of single-line function declarations. +Immediately inside parenthesis, brackets or braces, with the exception of single line function declarations. Yes:: -- cgit v1.2.3 From fcf66721da116c7f95c9eba9cef056bbd0f6cee6 Mon Sep 17 00:00:00 2001 From: walter-weinmann Date: Sun, 29 Oct 2017 11:57:01 +0100 Subject: abi-spec: example code doesn't fit grammar.txt. --- docs/abi-spec.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index 43757d24..77d15026 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -130,14 +130,14 @@ on the type of ``X`` being Note that in the dynamic case, ``head(X(i))`` is well-defined since the lengths of the head parts only depend on the types and not the values. Its value is the offset of the beginning of ``tail(X(i))`` relative to the start of ``enc(X)``. - + - ``T[k]`` for any ``T`` and ``k``: ``enc(X) = enc((X[0], ..., X[k-1]))`` - + i.e. it is encoded as if it were a tuple with ``k`` elements of the same type. - + - ``T[]`` where ``X`` has ``k`` elements (``k`` is assumed to be of type ``uint256``): ``enc(X) = enc(k) enc([X[1], ..., X[k]])`` @@ -326,7 +326,7 @@ An event description is a JSON object with fairly similar fields: - ``anonymous``: ``true`` if the event was declared as ``anonymous``. -For example, +For example, :: @@ -334,8 +334,8 @@ For example, contract Test { function Test(){ b = 0x12345678901234567890123456789012; } - event Event(uint indexed a, bytes32 b) - event Event2(uint indexed a, bytes32 b) + event Event(uint indexed a, bytes32 b); + event Event2(uint indexed a, bytes32 b); function foo(uint a) { Event(a, b); } bytes32 b; } -- cgit v1.2.3 From d4db1624b54adfac3513754df8db9278e4866d83 Mon Sep 17 00:00:00 2001 From: Chua Chee Wee <1757930+chuacw@users.noreply.github.com> Date: Mon, 30 Oct 2017 15:40:28 +0800 Subject: Changed limitation to feature Changed the word "limitation" to "feature" for the note where transfer is described. --- docs/types.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/types.rst b/docs/types.rst index 774c1d04..0be8255e 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -129,7 +129,7 @@ and to send Ether (in units of wei) to an address using the ``transfer`` functio if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10); .. note:: - If ``x`` is a contract address, its code (more specifically: its fallback function, if present) will be executed together with the ``transfer`` call (this is a limitation of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception. + If ``x`` is a contract address, its code (more specifically: its fallback function, if present) will be executed together with the ``transfer`` call (this is a feature of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception. * ``send`` -- cgit v1.2.3 From 664f33afe0703fc2687fb5cbfdac7efd67d39e96 Mon Sep 17 00:00:00 2001 From: Guanqun Lu Date: Sat, 11 Nov 2017 03:01:13 +0800 Subject: Update the script for Ubuntu Artful 17.10 --- scripts/install_deps.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index 49f864a0..15e864b5 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -294,12 +294,17 @@ case $(uname -s) in echo "Installing solidity dependencies on Ubuntu Zesty (17.04)." install_z3="libz3-dev" ;; + artful) + #artful + echo "Installing solidity dependencies on Ubuntu Artful (17.10)." + install_z3="libz3-dev" + ;; *) #other Ubuntu echo "ERROR - Unknown or unsupported Ubuntu version (" $(lsb_release -cs) ")" echo "ERROR - This might not work, but we are trying anyway." echo "Please drop us a message at https://gitter.im/ethereum/solidity-dev." - echo "We only support Trusty, Utopic, Vivid, Wily, Xenial and Yakkety." + echo "We only support Trusty, Utopic, Vivid, Wily, Xenial, Yakkety, Zesty and Artful." install_z3="libz3-dev" ;; esac -- cgit v1.2.3 From 486647fd38bc3ace6c80fa0d8e2a48f0c128db42 Mon Sep 17 00:00:00 2001 From: Kwang Yul Seo Date: Sun, 12 Nov 2017 22:25:17 +0900 Subject: Remove unused method The body of Function::functionIdentifier is missing and is not used. --- libsolidity/ast/Types.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index ce29975e..635279ab 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -707,10 +707,6 @@ public: /// Returns the function type of the constructor modified to return an object of the contract's type. FunctionTypePointer const& newExpressionType() const; - /// @returns the identifier of the function with the given name or Invalid256 if such a name does - /// not exist. - u256 functionIdentifier(std::string const& _functionName) const; - /// @returns a list of all state variables (including inherited) of the contract and their /// offsets in storage. std::vector> stateVariables() const; -- cgit v1.2.3 From d70ff5f8c3ce5410e215a47c3fe1fcc88fabd6a3 Mon Sep 17 00:00:00 2001 From: Kwang Yul Seo Date: Mon, 13 Nov 2017 09:54:29 +0900 Subject: Remove a redundant check m_type can't be FunctionType::Kind::DelegateCall. --- libsolidity/ast/Types.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index ee5f462b..21daac2c 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2574,7 +2574,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con "selector", make_shared(4) )); - if (m_kind != Kind::BareDelegateCall && m_kind != Kind::DelegateCall) + if (m_kind != Kind::BareDelegateCall) { if (isPayable()) members.push_back(MemberList::Member( -- cgit v1.2.3 From f3645297135e84156110d33a42ac845f185eed45 Mon Sep 17 00:00:00 2001 From: Kwang Yul Seo Date: Tue, 14 Nov 2017 21:45:51 +0900 Subject: Fix a typo A semicolon was mistakenly inserted where a string concatenation operator was meant. --- libsolidity/analysis/SyntaxChecker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 0ca4b86c..b6cc04da 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -54,7 +54,7 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit) string(".") + to_string(recommendedVersion.minor()) + string(".") + - to_string(recommendedVersion.patch()); + to_string(recommendedVersion.patch()) + string(";\""); m_errorReporter.warning(_sourceUnit.location(), errorString); -- cgit v1.2.3 From 0b3ef5bbc73fd942779a17e484adda9879f76e41 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Tue, 14 Nov 2017 16:30:41 -0300 Subject: Make tuple components optional in documented grammar --- docs/grammar.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/grammar.txt b/docs/grammar.txt index 72364b7c..7a60aa00 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -129,8 +129,8 @@ Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]* HexNumber = '0x' [0-9a-fA-F]+ DecimalNumber = [0-9]+ -TupleExpression = '(' ( Expression ( ',' Expression )* )? ')' - | '[' ( Expression ( ',' Expression )* )? ']' +TupleExpression = '(' ( Expression? ( ',' Expression? )* )? ')' + | '[' ( Expression ( ',' Expression )* )? ']' ElementaryTypeNameExpression = ElementaryTypeName -- cgit v1.2.3 From 9d328d308ddbda665f2a2b5895000a3304bdabff Mon Sep 17 00:00:00 2001 From: Liana Husikyan Date: Wed, 15 Nov 2017 03:18:30 +0100 Subject: Update index.rst Some minor text editing. --- docs/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 351f8ad7..14350043 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,8 +6,8 @@ Solidity :alt: Solidity logo :align: center -Solidity is a contract-oriented, high-level language whose syntax is similar to that of JavaScript -and it is designed to target the Ethereum Virtual Machine (EVM). +Solidity is a contract-oriented, high-level language for implementing smart contracts. +The syntax is similar to that of JavaScript and it is designed to target the Ethereum Virtual Machine (EVM). Solidity is statically typed, supports inheritance, libraries and complex user-defined types among other features. -- cgit v1.2.3 From 33664c04a8701ebb283f9a5826c2caacb6565fb0 Mon Sep 17 00:00:00 2001 From: Kwang Yul Seo Date: Thu, 16 Nov 2017 00:28:41 +0900 Subject: Update outdated comments _errors -> _errorReporter. --- libsolidity/analysis/PostTypeChecker.h | 2 +- libsolidity/analysis/StaticAnalyzer.h | 2 +- libsolidity/analysis/SyntaxChecker.h | 2 +- libsolidity/analysis/TypeChecker.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libsolidity/analysis/PostTypeChecker.h b/libsolidity/analysis/PostTypeChecker.h index 91d2b0b9..bafc1ae6 100644 --- a/libsolidity/analysis/PostTypeChecker.h +++ b/libsolidity/analysis/PostTypeChecker.h @@ -38,7 +38,7 @@ class ErrorReporter; class PostTypeChecker: private ASTConstVisitor { public: - /// @param _errors the reference to the list of errors and warnings to add them found during type checking. + /// @param _errorReporter provides the error logging functionality. PostTypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} bool check(ASTNode const& _astRoot); diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h index 24ed119f..124c4e7c 100644 --- a/libsolidity/analysis/StaticAnalyzer.h +++ b/libsolidity/analysis/StaticAnalyzer.h @@ -43,7 +43,7 @@ namespace solidity class StaticAnalyzer: private ASTConstVisitor { public: - /// @param _errors the reference to the list of errors and warnings to add them found during static analysis. + /// @param _errorReporter provides the error logging functionality. explicit StaticAnalyzer(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} /// Performs static analysis on the given source unit and all of its sub-nodes. diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index 7fffbec0..d5d72f14 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -38,7 +38,7 @@ namespace solidity class SyntaxChecker: private ASTConstVisitor { public: - /// @param _errors the reference to the list of errors and warnings to add them found during type checking. + /// @param _errorReporter provides the error logging functionality. SyntaxChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} bool checkSyntax(ASTNode const& _astRoot); diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index abe6dac1..344b019d 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -42,7 +42,7 @@ class ErrorReporter; class TypeChecker: private ASTConstVisitor { public: - /// @param _errors the reference to the list of errors and warnings to add them found during type checking. + /// @param _errorReporter provides the error logging functionality. TypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} /// Performs type checking on the given contract and all of its sub-nodes. -- cgit v1.2.3 From 3121608d7cba6ddf342b2c40460a534d64e4ba09 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 15 Nov 2017 17:08:42 +0100 Subject: Remove obsolote statement about msg.sender and library calls. --- docs/units-and-global-variables.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 7af97376..8261bdde 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -84,11 +84,6 @@ Block and Transaction Properties but the only guarantee is that it will be somewhere between the timestamps of two consecutive blocks in the canonical chain. -.. note:: - If you want to implement access restrictions in library functions using - ``msg.sender``, you have to manually supply the value of - ``msg.sender`` as an argument. - .. note:: The block hashes are not available for all blocks for scalability reasons. You can only access the hashes of the most recent 256 blocks, all other -- cgit v1.2.3 From 02db1ad5849cc98c3f8f5af0de3a7f62c080026e Mon Sep 17 00:00:00 2001 From: Kwang Yul Seo Date: Tue, 14 Nov 2017 00:15:56 +0900 Subject: Print using for directive in ASTPrinter --- libsolidity/ast/ASTPrinter.cpp | 12 ++++++++++++ libsolidity/ast/ASTPrinter.h | 2 ++ 2 files changed, 14 insertions(+) diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp index 81e6cc44..23c3cbe1 100644 --- a/libsolidity/ast/ASTPrinter.cpp +++ b/libsolidity/ast/ASTPrinter.cpp @@ -78,6 +78,13 @@ bool ASTPrinter::visit(InheritanceSpecifier const& _node) return goDeeper(); } +bool ASTPrinter::visit(UsingForDirective const& _node) +{ + writeLine("UsingForDirective"); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(StructDefinition const& _node) { writeLine("StructDefinition \"" + _node.name() + "\""); @@ -385,6 +392,11 @@ void ASTPrinter::endVisit(InheritanceSpecifier const&) m_indentation--; } +void ASTPrinter::endVisit(UsingForDirective const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(StructDefinition const&) { m_indentation--; diff --git a/libsolidity/ast/ASTPrinter.h b/libsolidity/ast/ASTPrinter.h index d6897dfd..01e4f7fc 100644 --- a/libsolidity/ast/ASTPrinter.h +++ b/libsolidity/ast/ASTPrinter.h @@ -51,6 +51,7 @@ public: bool visit(ImportDirective const& _node) override; bool visit(ContractDefinition const& _node) override; bool visit(InheritanceSpecifier const& _node) override; + bool visit(UsingForDirective const& _node) override; bool visit(StructDefinition const& _node) override; bool visit(EnumDefinition const& _node) override; bool visit(EnumValue const& _node) override; @@ -94,6 +95,7 @@ public: void endVisit(ImportDirective const&) override; void endVisit(ContractDefinition const&) override; void endVisit(InheritanceSpecifier const&) override; + void endVisit(UsingForDirective const&) override; void endVisit(StructDefinition const&) override; void endVisit(EnumDefinition const&) override; void endVisit(EnumValue const&) override; -- cgit v1.2.3 From c1e202618c60ffd09f12394da56da74120f2a656 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 15 Nov 2017 17:18:00 +0000 Subject: Add explanation to unimplemented assertions --- libsolidity/codegen/ABIFunctions.cpp | 4 ++-- libsolidity/codegen/CompilerUtils.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 080be359..d2cbac99 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -483,7 +483,7 @@ string ABIFunctions::abiEncodingFunctionCalldataArray( _to.identifier() + (_encodeAsLibraryTypes ? "_library" : ""); return createFunction(functionName, [&]() { - solUnimplementedAssert(fromArrayType.isByteArray(), ""); + solUnimplementedAssert(fromArrayType.isByteArray(), "Only byte arrays can be encoded from calldata currently."); // TODO if this is not a byte array, we might just copy byte-by-byte anyway, // because the encoding is position-independent, but we have to check that. Whiskers templ(R"( @@ -754,7 +754,7 @@ string ABIFunctions::abiEncodingFunctionStruct( _to.identifier() + (_encodeAsLibraryTypes ? "_library" : ""); - solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), ""); + solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "Encoding struct from calldata is not yet supported."); solAssert(&_from.structDefinition() == &_to.structDefinition(), ""); return createFunction(functionName, [&]() { diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index f9b181ae..053bed6a 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -121,7 +121,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound { if (auto ref = dynamic_cast(&_type)) { - solUnimplementedAssert(ref->location() == DataLocation::Memory, ""); + solUnimplementedAssert(ref->location() == DataLocation::Memory, "Only in-memory reference type can be stored."); storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries); } else if (auto str = dynamic_cast(&_type)) -- cgit v1.2.3 From 58a0f09f8ed6b4f74ca5757408f7e19f27703ff5 Mon Sep 17 00:00:00 2001 From: Federico Bond Date: Sun, 5 Nov 2017 09:40:57 -0500 Subject: Improve rule for decimal numbers in grammar.txt --- docs/grammar.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/grammar.txt b/docs/grammar.txt index 72364b7c..94fc2e1c 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -127,7 +127,7 @@ StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]* HexNumber = '0x' [0-9a-fA-F]+ -DecimalNumber = [0-9]+ +DecimalNumber = [0-9]+ ( '.' [0-9]* )? ( [eE] [0-9]+ )? TupleExpression = '(' ( Expression ( ',' Expression )* )? ')' | '[' ( Expression ( ',' Expression )* )? ']' @@ -143,9 +143,9 @@ Uint = 'uint' | 'uint8' | 'uint16' | 'uint24' | 'uint32' | 'uint40' | 'uint48' | Byte = 'byte' | 'bytes' | 'bytes1' | 'bytes2' | 'bytes3' | 'bytes4' | 'bytes5' | 'bytes6' | 'bytes7' | 'bytes8' | 'bytes9' | 'bytes10' | 'bytes11' | 'bytes12' | 'bytes13' | 'bytes14' | 'bytes15' | 'bytes16' | 'bytes17' | 'bytes18' | 'bytes19' | 'bytes20' | 'bytes21' | 'bytes22' | 'bytes23' | 'bytes24' | 'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32' -Fixed = 'fixed' | ( 'fixed' DecimalNumber 'x' DecimalNumber ) +Fixed = 'fixed' | ( 'fixed' [0-9]+ 'x' [0-9]+ ) -Ufixed = 'ufixed' | ( 'ufixed' DecimalNumber 'x' DecimalNumber ) +Ufixed = 'ufixed' | ( 'ufixed' [0-9]+ 'x' [0-9]+ ) InlineAssemblyBlock = '{' AssemblyItem* '}' -- cgit v1.2.3 From 03b3faa8ef5a4e4342c541102526214519ab9f5a Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 13 Nov 2017 18:39:27 +0100 Subject: Tone down similarity to JavaScript. --- docs/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 14350043..05fc094e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,7 +7,8 @@ Solidity :align: center Solidity is a contract-oriented, high-level language for implementing smart contracts. -The syntax is similar to that of JavaScript and it is designed to target the Ethereum Virtual Machine (EVM). +It was influenced by C++, Python and JavaScript +and is designed to target the Ethereum Virtual Machine (EVM). Solidity is statically typed, supports inheritance, libraries and complex user-defined types among other features. -- cgit v1.2.3 From 15427dc8d393f4eb3cca8dec2257f8f2408bef22 Mon Sep 17 00:00:00 2001 From: Chua Chee Wee <1757930+chuacw@users.noreply.github.com> Date: Thu, 16 Nov 2017 23:18:06 +0800 Subject: Change phrase in type deduction updated phrase in type deduction from "any value of this type" to "the highest value of this type" --- docs/types.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/types.rst b/docs/types.rst index 0be8255e..c716b95e 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -990,6 +990,6 @@ parameters or return parameters. .. warning:: The type is only deduced from the first assignment, so the loop in the following snippet is infinite, as ``i`` will have the type - ``uint8`` and any value of this type is smaller than ``2000``. + ``uint8`` and the highest value of this type is smaller than ``2000``. ``for (var i = 0; i < 2000; i++) { ... }`` -- cgit v1.2.3 From 8a6692b2cfb7cf53db6731acd6a9908bd36b5475 Mon Sep 17 00:00:00 2001 From: wadeAlexC Date: Thu, 5 Oct 2017 09:28:25 -0400 Subject: Improves address literal checksum error message --- Changelog.md | 1 + libdevcore/CommonData.cpp | 25 ++++++++++++++----------- libdevcore/CommonData.h | 4 ++++ libsolidity/analysis/TypeChecker.cpp | 8 +++++--- libsolidity/ast/AST.cpp | 6 ++++++ libsolidity/ast/AST.h | 2 ++ 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/Changelog.md b/Changelog.md index 45521f3e..2487b87c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Features: * Syntax Checker: Turn the usage of ``callcode`` into an error as experimental 0.5.0 feature. + * Type Checker: Improve address checksum warning. * Type Checker: More detailed errors for invalid array lengths (such as division by zero). Bugfixes: diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index db11e61c..85ad685b 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -86,20 +86,23 @@ bool dev::passesAddressChecksum(string const& _str, bool _strict) )) return true; + return _str == dev::getChecksummedAddress(_str); +} + +string dev::getChecksummedAddress(string const& _addr) +{ + string s = _addr.substr(0, 2) == "0x" ? _addr.substr(2) : _addr; h256 hash = keccak256(boost::algorithm::to_lower_copy(s, std::locale::classic())); - for (size_t i = 0; i < 40; ++i) + string ret = "0x"; + + for (size_t i = 0; i < s.length(); ++i) { char addressCharacter = s[i]; - bool lowerCase; - if ('a' <= addressCharacter && addressCharacter <= 'f') - lowerCase = true; - else if ('A' <= addressCharacter && addressCharacter <= 'F') - lowerCase = false; - else - continue; unsigned nibble = (unsigned(hash[i / 2]) >> (4 * (1 - (i % 2)))) & 0xf; - if ((nibble >= 8) == lowerCase) - return false; + if (nibble >= 8) + ret += toupper(addressCharacter); + else + ret += tolower(addressCharacter); } - return true; + return ret; } diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 765707f8..e76a0949 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -209,4 +209,8 @@ bool contains(T const& _t, V const& _v) /// are considered valid. bool passesAddressChecksum(std::string const& _str, bool _strict); +/// @returns the checksummed version of an address +/// @param hex strings that look like an address +std::string getChecksummedAddress(std::string const& _addr); + } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 746e762e..fee60797 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1122,7 +1122,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) var.annotation().type->toString() + ". Try converting to type " + valueComponentType->mobileType()->toString() + - " or use an explicit conversion." + " or use an explicit conversion." ); else m_errorReporter.typeError( @@ -1320,7 +1320,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple) _tuple.annotation().isPure = isPure; if (_tuple.isInlineArray()) { - if (!inlineArrayType) + if (!inlineArrayType) m_errorReporter.fatalTypeError(_tuple.location(), "Unable to deduce common type for array elements."); _tuple.annotation().type = make_shared(DataLocation::Memory, inlineArrayType, types.size()); } @@ -2000,7 +2000,9 @@ void TypeChecker::endVisit(Literal const& _literal) m_errorReporter.warning( _literal.location(), "This looks like an address but has an invalid checksum. " - "If this is not used as an address, please prepend '00'." + "If this is not used as an address, please prepend '00'. " + "Correct checksummed address: '" + _literal.getChecksummedAddress() + "'. " + "For more information please see https://solidity.readthedocs.io/en/develop/types.html#address-literals" ); } if (!_literal.annotation().type) diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 1048b610..4911f161 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -583,3 +583,9 @@ bool Literal::passesAddressChecksum() const solAssert(isHexNumber(), "Expected hex number"); return dev::passesAddressChecksum(value(), true); } + +std::string Literal::getChecksummedAddress() const +{ + solAssert(isHexNumber(), "Expected hex number"); + return dev::getChecksummedAddress(value()); +} diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 733e7c78..5d6763ca 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -1613,6 +1613,8 @@ public: bool looksLikeAddress() const; /// @returns true if it passes the address checksum test. bool passesAddressChecksum() const; + /// @returns the checksummed version of an address + std::string getChecksummedAddress() const; private: Token::Value m_token; -- cgit v1.2.3 From 6ebc094474837d922ac00a92c54c903c5eb78585 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 20 Oct 2017 15:23:28 +0100 Subject: Ensure that non-hex characters are caught in address checksumming --- libdevcore/CommonData.cpp | 8 ++++++-- libdevcore/Exceptions.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index 85ad685b..445d11cd 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -92,10 +93,13 @@ bool dev::passesAddressChecksum(string const& _str, bool _strict) string dev::getChecksummedAddress(string const& _addr) { string s = _addr.substr(0, 2) == "0x" ? _addr.substr(2) : _addr; + assertThrow(s.length() == 40, InvalidAddress, ""); + assertThrow(s.find_first_not_of("0123456789abcdefABCDEF") == string::npos, InvalidAddress, ""); + h256 hash = keccak256(boost::algorithm::to_lower_copy(s, std::locale::classic())); - string ret = "0x"; - for (size_t i = 0; i < s.length(); ++i) + string ret = "0x"; + for (size_t i = 0; i < 40; ++i) { char addressCharacter = s[i]; unsigned nibble = (unsigned(hash[i / 2]) >> (4 * (1 - (i % 2)))) & 0xf; diff --git a/libdevcore/Exceptions.h b/libdevcore/Exceptions.h index a3e638bf..cfe72fbf 100644 --- a/libdevcore/Exceptions.h +++ b/libdevcore/Exceptions.h @@ -44,6 +44,7 @@ private: #define DEV_SIMPLE_EXCEPTION(X) struct X: virtual Exception { const char* what() const noexcept override { return #X; } } +DEV_SIMPLE_EXCEPTION(InvalidAddress); DEV_SIMPLE_EXCEPTION(BadHexCharacter); DEV_SIMPLE_EXCEPTION(FileError); -- cgit v1.2.3 From 1d5dd909b4ed8625330e9ec859e02dfd067f4006 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 24 Oct 2017 10:54:51 +0100 Subject: Do not try to display checksummed address for too-short/long address literals --- libsolidity/analysis/TypeChecker.cpp | 4 ++-- libsolidity/ast/AST.cpp | 2 ++ libsolidity/ast/AST.h | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index fee60797..73047e76 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -2000,8 +2000,8 @@ void TypeChecker::endVisit(Literal const& _literal) m_errorReporter.warning( _literal.location(), "This looks like an address but has an invalid checksum. " - "If this is not used as an address, please prepend '00'. " - "Correct checksummed address: '" + _literal.getChecksummedAddress() + "'. " + "If this is not used as an address, please prepend '00'. " + + (!_literal.getChecksummedAddress().empty() ? "Correct checksummed address: '" + _literal.getChecksummedAddress() + "'. " : "") + "For more information please see https://solidity.readthedocs.io/en/develop/types.html#address-literals" ); } diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 4911f161..62507093 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -587,5 +587,7 @@ bool Literal::passesAddressChecksum() const std::string Literal::getChecksummedAddress() const { solAssert(isHexNumber(), "Expected hex number"); + if (value().length != 42) + return string(); return dev::getChecksummedAddress(value()); } diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 5d6763ca..feffde64 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -1613,7 +1613,7 @@ public: bool looksLikeAddress() const; /// @returns true if it passes the address checksum test. bool passesAddressChecksum() const; - /// @returns the checksummed version of an address + /// @returns the checksummed version of an address (or empty string if not valid) std::string getChecksummedAddress() const; private: -- cgit v1.2.3 From 8d26894841d7b5897b7c6f126f6ea1b8293ab5a2 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 24 Oct 2017 11:01:56 +0100 Subject: Show checksummed address always (prepend with 0) --- libsolidity/ast/AST.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 62507093..8da6964e 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -587,7 +587,10 @@ bool Literal::passesAddressChecksum() const std::string Literal::getChecksummedAddress() const { solAssert(isHexNumber(), "Expected hex number"); - if (value().length != 42) + /// Pad literal to be a proper hex address. + string address = value().substr(2); + if (address.length() > 40) return string(); - return dev::getChecksummedAddress(value()); + address.insert(address.begin(), 40 - address.size(), '0'); + return dev::getChecksummedAddress(address); } -- cgit v1.2.3 From f7249abe284b5f29996621f2e19cab7f09f51785 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 24 Oct 2017 11:55:30 +0100 Subject: Extend address checksum tests --- test/libsolidity/SolidityNameAndTypeResolution.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 30624260..88ec58ee 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -5766,7 +5766,7 @@ BOOST_AUTO_TEST_CASE(invalid_address_checksum) } } )"; - CHECK_WARNING(text, "checksum"); + CHECK_WARNING(text, "This looks like an address but has an invalid checksum."); } BOOST_AUTO_TEST_CASE(invalid_address_no_checksum) @@ -5779,10 +5779,10 @@ BOOST_AUTO_TEST_CASE(invalid_address_no_checksum) } } )"; - CHECK_WARNING(text, "checksum"); + CHECK_WARNING(text, "This looks like an address but has an invalid checksum."); } -BOOST_AUTO_TEST_CASE(invalid_address_length) +BOOST_AUTO_TEST_CASE(invalid_address_length_short) { char const* text = R"( contract C { @@ -5792,7 +5792,20 @@ BOOST_AUTO_TEST_CASE(invalid_address_length) } } )"; - CHECK_WARNING(text, "checksum"); + CHECK_WARNING(text, "This looks like an address but has an invalid checksum."); +} + +BOOST_AUTO_TEST_CASE(invalid_address_length_long) +{ + char const* text = R"( + contract C { + function f() pure public { + address x = 0xFA0bFc97E48458494Ccd857e1A85DC91F7F0046E0; + x; + } + } + )"; + CHECK_WARNING_ALLOW_MULTI(text, "This looks like an address but has an invalid checksum."); } BOOST_AUTO_TEST_CASE(address_test_for_bug_in_implementation) -- cgit v1.2.3 From 53796c0fe8bfd6ff2707bd12dd3141169489f763 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 17 Nov 2017 00:40:50 +0000 Subject: Add tests for getChecksummedAddress --- test/libdevcore/Checksum.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/libdevcore/Checksum.cpp b/test/libdevcore/Checksum.cpp index 17a17d22..4eedbd99 100644 --- a/test/libdevcore/Checksum.cpp +++ b/test/libdevcore/Checksum.cpp @@ -19,6 +19,8 @@ */ #include +#include + #include "../TestHelper.h" @@ -31,6 +33,38 @@ namespace test BOOST_AUTO_TEST_SUITE(Checksum) +BOOST_AUTO_TEST_CASE(calculate) +{ + BOOST_CHECK(!getChecksummedAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed").empty()); + BOOST_CHECK(!getChecksummedAddress("0x0123456789abcdefABCDEF0123456789abcdefAB").empty()); + // too short + BOOST_CHECK_THROW(getChecksummedAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beae"), InvalidAddress); + BOOST_CHECK_THROW(getChecksummedAddress("5aaeb6053f3e94c9b9a09f33669435e7ef1beae"), InvalidAddress); + // too long + BOOST_CHECK_THROW(getChecksummedAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed1"), InvalidAddress); + BOOST_CHECK_THROW(getChecksummedAddress("5aaeb6053f3e94c9b9a09f33669435e7ef1beaed1"), InvalidAddress); + // non-hex character + BOOST_CHECK_THROW(getChecksummedAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaeK"), InvalidAddress); + + // the official test suite from EIP-55 + vector cases { + // all upper case + "0x52908400098527886E0F7030069857D2E4169EE7", + "0x8617E340B3D01FA5F11F306F4090FD50E238070D", + // all lower case + "0xde709f2102306220921060314715629080e2fb77", + "0x27b1fdb04752bbc536007a920d24acb045561c26", + // regular + "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", + "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", + "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb" + }; + + for (size_t i = 0; i < cases.size(); i++) + BOOST_REQUIRE_MESSAGE(getChecksummedAddress(cases[i]) == cases[i], cases[i]); +} + BOOST_AUTO_TEST_CASE(regular) { BOOST_CHECK(passesAddressChecksum("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", true)); -- cgit v1.2.3 From 5c9e273d028bdcc65af50fdc1a2c5545e54739c3 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 21 Nov 2017 07:03:47 -0500 Subject: Link to translations. (#3212) --- docs/index.rst | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 14350043..67fc87c2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -20,6 +20,15 @@ crowdfunding, blind auctions, multi-signature wallets and more. `Remix `_ (it can take a while to load, please be patient). +Translations +------------ + +This documentation is translated into several languages by community volunteers, but the English version stands as a reference. + +* `Spanish `_ +* `Russian `_ (rather outdated) + + Useful links ------------ @@ -131,8 +140,6 @@ If you still have questions, you can try searching or asking on the site, or come to our `gitter channel `_. Ideas for improving Solidity or this documentation are always welcome! -See also `Russian version (русский перевод) `_. - Contents ======== -- cgit v1.2.3 From 165857b1d40ecb8a1bcf40eec9e371370ebe2541 Mon Sep 17 00:00:00 2001 From: Balajiganapathi S Date: Sat, 28 Oct 2017 16:33:11 +0530 Subject: Allow constant integer variables as array lengths. --- Changelog.md | 1 + libsolidity/analysis/ConstantEvaluator.cpp | 18 ++++++++++++++++ libsolidity/analysis/ConstantEvaluator.h | 1 + test/libsolidity/SolidityEndToEndTest.cpp | 18 ++++++++++++++++ test/libsolidity/SolidityNameAndTypeResolution.cpp | 24 +++++++++++++++++++++- 5 files changed, 61 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 2487b87c..a362138e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.4.19 (unreleased) Features: + * Allow constant variables to be used as array length * Syntax Checker: Turn the usage of ``callcode`` into an error as experimental 0.5.0 feature. * Type Checker: Improve address checksum warning. * Type Checker: More detailed errors for invalid array lengths (such as division by zero). diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index bc3b7cf1..50044eb6 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -74,3 +74,21 @@ void ConstantEvaluator::endVisit(Literal const& _literal) if (!_literal.annotation().type) m_errorReporter.fatalTypeError(_literal.location(), "Invalid literal value."); } + +void ConstantEvaluator::endVisit(Identifier const& _identifier) +{ + VariableDeclaration const *variableDeclaration = dynamic_cast(_identifier.annotation().referencedDeclaration); + if (!variableDeclaration) + return; + if (!variableDeclaration->isConstant()) + m_errorReporter.fatalTypeError(_identifier.location(), "Identifier must be declared constant."); + + ASTPointer value = variableDeclaration->value(); + if (value) + { + if (!value->annotation().type) + ConstantEvaluator e(*value, m_errorReporter); + + _identifier.annotation().type = value->annotation().type; + } +} diff --git a/libsolidity/analysis/ConstantEvaluator.h b/libsolidity/analysis/ConstantEvaluator.h index 90bceb5d..42ccfe7e 100644 --- a/libsolidity/analysis/ConstantEvaluator.h +++ b/libsolidity/analysis/ConstantEvaluator.h @@ -48,6 +48,7 @@ private: virtual void endVisit(BinaryOperation const& _operation); virtual void endVisit(UnaryOperation const& _operation); virtual void endVisit(Literal const& _literal); + virtual void endVisit(Identifier const& _identifier); ErrorReporter& m_errorReporter; }; diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 9a837113..c2f96aaa 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2345,6 +2345,24 @@ BOOST_AUTO_TEST_CASE(constructor_static_array_argument) ABI_CHECK(callContractFunction("b(uint256)", u256(2)), encodeArgs(u256(4))); } +BOOST_AUTO_TEST_CASE(constant_var_as_array_length) +{ + char const* sourceCode = R"( + contract C { + uint constant LEN = 3; + uint[LEN] public a; + + function C(uint[LEN] _a) { + a = _a; + } + } + )"; + compileAndRun(sourceCode, 0, "C", encodeArgs(u256(1), u256(2), u256(3))); + ABI_CHECK(callContractFunction("a(uint256)", u256(0)), encodeArgs(u256(1))); + ABI_CHECK(callContractFunction("a(uint256)", u256(1)), encodeArgs(u256(2))); + ABI_CHECK(callContractFunction("a(uint256)", u256(2)), encodeArgs(u256(3))); +} + BOOST_AUTO_TEST_CASE(functions_called_by_constructor) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 88ec58ee..e00beefe 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -2107,7 +2107,7 @@ BOOST_AUTO_TEST_CASE(array_with_nonconstant_length) function f(uint a) public { uint8[a] x; } } )"; - CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); + CHECK_ERROR(text, TypeError, "Identifier must be declared constant"); } BOOST_AUTO_TEST_CASE(array_with_negative_length) @@ -7263,6 +7263,28 @@ BOOST_AUTO_TEST_CASE(array_length_not_convertible_to_integer) CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); } +BOOST_AUTO_TEST_CASE(array_length_constant_var) +{ + char const* text = R"( + contract C { + uint constant LEN = 10; + uint[LEN] ids; + } + )"; + CHECK_SUCCESS(text); +} + +BOOST_AUTO_TEST_CASE(array_length_non_integer_constant_var) +{ + char const* text = R"( + contract C { + bool constant LEN = true; + uint[LEN] ids; + } + )"; + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); +} + BOOST_AUTO_TEST_CASE(array_length_invalid_expression) { char const* text = R"( -- cgit v1.2.3 From c0b49694518d333a8ad62623321f839fc3b4bc6a Mon Sep 17 00:00:00 2001 From: Balajiganapathi S Date: Sat, 28 Oct 2017 18:06:46 +0530 Subject: Add more tests for constant var as array lengths --- test/libsolidity/SolidityNameAndTypeResolution.cpp | 97 +++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index e00beefe..cedf7f3a 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -2107,7 +2107,7 @@ BOOST_AUTO_TEST_CASE(array_with_nonconstant_length) function f(uint a) public { uint8[a] x; } } )"; - CHECK_ERROR(text, TypeError, "Identifier must be declared constant"); + CHECK_ERROR(text, TypeError, "Identifier must be declared constant."); } BOOST_AUTO_TEST_CASE(array_with_negative_length) @@ -7285,6 +7285,101 @@ BOOST_AUTO_TEST_CASE(array_length_non_integer_constant_var) CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); } +BOOST_AUTO_TEST_CASE(array_length_cannot_be_function) +{ + char const* text = R"( + contract C { + function f() {} + uint[f] ids; + } + )"; + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); +} + +BOOST_AUTO_TEST_CASE(array_length_can_be_recursive_constant) +{ + char const* text = R"( + contract C { + uint constant L = 5; + uint constant LEN = L + 4 * L; + uint[LEN] ids; + } + )"; + CHECK_SUCCESS(text); +} + +BOOST_AUTO_TEST_CASE(array_length_cannot_be_function_call) +{ + char const* text = R"( + contract C { + function f(uint x) {} + uint constant LEN = f(); + uint[LEN] ids; + } + )"; + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); +} + +BOOST_AUTO_TEST_CASE(array_length_const_cannot_be_fractional) +{ + char const* text = R"( + contract C { + fixed constant L = 10.5; + uint[L] ids; + } + )"; + CHECK_ERROR(text, TypeError, "Array with fractional length specified"); +} + +BOOST_AUTO_TEST_CASE(array_length_can_be_constant_in_struct) +{ + char const* text = R"( + contract C { + uint constant LEN = 10; + struct Test { + uint[LEN] ids; + } + } + )"; + CHECK_SUCCESS(text); +} + +BOOST_AUTO_TEST_CASE(array_length_can_be_constant_in_function) +{ + char const* text = R"( + contract C { + uint constant LEN = 10; + function f() { + uint[LEN] a; + } + } + )"; + CHECK_SUCCESS(text); +} + +BOOST_AUTO_TEST_CASE(array_length_cannot_be_constant_function_parameter) +{ + char const* text = R"( + contract C { + function f(uint constant LEN) { + uint[LEN] a; + } + } + )"; + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); +} + +BOOST_AUTO_TEST_CASE(array_length_with_pure_functions) +{ + char const* text = R"( + contract C { + uint constant LEN = keccak256(ripemd160(33)); + uint[LEN] ids; + } + )"; + CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); +} + BOOST_AUTO_TEST_CASE(array_length_invalid_expression) { char const* text = R"( -- cgit v1.2.3 From d102deaec90f6f5bfe38140b459a05215d3254f9 Mon Sep 17 00:00:00 2001 From: Balajiganapathi S Date: Fri, 17 Nov 2017 22:25:07 +0530 Subject: Detect cyclic constant definitions --- libsolidity/analysis/ConstantEvaluator.cpp | 16 +++++++----- libsolidity/analysis/ConstantEvaluator.h | 7 +++-- test/libsolidity/SolidityNameAndTypeResolution.cpp | 30 +++++++++++++++++++++- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index 50044eb6..cb8bbc05 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -77,18 +77,22 @@ void ConstantEvaluator::endVisit(Literal const& _literal) void ConstantEvaluator::endVisit(Identifier const& _identifier) { - VariableDeclaration const *variableDeclaration = dynamic_cast(_identifier.annotation().referencedDeclaration); + VariableDeclaration const* variableDeclaration = dynamic_cast(_identifier.annotation().referencedDeclaration); if (!variableDeclaration) return; if (!variableDeclaration->isConstant()) m_errorReporter.fatalTypeError(_identifier.location(), "Identifier must be declared constant."); ASTPointer value = variableDeclaration->value(); - if (value) - { - if (!value->annotation().type) - ConstantEvaluator e(*value, m_errorReporter); + if (!value) + m_errorReporter.fatalTypeError(_identifier.location(), "Constant identifier declaration must have a constant value."); - _identifier.annotation().type = value->annotation().type; + if (!value->annotation().type) + { + if (m_depth > 32) + m_errorReporter.fatalTypeError(_identifier.location(), "Cyclic constant definition."); + ConstantEvaluator e(*value, m_errorReporter, m_depth + 1); } + + _identifier.annotation().type = value->annotation().type; } diff --git a/libsolidity/analysis/ConstantEvaluator.h b/libsolidity/analysis/ConstantEvaluator.h index 42ccfe7e..6725d610 100644 --- a/libsolidity/analysis/ConstantEvaluator.h +++ b/libsolidity/analysis/ConstantEvaluator.h @@ -38,8 +38,9 @@ class TypeChecker; class ConstantEvaluator: private ASTConstVisitor { public: - ConstantEvaluator(Expression const& _expr, ErrorReporter& _errorReporter): - m_errorReporter(_errorReporter) + ConstantEvaluator(Expression const& _expr, ErrorReporter& _errorReporter, size_t _newDepth = 0): + m_errorReporter(_errorReporter), + m_depth(_newDepth) { _expr.accept(*this); } @@ -51,6 +52,8 @@ private: virtual void endVisit(Identifier const& _identifier); ErrorReporter& m_errorReporter; + /// Current recursion depth. + size_t m_depth; }; } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index cedf7f3a..0d02ac34 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -7366,7 +7366,35 @@ BOOST_AUTO_TEST_CASE(array_length_cannot_be_constant_function_parameter) } } )"; - CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); + CHECK_ERROR(text, TypeError, "Constant identifier declaration must have a constant value."); +} + +BOOST_AUTO_TEST_CASE(array_length_with_cyclic_constant) +{ + char const* text = R"( + contract C { + uint constant LEN = LEN; + function f() { + uint[LEN] a; + } + } + )"; + CHECK_ERROR(text, TypeError, "Cyclic constant definition."); +} + +BOOST_AUTO_TEST_CASE(array_length_with_complex_cyclic_constant) +{ + char const* text = R"( + contract C { + uint constant L2 = LEN - 10; + uint constant L1 = L2 / 10; + uint constant LEN = 10 + L1 * 5; + function f() { + uint[LEN] a; + } + } + )"; + CHECK_ERROR(text, TypeError, "Cyclic constant definition."); } BOOST_AUTO_TEST_CASE(array_length_with_pure_functions) -- cgit v1.2.3 From 455e51a608910d60348adb4b464590d976ce7fd7 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 22 Nov 2017 01:05:26 +0000 Subject: Improve cyclic constant error message --- libsolidity/analysis/ConstantEvaluator.cpp | 2 +- test/libsolidity/SolidityNameAndTypeResolution.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index cb8bbc05..4d546e68 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -90,7 +90,7 @@ void ConstantEvaluator::endVisit(Identifier const& _identifier) if (!value->annotation().type) { if (m_depth > 32) - m_errorReporter.fatalTypeError(_identifier.location(), "Cyclic constant definition."); + m_errorReporter.fatalTypeError(_identifier.location(), "Cyclic constant definition (or maximum recursion depth exhausted)."); ConstantEvaluator e(*value, m_errorReporter, m_depth + 1); } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 0d02ac34..73c1660e 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -7379,7 +7379,7 @@ BOOST_AUTO_TEST_CASE(array_length_with_cyclic_constant) } } )"; - CHECK_ERROR(text, TypeError, "Cyclic constant definition."); + CHECK_ERROR(text, TypeError, "Cyclic constant definition (or maximum recursion depth exhausted)."); } BOOST_AUTO_TEST_CASE(array_length_with_complex_cyclic_constant) @@ -7394,7 +7394,7 @@ BOOST_AUTO_TEST_CASE(array_length_with_complex_cyclic_constant) } } )"; - CHECK_ERROR(text, TypeError, "Cyclic constant definition."); + CHECK_ERROR(text, TypeError, "Cyclic constant definition (or maximum recursion depth exhausted)."); } BOOST_AUTO_TEST_CASE(array_length_with_pure_functions) -- cgit v1.2.3 From f62caf587e9df37c67518d199065ab50bcbb320c Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 28 Sep 2017 13:44:56 +0200 Subject: Handle branches. --- libsolidity/formal/SMTChecker.cpp | 132 ++++++++++++++++++++++---------------- libsolidity/formal/SMTChecker.h | 10 +++ 2 files changed, 88 insertions(+), 54 deletions(-) diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 2d2f05ec..0dc6e642 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -45,12 +45,7 @@ SMTChecker::SMTChecker(ErrorReporter& _errorReporter, ReadCallback::Callback con void SMTChecker::analyze(SourceUnit const& _source) { if (_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker)) - { - m_interface->reset(); - m_currentSequenceCounter.clear(); - m_nextFreeSequenceCounter.clear(); _source.accept(*this); - } } void SMTChecker::endVisit(VariableDeclaration const& _varDecl) @@ -75,20 +70,21 @@ bool SMTChecker::visit(FunctionDefinition const& _function) _function.location(), "Assertion checker does not yet support constructors and functions with modifiers." ); - // TODO actually we probably also have to reset all local variables and similar things. m_currentFunction = &_function; - m_interface->push(); + // We only handle local variables, so we clear at the beginning of the function. + // If we add storage variables, those should be cleared differently. + m_interface->reset(); + m_currentSequenceCounter.clear(); + m_nextFreeSequenceCounter.clear(); + m_conditionalExecutionHappened = false; return true; } void SMTChecker::endVisit(FunctionDefinition const&) { // TOOD we could check for "reachability", i.e. satisfiability here. - // We only handle local variables, so we clear everything. + // We only handle local variables, so we clear at the beginning of the function. // If we add storage variables, those should be cleared differently. - m_currentSequenceCounter.clear(); - m_nextFreeSequenceCounter.clear(); - m_interface->pop(); m_currentFunction = nullptr; } @@ -98,55 +94,39 @@ bool SMTChecker::visit(IfStatement const& _node) // TODO Check if condition is always true - auto countersAtStart = m_currentSequenceCounter; - m_interface->push(); - m_interface->addAssertion(expr(_node.condition())); - _node.trueStatement().accept(*this); - auto countersAtEndOfTrue = m_currentSequenceCounter; - m_interface->pop(); - - decltype(m_currentSequenceCounter) countersAtEndOfFalse; + auto touchedVariables = visitBranch(_node.trueStatement(), expr(_node.condition())); if (_node.falseStatement()) - { - m_currentSequenceCounter = countersAtStart; - m_interface->push(); - m_interface->addAssertion(!expr(_node.condition())); - _node.falseStatement()->accept(*this); - countersAtEndOfFalse = m_currentSequenceCounter; - m_interface->pop(); - } - else - countersAtEndOfFalse = countersAtStart; - - // Reset all values that have been touched. + touchedVariables += visitBranch(*_node.falseStatement(), !expr(_node.condition())); - // TODO this should use a previously generated side-effect structure + resetVariables(touchedVariables); - solAssert(countersAtEndOfFalse.size() == countersAtEndOfTrue.size(), ""); - for (auto const& declCounter: countersAtEndOfTrue) - { - solAssert(countersAtEndOfFalse.count(declCounter.first), ""); - auto decl = declCounter.first; - int trueCounter = countersAtEndOfTrue.at(decl); - int falseCounter = countersAtEndOfFalse.at(decl); - if (trueCounter == falseCounter) - continue; // Was not modified - newValue(*decl); - setValue(*decl, 0); - } return false; } bool SMTChecker::visit(WhileStatement const& _node) { - _node.condition().accept(*this); + // TODO Check if condition is always true - //m_interface->push(); - //m_interface->addAssertion(expr(_node.condition())); - // TDOO clear knowledge (increment sequence numbers and add bounds assertions ) apart from assertions + // TODO Weird side effects like + // uint x = 1; + // while (x ++ > 0) { assert(x == 2); } + // solution: clear variables first, then execute and assert condition, then executed body. + if (_node.isDoWhile()) + { + auto touchedVariables = visitBranch(_node.body()); + // TODO what about the side-effects of this? + // If we have a "break", this might never be executed. + _node.condition().accept(*this); + resetVariables(touchedVariables); + } + else + { + _node.condition().accept(*this); + auto touchedVariables = visitBranch(_node.body(), expr(_node.condition())); + resetVariables(touchedVariables); + } - // TODO combine similar to if - return true; + return false; } void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl) @@ -386,6 +366,7 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op) solAssert(_op.annotation().commonType, ""); if (_op.annotation().commonType->category() == Type::Category::Bool) { + // @TODO check that both of them are not constant if (_op.getOperator() == Token::And) m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) && expr(_op.rightExpression())); else @@ -395,7 +376,37 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op) m_errorReporter.warning( _op.location(), "Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for boolean operations" - ); + ); +} + +set SMTChecker::visitBranch(Statement const& _statement, smt::Expression _condition) +{ + return visitBranch(_statement, &_condition); +} + +set SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition) +{ + VariableSequenceCounters sequenceCountersStart = m_currentSequenceCounter; + + m_interface->push(); + if (_condition) + m_interface->addAssertion(*_condition); + _statement.accept(*this); + m_interface->pop(); + + m_conditionalExecutionHappened = true; + + set touched; + for (auto const& seq: m_currentSequenceCounter) + { + if (!sequenceCountersStart.count(seq.first)) + touched.insert(seq.first); + else if (sequenceCountersStart[seq.first] != seq.second) + touched.insert(seq.first); + } + m_currentSequenceCounter = sequenceCountersStart; + + return touched; } void SMTChecker::checkCondition( @@ -446,6 +457,11 @@ void SMTChecker::checkCondition( return; } + string conditionalComment; + if (m_conditionalExecutionHappened) + conditionalComment = + "\nNote that some information is erased after conditional execution of parts of the code.\n" + "You can re-introduce information using require()."; switch (result) { case smt::CheckResult::SATISFIABLE: @@ -471,13 +487,13 @@ void SMTChecker::checkCondition( } else message << "."; - m_errorReporter.warning(_location, message.str()); + m_errorReporter.warning(_location, message.str() + conditionalComment); break; } case smt::CheckResult::UNSATISFIABLE: break; case smt::CheckResult::UNKNOWN: - m_errorReporter.warning(_location, _description + " might happen here."); + m_errorReporter.warning(_location, _description + " might happen here." + conditionalComment); break; case smt::CheckResult::ERROR: m_errorReporter.warning(_location, "Error trying to invoke SMT solver."); @@ -488,6 +504,15 @@ void SMTChecker::checkCondition( m_interface->pop(); } +void SMTChecker::resetVariables(set _variables) +{ + for (auto const* decl: _variables) + { + newValue(*decl); + setValue(*decl, false); + } +} + void SMTChecker::createVariable(VariableDeclaration const& _varDecl, bool _setToZero) { if (dynamic_cast(_varDecl.type().get())) @@ -535,7 +560,6 @@ smt::Expression SMTChecker::valueAtSequence(const Declaration& _decl, int _seque smt::Expression SMTChecker::newValue(Declaration const& _decl) { - solAssert(m_currentSequenceCounter.count(&_decl), ""); solAssert(m_nextFreeSequenceCounter.count(&_decl), ""); m_currentSequenceCounter[&_decl] = m_nextFreeSequenceCounter[&_decl]++; return currentValue(_decl); diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index faaac639..6575dc1b 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -61,6 +61,12 @@ private: void compareOperation(BinaryOperation const& _op); void booleanOperation(BinaryOperation const& _op); + // Visits the branch given by the statement, pushes and pops the SMT checker. + // @returns the set of touched declarations + // @param _condition if present, asserts that this condition is true within the branch. + std::set visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr); + std::set visitBranch(Statement const& _statement, smt::Expression _condition); + void checkCondition( smt::Expression _condition, SourceLocation const& _location, @@ -69,6 +75,7 @@ private: smt::Expression* _additionalValue = nullptr ); + void resetVariables(std::set _variables); void createVariable(VariableDeclaration const& _varDecl, bool _setToZero); static std::string uniqueSymbol(Declaration const& _decl); @@ -93,6 +100,8 @@ private: static smt::Expression minValue(IntegerType const& _t); static smt::Expression maxValue(IntegerType const& _t); + using VariableSequenceCounters = std::map; + /// Returns the expression corresponding to the AST node. Creates a new expression /// if it does not exist yet. smt::Expression expr(Expression const& _e); @@ -101,6 +110,7 @@ private: smt::Expression var(Declaration const& _decl); std::shared_ptr m_interface; + bool m_conditionalExecutionHappened = false; std::map m_currentSequenceCounter; std::map m_nextFreeSequenceCounter; std::map m_expressions; -- cgit v1.2.3 From b37377641dd1cac7d1b5d5307ea6f7517c0f321f Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 28 Sep 2017 15:24:24 +0200 Subject: Track usage of variables. --- libsolidity/formal/SMTChecker.cpp | 130 ++++++++++++++++++----------------- libsolidity/formal/SMTChecker.h | 25 ++++--- libsolidity/formal/VariableUsage.cpp | 80 +++++++++++++++++++++ libsolidity/formal/VariableUsage.h | 50 ++++++++++++++ 4 files changed, 215 insertions(+), 70 deletions(-) create mode 100644 libsolidity/formal/VariableUsage.cpp create mode 100644 libsolidity/formal/VariableUsage.h diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 0dc6e642..428afa9f 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -23,6 +23,8 @@ #include #endif +#include + #include #include @@ -44,23 +46,15 @@ SMTChecker::SMTChecker(ErrorReporter& _errorReporter, ReadCallback::Callback con void SMTChecker::analyze(SourceUnit const& _source) { + m_variableUsage = make_shared(_source); if (_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker)) _source.accept(*this); } void SMTChecker::endVisit(VariableDeclaration const& _varDecl) { - if (_varDecl.value()) - { - m_errorReporter.warning( - _varDecl.location(), - "Assertion checker does not yet support this." - ); - } - else if (_varDecl.isLocalOrReturn()) - createVariable(_varDecl, true); - else if (_varDecl.isCallableParameter()) - createVariable(_varDecl, false); + if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value()) + assignment(_varDecl, *_varDecl.value()); } bool SMTChecker::visit(FunctionDefinition const& _function) @@ -77,6 +71,7 @@ bool SMTChecker::visit(FunctionDefinition const& _function) m_currentSequenceCounter.clear(); m_nextFreeSequenceCounter.clear(); m_conditionalExecutionHappened = false; + initializeLocalVariables(_function); return true; } @@ -94,9 +89,13 @@ bool SMTChecker::visit(IfStatement const& _node) // TODO Check if condition is always true - auto touchedVariables = visitBranch(_node.trueStatement(), expr(_node.condition())); + visitBranch(_node.trueStatement(), expr(_node.condition())); + vector touchedVariables = m_variableUsage->touchedVariables(_node.trueStatement()); if (_node.falseStatement()) - touchedVariables += visitBranch(*_node.falseStatement(), !expr(_node.condition())); + { + visitBranch(*_node.falseStatement(), !expr(_node.condition())); + touchedVariables += m_variableUsage->touchedVariables(*_node.falseStatement()); + } resetVariables(touchedVariables); @@ -111,20 +110,21 @@ bool SMTChecker::visit(WhileStatement const& _node) // uint x = 1; // while (x ++ > 0) { assert(x == 2); } // solution: clear variables first, then execute and assert condition, then executed body. + + auto touchedVariables = m_variableUsage->touchedVariables(_node); + resetVariables(touchedVariables); if (_node.isDoWhile()) { - auto touchedVariables = visitBranch(_node.body()); - // TODO what about the side-effects of this? - // If we have a "break", this might never be executed. + visitBranch(_node.body()); + // TODO the assertions generated in the body should still be active in the condition _node.condition().accept(*this); - resetVariables(touchedVariables); } else { _node.condition().accept(*this); - auto touchedVariables = visitBranch(_node.body(), expr(_node.condition())); - resetVariables(touchedVariables); + visitBranch(_node.body(), expr(_node.condition())); } + resetVariables(touchedVariables); return false; } @@ -140,8 +140,7 @@ void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl) { if (_varDecl.initialValue()) // TODO more checks? - // TODO add restrictions about type (might be assignment from smaller type) - m_interface->addAssertion(newValue(*_varDecl.declarations()[0]) == expr(*_varDecl.initialValue())); + assignment(*_varDecl.declarations()[0], *_varDecl.initialValue()); } else m_errorReporter.warning( @@ -170,9 +169,7 @@ void SMTChecker::endVisit(Assignment const& _assignment) { Declaration const* decl = identifier->annotation().referencedDeclaration; if (knownVariable(*decl)) - // TODO more checks? - // TODO add restrictions about type (might be assignment from smaller type) - m_interface->addAssertion(newValue(*decl) == expr(_assignment.rightHandSide())); + assignment(*decl, _assignment.rightHandSide()); else m_errorReporter.warning( _assignment.location(), @@ -249,23 +246,17 @@ void SMTChecker::endVisit(Identifier const& _identifier) { Declaration const* decl = _identifier.annotation().referencedDeclaration; solAssert(decl, ""); - if (dynamic_cast(_identifier.annotation().type.get())) + if (_identifier.annotation().lValueRequested) { - m_interface->addAssertion(expr(_identifier) == currentValue(*decl)); - return; + // Will be translated as part of the node that requested the lvalue. } + else if (dynamic_cast(_identifier.annotation().type.get())) + m_interface->addAssertion(expr(_identifier) == currentValue(*decl)); else if (FunctionType const* fun = dynamic_cast(_identifier.annotation().type.get())) { if (fun->kind() == FunctionType::Kind::Assert || fun->kind() == FunctionType::Kind::Require) return; - // TODO for others, clear our knowledge about storage and memory } - m_errorReporter.warning( - _identifier.location(), - "Assertion checker does not yet support the type of this expression (" + - _identifier.annotation().type->toString() + - ")." - ); } void SMTChecker::endVisit(Literal const& _literal) @@ -281,7 +272,7 @@ void SMTChecker::endVisit(Literal const& _literal) else m_errorReporter.warning( _literal.location(), - "Assertion checker does not yet support the type of this expression (" + + "Assertion checker does not yet support the type of this literal (" + _literal.annotation().type->toString() + ")." ); @@ -379,12 +370,19 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op) ); } -set SMTChecker::visitBranch(Statement const& _statement, smt::Expression _condition) +void SMTChecker::assignment(Declaration const& _variable, Expression const& _value) { - return visitBranch(_statement, &_condition); + // TODO more checks? + // TODO add restrictions about type (might be assignment from smaller type) + m_interface->addAssertion(newValue(_variable) == expr(_value)); } -set SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition) +void SMTChecker::visitBranch(Statement const& _statement, smt::Expression _condition) +{ + visitBranch(_statement, &_condition); +} + +void SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition) { VariableSequenceCounters sequenceCountersStart = m_currentSequenceCounter; @@ -395,18 +393,7 @@ set SMTChecker::visitBranch(Statement const& _statement, smt m_interface->pop(); m_conditionalExecutionHappened = true; - - set touched; - for (auto const& seq: m_currentSequenceCounter) - { - if (!sequenceCountersStart.count(seq.first)) - touched.insert(seq.first); - else if (sequenceCountersStart[seq.first] != seq.second) - touched.insert(seq.first); - } m_currentSequenceCounter = sequenceCountersStart; - - return touched; } void SMTChecker::checkCondition( @@ -504,16 +491,36 @@ void SMTChecker::checkCondition( m_interface->pop(); } -void SMTChecker::resetVariables(set _variables) +void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function) +{ + for (auto const& variable: _function.localVariables()) + { + createVariable(*variable); + setZeroValue(*variable); + } + for (auto const& param: _function.parameters()) + { + createVariable(*param); + setUnknownValue(*param); + } + if (_function.returnParameterList()) + for (auto const& retParam: _function.returnParameters()) + { + createVariable(*retParam); + setZeroValue(*retParam); + } +} + +void SMTChecker::resetVariables(vector _variables) { for (auto const* decl: _variables) { newValue(*decl); - setValue(*decl, false); + setUnknownValue(*decl); } } -void SMTChecker::createVariable(VariableDeclaration const& _varDecl, bool _setToZero) +void SMTChecker::createVariable(VariableDeclaration const& _varDecl) { if (dynamic_cast(_varDecl.type().get())) { @@ -523,7 +530,6 @@ void SMTChecker::createVariable(VariableDeclaration const& _varDecl, bool _setTo m_currentSequenceCounter[&_varDecl] = 0; m_nextFreeSequenceCounter[&_varDecl] = 1; m_variables.emplace(&_varDecl, m_interface->newFunction(uniqueSymbol(_varDecl), smt::Sort::Int, smt::Sort::Int)); - setValue(_varDecl, _setToZero); } else m_errorReporter.warning( @@ -565,17 +571,17 @@ smt::Expression SMTChecker::newValue(Declaration const& _decl) return currentValue(_decl); } -void SMTChecker::setValue(Declaration const& _decl, bool _setToZero) +void SMTChecker::setZeroValue(Declaration const& _decl) { - auto const& intType = dynamic_cast(*_decl.type()); + solAssert(_decl.type()->category() == Type::Category::Integer, ""); + m_interface->addAssertion(currentValue(_decl) == 0); +} - if (_setToZero) - m_interface->addAssertion(currentValue(_decl) == 0); - else - { - m_interface->addAssertion(currentValue(_decl) >= minValue(intType)); - m_interface->addAssertion(currentValue(_decl) <= maxValue(intType)); - } +void SMTChecker::setUnknownValue(Declaration const& _decl) +{ + auto const& intType = dynamic_cast(*_decl.type()); + m_interface->addAssertion(currentValue(_decl) >= minValue(intType)); + m_interface->addAssertion(currentValue(_decl) <= maxValue(intType)); } smt::Expression SMTChecker::minValue(IntegerType const& _t) diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index 6575dc1b..b2726a42 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -17,8 +17,11 @@ #pragma once -#include + #include + +#include + #include #include @@ -29,6 +32,7 @@ namespace dev namespace solidity { +class VariableUsage; class ErrorReporter; class SMTChecker: private ASTConstVisitor @@ -61,11 +65,12 @@ private: void compareOperation(BinaryOperation const& _op); void booleanOperation(BinaryOperation const& _op); + void assignment(Declaration const& _variable, Expression const& _value); + // Visits the branch given by the statement, pushes and pops the SMT checker. - // @returns the set of touched declarations // @param _condition if present, asserts that this condition is true within the branch. - std::set visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr); - std::set visitBranch(Statement const& _statement, smt::Expression _condition); + void visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr); + void visitBranch(Statement const& _statement, smt::Expression _condition); void checkCondition( smt::Expression _condition, @@ -75,8 +80,9 @@ private: smt::Expression* _additionalValue = nullptr ); - void resetVariables(std::set _variables); - void createVariable(VariableDeclaration const& _varDecl, bool _setToZero); + void initializeLocalVariables(FunctionDefinition const& _function); + void resetVariables(std::vector _variables); + void createVariable(VariableDeclaration const& _varDecl); static std::string uniqueSymbol(Declaration const& _decl); static std::string uniqueSymbol(Expression const& _expr); @@ -94,8 +100,10 @@ private: /// sequence number to this value and returns the expression. smt::Expression newValue(Declaration const& _decl); - /// Sets the value of the declaration either to zero or to its intrinsic range. - void setValue(Declaration const& _decl, bool _setToZero); + /// Sets the value of the declaration to zero. + void setZeroValue(Declaration const& _decl); + /// Resets the variable to an unknown value (in its range). + void setUnknownValue(Declaration const& decl); static smt::Expression minValue(IntegerType const& _t); static smt::Expression maxValue(IntegerType const& _t); @@ -110,6 +118,7 @@ private: smt::Expression var(Declaration const& _decl); std::shared_ptr m_interface; + std::shared_ptr m_variableUsage; bool m_conditionalExecutionHappened = false; std::map m_currentSequenceCounter; std::map m_nextFreeSequenceCounter; diff --git a/libsolidity/formal/VariableUsage.cpp b/libsolidity/formal/VariableUsage.cpp new file mode 100644 index 00000000..4e96059d --- /dev/null +++ b/libsolidity/formal/VariableUsage.cpp @@ -0,0 +1,80 @@ +/* + 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 . +*/ + +#include + +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +VariableUsage::VariableUsage(ASTNode const& _node) +{ + auto nodeFun = [&](ASTNode const& n) -> bool + { + if (Identifier const* identifier = dynamic_cast(&n)) + { + Declaration const* declaration = identifier->annotation().referencedDeclaration; + solAssert(declaration, ""); + if (VariableDeclaration const* varDecl = dynamic_cast(declaration)) + if ( + varDecl->isLocalVariable() && + identifier->annotation().lValueRequested && + varDecl->annotation().type->isValueType() + ) + m_touchedVariable[&n] = varDecl; + } + return true; + }; + auto edgeFun = [&](ASTNode const& _parent, ASTNode const& _child) + { + if (m_touchedVariable.count(&_child) || m_children.count(&_child)) + m_children[&_parent].push_back(&_child); + }; + + ASTReduce reducer(nodeFun, edgeFun); + _node.accept(reducer); +} + +vector VariableUsage::touchedVariables(ASTNode const& _node) const +{ + if (!m_children.count(&_node) && !m_touchedVariable.count(&_node)) + return {}; + + set touched; + vector toVisit; + toVisit.push_back(&_node); + + while (!toVisit.empty()) + { + ASTNode const* n = toVisit.back(); + toVisit.pop_back(); + if (m_children.count(n)) + { + solAssert(!m_touchedVariable.count(n), ""); + toVisit += m_children.at(n); + } + else + { + solAssert(m_touchedVariable.count(n), ""); + touched.insert(m_touchedVariable.at(n)); + } + } + + return {touched.begin(), touched.end()}; +} diff --git a/libsolidity/formal/VariableUsage.h b/libsolidity/formal/VariableUsage.h new file mode 100644 index 00000000..62561cce --- /dev/null +++ b/libsolidity/formal/VariableUsage.h @@ -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 . +*/ + +#pragma once + +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +class ASTNode; +class Declaration; + +/** + * This class collects information about which local variables of value type + * are modified in which parts of the AST. + */ +class VariableUsage +{ +public: + explicit VariableUsage(ASTNode const& _node); + + std::vector touchedVariables(ASTNode const& _node) const; + +private: + // Variable touched by a specific AST node. + std::map m_touchedVariable; + std::map> m_children; +}; + +} +} -- cgit v1.2.3 From e5de4a66eda8211bb38b874f7683534d6cfc1c24 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 29 Sep 2017 10:30:12 +0200 Subject: Tests. --- libsolidity/formal/SMTChecker.cpp | 5 - test/libsolidity/SMTChecker.cpp | 240 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+), 5 deletions(-) diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 428afa9f..1cf5dc95 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -106,11 +106,6 @@ bool SMTChecker::visit(WhileStatement const& _node) { // TODO Check if condition is always true - // TODO Weird side effects like - // uint x = 1; - // while (x ++ > 0) { assert(x == 2); } - // solution: clear variables first, then execute and assert condition, then executed body. - auto touchedVariables = m_variableUsage->touchedVariables(_node); resetVariables(touchedVariables); if (_node.isDoWhile()) diff --git a/test/libsolidity/SMTChecker.cpp b/test/libsolidity/SMTChecker.cpp index 8d712a80..400f696c 100644 --- a/test/libsolidity/SMTChecker.cpp +++ b/test/libsolidity/SMTChecker.cpp @@ -105,6 +105,246 @@ BOOST_AUTO_TEST_CASE(warn_on_struct) CHECK_WARNING_ALLOW_MULTI(text, ""); } +BOOST_AUTO_TEST_CASE(simple_assert) +{ + string text = R"( + contract C { + function f(uint a) public pure { assert(a == 2); } + } + )"; + CHECK_WARNING(text, "Assertion violation happens here for"); +} + +BOOST_AUTO_TEST_CASE(simple_assert_with_require) +{ + string text = R"( + contract C { + function f(uint a) public pure { require(a < 10); assert(a < 20); } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(assignment_in_declaration) +{ + string text = R"( + contract C { + function f() public pure { uint a = 2; assert(a == 2); } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(use_before_declaration) +{ + string text = R"( + contract C { + function f() public pure { a = 3; uint a = 2; assert(a == 2); } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f() public pure { assert(a == 0); uint a = 2; assert(a == 2); } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(function_call_does_not_clear_local_vars) +{ + string text = R"( + contract C { + function f() public { + uint a = 3; + this.f(); + assert(a == 3); + f(); + assert(a == 3); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(branches_clear_variables) +{ + // Only clears accessed variables + string text = R"( + contract C { + function f(uint x) public pure { + uint a = 3; + if (x > 10) { + } + assert(a == 3); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + // It is just a plain clear and will not combine branches. + text = R"( + contract C { + function f(uint x) public pure { + uint a = 3; + if (x > 10) { + a = 3; + } + assert(a == 3); + } + } + )"; + CHECK_WARNING(text, "Assertion violation happens here"); + // Clear also works on the else branch + text = R"( + contract C { + function f(uint x) public pure { + uint a = 3; + if (x > 10) { + } else { + a = 3; + } + assert(a == 3); + } + } + )"; + CHECK_WARNING(text, "Assertion violation happens here"); + // Variable is not cleared, if it is only read. + text = R"( + contract C { + function f(uint x) public pure { + uint a = 3; + if (x > 10) { + assert(a == 3); + } else { + assert(a == 3); + } + assert(a == 3); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(branches_assert_condition) +{ + string text = R"( + contract C { + function f(uint x) public pure { + if (x > 10) { + assert(x > 9); + } + else + { + assert(x < 11); + } + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(ways_to_clear_variables) +{ + string text = R"( + contract C { + function f(uint x) public pure { + uint a = 3; + if (x > 10) { + a++; + } + assert(a == 3); + } + } + )"; + text = R"( + contract C { + function f(uint x) public pure { + uint a = 3; + if (x > 10) { + ++a; + } + assert(a == 3); + } + } + )"; + CHECK_WARNING(text, "Assertion violation happens here"); + text = R"( + contract C { + function f(uint x) public pure { + uint a = 3; + if (x > 10) { + a = 5; + } + assert(a == 3); + } + } + )"; + CHECK_WARNING(text, "Assertion violation happens here"); +} + +BOOST_AUTO_TEST_CASE(while_loop_simple) +{ + // Check that variables are cleared + string text = R"( + contract C { + function f(uint x) public pure { + x = 2; + while (x > 1) { + x = 2; + } + assert(x == 2); + } + } + )"; + CHECK_WARNING(text, "Assertion violation happens here"); + // Check that condition is assumed. + text = R"( + contract C { + function f(uint x) public pure { + while (x == 2) { + assert(x == 2); + } + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + // Check that condition is not assumed after the body anymore + text = R"( + contract C { + function f(uint x) public pure { + while (x == 2) { + } + assert(x == 2); + } + } + )"; + CHECK_WARNING(text, "Assertion violation happens here"); + // Check that negation of condition is not assumed after the body anymore + text = R"( + contract C { + function f(uint x) public pure { + while (x == 2) { + } + assert(x != 2); + } + } + )"; + CHECK_WARNING(text, "Assertion violation happens here"); + // Check that side-effects of condition are taken into account + text = R"( + contract C { + function f(uint x) public pure { + x = 7; + while ((x = 5) > 0) { + } + assert(x == 7); + } + } + )"; + CHECK_WARNING(text, "Assertion violation happens here"); +} + + BOOST_AUTO_TEST_SUITE_END() } -- cgit v1.2.3 From 22c689d516bc69b49797005de0cd64c23fcaad2f Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 29 Sep 2017 16:53:26 +0200 Subject: Check for conditions being constant. --- libsolidity/formal/SMTChecker.cpp | 113 +++++++++++++++++++++++++++-------- libsolidity/formal/SMTChecker.h | 13 ++++ libsolidity/formal/SolverInterface.h | 1 + libsolidity/formal/Z3Interface.cpp | 2 +- 4 files changed, 102 insertions(+), 27 deletions(-) diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 1cf5dc95..3ad9db92 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -28,6 +28,7 @@ #include #include +#include using namespace std; using namespace dev; @@ -87,7 +88,7 @@ bool SMTChecker::visit(IfStatement const& _node) { _node.condition().accept(*this); - // TODO Check if condition is always true + checkBooleanNotConstant(_node.condition(), "Condition is always $VALUE."); visitBranch(_node.trueStatement(), expr(_node.condition())); vector touchedVariables = m_variableUsage->touchedVariables(_node.trueStatement()); @@ -104,8 +105,6 @@ bool SMTChecker::visit(IfStatement const& _node) bool SMTChecker::visit(WhileStatement const& _node) { - // TODO Check if condition is always true - auto touchedVariables = m_variableUsage->touchedVariables(_node); resetVariables(touchedVariables); if (_node.isDoWhile()) @@ -113,10 +112,13 @@ bool SMTChecker::visit(WhileStatement const& _node) visitBranch(_node.body()); // TODO the assertions generated in the body should still be active in the condition _node.condition().accept(*this); + checkBooleanNotConstant(_node.condition(), "Do-while loop condition is always $VALUE."); } else { _node.condition().accept(*this); + checkBooleanNotConstant(_node.condition(), "While loop condition is always $VALUE."); + visitBranch(_node.body(), expr(_node.condition())); } resetVariables(touchedVariables); @@ -264,6 +266,8 @@ void SMTChecker::endVisit(Literal const& _literal) m_interface->addAssertion(expr(_literal) == smt::Expression(type.literalValue(&_literal))); } + else if (type.category() == Type::Category::Bool) + m_interface->addAssertion(expr(_literal) == smt::Expression(_literal.token() == Token::TrueLiteral ? true : false)); else m_errorReporter.warning( _literal.location(), @@ -426,18 +430,7 @@ void SMTChecker::checkCondition( } smt::CheckResult result; vector values; - try - { - tie(result, values) = m_interface->check(expressionsToEvaluate); - } - catch (smt::SolverError const& _e) - { - string description("Error querying SMT solver"); - if (_e.comment()) - description += ": " + *_e.comment(); - m_errorReporter.warning(_location, description); - return; - } + tie(result, values) = checkSatisifableAndGenerateModel(expressionsToEvaluate); string conditionalComment; if (m_conditionalExecutionHappened) @@ -455,17 +448,7 @@ void SMTChecker::checkCondition( message << " for:\n"; solAssert(values.size() == expressionNames.size(), ""); for (size_t i = 0; i < values.size(); ++i) - { - string formattedValue = values.at(i); - try - { - // Parse and re-format nicely - formattedValue = formatNumber(bigint(formattedValue)); - } - catch (...) { } - - message << " " << expressionNames.at(i) << " = " << formattedValue << "\n"; - } + message << " " << expressionNames.at(i) << " = " << values.at(i) << "\n"; } else message << "."; @@ -486,6 +469,84 @@ void SMTChecker::checkCondition( m_interface->pop(); } +void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string const& _description) +{ + // Do not check for const-ness if this is a constant. + if (dynamic_cast(&_condition)) + return; + + m_interface->push(); + m_interface->addAssertion(expr(_condition)); + auto positiveResult = checkSatisifable(); + m_interface->pop(); + + m_interface->push(); + m_interface->addAssertion(!expr(_condition)); + auto negatedResult = checkSatisifable(); + m_interface->pop(); + + if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR) + m_errorReporter.warning(_condition.location(), "Error trying to invoke SMT solver."); + else if (positiveResult == smt::CheckResult::SATISFIABLE && negatedResult == smt::CheckResult::SATISFIABLE) + { + // everything fine. + } + else if (positiveResult == smt::CheckResult::UNSATISFIABLE && negatedResult == smt::CheckResult::UNSATISFIABLE) + m_errorReporter.warning(_condition.location(), "Condition unreachable."); + else + { + string value; + if (positiveResult == smt::CheckResult::SATISFIABLE) + { + solAssert(negatedResult == smt::CheckResult::UNSATISFIABLE, ""); + value = "true"; + } + else + { + solAssert(positiveResult == smt::CheckResult::UNSATISFIABLE, ""); + solAssert(negatedResult == smt::CheckResult::SATISFIABLE, ""); + value = "false"; + } + m_errorReporter.warning(_condition.location(), boost::algorithm::replace_all_copy(_description, "$VALUE", value)); + } +} + +pair> +SMTChecker::checkSatisifableAndGenerateModel(vector const& _expressionsToEvaluate) +{ + smt::CheckResult result; + vector values; + try + { + tie(result, values) = m_interface->check(_expressionsToEvaluate); + } + catch (smt::SolverError const& _e) + { + string description("Error querying SMT solver"); + if (_e.comment()) + description += ": " + *_e.comment(); + m_errorReporter.warning(description); + result = smt::CheckResult::ERROR; + } + + for (string& value: values) + { + try + { + // Parse and re-format nicely + value = formatNumber(bigint(value)); + } + catch (...) { } + } + + return make_pair(result, values); +} + +smt::CheckResult SMTChecker::checkSatisifable() +{ + return checkSatisifableAndGenerateModel({}).first; +} + void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function) { for (auto const& variable: _function.localVariables()) diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index b2726a42..85a37f2c 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -72,6 +72,7 @@ private: void visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr); void visitBranch(Statement const& _statement, smt::Expression _condition); + /// Check that a condition can be satisfied. void checkCondition( smt::Expression _condition, SourceLocation const& _location, @@ -79,6 +80,18 @@ private: std::string const& _additionalValueName = "", smt::Expression* _additionalValue = nullptr ); + /// Checks that a boolean condition is not constant. Do not warn if the expression + /// is a literal constant. + /// @param _description the warning string, $VALUE will be replaced by the constant value. + void checkBooleanNotConstant( + Expression const& _condition, + std::string const& _description + ); + + std::pair> + checkSatisifableAndGenerateModel(std::vector const& _expressionsToEvaluate); + + smt::CheckResult checkSatisifable(); void initializeLocalVariables(FunctionDefinition const& _function); void resetVariables(std::vector _variables); diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index 70dc1585..a69d19d5 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -52,6 +52,7 @@ class Expression { friend class SolverInterface; public: + explicit Expression(bool _v): name(_v ? "true" : "false") {} Expression(size_t _number): name(std::to_string(_number)) {} Expression(u256 const& _number): name(_number.str()) {} Expression(bigint const& _number): name(_number.str()) {} diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index 6111b2c8..0c083abc 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -91,7 +91,7 @@ pair> Z3Interface::check(vector const& _ solAssert(false, ""); } - if (result != CheckResult::UNSATISFIABLE) + if (result != CheckResult::UNSATISFIABLE && !_expressionsToEvaluate.empty()) { z3::model m = m_solver.get_model(); for (Expression const& e: _expressionsToEvaluate) -- cgit v1.2.3 From 90fb14f525fa37a15421e3dc40ffc98a5bce8378 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 29 Sep 2017 16:53:38 +0200 Subject: Tests. --- test/libsolidity/SMTChecker.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/libsolidity/SMTChecker.cpp b/test/libsolidity/SMTChecker.cpp index 400f696c..9d014125 100644 --- a/test/libsolidity/SMTChecker.cpp +++ b/test/libsolidity/SMTChecker.cpp @@ -344,6 +344,37 @@ BOOST_AUTO_TEST_CASE(while_loop_simple) CHECK_WARNING(text, "Assertion violation happens here"); } +BOOST_AUTO_TEST_CASE(constant_condition) +{ + string text = R"( + contract C { + function f(uint x) public pure { + if (x >= 0) { revert(); } + } + } + )"; + CHECK_WARNING(text, "Condition is always true"); + text = R"( + contract C { + function f(uint x) public pure { + if (x >= 10) { if (x < 10) { revert(); } } + } + } + )"; + CHECK_WARNING(text, "Condition is always false"); +// TODO +// // a plain literal constant is fine +// text = R"( +// contract C { +// function f(uint x) public pure { +// if (true) { revert(); } +// } +// } +// )"; +// CHECK_SUCCESS_NO_WARNINGS(text); + +// TODO test unreacheable code +} BOOST_AUTO_TEST_SUITE_END() -- cgit v1.2.3 From 95a65dc04c6f6a5c611c25d6f07d54843f2daec9 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 4 Oct 2017 18:20:56 +0200 Subject: Fix boolean constants. --- libsolidity/formal/Z3Interface.cpp | 9 +++++++-- test/libsolidity/SMTChecker.cpp | 21 +++++++++------------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index 0c083abc..e5c1aef4 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -139,8 +139,13 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) } else if (arguments.empty()) { - // We assume it is an integer... - return m_context.int_val(n.c_str()); + if (n == "true") + return m_context.bool_val(true); + else if (n == "false") + return m_context.bool_val(false); + else + // We assume it is an integer... + return m_context.int_val(n.c_str()); } solAssert(arity.count(n) && arity.at(n) == arguments.size(), ""); diff --git a/test/libsolidity/SMTChecker.cpp b/test/libsolidity/SMTChecker.cpp index 9d014125..9fbab03a 100644 --- a/test/libsolidity/SMTChecker.cpp +++ b/test/libsolidity/SMTChecker.cpp @@ -362,18 +362,15 @@ BOOST_AUTO_TEST_CASE(constant_condition) } )"; CHECK_WARNING(text, "Condition is always false"); -// TODO -// // a plain literal constant is fine -// text = R"( -// contract C { -// function f(uint x) public pure { -// if (true) { revert(); } -// } -// } -// )"; -// CHECK_SUCCESS_NO_WARNINGS(text); - -// TODO test unreacheable code + // a plain literal constant is fine + text = R"( + contract C { + function f(uint) public pure { + if (true) { revert(); } + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); } BOOST_AUTO_TEST_SUITE_END() -- cgit v1.2.3 From 5e2c066778c2018b5ee627585c917dd2c92ae848 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 4 Oct 2017 21:21:17 +0200 Subject: Test for multi-branches. --- test/libsolidity/SMTChecker.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/libsolidity/SMTChecker.cpp b/test/libsolidity/SMTChecker.cpp index 9fbab03a..ff93f7c7 100644 --- a/test/libsolidity/SMTChecker.cpp +++ b/test/libsolidity/SMTChecker.cpp @@ -241,6 +241,24 @@ BOOST_AUTO_TEST_CASE(branches_assert_condition) } )"; CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(uint x) public pure { + if (x > 10) { + assert(x > 9); + } + else if (x > 2) + { + assert(x <= 10 && x > 2); + } + else + { + assert(0 <= x && x <= 2); + } + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); } BOOST_AUTO_TEST_CASE(ways_to_clear_variables) -- cgit v1.2.3 From 19d5c424295df9638b32a657b31463abac9eb000 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 4 Oct 2017 21:45:45 +0200 Subject: For loop. --- libsolidity/formal/SMTChecker.cpp | 42 ++++++++++++++++++++++++ libsolidity/formal/SMTChecker.h | 1 + test/libsolidity/SMTChecker.cpp | 67 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 3ad9db92..6c4662ac 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -126,6 +126,48 @@ bool SMTChecker::visit(WhileStatement const& _node) return false; } +bool SMTChecker::visit(ForStatement const& _node) +{ + if (_node.initializationExpression()) + _node.initializationExpression()->accept(*this); + + // Do not reset the init expression part. + auto touchedVariables = + m_variableUsage->touchedVariables(_node.body()); + if (_node.condition()) + touchedVariables += m_variableUsage->touchedVariables(*_node.condition()); + if (_node.loopExpression()) + touchedVariables += m_variableUsage->touchedVariables(*_node.loopExpression()); + // Remove duplicates + std::sort(touchedVariables.begin(), touchedVariables.end()); + touchedVariables.erase(std::unique(touchedVariables.begin(), touchedVariables.end()), touchedVariables.end()); + + resetVariables(touchedVariables); + + if (_node.condition()) + { + _node.condition()->accept(*this); + checkBooleanNotConstant(*_node.condition(), "For loop condition is always $VALUE."); + } + + VariableSequenceCounters sequenceCountersStart = m_currentSequenceCounter; + m_interface->push(); + if (_node.condition()) + m_interface->addAssertion(expr(*_node.condition())); + _node.body().accept(*this); + if (_node.loopExpression()) + _node.loopExpression()->accept(*this); + + m_interface->pop(); + + m_conditionalExecutionHappened = true; + m_currentSequenceCounter = sequenceCountersStart; + + resetVariables(touchedVariables); + + return false; +} + void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl) { if (_varDecl.declarations().size() != 1) diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index 85a37f2c..54e3b22a 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -52,6 +52,7 @@ private: virtual void endVisit(FunctionDefinition const& _node) override; virtual bool visit(IfStatement const& _node) override; virtual bool visit(WhileStatement const& _node) override; + virtual bool visit(ForStatement const& _node) override; virtual void endVisit(VariableDeclarationStatement const& _node) override; virtual void endVisit(ExpressionStatement const& _node) override; virtual void endVisit(Assignment const& _node) override; diff --git a/test/libsolidity/SMTChecker.cpp b/test/libsolidity/SMTChecker.cpp index ff93f7c7..667d666b 100644 --- a/test/libsolidity/SMTChecker.cpp +++ b/test/libsolidity/SMTChecker.cpp @@ -391,6 +391,73 @@ BOOST_AUTO_TEST_CASE(constant_condition) CHECK_SUCCESS_NO_WARNINGS(text); } + +BOOST_AUTO_TEST_CASE(for_loop) +{ + string text = R"( + contract C { + function f(uint x) public pure { + require(x == 2); + for (;;) {} + assert(x == 2); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(uint x) public pure { + for (; x == 2; ) { + assert(x == 2); + } + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(uint x) public pure { + for (uint y = 2; x < 10; ) { + assert(y == 2); + } + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(uint x) public pure { + for (uint y = 2; x < 10; y = 3) { + assert(y == 2); + } + } + } + )"; + CHECK_WARNING(text, "Assertion violation"); + text = R"( + contract C { + function f(uint x) public pure { + for (uint y = 2; x < 10; ) { + y = 3; + } + assert(y == 3); + } + } + )"; + CHECK_WARNING(text, "Assertion violation"); + text = R"( + contract C { + function f(uint x) public pure { + for (uint y = 2; x < 10; ) { + y = 3; + } + assert(y == 2); + } + } + )"; + CHECK_WARNING(text, "Assertion violation"); +} + BOOST_AUTO_TEST_SUITE_END() } -- cgit v1.2.3 From 8538a25f8d97f8416d20ace7dcd1bd760a425db9 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 5 Oct 2017 19:31:17 +0200 Subject: Fix problem with non-value-typed variables. --- libsolidity/formal/SMTChecker.cpp | 26 +++++++++++++------------- libsolidity/formal/SMTChecker.h | 4 +++- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 6c4662ac..1050621e 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -592,21 +592,17 @@ smt::CheckResult SMTChecker::checkSatisifable() void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function) { for (auto const& variable: _function.localVariables()) - { - createVariable(*variable); - setZeroValue(*variable); - } + if (createVariable(*variable)) + setZeroValue(*variable); + for (auto const& param: _function.parameters()) - { - createVariable(*param); - setUnknownValue(*param); - } + if (createVariable(*param)) + setUnknownValue(*param); + if (_function.returnParameterList()) for (auto const& retParam: _function.returnParameters()) - { - createVariable(*retParam); - setZeroValue(*retParam); - } + if (createVariable(*retParam)) + setZeroValue(*retParam); } void SMTChecker::resetVariables(vector _variables) @@ -618,7 +614,7 @@ void SMTChecker::resetVariables(vector _variables) } } -void SMTChecker::createVariable(VariableDeclaration const& _varDecl) +bool SMTChecker::createVariable(VariableDeclaration const& _varDecl) { if (dynamic_cast(_varDecl.type().get())) { @@ -628,12 +624,16 @@ void SMTChecker::createVariable(VariableDeclaration const& _varDecl) m_currentSequenceCounter[&_varDecl] = 0; m_nextFreeSequenceCounter[&_varDecl] = 1; m_variables.emplace(&_varDecl, m_interface->newFunction(uniqueSymbol(_varDecl), smt::Sort::Int, smt::Sort::Int)); + return true; } else + { m_errorReporter.warning( _varDecl.location(), "Assertion checker does not yet support the type of this variable." ); + return false; + } } string SMTChecker::uniqueSymbol(Declaration const& _decl) diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index 54e3b22a..8e07d74d 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -96,7 +96,9 @@ private: void initializeLocalVariables(FunctionDefinition const& _function); void resetVariables(std::vector _variables); - void createVariable(VariableDeclaration const& _varDecl); + /// Tries to create an uninitialized variable and returns true on success. + /// This fails if the type is not supported. + bool createVariable(VariableDeclaration const& _varDecl); static std::string uniqueSymbol(Declaration const& _decl); static std::string uniqueSymbol(Expression const& _expr); -- cgit v1.2.3 From 243002e5f3e491b91f017e3ac1371ed45a716f3b Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 3 Apr 2017 19:17:17 +0200 Subject: Describe Julia. --- docs/assembly.rst | 125 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 114 insertions(+), 11 deletions(-) diff --git a/docs/assembly.rst b/docs/assembly.rst index f5abcdc8..ce45c55c 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -1,18 +1,116 @@ -################# -Solidity Assembly -################# +################################################# +Joyfully Universal Language for (Inline) Assembly +################################################# + +.. _julia: .. index:: ! assembly, ! asm, ! evmasm -Solidity defines an assembly language that can also be used without Solidity. -This assembly language can also be used as "inline assembly" inside Solidity -source code. We start with describing how to use inline assembly and how it -differs from standalone assembly and then specify assembly itself. +Julia is an intermediate language that can compile to various different backends +(EVM 1.0, EVM 1.5 and eWASM are planned). +Because of that, it is designed to be as featureless as possible. +It can already be used for "inline assembly" inside Solidity and +future versions of the Solidity compiler will even use Julia as intermediate +language. It should also be easy to build high-level optimizer stages for Julia. -.. note:: - TODO: Write about how scoping rules of inline assembly are a bit different - and the complications that arise when for example using internal functions - of libraries. Furthermore, write about the symbols defined by the compiler. +The core components of Julia are functions, blocks, variables, literals, +for-loops, switch-statements, expressions and assignments to variables. + +Julia in itself does not even provide operators. If the EVM is targeted, +opcodes will be available as built-in functions, but they can be reimplemented +if the backend changes. + +The following example program assumes that the EVM opcodes ``mul``, ``div`` +and ``mod`` are available either natively or as functions and computes exponentiation. + +.. code:: + { + function power(base, exponent) -> (result) + { + switch exponent + 0: { result := 1 } + 1: { result := base } + default: + { + result := power(mul(base, base), div(exponent, 2)) + switch mod(exponent, 2) + 1: { result := mul(base, result) } + } + } + } + +It is also possible to implement the same function using a for-loop +instead of recursion. Here, we need the EVM opcodes ``lt`` (less-than) +and ``add`` to be available. + +.. code:: + { + function power(base, exponent) -> (result) + { + result := 1 + for { let i := 0 } lt(i, exponent) { i := add(i, 1) } + { + result := mul(result, base) + } + } + } + +Specification of Julia +====================== + +Grammar:: + + Block = '{' Statement* '}' + Statement = + Block | + FunctionDefinition | + VariableDeclaration | + Assignment | + Expression | + Switch | + ForLoop | + 'break' | 'continue' + SubAssembly + FunctionDefinition = + 'function' Identifier '(' IdentifierList? ')' + ( '->' '(' IdentifierList ')' )? Block + VariableDeclaration = + 'let' IdentifierOrList ':=' Expression + Assignment = + IdentifierOrList ':=' Expression + Expression = + Identifier | Literal | FunctionCall + Switch = + 'switch' Expression Case* ( 'default' ':' Block )? + Case = + 'case' Expression ':' Block + ForLoop = + 'for' Block Expression Block Block + SubAssembly = + 'assembly' Identifier Block + FunctionCall = + Identifier '(' ( AssemblyItem ( ',' AssemblyItem )* )? ')' + IdentifierOrList = Identifier | '(' IdentifierList ')' + Identifier = [a-zA-Z_$] [a-zA-Z_0-9]* + IdentifierList = Identifier ( ',' Identifier)* + Literal = + NumberLiteral | StringLiteral | HexLiteral + NumberLiteral = HexNumber | DecimalNumber + HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'') + StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' + HexNumber = '0x' [0-9a-fA-F]+ + DecimalNumber = [0-9]+ + + + + | 'dataSize' '(' Identifier ')' | + LinkerSymbol | + 'bytecodeSize' | + +Restriction for Expression: Functions can only return single item, +top level has to return nothing. +Restriction for VariableDeclaration and Assignment: Number of elements left and right needs to be the same +continue and break only in for loop .. _inline-assembly: @@ -41,6 +139,11 @@ We now want to describe the inline assembly language in detail. at a low level. This discards several important safety features of Solidity. +.. note:: + TODO: Write about how scoping rules of inline assembly are a bit different + and the complications that arise when for example using internal functions + of libraries. Furthermore, write about the symbols defined by the compiler. + Example ------- -- cgit v1.2.3 From f73660423ab2a2ff340624fcbbb67f891ace95a2 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 4 Apr 2017 14:20:43 +0200 Subject: First take in formal specification. --- docs/assembly.rst | 107 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 94 insertions(+), 13 deletions(-) diff --git a/docs/assembly.rst b/docs/assembly.rst index ce45c55c..a06c616d 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -6,17 +6,17 @@ Joyfully Universal Language for (Inline) Assembly .. index:: ! assembly, ! asm, ! evmasm -Julia is an intermediate language that can compile to various different backends +JULIA is an intermediate language that can compile to various different backends (EVM 1.0, EVM 1.5 and eWASM are planned). Because of that, it is designed to be as featureless as possible. It can already be used for "inline assembly" inside Solidity and -future versions of the Solidity compiler will even use Julia as intermediate -language. It should also be easy to build high-level optimizer stages for Julia. +future versions of the Solidity compiler will even use JULIA as intermediate +language. It should also be easy to build high-level optimizer stages for JULIA. -The core components of Julia are functions, blocks, variables, literals, +The core components of JULIA are functions, blocks, variables, literals, for-loops, switch-statements, expressions and assignments to variables. -Julia in itself does not even provide operators. If the EVM is targeted, +JULIA in itself does not even provide operators. If the EVM is targeted, opcodes will be available as built-in functions, but they can be reimplemented if the backend changes. @@ -55,7 +55,7 @@ and ``add`` to be available. } } -Specification of Julia +Specification of JULIA ====================== Grammar:: @@ -69,7 +69,7 @@ Grammar:: Expression | Switch | ForLoop | - 'break' | 'continue' + BreakContinue | SubAssembly FunctionDefinition = 'function' Identifier '(' IdentifierList? ')' @@ -79,17 +79,19 @@ Grammar:: Assignment = IdentifierOrList ':=' Expression Expression = - Identifier | Literal | FunctionCall + FunctionCall | Identifier | Literal Switch = 'switch' Expression Case* ( 'default' ':' Block )? Case = 'case' Expression ':' Block ForLoop = 'for' Block Expression Block Block + BreakContinue = + 'break' | 'continue' SubAssembly = 'assembly' Identifier Block FunctionCall = - Identifier '(' ( AssemblyItem ( ',' AssemblyItem )* )? ')' + Identifier '(' ( Expression ( ',' Expression )* )? ')' IdentifierOrList = Identifier | '(' IdentifierList ')' Identifier = [a-zA-Z_$] [a-zA-Z_0-9]* IdentifierList = Identifier ( ',' Identifier)* @@ -101,16 +103,95 @@ Grammar:: HexNumber = '0x' [0-9a-fA-F]+ DecimalNumber = [0-9]+ +Restrictions on the Grammar +--------------------------- + +Scopes in JULIA are tied to Blocks and all declarations +(``FunctionDefinition``, ``VariableDeclaration`` and ``SubAssembly``) +introduce new identifiers into these scopes. Shadowing is disallowed + +Talk about identifiers across functions etc + + +Restriction for Expression: Statements have to return empty tuple +Function arguments have to be single item + +Restriction for VariableDeclaration and Assignment: Number of elements left and right needs to be the same +continue and break only in for loop + +Literals have to fit 32 bytes + | 'dataSize' '(' Identifier ')' | LinkerSymbol | 'bytecodeSize' | -Restriction for Expression: Functions can only return single item, -top level has to return nothing. -Restriction for VariableDeclaration and Assignment: Number of elements left and right needs to be the same -continue and break only in for loop + +Formal Specification +-------------------- + +We formally specify JULIA by providing an evaluation function E overloaded +on the various nodes of the AST. Any functions can have side effects, so +E takes a state objects and the actual argument and also returns new +state objects and new arguments. There is a global state object +(which in the context of the EVM is the memory, storage and state of the +blockchain) and a local state object (the state of local variables, i.e. a +segment of the stack in the EVM). + +The the evaluation function E takes a global state, a local state and +a node of the AST and returns a new global state, a new local state +and a value (if the AST node is an expression). + +We use sequence numbers as a shorthand for the order of evaluation +and how state is forwarded. For example, ``E2(x), E1(y)`` is a shorthand +for + +For ``(S1, z) = E(S, y)`` let ``(S2, w) = E(S1, x)``. TODO + +.. code:: + E(G, L, <{St1, ..., Stn}>: Block) = + let L' be a copy of L that adds a new inner scope which contains + all functions and variables declared in the block (but not its sub-blocks) + variables are marked inactive for now + TODO: more formal + G1, L'1 = E(G, L', St1) + G2, L'2 = E(G1, L'1, St2) + ... + Gn, L'n = E(G(n-1), L'(n-1), Stn) + let L'' be a copy of L'n where the innermost scope is removed + Gn, L'' + E(G, L, (ret1, ..., retm) block>: FunctionDefinition) = + G, L + E(G, L, : VariableDeclaration) = + E(G, L, <(var1, ..., varn) := value>: Assignment) + E(G, L, <(var1, ..., varn) := value>: Assignment) = + let G', L', v1, ..., vn = E(G, L, value) + let L'' be a copy of L' where L'[vi] = vi for i = 1, ..., n + G, L'' + E(G, L, name: Identifier) = + G, L, L[name] + E(G, L, fname(arg1, ..., argn): FunctionCall) = + G1, L1, vn = E(G, L, argn) + ... + G(n-1), L(n-1), v2 = E(G(n-2), L(n-2), arg2) + Gn, Ln, v1 = E(G(n-1), L(n-1), arg1) + Let (ret1, ..., retm) block> + be the function L[fname]. + Let L' be a copy of L that does not contain any variables in any scope, + but which has a new innermost scope such that + L'[parami] = vi and L'[reti] = 0 + Let G'', L'', rv1, ..., rvm = E(Gn, L', block) + G'', Ln, rv1, ..., rvm + E(G, L, l: HexLiteral) = G, L, hexString(l), + where hexString decodes l from hex and left-aligns in into 32 bytes + E(G, L, l: StringLiteral) = G, L, utf8EncodeLeftAligned(l), + where utf8EncodeLeftAligned performs a utf8 encoding of l + and aligns it left into 32 bytes + E(G, L, n: HexNumber) = G, L, hex(n) + where hex is the hexadecimal decoding function + E(G, L, n: DecimalNumber) = G, L, dec(n), + where dec is the decimal decoding function .. _inline-assembly: -- cgit v1.2.3 From 2a91eb953850b83b121867d95ba1cf6541b4ce7c Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 18 Apr 2017 13:12:04 +0100 Subject: Move Julia documentation to its own file --- docs/assembly.rst | 198 ++---------------------------------------------------- docs/julia.rst | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 191 deletions(-) create mode 100644 docs/julia.rst diff --git a/docs/assembly.rst b/docs/assembly.rst index a06c616d..00bfb388 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -1,197 +1,13 @@ -################################################# -Joyfully Universal Language for (Inline) Assembly -################################################# - -.. _julia: +################# +Solidity Assembly +################# .. index:: ! assembly, ! asm, ! evmasm -JULIA is an intermediate language that can compile to various different backends -(EVM 1.0, EVM 1.5 and eWASM are planned). -Because of that, it is designed to be as featureless as possible. -It can already be used for "inline assembly" inside Solidity and -future versions of the Solidity compiler will even use JULIA as intermediate -language. It should also be easy to build high-level optimizer stages for JULIA. - -The core components of JULIA are functions, blocks, variables, literals, -for-loops, switch-statements, expressions and assignments to variables. - -JULIA in itself does not even provide operators. If the EVM is targeted, -opcodes will be available as built-in functions, but they can be reimplemented -if the backend changes. - -The following example program assumes that the EVM opcodes ``mul``, ``div`` -and ``mod`` are available either natively or as functions and computes exponentiation. - -.. code:: - { - function power(base, exponent) -> (result) - { - switch exponent - 0: { result := 1 } - 1: { result := base } - default: - { - result := power(mul(base, base), div(exponent, 2)) - switch mod(exponent, 2) - 1: { result := mul(base, result) } - } - } - } - -It is also possible to implement the same function using a for-loop -instead of recursion. Here, we need the EVM opcodes ``lt`` (less-than) -and ``add`` to be available. - -.. code:: - { - function power(base, exponent) -> (result) - { - result := 1 - for { let i := 0 } lt(i, exponent) { i := add(i, 1) } - { - result := mul(result, base) - } - } - } - -Specification of JULIA -====================== - -Grammar:: - - Block = '{' Statement* '}' - Statement = - Block | - FunctionDefinition | - VariableDeclaration | - Assignment | - Expression | - Switch | - ForLoop | - BreakContinue | - SubAssembly - FunctionDefinition = - 'function' Identifier '(' IdentifierList? ')' - ( '->' '(' IdentifierList ')' )? Block - VariableDeclaration = - 'let' IdentifierOrList ':=' Expression - Assignment = - IdentifierOrList ':=' Expression - Expression = - FunctionCall | Identifier | Literal - Switch = - 'switch' Expression Case* ( 'default' ':' Block )? - Case = - 'case' Expression ':' Block - ForLoop = - 'for' Block Expression Block Block - BreakContinue = - 'break' | 'continue' - SubAssembly = - 'assembly' Identifier Block - FunctionCall = - Identifier '(' ( Expression ( ',' Expression )* )? ')' - IdentifierOrList = Identifier | '(' IdentifierList ')' - Identifier = [a-zA-Z_$] [a-zA-Z_0-9]* - IdentifierList = Identifier ( ',' Identifier)* - Literal = - NumberLiteral | StringLiteral | HexLiteral - NumberLiteral = HexNumber | DecimalNumber - HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'') - StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' - HexNumber = '0x' [0-9a-fA-F]+ - DecimalNumber = [0-9]+ - -Restrictions on the Grammar ---------------------------- - -Scopes in JULIA are tied to Blocks and all declarations -(``FunctionDefinition``, ``VariableDeclaration`` and ``SubAssembly``) -introduce new identifiers into these scopes. Shadowing is disallowed - -Talk about identifiers across functions etc - - -Restriction for Expression: Statements have to return empty tuple -Function arguments have to be single item - -Restriction for VariableDeclaration and Assignment: Number of elements left and right needs to be the same -continue and break only in for loop - -Literals have to fit 32 bytes - - - - | 'dataSize' '(' Identifier ')' | - LinkerSymbol | - 'bytecodeSize' | - - -Formal Specification --------------------- - -We formally specify JULIA by providing an evaluation function E overloaded -on the various nodes of the AST. Any functions can have side effects, so -E takes a state objects and the actual argument and also returns new -state objects and new arguments. There is a global state object -(which in the context of the EVM is the memory, storage and state of the -blockchain) and a local state object (the state of local variables, i.e. a -segment of the stack in the EVM). - -The the evaluation function E takes a global state, a local state and -a node of the AST and returns a new global state, a new local state -and a value (if the AST node is an expression). - -We use sequence numbers as a shorthand for the order of evaluation -and how state is forwarded. For example, ``E2(x), E1(y)`` is a shorthand -for - -For ``(S1, z) = E(S, y)`` let ``(S2, w) = E(S1, x)``. TODO - -.. code:: - E(G, L, <{St1, ..., Stn}>: Block) = - let L' be a copy of L that adds a new inner scope which contains - all functions and variables declared in the block (but not its sub-blocks) - variables are marked inactive for now - TODO: more formal - G1, L'1 = E(G, L', St1) - G2, L'2 = E(G1, L'1, St2) - ... - Gn, L'n = E(G(n-1), L'(n-1), Stn) - let L'' be a copy of L'n where the innermost scope is removed - Gn, L'' - E(G, L, (ret1, ..., retm) block>: FunctionDefinition) = - G, L - E(G, L, : VariableDeclaration) = - E(G, L, <(var1, ..., varn) := value>: Assignment) - E(G, L, <(var1, ..., varn) := value>: Assignment) = - let G', L', v1, ..., vn = E(G, L, value) - let L'' be a copy of L' where L'[vi] = vi for i = 1, ..., n - G, L'' - E(G, L, name: Identifier) = - G, L, L[name] - E(G, L, fname(arg1, ..., argn): FunctionCall) = - G1, L1, vn = E(G, L, argn) - ... - G(n-1), L(n-1), v2 = E(G(n-2), L(n-2), arg2) - Gn, Ln, v1 = E(G(n-1), L(n-1), arg1) - Let (ret1, ..., retm) block> - be the function L[fname]. - Let L' be a copy of L that does not contain any variables in any scope, - but which has a new innermost scope such that - L'[parami] = vi and L'[reti] = 0 - Let G'', L'', rv1, ..., rvm = E(Gn, L', block) - G'', Ln, rv1, ..., rvm - E(G, L, l: HexLiteral) = G, L, hexString(l), - where hexString decodes l from hex and left-aligns in into 32 bytes - E(G, L, l: StringLiteral) = G, L, utf8EncodeLeftAligned(l), - where utf8EncodeLeftAligned performs a utf8 encoding of l - and aligns it left into 32 bytes - E(G, L, n: HexNumber) = G, L, hex(n) - where hex is the hexadecimal decoding function - E(G, L, n: DecimalNumber) = G, L, dec(n), - where dec is the decimal decoding function +Solidity defines an assembly language that can also be used without Solidity. +This assembly language can also be used as "inline assembly" inside Solidity +source code. We start with describing how to use inline assembly and how it +differs from standalone assembly and then specify assembly itself. .. _inline-assembly: diff --git a/docs/julia.rst b/docs/julia.rst new file mode 100644 index 00000000..1343f706 --- /dev/null +++ b/docs/julia.rst @@ -0,0 +1,189 @@ +################################################# +Joyfully Universal Language for (Inline) Assembly +################################################# + +.. _julia: + +.. index:: ! assembly, ! asm, ! evmasm, ! julia + +JULIA is an intermediate language that can compile to various different backends +(EVM 1.0, EVM 1.5 and eWASM are planned). +Because of that, it is designed to be as featureless as possible. +It can already be used for "inline assembly" inside Solidity and +future versions of the Solidity compiler will even use JULIA as intermediate +language. It should also be easy to build high-level optimizer stages for JULIA. + +The core components of JULIA are functions, blocks, variables, literals, +for-loops, switch-statements, expressions and assignments to variables. + +JULIA in itself does not even provide operators. If the EVM is targeted, +opcodes will be available as built-in functions, but they can be reimplemented +if the backend changes. + +The following example program assumes that the EVM opcodes ``mul``, ``div`` +and ``mod`` are available either natively or as functions and computes exponentiation. + +.. code:: + + { + function power(base, exponent) -> (result) + { + switch exponent + 0: { result := 1 } + 1: { result := base } + default: + { + result := power(mul(base, base), div(exponent, 2)) + switch mod(exponent, 2) + 1: { result := mul(base, result) } + } + } + } + +It is also possible to implement the same function using a for-loop +instead of recursion. Here, we need the EVM opcodes ``lt`` (less-than) +and ``add`` to be available. + +.. code:: + + { + function power(base, exponent) -> (result) + { + result := 1 + for { let i := 0 } lt(i, exponent) { i := add(i, 1) } + { + result := mul(result, base) + } + } + } + +Specification of JULIA +====================== + +Grammar:: + + Block = '{' Statement* '}' + Statement = + Block | + FunctionDefinition | + VariableDeclaration | + Assignment | + Expression | + Switch | + ForLoop | + BreakContinue | + SubAssembly + FunctionDefinition = + 'function' Identifier '(' IdentifierList? ')' + ( '->' '(' IdentifierList ')' )? Block + VariableDeclaration = + 'let' IdentifierOrList ':=' Expression + Assignment = + IdentifierOrList ':=' Expression + Expression = + FunctionCall | Identifier | Literal + Switch = + 'switch' Expression Case* ( 'default' ':' Block )? + Case = + 'case' Expression ':' Block + ForLoop = + 'for' Block Expression Block Block + BreakContinue = + 'break' | 'continue' + SubAssembly = + 'assembly' Identifier Block + FunctionCall = + Identifier '(' ( Expression ( ',' Expression )* )? ')' + IdentifierOrList = Identifier | '(' IdentifierList ')' + Identifier = [a-zA-Z_$] [a-zA-Z_0-9]* + IdentifierList = Identifier ( ',' Identifier)* + Literal = + NumberLiteral | StringLiteral | HexLiteral + NumberLiteral = HexNumber | DecimalNumber + HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'') + StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' + HexNumber = '0x' [0-9a-fA-F]+ + DecimalNumber = [0-9]+ + +Restrictions on the Grammar +--------------------------- + +Scopes in JULIA are tied to Blocks and all declarations +(``FunctionDefinition``, ``VariableDeclaration`` and ``SubAssembly``) +introduce new identifiers into these scopes. Shadowing is disallowed + +Talk about identifiers across functions etc + +Restriction for Expression: Statements have to return empty tuple +Function arguments have to be single item + +Restriction for VariableDeclaration and Assignment: Number of elements left and right needs to be the same +continue and break only in for loop + +Literals have to fit 32 bytes + +Formal Specification +-------------------- + +We formally specify JULIA by providing an evaluation function E overloaded +on the various nodes of the AST. Any functions can have side effects, so +E takes a state objects and the actual argument and also returns new +state objects and new arguments. There is a global state object +(which in the context of the EVM is the memory, storage and state of the +blockchain) and a local state object (the state of local variables, i.e. a +segment of the stack in the EVM). + +The the evaluation function E takes a global state, a local state and +a node of the AST and returns a new global state, a new local state +and a value (if the AST node is an expression). + +We use sequence numbers as a shorthand for the order of evaluation +and how state is forwarded. For example, ``E2(x), E1(y)`` is a shorthand +for + +For ``(S1, z) = E(S, y)`` let ``(S2, w) = E(S1, x)``. TODO + +.. code:: + + E(G, L, <{St1, ..., Stn}>: Block) = + let L' be a copy of L that adds a new inner scope which contains + all functions and variables declared in the block (but not its sub-blocks) + variables are marked inactive for now + TODO: more formal + G1, L'1 = E(G, L', St1) + G2, L'2 = E(G1, L'1, St2) + ... + Gn, L'n = E(G(n-1), L'(n-1), Stn) + let L'' be a copy of L'n where the innermost scope is removed + Gn, L'' + E(G, L, (ret1, ..., retm) block>: FunctionDefinition) = + G, L + E(G, L, : VariableDeclaration) = + E(G, L, <(var1, ..., varn) := value>: Assignment) + E(G, L, <(var1, ..., varn) := value>: Assignment) = + let G', L', v1, ..., vn = E(G, L, value) + let L'' be a copy of L' where L'[vi] = vi for i = 1, ..., n + G, L'' + E(G, L, name: Identifier) = + G, L, L[name] + E(G, L, fname(arg1, ..., argn): FunctionCall) = + G1, L1, vn = E(G, L, argn) + ... + G(n-1), L(n-1), v2 = E(G(n-2), L(n-2), arg2) + Gn, Ln, v1 = E(G(n-1), L(n-1), arg1) + Let (ret1, ..., retm) block> + be the function L[fname]. + Let L' be a copy of L that does not contain any variables in any scope, + but which has a new innermost scope such that + L'[parami] = vi and L'[reti] = 0 + Let G'', L'', rv1, ..., rvm = E(Gn, L', block) + G'', Ln, rv1, ..., rvm + E(G, L, l: HexLiteral) = G, L, hexString(l), + where hexString decodes l from hex and left-aligns in into 32 bytes + E(G, L, l: StringLiteral) = G, L, utf8EncodeLeftAligned(l), + where utf8EncodeLeftAligned performs a utf8 encoding of l + and aligns it left into 32 bytes + E(G, L, n: HexNumber) = G, L, hex(n) + where hex is the hexadecimal decoding function + E(G, L, n: DecimalNumber) = G, L, dec(n), + where dec is the decimal decoding function -- cgit v1.2.3 From c3a6db725606f14183b3af1f4954f196857afe33 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 18 Apr 2017 13:41:16 +0100 Subject: Describe built-in Julia functions --- docs/julia.rst | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 1 deletion(-) diff --git a/docs/julia.rst b/docs/julia.rst index 1343f706..5203b522 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -18,7 +18,7 @@ for-loops, switch-statements, expressions and assignments to variables. JULIA in itself does not even provide operators. If the EVM is targeted, opcodes will be available as built-in functions, but they can be reimplemented -if the backend changes. +if the backend changes. For a list of mandatory built-in functions, see the section below. The following example program assumes that the EVM opcodes ``mul``, ``div`` and ``mod`` are available either natively or as functions and computes exponentiation. @@ -187,3 +187,165 @@ For ``(S1, z) = E(S, y)`` let ``(S2, w) = E(S1, x)``. TODO where hex is the hexadecimal decoding function E(G, L, n: DecimalNumber) = G, L, dec(n), where dec is the decimal decoding function + +Low-level Functions +------------------- + +The following functions must be available: + ++---------------------------------------------------------------------------------------------------------------+ +| *Arithmetics* | ++---------------------------------------------------------------------------------------------------------------+ +| add256(x:256, y:256) -> z:256 | x + y | ++---------------------------------------------------------------------------------------------------------------+ +| sub256(x:256, y:256) -> z:256 | x - y | ++---------------------------------------------------------------------------------------------------------------+ +| mul256(x:256, y:256) -> z:256 | x * y | ++---------------------------------------------------------------------------------------------------------------+ +| div256(x:256, y:256) -> z:256 | x / y | ++---------------------------------------------------------------------------------------------------------------+ +| sdiv256(x:256, y:256) -> z:256 | x / y, for signed numbers in two's complement | ++---------------------------------------------------------------------------------------------------------------+ +| mod256(x:256, y:256) -> z:256 | x % y | ++---------------------------------------------------------------------------------------------------------------+ +| smod256(x:256, y:256) -> z:256 | x % y, for signed numbers in two's complement | ++---------------------------------------------------------------------------------------------------------------+ +| signextend256(i:256, x:256) -> z:256 | sign extend from (i*8+7)th bit counting from least significant | ++---------------------------------------------------------------------------------------------------------------+ +| exp256(x:256, y:256) -> z:256 | x to the power of y | ++---------------------------------------------------------------------------------------------------------------+ +| addmod256(x:256, y:256, m:256) -> z:256 | (x + y) % m with arbitrary precision arithmetics | ++---------------------------------------------------------------------------------------------------------------+ +| mulmod256(x:256, y:256, m:256) -> z:256 | (x * y) % m with arbitrary precision arithmetics | ++---------------------------------------------------------------------------------------------------------------+ +| lt256(x:256, y:256) -> z:bool | 1 if x < y, 0 otherwise | ++---------------------------------------------------------------------------------------------------------------+ +| gt256(x:256, y:256) -> z:bool | 1 if x > y, 0 otherwise | ++---------------------------------------------------------------------------------------------------------------+ +| slt256(x:256, y:256) -> z:bool | 1 if x < y, 0 otherwise, for signed numbers in two's complement | ++---------------------------------------------------------------------------------------------------------------+ +| sgt256(x:256, y:256) -> z:bool | 1 if x > y, 0 otherwise, for signed numbers in two's complement | ++---------------------------------------------------------------------------------------------------------------+ +| eq256(x:256, y:256) -> z:bool | 1 if x == y, 0 otherwise | ++---------------------------------------------------------------------------------------------------------------+ +| not256(x:256) -> z:256 | ~x, every bit of x is negated | ++---------------------------------------------------------------------------------------------------------------+ +| and256(x:256, y:256) -> z:256 | bitwise and of x and y | ++---------------------------------------------------------------------------------------------------------------+ +| or256(x:256, y:256) -> z:256 | bitwise or of x and y | ++---------------------------------------------------------------------------------------------------------------+ +| xor256(x:256, y:256) -> z:256 | bitwise xor of x and y | ++---------------------------------------------------------------------------------------------------------------+ +| shl256(x:256, y:256) -> z:256 | logical left shift of x by y | ++---------------------------------------------------------------------------------------------------------------+ +| shr256(x:256, y:256) -> z:256 | logical right shift of x by y | ++---------------------------------------------------------------------------------------------------------------+ +| sar256(x:256, y:256) -> z:256 | arithmetic right shift of x by y | ++---------------------------------------------------------------------------------------------------------------+ +| byte(n:256, x:256) -> v:256 | nth byte of x, where the most significant byte is the 0th byte | +| Cannot this be just replaced by and256(shr256(n, x), 0xff) and let it be optimised out by the EVM backend? | ++---------------------------------------------------------------------------------------------------------------+ +| *Memory and storage* | ++---------------------------------------------------------------------------------------------------------------+ +| mload(p:256) -> v:256 | mem[p..(p+32)) | ++---------------------------------------------------------------------------------------------------------------+ +| mstore(p:256, v:256) | mem[p..(p+32)) := v | ++---------------------------------------------------------------------------------------------------------------+ +| mstore8(p:256, v:256) | mem[p] := v & 0xff - only modifies a single byte | ++---------------------------------------------------------------------------------------------------------------+ +| sload(p:256) -> v:256 | storage[p] | ++---------------------------------------------------------------------------------------------------------------+ +| sstore(p:256, v:256) | storage[p] := v | ++---------------------------------------------------------------------------------------------------------------+ +| msize() -> size:256 | size of memory, i.e. largest accessed memory index, albeit due | +| | due to the memory extension function, which extends by words, | +| | this will always be a multiple of 32 bytes | ++---------------------------------------------------------------------------------------------------------------+ +| *Execution control* | ++---------------------------------------------------------------------------------------------------------------+ +| create(v:256, p:256, s:256) | create new contract with code mem[p..(p+s)) and send v wei | +| | and return the new address | ++---------------------------------------------------------------------------------------------------------------+ +| call(g:256, a:256, v:256, in:256, | call contract at address a with input mem[in..(in+insize)) | +| insize:256, out:256, outsize:256) -> r:256 | providing g gas and v wei and output area | +| | mem[out..(out+outsize)) returning 0 on error (eg. out of gas) | +| | and 1 on success | ++---------------------------------------------------------------------------------------------------------------+ +| callcode(g:256, a:256, v:256, in:256, | identical to `call` but only use the code from a and stay | +| insize:256, out:256, outsize:256) -> r:256 | in the context of the current contract otherwise | ++---------------------------------------------------------------------------------------------------------------+ +| delegatecall(g:256, a:256, in:256, | identical to `callcode` but also keep ``caller`` | +| insize:256, out:256, outsize:256) -> r:256 | and ``callvalue`` | ++---------------------------------------------------------------------------------------------------------------+ +| stop() | stop execution, identical to return(0,0) | +| Perhaps it would make sense retiring this as it equals to return(0,0). It can be an optimisation by the EVM | +| backend. | ++---------------------------------------------------------------------------------------------------------------+ +| abort() | abort (equals to invalid instruction on EVM) | ++---------------------------------------------------------------------------------------------------------------+ +| return(p:256, s:256) | end execution, return data mem[p..(p+s)) | ++---------------------------------------------------------------------------------------------------------------+ +| revert(p:256, s:256) | end execution, revert state changes, return data mem[p..(p+s)) | ++---------------------------------------------------------------------------------------------------------------+ +| selfdestruct(a:256) | end execution, destroy current contract and send funds to a | ++---------------------------------------------------------------------------------------------------------------+ +| log0(p:256, s:256) | log without topics and data mem[p..(p+s)) | ++---------------------------------------------------------------------------------------------------------------+ +| log1(p:256, s:256, t1:256) | log with topic t1 and data mem[p..(p+s)) | ++---------------------------------------------------------------------------------------------------------------+ +| log2(p:256, s:256, t1:256, t2:256) | log with topics t1, t2 and data mem[p..(p+s)) | ++---------------------------------------------------------------------------------------------------------------+ +| log3(p:256, s:256, t1:256, t2:256, | log with topics t, t2, t3 and data mem[p..(p+s)) | +| t3:256) | | ++---------------------------------------------------------------------------------------------------------------+ +| log4(p:256, s:256, t1:256, t2:256, | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) | +| t3:256, t4:256) | | ++---------------------------------------------------------------------------------------------------------------+ +| *State queries* | ++---------------------------------------------------------------------------------------------------------------+ +| blockcoinbase() -> address:256 | current mining beneficiary | ++---------------------------------------------------------------------------------------------------------------+ +| blockdifficulty() -> difficulty:256 | difficulty of the current block | ++---------------------------------------------------------------------------------------------------------------+ +| blockgaslimit() -> limit:256 | block gas limit of the current block | ++---------------------------------------------------------------------------------------------------------------+ +| blockhash(b:256) -> hash:256 | hash of block nr b - only for last 256 blocks excluding current | ++---------------------------------------------------------------------------------------------------------------+ +| blocknumber() -> block:256 | current block number | ++---------------------------------------------------------------------------------------------------------------+ +| blocktimestamp() -> timestamp:256 | timestamp of the current block in seconds since the epoch | ++---------------------------------------------------------------------------------------------------------------+ +| txorigin() -> address:256 | transaction sender | ++---------------------------------------------------------------------------------------------------------------+ +| txgasprice() -> price:256 | gas price of the transaction | ++---------------------------------------------------------------------------------------------------------------+ +| gasleft() -> gas:256 | gas still available to execution | ++---------------------------------------------------------------------------------------------------------------+ +| balance(a:256) -> v:256 | wei balance at address a | ++---------------------------------------------------------------------------------------------------------------+ +| this() -> address:256 | address of the current contract / execution context | ++---------------------------------------------------------------------------------------------------------------+ +| caller() -> address:256 | call sender (excluding delegatecall) | ++---------------------------------------------------------------------------------------------------------------+ +| callvalue() -> v:256 | wei sent together with the current call | ++---------------------------------------------------------------------------------------------------------------+ +| calldataload(p:256) -> v:256 | call data starting from position p (32 bytes) | ++---------------------------------------------------------------------------------------------------------------+ +| calldatasize() -> v:256 | size of call data in bytes | ++---------------------------------------------------------------------------------------------------------------+ +| calldatacopy(t:256, f:256, s:256) | copy s bytes from calldata at position f to mem at position t | ++---------------------------------------------------------------------------------------------------------------+ +| codesize() -> size:256 | size of the code of the current contract / execution context | ++---------------------------------------------------------------------------------------------------------------+ +| codecopy(t:256, f:256, s:256) | copy s bytes from code at position f to mem at position t | ++---------------------------------------------------------------------------------------------------------------+ +| extcodesize(a:256) -> size:256 | size of the code at address a | ++---------------------------------------------------------------------------------------------------------------+ +| extcodecopy(a:256, t:256, f:256, s:256) | like codecopy(t, f, s) but take code at address a | ++---------------------------------------------------------------------------------------------------------------+ +| *Others* | ++---------------------------------------------------------------------------------------------------------------+ +| discard256(unused:256) | discard value | ++---------------------------------------------------------------------------------------------------------------+ +| sha3(p:256, s:256) -> v:256 | keccak(mem[p...(p+s))) | ++---------------------------------------------------------------------------------------------------------------+ -- cgit v1.2.3 From 173bdb0df6ce338d2b6fff80877224a9cfdb3d68 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 18 Apr 2017 16:40:31 +0100 Subject: Define types in Julia --- docs/julia.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/julia.rst b/docs/julia.rst index 5203b522..f1a6d8f8 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -16,6 +16,10 @@ language. It should also be easy to build high-level optimizer stages for JULIA. The core components of JULIA are functions, blocks, variables, literals, for-loops, switch-statements, expressions and assignments to variables. +JULIA is typed, both variables and literals must specify the type with postfix +notation. The supported types are ``bool``, ``u8``, ``s8``, ``u32``, ``s32``, +``u64``, ``s64``, ``u128``, ``s128``, ``u256`` and ``s256``. + JULIA in itself does not even provide operators. If the EVM is targeted, opcodes will be available as built-in functions, but they can be reimplemented if the backend changes. For a list of mandatory built-in functions, see the section below. -- cgit v1.2.3 From 3d99e8279575c686bd48801c3c3bad2fde8a30c6 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 18 Apr 2017 16:53:13 +0100 Subject: Add types to the examples --- docs/julia.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index f1a6d8f8..42c8047e 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -30,16 +30,16 @@ and ``mod`` are available either natively or as functions and computes exponenti .. code:: { - function power(base, exponent) -> (result) + function power(base:u256, exponent:u256) -> (result:u256) { switch exponent - 0: { result := 1 } - 1: { result := base } + 0:u256: { result := 1:u256 } + 1:u256: { result := base } default: { - result := power(mul(base, base), div(exponent, 2)) - switch mod(exponent, 2) - 1: { result := mul(base, result) } + result := power(mul(base, base), div(exponent, 2:u256)) + switch mod(exponent, 2:u256) + 1:u256: { result := mul(base, result) } } } } @@ -51,10 +51,10 @@ and ``add`` to be available. .. code:: { - function power(base, exponent) -> (result) + function power(base:u256, exponent:u256) -> (result:u256) { - result := 1 - for { let i := 0 } lt(i, exponent) { i := add(i, 1) } + result := 1:u256 + for { let i := 0:u256 } lt(i, exponent) { i := add(i, 1:u256) } { result := mul(result, base) } -- cgit v1.2.3 From 0339cc1bb1dd84455237ffa4b3235ad4deb44f0f Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 18 Apr 2017 23:23:37 +0100 Subject: Case is missing from switch statements --- docs/julia.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index 42c8047e..58c3c557 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -33,13 +33,13 @@ and ``mod`` are available either natively or as functions and computes exponenti function power(base:u256, exponent:u256) -> (result:u256) { switch exponent - 0:u256: { result := 1:u256 } - 1:u256: { result := base } + case 0:u256: { result := 1:u256 } + case 1:u256: { result := base } default: { result := power(mul(base, base), div(exponent, 2:u256)) switch mod(exponent, 2:u256) - 1:u256: { result := mul(base, result) } + case 1:u256: { result := mul(base, result) } } } } -- cgit v1.2.3 From 183f70262a59d10b264cb71ba82d31d37c65dd63 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 19 Apr 2017 13:40:56 +0100 Subject: Document backends --- docs/julia.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/julia.rst b/docs/julia.rst index 58c3c557..f0622dc9 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -353,3 +353,24 @@ The following functions must be available: +---------------------------------------------------------------------------------------------------------------+ | sha3(p:256, s:256) -> v:256 | keccak(mem[p...(p+s))) | +---------------------------------------------------------------------------------------------------------------+ + +Backends +-------- + +Backends or targets are the translators from JULIA to a specific bytecode. Each of the backends can expose functions +prefixed with the name of the backend. We reserve ``evm_`` and ``ewasm_`` prefixes for the two proposed backends. + +Backend: EVM +------------ + +The EVM target will have all the underlying EVM opcodes exposed with the `evm_` prefix. + +Backend: "EVM 1.5" +------------------ + +TBD + +Backend: eWASM +-------------- + +TBD -- cgit v1.2.3 From 0e4d236558f516bb39426ff38452006923d4494a Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 20 Apr 2017 12:48:49 +0100 Subject: Include implicit type conversions --- docs/julia.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/julia.rst b/docs/julia.rst index f0622dc9..bd8e35a9 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -192,6 +192,19 @@ For ``(S1, z) = E(S, y)`` let ``(S2, w) = E(S1, x)``. TODO E(G, L, n: DecimalNumber) = G, L, dec(n), where dec is the decimal decoding function +Type Conversion Functions +------------------------- + +JULIA has no support for implicit type conversion and therefore functions exists to provide explicit conversion. +When converting a larger type to a shorter type a runtime exception can occur in case of an overflow. + +The following type conversion functions must be available: +- ``u32tobool(x:u32) -> (y:bool)`` +- ``booltou32(x:bool) -> (y:u32)`` +- ``u32tou64(x:u32) -> (y:u64)`` +- ``u64tou32(x:u64) -> (y:u32)`` +- etc. (TBD) + Low-level Functions ------------------- -- cgit v1.2.3 From aa6f33db2c20a55c72cbef834b255d029c7adc44 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 20 Apr 2017 12:57:02 +0100 Subject: Use signed/unsigned types in low-level functions --- docs/julia.rst | 144 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 73 insertions(+), 71 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index bd8e35a9..98b19662 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -213,86 +213,88 @@ The following functions must be available: +---------------------------------------------------------------------------------------------------------------+ | *Arithmetics* | +---------------------------------------------------------------------------------------------------------------+ -| add256(x:256, y:256) -> z:256 | x + y | +| addu256(x:u256, y:u256) -> z:u256 | x + y | +---------------------------------------------------------------------------------------------------------------+ -| sub256(x:256, y:256) -> z:256 | x - y | +| subu256(x:u256, y:u256) -> z:u256 | x - y | +---------------------------------------------------------------------------------------------------------------+ -| mul256(x:256, y:256) -> z:256 | x * y | +| mulu256(x:u256, y:u256) -> z:u256 | x * y | +---------------------------------------------------------------------------------------------------------------+ -| div256(x:256, y:256) -> z:256 | x / y | +| divu256(x:u256, y:u256) -> z:u256 | x / y | +---------------------------------------------------------------------------------------------------------------+ -| sdiv256(x:256, y:256) -> z:256 | x / y, for signed numbers in two's complement | +| divs256(x:s256, y:s256) -> z:s256 | x / y, for signed numbers in two's complement | +---------------------------------------------------------------------------------------------------------------+ -| mod256(x:256, y:256) -> z:256 | x % y | +| modu256(x:u256, y:u256) -> z:u256 | x % y | +---------------------------------------------------------------------------------------------------------------+ -| smod256(x:256, y:256) -> z:256 | x % y, for signed numbers in two's complement | +| mods256(x:s256, y:s256) -> z:s256 | x % y, for signed numbers in two's complement | +---------------------------------------------------------------------------------------------------------------+ -| signextend256(i:256, x:256) -> z:256 | sign extend from (i*8+7)th bit counting from least significant | +| signextendu256(i:u256, x:u256) -> z:u256 | sign extend from (i*8+7)th bit counting from least significant | +---------------------------------------------------------------------------------------------------------------+ -| exp256(x:256, y:256) -> z:256 | x to the power of y | +| expu256(x:u256, y:u256) -> z:u256 | x to the power of y | +---------------------------------------------------------------------------------------------------------------+ -| addmod256(x:256, y:256, m:256) -> z:256 | (x + y) % m with arbitrary precision arithmetics | +| addmodu256(x:u256, y:u256, m:u256) -> z:u256| (x + y) % m with arbitrary precision arithmetics | +---------------------------------------------------------------------------------------------------------------+ -| mulmod256(x:256, y:256, m:256) -> z:256 | (x * y) % m with arbitrary precision arithmetics | +| mulmodu256(x:u256, y:u256, m:u256) -> z:u256| (x * y) % m with arbitrary precision arithmetics | +---------------------------------------------------------------------------------------------------------------+ -| lt256(x:256, y:256) -> z:bool | 1 if x < y, 0 otherwise | +| ltu256(x:u256, y:u256) -> z:bool | 1 if x < y, 0 otherwise | +---------------------------------------------------------------------------------------------------------------+ -| gt256(x:256, y:256) -> z:bool | 1 if x > y, 0 otherwise | +| gtu256(x:u256, y:u256) -> z:bool | 1 if x > y, 0 otherwise | +---------------------------------------------------------------------------------------------------------------+ -| slt256(x:256, y:256) -> z:bool | 1 if x < y, 0 otherwise, for signed numbers in two's complement | +| sltu256(x:s256, y:s256) -> z:bool | 1 if x < y, 0 otherwise, for signed numbers in two's complement | +---------------------------------------------------------------------------------------------------------------+ -| sgt256(x:256, y:256) -> z:bool | 1 if x > y, 0 otherwise, for signed numbers in two's complement | +| sgtu256(x:s256, y:s256) -> z:bool | 1 if x > y, 0 otherwise, for signed numbers in two's complement | +---------------------------------------------------------------------------------------------------------------+ -| eq256(x:256, y:256) -> z:bool | 1 if x == y, 0 otherwise | +| equ256(x:u256, y:u256) -> z:bool | 1 if x == y, 0 otherwise | +---------------------------------------------------------------------------------------------------------------+ -| not256(x:256) -> z:256 | ~x, every bit of x is negated | +| notu256(x:u256) -> z:u256 | ~x, every bit of x is negated | +---------------------------------------------------------------------------------------------------------------+ -| and256(x:256, y:256) -> z:256 | bitwise and of x and y | +| andu256(x:u256, y:u256) -> z:u256 | bitwise and of x and y | +---------------------------------------------------------------------------------------------------------------+ -| or256(x:256, y:256) -> z:256 | bitwise or of x and y | +| oru256(x:u256, y:u256) -> z:u256 | bitwise or of x and y | +---------------------------------------------------------------------------------------------------------------+ -| xor256(x:256, y:256) -> z:256 | bitwise xor of x and y | +| xoru256(x:u256, y:u256) -> z:u256 | bitwise xor of x and y | +---------------------------------------------------------------------------------------------------------------+ -| shl256(x:256, y:256) -> z:256 | logical left shift of x by y | +| shlu256(x:u256, y:u256) -> z:u256 | logical left shift of x by y | +---------------------------------------------------------------------------------------------------------------+ -| shr256(x:256, y:256) -> z:256 | logical right shift of x by y | +| shru256(x:u256, y:u256) -> z:u256 | logical right shift of x by y | +---------------------------------------------------------------------------------------------------------------+ -| sar256(x:256, y:256) -> z:256 | arithmetic right shift of x by y | +| saru256(x:u256, y:u256) -> z:u256 | arithmetic right shift of x by y | +---------------------------------------------------------------------------------------------------------------+ -| byte(n:256, x:256) -> v:256 | nth byte of x, where the most significant byte is the 0th byte | +| byte(n:u256, x:u256) -> v:u256 | nth byte of x, where the most significant byte is the 0th byte | | Cannot this be just replaced by and256(shr256(n, x), 0xff) and let it be optimised out by the EVM backend? | +---------------------------------------------------------------------------------------------------------------+ | *Memory and storage* | +---------------------------------------------------------------------------------------------------------------+ -| mload(p:256) -> v:256 | mem[p..(p+32)) | +| mload(p:u256) -> v:u256 | mem[p..(p+32)) | +---------------------------------------------------------------------------------------------------------------+ -| mstore(p:256, v:256) | mem[p..(p+32)) := v | +| mstore(p:u256, v:u256) | mem[p..(p+32)) := v | +---------------------------------------------------------------------------------------------------------------+ -| mstore8(p:256, v:256) | mem[p] := v & 0xff - only modifies a single byte | +| mstore8(p:u256, v:u256) | mem[p] := v & 0xff - only modifies a single byte | +---------------------------------------------------------------------------------------------------------------+ -| sload(p:256) -> v:256 | storage[p] | +| sload(p:u256) -> v:u256 | storage[p] | +---------------------------------------------------------------------------------------------------------------+ -| sstore(p:256, v:256) | storage[p] := v | +| sstore(p:u256, v:u256) | storage[p] := v | +---------------------------------------------------------------------------------------------------------------+ -| msize() -> size:256 | size of memory, i.e. largest accessed memory index, albeit due | +| msize() -> size:u256 | size of memory, i.e. largest accessed memory index, albeit due | | | due to the memory extension function, which extends by words, | | | this will always be a multiple of 32 bytes | +---------------------------------------------------------------------------------------------------------------+ | *Execution control* | +---------------------------------------------------------------------------------------------------------------+ -| create(v:256, p:256, s:256) | create new contract with code mem[p..(p+s)) and send v wei | +| create(v:u256, p:u256, s:u256) | create new contract with code mem[p..(p+s)) and send v wei | | | and return the new address | +---------------------------------------------------------------------------------------------------------------+ -| call(g:256, a:256, v:256, in:256, | call contract at address a with input mem[in..(in+insize)) | -| insize:256, out:256, outsize:256) -> r:256 | providing g gas and v wei and output area | -| | mem[out..(out+outsize)) returning 0 on error (eg. out of gas) | -| | and 1 on success | +| call(g:u256, a:u256, v:u256, in:u256, | call contract at address a with input mem[in..(in+insize)) | +| insize:u256, out:u256, | providing g gas and v wei and output area | +| outsize:u256) | mem[out..(out+outsize)) returning 0 on error (eg. out of gas) | +| -> r:u256 | and 1 on success | +---------------------------------------------------------------------------------------------------------------+ -| callcode(g:256, a:256, v:256, in:256, | identical to `call` but only use the code from a and stay | -| insize:256, out:256, outsize:256) -> r:256 | in the context of the current contract otherwise | +| callcode(g:u256, a:u256, v:u256, in:u256, | identical to ``call`` but only use the code from a | +| insize:u256, out:u256, | and stay in the context of the | +| outsize:u256) -> r:u256 | current contract otherwise | +---------------------------------------------------------------------------------------------------------------+ -| delegatecall(g:256, a:256, in:256, | identical to `callcode` but also keep ``caller`` | -| insize:256, out:256, outsize:256) -> r:256 | and ``callvalue`` | +| delegatecall(g:u256, a:u256, in:u256, | identical to ``callcode``, | +| insize:u256, out:u256, | but also keep ``caller`` | +| outsize:u256) -> r:u256 | and ``callvalue`` | +---------------------------------------------------------------------------------------------------------------+ | stop() | stop execution, identical to return(0,0) | | Perhaps it would make sense retiring this as it equals to return(0,0). It can be an optimisation by the EVM | @@ -300,71 +302,71 @@ The following functions must be available: +---------------------------------------------------------------------------------------------------------------+ | abort() | abort (equals to invalid instruction on EVM) | +---------------------------------------------------------------------------------------------------------------+ -| return(p:256, s:256) | end execution, return data mem[p..(p+s)) | +| return(p:u256, s:u256) | end execution, return data mem[p..(p+s)) | +---------------------------------------------------------------------------------------------------------------+ -| revert(p:256, s:256) | end execution, revert state changes, return data mem[p..(p+s)) | +| revert(p:u256, s:u256) | end execution, revert state changes, return data mem[p..(p+s)) | +---------------------------------------------------------------------------------------------------------------+ -| selfdestruct(a:256) | end execution, destroy current contract and send funds to a | +| selfdestruct(a:u256) | end execution, destroy current contract and send funds to a | +---------------------------------------------------------------------------------------------------------------+ -| log0(p:256, s:256) | log without topics and data mem[p..(p+s)) | +| log0(p:u256, s:u256) | log without topics and data mem[p..(p+s)) | +---------------------------------------------------------------------------------------------------------------+ -| log1(p:256, s:256, t1:256) | log with topic t1 and data mem[p..(p+s)) | +| log1(p:u256, s:u256, t1:u256) | log with topic t1 and data mem[p..(p+s)) | +---------------------------------------------------------------------------------------------------------------+ -| log2(p:256, s:256, t1:256, t2:256) | log with topics t1, t2 and data mem[p..(p+s)) | +| log2(p:u256, s:u256, t1:u256, t2:u256) | log with topics t1, t2 and data mem[p..(p+s)) | +---------------------------------------------------------------------------------------------------------------+ -| log3(p:256, s:256, t1:256, t2:256, | log with topics t, t2, t3 and data mem[p..(p+s)) | -| t3:256) | | +| log3(p:u256, s:u256, t1:u256, t2:u256, | log with topics t, t2, t3 and data mem[p..(p+s)) | +| t3:u256) | | +---------------------------------------------------------------------------------------------------------------+ -| log4(p:256, s:256, t1:256, t2:256, | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) | -| t3:256, t4:256) | | +| log4(p:u256, s:u256, t1:u256, t2:u256, | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) | +| t3:u256, t4:u256) | | +---------------------------------------------------------------------------------------------------------------+ | *State queries* | +---------------------------------------------------------------------------------------------------------------+ -| blockcoinbase() -> address:256 | current mining beneficiary | +| blockcoinbase() -> address:u256 | current mining beneficiary | +---------------------------------------------------------------------------------------------------------------+ -| blockdifficulty() -> difficulty:256 | difficulty of the current block | +| blockdifficulty() -> difficulty:u256 | difficulty of the current block | +---------------------------------------------------------------------------------------------------------------+ -| blockgaslimit() -> limit:256 | block gas limit of the current block | +| blockgaslimit() -> limit:u256 | block gas limit of the current block | +---------------------------------------------------------------------------------------------------------------+ -| blockhash(b:256) -> hash:256 | hash of block nr b - only for last 256 blocks excluding current | +| blockhash(b:u256) -> hash:u256 | hash of block nr b - only for last 256 blocks excluding current | +---------------------------------------------------------------------------------------------------------------+ -| blocknumber() -> block:256 | current block number | +| blocknumber() -> block:u256 | current block number | +---------------------------------------------------------------------------------------------------------------+ -| blocktimestamp() -> timestamp:256 | timestamp of the current block in seconds since the epoch | +| blocktimestamp() -> timestamp:u256 | timestamp of the current block in seconds since the epoch | +---------------------------------------------------------------------------------------------------------------+ -| txorigin() -> address:256 | transaction sender | +| txorigin() -> address:u256 | transaction sender | +---------------------------------------------------------------------------------------------------------------+ -| txgasprice() -> price:256 | gas price of the transaction | +| txgasprice() -> price:u256 | gas price of the transaction | +---------------------------------------------------------------------------------------------------------------+ -| gasleft() -> gas:256 | gas still available to execution | +| gasleft() -> gas:u256 | gas still available to execution | +---------------------------------------------------------------------------------------------------------------+ -| balance(a:256) -> v:256 | wei balance at address a | +| balance(a:u256) -> v:u256 | wei balance at address a | +---------------------------------------------------------------------------------------------------------------+ -| this() -> address:256 | address of the current contract / execution context | +| this() -> address:u256 | address of the current contract / execution context | +---------------------------------------------------------------------------------------------------------------+ -| caller() -> address:256 | call sender (excluding delegatecall) | +| caller() -> address:u256 | call sender (excluding delegatecall) | +---------------------------------------------------------------------------------------------------------------+ -| callvalue() -> v:256 | wei sent together with the current call | +| callvalue() -> v:u256 | wei sent together with the current call | +---------------------------------------------------------------------------------------------------------------+ -| calldataload(p:256) -> v:256 | call data starting from position p (32 bytes) | +| calldataload(p:u256) -> v:u256 | call data starting from position p (32 bytes) | +---------------------------------------------------------------------------------------------------------------+ -| calldatasize() -> v:256 | size of call data in bytes | +| calldatasize() -> v:u256 | size of call data in bytes | +---------------------------------------------------------------------------------------------------------------+ -| calldatacopy(t:256, f:256, s:256) | copy s bytes from calldata at position f to mem at position t | +| calldatacopy(t:u256, f:u256, s:u256) | copy s bytes from calldata at position f to mem at position t | +---------------------------------------------------------------------------------------------------------------+ -| codesize() -> size:256 | size of the code of the current contract / execution context | +| codesize() -> size:u256 | size of the code of the current contract / execution context | +---------------------------------------------------------------------------------------------------------------+ -| codecopy(t:256, f:256, s:256) | copy s bytes from code at position f to mem at position t | +| codecopy(t:u256, f:u256, s:u256) | copy s bytes from code at position f to mem at position t | +---------------------------------------------------------------------------------------------------------------+ -| extcodesize(a:256) -> size:256 | size of the code at address a | +| extcodesize(a:u256) -> size:u256 | size of the code at address a | +---------------------------------------------------------------------------------------------------------------+ -| extcodecopy(a:256, t:256, f:256, s:256) | like codecopy(t, f, s) but take code at address a | +| extcodecopy(a:u256, t:u256, f:u256, s:u256) | like codecopy(t, f, s) but take code at address a | +---------------------------------------------------------------------------------------------------------------+ | *Others* | +---------------------------------------------------------------------------------------------------------------+ -| discard256(unused:256) | discard value | +| discardu256(unused:u256) | discard value | +---------------------------------------------------------------------------------------------------------------+ -| sha3(p:256, s:256) -> v:256 | keccak(mem[p...(p+s))) | +| sha3(p:u256, s:u256) -> v:u256 | keccak(mem[p...(p+s))) | +---------------------------------------------------------------------------------------------------------------+ Backends -- cgit v1.2.3 From e33a9b43ad1d11e7f6877d73032bf5f59ba1be95 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 20 Apr 2017 12:57:28 +0100 Subject: Include split/combine256 --- docs/julia.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/julia.rst b/docs/julia.rst index 98b19662..d9b92b24 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -366,6 +366,12 @@ The following functions must be available: +---------------------------------------------------------------------------------------------------------------+ | discardu256(unused:u256) | discard value | +---------------------------------------------------------------------------------------------------------------+ +| splitu256tou64(x:u256) -> (x1:u64, x2:u64, | split u256 to four u64's | +| x3:u64, x4:u64) | | ++---------------------------------------------------------------------------------------------------------------+ +| combineu64tou256(x1:u64, x2:u64, x3:u64, | combine four u64's into a single u256 | +| x4:u64) -> (x:u256) | | ++---------------------------------------------------------------------------------------------------------------+ | sha3(p:u256, s:u256) -> v:u256 | keccak(mem[p...(p+s))) | +---------------------------------------------------------------------------------------------------------------+ -- cgit v1.2.3 From f17bdaabda4f94c7160b662b2b40cdb638c326c4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 21 Apr 2017 12:22:58 +0200 Subject: Improve semantics description. --- docs/julia.rst | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index d9b92b24..f360da0a 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -139,32 +139,32 @@ segment of the stack in the EVM). The the evaluation function E takes a global state, a local state and a node of the AST and returns a new global state, a new local state -and a value (if the AST node is an expression). +and a variable number of values. The number of values is equal to the +number of values of the expresison if the AST node is an expresison and +zero otherwise. -We use sequence numbers as a shorthand for the order of evaluation -and how state is forwarded. For example, ``E2(x), E1(y)`` is a shorthand -for - -For ``(S1, z) = E(S, y)`` let ``(S2, w) = E(S1, x)``. TODO +The exact nature of the global state is unspecified for this high level +description. The local state `L` is a mapping of identifiers `i` to values `v`, +denoted as `L[i] = v`. +The special value `⊥` is used to signify that a variable cannot be +used yet. .. code:: E(G, L, <{St1, ..., Stn}>: Block) = - let L' be a copy of L that adds a new inner scope which contains - all functions and variables declared in the block (but not its sub-blocks) - variables are marked inactive for now - TODO: more formal + let L' be an extension of L to all variables v declared in Block + (but not in its sub-blocks), such that L'[v] = ⊥. G1, L'1 = E(G, L', St1) G2, L'2 = E(G1, L'1, St2) ... Gn, L'n = E(G(n-1), L'(n-1), Stn) - let L'' be a copy of L'n where the innermost scope is removed + let L'' be a restriction of L'n to the identifiers of L' Gn, L'' E(G, L, (ret1, ..., retm) block>: FunctionDefinition) = G, L - E(G, L, : VariableDeclaration) = - E(G, L, <(var1, ..., varn) := value>: Assignment) - E(G, L, <(var1, ..., varn) := value>: Assignment) = + E(G, L, : VariableDeclaration) = + E(G, L, : Assignment) + E(G, L, : Assignment) = let G', L', v1, ..., vn = E(G, L, value) let L'' be a copy of L' where L'[vi] = vi for i = 1, ..., n G, L'' @@ -175,11 +175,10 @@ For ``(S1, z) = E(S, y)`` let ``(S2, w) = E(S1, x)``. TODO ... G(n-1), L(n-1), v2 = E(G(n-2), L(n-2), arg2) Gn, Ln, v1 = E(G(n-1), L(n-1), arg1) - Let (ret1, ..., retm) block> - be the function L[fname]. - Let L' be a copy of L that does not contain any variables in any scope, - but which has a new innermost scope such that - L'[parami] = vi and L'[reti] = 0 + Let ret1, ..., retm block> + be the function of name fname visible at the point of the call. + Let L' be a new local state such that + L'[parami] = vi and L'[reti] = 0 for all i. Let G'', L'', rv1, ..., rvm = E(Gn, L', block) G'', Ln, rv1, ..., rvm E(G, L, l: HexLiteral) = G, L, hexString(l), -- cgit v1.2.3 From ad5cd215712d6baea82805a46ba17a6f063564df Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 21 Apr 2017 14:56:19 +0200 Subject: More specification. --- docs/julia.rst | 108 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 80 insertions(+), 28 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index f360da0a..c0e0c97a 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -81,7 +81,7 @@ Grammar:: 'function' Identifier '(' IdentifierList? ')' ( '->' '(' IdentifierList ')' )? Block VariableDeclaration = - 'let' IdentifierOrList ':=' Expression + 'let' IdentifierOrList ( ':=' Expression )? Assignment = IdentifierOrList ':=' Expression Expression = @@ -113,18 +113,37 @@ Restrictions on the Grammar --------------------------- Scopes in JULIA are tied to Blocks and all declarations -(``FunctionDefinition``, ``VariableDeclaration`` and ``SubAssembly``) -introduce new identifiers into these scopes. Shadowing is disallowed +(``FunctionDefinition``, ``VariableDeclaration``) +introduce new identifiers into these scopes. Identifiers are visible in +the block they are defined in (including all sub-nodes and sub-blocks). +Shadowing is disallowed, i.e. you cannot declare an identifier at a point +where another identifier with the same name is also visible. -Talk about identifiers across functions etc +In for-loops, identifiers declared in the first block (the init block) +are visible in all other parts of the for loop (but not outside of the loop). +Identifiers declared in the other parts of the for loop respect the regular +syntatical scoping rules. -Restriction for Expression: Statements have to return empty tuple -Function arguments have to be single item +Inside functions, it is not possible to access a variable that was declared +outside of that function. -Restriction for VariableDeclaration and Assignment: Number of elements left and right needs to be the same -continue and break only in for loop +Every expression evaluates to zero or more values. Literals evaluate to exactly +one value and function calls evaluate to a number of values equal to the +number of return values of the function called. An expression that is also +a statement is invalid if it evaluates to more than one value, i.e. at the +block-level, only expressions evaluating to zero values are allowed. -Literals have to fit 32 bytes +For variable declarations and assignments, the right-hand-side expression +(if present) has to evaluate to a number of values equal to the number of +variables on the left-hand-side. + +An expression used as an argument to a function call has to evaluate to exactly +one value. + +The ``continue`` and ``break`` statements can only be used inside loop bodies. +The condition part of the for-loop has to evaluate to exactly one value. + +Literals cannot be larger than 32 bytes. Formal Specification -------------------- @@ -139,9 +158,7 @@ segment of the stack in the EVM). The the evaluation function E takes a global state, a local state and a node of the AST and returns a new global state, a new local state -and a variable number of values. The number of values is equal to the -number of values of the expresison if the AST node is an expresison and -zero otherwise. +and a variable number of values. The exact nature of the global state is unspecified for this high level description. The local state `L` is a mapping of identifiers `i` to values `v`, @@ -154,30 +171,65 @@ used yet. E(G, L, <{St1, ..., Stn}>: Block) = let L' be an extension of L to all variables v declared in Block (but not in its sub-blocks), such that L'[v] = ⊥. - G1, L'1 = E(G, L', St1) - G2, L'2 = E(G1, L'1, St2) - ... - Gn, L'n = E(G(n-1), L'(n-1), Stn) - let L'' be a restriction of L'n to the identifiers of L' - Gn, L'' + let Gi, Li, mode = E(G, L', St1, ..., Stn) + let L'' be a restriction of Li to the identifiers of L + Gi, L'', mode + E(G, L, St1, ..., Stn: Statement) = + if n is zero: + G, L + else: + let G', L', mode = E(G, L, St1) + if mode is regular then + E(G', L', St2, ..., Stn) + otherwise + G', L', mode E(G, L, (ret1, ..., retm) block>: FunctionDefinition) = - G, L - E(G, L, : VariableDeclaration) = - E(G, L, : Assignment) - E(G, L, : Assignment) = - let G', L', v1, ..., vn = E(G, L, value) + G, L, regular + E(G, L, : VariableDeclaration) = + E(G, L, : Assignment) + E(G, L, : VariableDeclaration) = + let L' be a copy of L where L'[vi] = 0 for i = 1, ..., n + G, L', regular + E(G, L, : Assignment) = + let G', L', v1, ..., vn = E(G, L, rhs) let L'' be a copy of L' where L'[vi] = vi for i = 1, ..., n - G, L'' - E(G, L, name: Identifier) = - G, L, L[name] - E(G, L, fname(arg1, ..., argn): FunctionCall) = + G, L'', regular + E(G, L, : ForLoop) = + if n >= 1: + let L' be an extension of L to all variables v declared in i1, ..., in + (but not in sub-blocks), such that L'[v] = ⊥. + let G'', L'', mode = E(G, L', i1, ..., in) + explode if mode is not regular + let G''', L''', mode = E(G'', L'', for {} condition post body) + explode if mode is not regular + let Lend be the restriction of L''' to only variables of L + G''', Lend + else: + let G', L', v = E(G, L, condition) + if v is false: + G', L', regular + else: + let G'', L'', mode = E(G, L, body) + if mode is break: + G'', L'', regular + else: + G''', L''', mode = E(G'', L'', post) + E(G''', L''', for {} condition post body) + E(G, L, break: BreakContinue) = + G, L, break + E(G, L, continue: BreakContinue) = + G, L, continue + + E(G, L, : Identifier) = + G, L, regular, L[name] + E(G, L, : FunctionCall) = G1, L1, vn = E(G, L, argn) ... G(n-1), L(n-1), v2 = E(G(n-2), L(n-2), arg2) Gn, Ln, v1 = E(G(n-1), L(n-1), arg1) Let ret1, ..., retm block> be the function of name fname visible at the point of the call. - Let L' be a new local state such that + Let L' be a new local state such that L'[parami] = vi and L'[reti] = 0 for all i. Let G'', L'', rv1, ..., rvm = E(Gn, L', block) G'', Ln, rv1, ..., rvm -- cgit v1.2.3 From 29da069bf02e669c019d6fa136df994bc8ef6d72 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 21 Apr 2017 16:53:20 +0100 Subject: Do not require parentheses on function return values --- docs/julia.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index c0e0c97a..29970bd6 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -30,7 +30,7 @@ and ``mod`` are available either natively or as functions and computes exponenti .. code:: { - function power(base:u256, exponent:u256) -> (result:u256) + function power(base:u256, exponent:u256) -> result:u256 { switch exponent case 0:u256: { result := 1:u256 } @@ -51,7 +51,7 @@ and ``add`` to be available. .. code:: { - function power(base:u256, exponent:u256) -> (result:u256) + function power(base:u256, exponent:u256) -> result:u256 { result := 1:u256 for { let i := 0:u256 } lt(i, exponent) { i := add(i, 1:u256) } @@ -79,7 +79,7 @@ Grammar:: SubAssembly FunctionDefinition = 'function' Identifier '(' IdentifierList? ')' - ( '->' '(' IdentifierList ')' )? Block + ( '->' IdentifierList )? Block VariableDeclaration = 'let' IdentifierOrList ( ':=' Expression )? Assignment = @@ -250,10 +250,10 @@ JULIA has no support for implicit type conversion and therefore functions exists When converting a larger type to a shorter type a runtime exception can occur in case of an overflow. The following type conversion functions must be available: -- ``u32tobool(x:u32) -> (y:bool)`` -- ``booltou32(x:bool) -> (y:u32)`` -- ``u32tou64(x:u32) -> (y:u64)`` -- ``u64tou32(x:u64) -> (y:u32)`` +- ``u32tobool(x:u32) -> y:bool`` +- ``booltou32(x:bool) -> y:u32`` +- ``u32tou64(x:u32) -> y:u64`` +- ``u64tou32(x:u64) -> y:u32`` - etc. (TBD) Low-level Functions -- cgit v1.2.3 From 0493828916546d6fb19918c81e44eec402f7b56a Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 21 Apr 2017 16:58:55 +0100 Subject: Clarify literals --- docs/julia.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/julia.rst b/docs/julia.rst index 29970bd6..a1b1e678 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -143,7 +143,7 @@ one value. The ``continue`` and ``break`` statements can only be used inside loop bodies. The condition part of the for-loop has to evaluate to exactly one value. -Literals cannot be larger than 32 bytes. +Literals cannot be larger than the their type. The largest type defined is 256-bit wide. Formal Specification -------------------- -- cgit v1.2.3 From be470f107eac284ff599dd5b0632f396d86dd025 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 21 Apr 2017 17:04:27 +0100 Subject: Support types in grammar --- docs/julia.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index a1b1e678..988f1980 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -78,12 +78,12 @@ Grammar:: BreakContinue | SubAssembly FunctionDefinition = - 'function' Identifier '(' IdentifierList? ')' - ( '->' IdentifierList )? Block + 'function' Identifier '(' TypedIdentifierList? ')' + ( '->' TypedIdentifierList )? Block VariableDeclaration = - 'let' IdentifierOrList ( ':=' Expression )? + 'let' TypedIdentifierList ( ':=' Expression )? Assignment = - IdentifierOrList ':=' Expression + IdentifierList ':=' Expression Expression = FunctionCall | Identifier | Literal Switch = @@ -98,11 +98,11 @@ Grammar:: 'assembly' Identifier Block FunctionCall = Identifier '(' ( Expression ( ',' Expression )* )? ')' - IdentifierOrList = Identifier | '(' IdentifierList ')' Identifier = [a-zA-Z_$] [a-zA-Z_0-9]* IdentifierList = Identifier ( ',' Identifier)* + TypedIdentifierList = Identifier ':' Identifier ( ',' Identifier ':' Identifier )* Literal = - NumberLiteral | StringLiteral | HexLiteral + (NumberLiteral | StringLiteral | HexLiteral) ':' Identifier NumberLiteral = HexNumber | DecimalNumber HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'') StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' -- cgit v1.2.3 From b2f2b36b7c77859860a68aa6991a3768a680ad5f Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 21 Apr 2017 17:35:15 +0100 Subject: Remove subassembly --- docs/julia.rst | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index 988f1980..054f8627 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -75,8 +75,7 @@ Grammar:: Expression | Switch | ForLoop | - BreakContinue | - SubAssembly + BreakContinue FunctionDefinition = 'function' Identifier '(' TypedIdentifierList? ')' ( '->' TypedIdentifierList )? Block @@ -94,8 +93,6 @@ Grammar:: 'for' Block Expression Block Block BreakContinue = 'break' | 'continue' - SubAssembly = - 'assembly' Identifier Block FunctionCall = Identifier '(' ( Expression ( ',' Expression )* )? ')' Identifier = [a-zA-Z_$] [a-zA-Z_0-9]* -- cgit v1.2.3 From 29502f9d4476dc8c8b9ee66bfb9309cf209acbce Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 21 Apr 2017 17:35:44 +0100 Subject: Include type names in grammar --- docs/julia.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index 054f8627..a441b38b 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -97,9 +97,11 @@ Grammar:: Identifier '(' ( Expression ( ',' Expression )* )? ')' Identifier = [a-zA-Z_$] [a-zA-Z_0-9]* IdentifierList = Identifier ( ',' Identifier)* - TypedIdentifierList = Identifier ':' Identifier ( ',' Identifier ':' Identifier )* + TypeName = Identifier | BuiltinTypeName + BuiltinTypeName = 'bool' | [us] ( '8' | '32' | '64' | '128' | '256' ) + TypedIdentifierList = Identifier ':' TypeName ( ',' Identifier ':' TypeName )* Literal = - (NumberLiteral | StringLiteral | HexLiteral) ':' Identifier + (NumberLiteral | StringLiteral | HexLiteral) ':' TypeName NumberLiteral = HexNumber | DecimalNumber HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'') StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' -- cgit v1.2.3 From 15ca9870481e51f1923c0f41cbae449c14f4fc4d Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 21 Apr 2017 17:35:56 +0100 Subject: Include section for Julia objects --- docs/julia.rst | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/docs/julia.rst b/docs/julia.rst index a441b38b..5d6b8497 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -64,6 +64,8 @@ and ``add`` to be available. Specification of JULIA ====================== +JULIA code is described in this chapter. JULIA code is usually placed into a JULIA object, which is described in the following chapter. + Grammar:: Block = '{' Statement* '}' @@ -445,3 +447,69 @@ Backend: eWASM -------------- TBD + +Specification of JULIA Object +============================= + +Grammar:: + + TopLevelObject = 'object' '{' Code? ( Object | Data )* '}' + Object = 'object' StringLiteral '{' Code? ( Object | Data )* '}' + Code = 'code' Block + Data = 'data' StringLiteral HexLiteral + HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'') + StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' + +Above, ``Block`` refers to ``Block`` in the JULIA code grammar explained in the previous chapter. + +An example JULIA Object is shown below: + +..code:: + + // Code consists of a single object. A single "code" node is the code of the object. + // Every (other) named object or data section is serialized and + // made accessible to the special built-in functions datacopy / dataoffset / datasize + object { + code { + let size = datasize("runtime") + let offset = allocate(size) + // This will turn into a memory->memory copy for eWASM and + // a codecopy for EVM + datacopy(dataoffset("runtime"), offset, size) + // this is a constructor and the runtime code is returned + return(offset, size) + } + + data "Table2" hex"4123" + + object "runtime" { + code { + // runtime code + + let size = datasize("Contract2") + let offset = allocate(size) + // This will turn into a memory->memory copy for eWASM and + // a codecopy for EVM + datacopy(dataoffset("Contract2"), offset, size) + // constructor parameter is a single number 0x1234 + mstore(add(offset, size), 0x1234) + create(offset, add(size, 32)) + } + + // Embedded object. Use case is that the outside is a factory contract, + // and Contract2 is the code to be created by the factory + object "Contract2" { + code { + // code here ... + } + + object "runtime" { + code { + // code here ... + } + } + + data "Table1" hex"4123" + } + } + } -- cgit v1.2.3 From e9b08e029eb48fff9cd3b3287a88e1282395bfb4 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 21 Apr 2017 17:49:04 +0100 Subject: Restriction of switch --- docs/julia.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/julia.rst b/docs/julia.rst index 5d6b8497..7740bc70 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -90,7 +90,7 @@ Grammar:: Switch = 'switch' Expression Case* ( 'default' ':' Block )? Case = - 'case' Expression ':' Block + 'case' Literal ':' Block ForLoop = 'for' Block Expression Block Block BreakContinue = @@ -120,6 +120,11 @@ the block they are defined in (including all sub-nodes and sub-blocks). Shadowing is disallowed, i.e. you cannot declare an identifier at a point where another identifier with the same name is also visible. +Switches must have at least one (or the default) and at most one default case. +If all possible values of the expression is covered, the default case should +not be allowed (i.e. a switch with a ``bool`` expression and having both a +true and false case should not allow a default case). + In for-loops, identifiers declared in the first block (the init block) are visible in all other parts of the for loop (but not outside of the loop). Identifiers declared in the other parts of the for loop respect the regular -- cgit v1.2.3 From d9abe7b712a80aab26366f772cea15221184bcbf Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 19 May 2017 19:07:30 +0200 Subject: Require at least one case in switch --- docs/julia.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/julia.rst b/docs/julia.rst index 7740bc70..42a3b663 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -88,7 +88,7 @@ Grammar:: Expression = FunctionCall | Identifier | Literal Switch = - 'switch' Expression Case* ( 'default' ':' Block )? + 'switch' Expression Case+ ( 'default' ':' Block )? Case = 'case' Literal ':' Block ForLoop = -- cgit v1.2.3 From 0e0de7b7fdd83c0a458e51a4bf12b6444025c0f8 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 19 May 2017 23:15:07 +0100 Subject: Switch cases do not require colon --- docs/julia.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index 42a3b663..f67b3384 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -88,9 +88,9 @@ Grammar:: Expression = FunctionCall | Identifier | Literal Switch = - 'switch' Expression Case+ ( 'default' ':' Block )? + 'switch' Expression Case+ ( 'default' Block )? Case = - 'case' Literal ':' Block + 'case' Literal Block ForLoop = 'for' Block Expression Block Block BreakContinue = -- cgit v1.2.3 From 980ba42faf825a1e66fb4689fc1a7553e7b9ef87 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 19 May 2017 23:16:01 +0100 Subject: Remove from examples --- docs/julia.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index f67b3384..f97fb4b0 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -33,13 +33,13 @@ and ``mod`` are available either natively or as functions and computes exponenti function power(base:u256, exponent:u256) -> result:u256 { switch exponent - case 0:u256: { result := 1:u256 } - case 1:u256: { result := base } + case 0:u256 { result := 1:u256 } + case 1:u256 { result := base } default: { result := power(mul(base, base), div(exponent, 2:u256)) switch mod(exponent, 2:u256) - case 1:u256: { result := mul(base, result) } + case 1:u256 { result := mul(base, result) } } } } -- cgit v1.2.3 From 72f9e45b1a6d4a5163610a96869c8716bbfda74b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sat, 20 May 2017 00:05:14 +0100 Subject: Add true/false literals --- docs/julia.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/julia.rst b/docs/julia.rst index f97fb4b0..1bd67670 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -103,10 +103,12 @@ Grammar:: BuiltinTypeName = 'bool' | [us] ( '8' | '32' | '64' | '128' | '256' ) TypedIdentifierList = Identifier ':' TypeName ( ',' Identifier ':' TypeName )* Literal = - (NumberLiteral | StringLiteral | HexLiteral) ':' TypeName + (NumberLiteral | StringLiteral | HexLiteral | TrueLiteral | FalseLiteral) ':' TypeName NumberLiteral = HexNumber | DecimalNumber HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'') StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' + TrueLiteral = 'true' + FalseLiteral = 'false' HexNumber = '0x' [0-9a-fA-F]+ DecimalNumber = [0-9]+ -- cgit v1.2.3 From a67bd2fb6a68976710595ad2a1548ae266d84de4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 12 Jul 2017 16:52:26 +0200 Subject: Include JULIA section in the TOC. --- docs/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.rst b/docs/index.rst index 67fc87c2..afdab81f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -156,6 +156,7 @@ Contents using-the-compiler.rst metadata.rst abi-spec.rst + julia.rst style-guide.rst common-patterns.rst bugs.rst -- cgit v1.2.3 From 5eaef9e87ef14f0a22d192ebf1efd552ab97bbc7 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 12 Jul 2017 18:07:02 +0200 Subject: Typos and clarifications. --- docs/julia.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index 1bd67670..8812bab2 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -8,7 +8,8 @@ Joyfully Universal Language for (Inline) Assembly JULIA is an intermediate language that can compile to various different backends (EVM 1.0, EVM 1.5 and eWASM are planned). -Because of that, it is designed to be as featureless as possible. +Because of that, it is designed to be a usable common denominator of all three +platforms. It can already be used for "inline assembly" inside Solidity and future versions of the Solidity compiler will even use JULIA as intermediate language. It should also be easy to build high-level optimizer stages for JULIA. @@ -45,7 +46,7 @@ and ``mod`` are available either natively or as functions and computes exponenti } It is also possible to implement the same function using a for-loop -instead of recursion. Here, we need the EVM opcodes ``lt`` (less-than) +instead of with recursion. Here, we need the EVM opcodes ``lt`` (less-than) and ``add`` to be available. .. code:: @@ -115,7 +116,7 @@ Grammar:: Restrictions on the Grammar --------------------------- -Scopes in JULIA are tied to Blocks and all declarations +Scopes in JULIA are tied to Blocks (exceptions are functions and the for loop) and all declarations (``FunctionDefinition``, ``VariableDeclaration``) introduce new identifiers into these scopes. Identifiers are visible in the block they are defined in (including all sub-nodes and sub-blocks). @@ -164,7 +165,7 @@ state objects and new arguments. There is a global state object blockchain) and a local state object (the state of local variables, i.e. a segment of the stack in the EVM). -The the evaluation function E takes a global state, a local state and +The evaluation function E takes a global state, a local state and a node of the AST and returns a new global state, a new local state and a variable number of values. -- cgit v1.2.3 From c2f2b25064dd6710532e2246ae46c91bd8c24108 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 12 Jul 2017 20:09:13 +0200 Subject: Some clarifications. --- docs/julia.rst | 154 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 87 insertions(+), 67 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index 8812bab2..9f098880 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -89,7 +89,7 @@ Grammar:: Expression = FunctionCall | Identifier | Literal Switch = - 'switch' Expression Case+ ( 'default' Block )? + 'switch' Expression Case* ( 'default' Block )? Case = 'case' Literal Block ForLoop = @@ -116,134 +116,154 @@ Grammar:: Restrictions on the Grammar --------------------------- -Scopes in JULIA are tied to Blocks (exceptions are functions and the for loop) and all declarations -(``FunctionDefinition``, ``VariableDeclaration``) -introduce new identifiers into these scopes. Identifiers are visible in -the block they are defined in (including all sub-nodes and sub-blocks). -Shadowing is disallowed, i.e. you cannot declare an identifier at a point -where another identifier with the same name is also visible. - -Switches must have at least one (or the default) and at most one default case. +Switches must have at least one case (including the default case). If all possible values of the expression is covered, the default case should not be allowed (i.e. a switch with a ``bool`` expression and having both a true and false case should not allow a default case). -In for-loops, identifiers declared in the first block (the init block) -are visible in all other parts of the for loop (but not outside of the loop). -Identifiers declared in the other parts of the for loop respect the regular -syntatical scoping rules. - -Inside functions, it is not possible to access a variable that was declared -outside of that function. - -Every expression evaluates to zero or more values. Literals evaluate to exactly +Every expression evaluates to zero or more values. Identifiers and Literals +evaluate to exactly one value and function calls evaluate to a number of values equal to the -number of return values of the function called. An expression that is also -a statement is invalid if it evaluates to more than one value, i.e. at the -block-level, only expressions evaluating to zero values are allowed. +number of return values of the function called. -For variable declarations and assignments, the right-hand-side expression +In variable declarations and assignments, the right-hand-side expression (if present) has to evaluate to a number of values equal to the number of variables on the left-hand-side. +This is the only situation where an expression evaluating +to more than one value is allowed. -An expression used as an argument to a function call has to evaluate to exactly -one value. +Expressions that are also statements (i.e. at the block level) have to +evaluate to zero values. -The ``continue`` and ``break`` statements can only be used inside loop bodies. +In all other situations, expressions have to evaluate to exactly one value. + +The ``continue`` and ``break`` statements can only be used inside loop bodies +and have to be in the same function as the loop (or both have to be at the +top level). The condition part of the for-loop has to evaluate to exactly one value. Literals cannot be larger than the their type. The largest type defined is 256-bit wide. +Scoping Rules +------------- + +Scopes in JULIA are tied to Blocks (exceptions are functions and the for loop +as explained below) and all declarations +(``FunctionDefinition``, ``VariableDeclaration``) +introduce new identifiers into these scopes. + +Identifiers are visible in +the block they are defined in (including all sub-nodes and sub-blocks). +As an exception, identifiers defined in the "init" part of the for-loop +(the first block) are visible in all other parts of the for-loop +(but not outside of the loop). +Identifiers declared in the other parts of the for loop respect the regular +syntatical scoping rules. +The parameters and return parameters of functions are visible in the +function body and their names cannot overlap. + +Variables can only be referenced after their declaration. In particular, +variables cannot be referenced in the right hand side of their own variable +declaration. +Functions can be referenced already before their declaration (if they are visible). + +Shadowing is disallowed, i.e. you cannot declare an identifier at a point +where another identifier with the same name is also visible, even if it is +not accessible. + +Inside functions, it is not possible to access a variable that was declared +outside of that function. + Formal Specification -------------------- We formally specify JULIA by providing an evaluation function E overloaded on the various nodes of the AST. Any functions can have side effects, so -E takes a state objects and the actual argument and also returns new -state objects and new arguments. There is a global state object +E takes two state objects and the AST node and returns two new +state objects and a variable number of other values. +The two state objects are the global state object (which in the context of the EVM is the memory, storage and state of the -blockchain) and a local state object (the state of local variables, i.e. a +blockchain) and the local state object (the state of local variables, i.e. a segment of the stack in the EVM). +If the AST node is a statement, E returns the two state objects and a "mode", +which is used for the ``break`` and ``continue`` statements. +If the AST node is an expression, E returns the two state objects and +as many values as the expression evaluates to. -The evaluation function E takes a global state, a local state and -a node of the AST and returns a new global state, a new local state -and a variable number of values. The exact nature of the global state is unspecified for this high level -description. The local state `L` is a mapping of identifiers `i` to values `v`, -denoted as `L[i] = v`. -The special value `⊥` is used to signify that a variable cannot be -used yet. +description. The local state ``L`` is a mapping of identifiers ``i`` to values ``v``, +denoted as ``L[i] = v``. + +For an identifier ``v``, let ``$v`` be the name of the identifier. + +We will use a destructuring notation for the AST nodes. .. code:: E(G, L, <{St1, ..., Stn}>: Block) = - let L' be an extension of L to all variables v declared in Block - (but not in its sub-blocks), such that L'[v] = ⊥. - let Gi, Li, mode = E(G, L', St1, ..., Stn) - let L'' be a restriction of Li to the identifiers of L - Gi, L'', mode + let G1, L1, mode = E(G, L, St1, ..., Stn) + let L2 be a restriction of L1 to the identifiers of L + G1, L2, mode E(G, L, St1, ..., Stn: Statement) = if n is zero: G, L else: - let G', L', mode = E(G, L, St1) + let G1, L1, mode = E(G, L, St1) if mode is regular then - E(G', L', St2, ..., Stn) + E(G1, L1, St2, ..., Stn) otherwise - G', L', mode - E(G, L, (ret1, ..., retm) block>: FunctionDefinition) = + G1, L1, mode + E(G, L, FunctionDefinition) = G, L, regular E(G, L, : VariableDeclaration) = E(G, L, : Assignment) E(G, L, : VariableDeclaration) = - let L' be a copy of L where L'[vi] = 0 for i = 1, ..., n - G, L', regular + let L1 be a copy of L where L1[$vari] = 0 for i = 1, ..., n + G, L1, regular E(G, L, : Assignment) = - let G', L', v1, ..., vn = E(G, L, rhs) - let L'' be a copy of L' where L'[vi] = vi for i = 1, ..., n - G, L'', regular + let G1, L1, v1, ..., vn = E(G, L, rhs) + let L2 be a copy of L1 where L2[$vari] = vi for i = 1, ..., n + G, L2, regular E(G, L, : ForLoop) = if n >= 1: - let L' be an extension of L to all variables v declared in i1, ..., in - (but not in sub-blocks), such that L'[v] = ⊥. - let G'', L'', mode = E(G, L', i1, ..., in) - explode if mode is not regular - let G''', L''', mode = E(G'', L'', for {} condition post body) - explode if mode is not regular - let Lend be the restriction of L''' to only variables of L - G''', Lend + let G1, L1, mode = E(G, L, i1, ..., in) + // mode has to be regular due to the syntactic restrictions + let G2, L2, mode = E(G1, L1, for {} condition post body) + // mode has to be regular due to the syntactic restrictions + let L3 be the restriction of L2 to only variables of L + G2, L3 else: - let G', L', v = E(G, L, condition) + let G1, L1, v = E(G, L, condition) if v is false: - G', L', regular + G1, L1, regular else: - let G'', L'', mode = E(G, L, body) + let G2, L2, mode = E(G1, L, body) if mode is break: - G'', L'', regular + G2, L2, regular else: - G''', L''', mode = E(G'', L'', post) - E(G''', L''', for {} condition post body) + G3, L3, mode = E(G2, L2, post) + E(G3, L3, for {} condition post body) E(G, L, break: BreakContinue) = G, L, break E(G, L, continue: BreakContinue) = G, L, continue E(G, L, : Identifier) = - G, L, regular, L[name] + G, L, regular, L[$name] E(G, L, : FunctionCall) = G1, L1, vn = E(G, L, argn) ... G(n-1), L(n-1), v2 = E(G(n-2), L(n-2), arg2) Gn, Ln, v1 = E(G(n-1), L(n-1), arg1) Let ret1, ..., retm block> - be the function of name fname visible at the point of the call. + be the function of name $fname visible at the point of the call. Let L' be a new local state such that - L'[parami] = vi and L'[reti] = 0 for all i. + L'[$parami] = vi and L'[$reti] = 0 for all i. Let G'', L'', rv1, ..., rvm = E(Gn, L', block) G'', Ln, rv1, ..., rvm E(G, L, l: HexLiteral) = G, L, hexString(l), - where hexString decodes l from hex and left-aligns in into 32 bytes + where hexString decodes l from hex and left-aligns it into 32 bytes E(G, L, l: StringLiteral) = G, L, utf8EncodeLeftAligned(l), where utf8EncodeLeftAligned performs a utf8 encoding of l and aligns it left into 32 bytes -- cgit v1.2.3 From d4452d8c1fc5b3a564329097fa72e66798946a9b Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 7 Aug 2017 16:28:10 +0200 Subject: Small fixes suggested by @mrsmkl --- docs/julia.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index 9f098880..2f0b7d28 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -207,7 +207,7 @@ We will use a destructuring notation for the AST nodes. G1, L2, mode E(G, L, St1, ..., Stn: Statement) = if n is zero: - G, L + G, L, regular else: let G1, L1, mode = E(G, L, St1) if mode is regular then @@ -232,7 +232,7 @@ We will use a destructuring notation for the AST nodes. let G2, L2, mode = E(G1, L1, for {} condition post body) // mode has to be regular due to the syntactic restrictions let L3 be the restriction of L2 to only variables of L - G2, L3 + G2, L3, regular else: let G1, L1, v = E(G, L, condition) if v is false: @@ -250,7 +250,7 @@ We will use a destructuring notation for the AST nodes. G, L, continue E(G, L, : Identifier) = - G, L, regular, L[$name] + G, L, L[$name] E(G, L, : FunctionCall) = G1, L1, vn = E(G, L, argn) ... @@ -260,8 +260,8 @@ We will use a destructuring notation for the AST nodes. be the function of name $fname visible at the point of the call. Let L' be a new local state such that L'[$parami] = vi and L'[$reti] = 0 for all i. - Let G'', L'', rv1, ..., rvm = E(Gn, L', block) - G'', Ln, rv1, ..., rvm + Let G'', L'', mode = E(Gn, L', block) + G'', Ln, L''[$ret1], ..., L''[$retm] E(G, L, l: HexLiteral) = G, L, hexString(l), where hexString decodes l from hex and left-aligns it into 32 bytes E(G, L, l: StringLiteral) = G, L, utf8EncodeLeftAligned(l), -- cgit v1.2.3 From 16f81871669f205e11d7965a989b6f467f80c5d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Tue, 15 Aug 2017 22:15:43 +0300 Subject: added Switch specification --- docs/julia.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/julia.rst b/docs/julia.rst index 2f0b7d28..c10e3d29 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -248,6 +248,19 @@ We will use a destructuring notation for the AST nodes. G, L, break E(G, L, continue: BreakContinue) = G, L, continue + E(G, L, : Switch) = + E(G, L, switch condition case l1:t1 st1 ... case ln:tn stn default {}) = + E(G, L, : Switch) = + let G0, L0, v = E(G, L, condition) + // i = 1 .. n + // Evaluate literals, context doesn't matter + let G0, L0, v1 = E(G0, L0, l1) + ... + let G0, L0, vn = E(G0, L0, ln) + if there exists smallest i such that vi = v: + E(G0, L0, sti) + else: + E(G0, L0, st') E(G, L, : Identifier) = G, L, L[$name] -- cgit v1.2.3 From a4310fec55fa522eef6eee3bc27fad15bfafbc53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sami=20M=C3=A4kel=C3=A4?= Date: Wed, 16 Aug 2017 13:29:12 +0300 Subject: avoid reassigning variables --- docs/julia.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index c10e3d29..cf798363 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -254,9 +254,9 @@ We will use a destructuring notation for the AST nodes. let G0, L0, v = E(G, L, condition) // i = 1 .. n // Evaluate literals, context doesn't matter - let G0, L0, v1 = E(G0, L0, l1) + let _, _, v1 = E(G0, L0, l1) ... - let G0, L0, vn = E(G0, L0, ln) + let _, _, vn = E(G0, L0, ln) if there exists smallest i such that vi = v: E(G0, L0, sti) else: -- cgit v1.2.3 From 23379e10614cccf9126fca09781a1d2dcdfede90 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sun, 29 Oct 2017 13:28:42 +0000 Subject: Ensure each code snippet in the docs can be extracted for tests --- docs/abi-spec.rst | 26 ++++++------ docs/contracts.rst | 40 ++++++------------- docs/control-structures.rst | 20 ++++++---- docs/security-considerations.rst | 50 ++++++++++++----------- docs/structure-of-a-contract.rst | 86 ++++++++++++++++++++-------------------- 5 files changed, 107 insertions(+), 115 deletions(-) diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index 77d15026..8bd6d1a7 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -330,15 +330,15 @@ For example, :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.0; - contract Test { - function Test(){ b = 0x12345678901234567890123456789012; } - event Event(uint indexed a, bytes32 b); - event Event2(uint indexed a, bytes32 b); - function foo(uint a) { Event(a, b); } - bytes32 b; - } + contract Test { + function Test(){ b = 0x12345678901234567890123456789012; } + event Event(uint indexed a, bytes32 b); + event Event2(uint indexed a, bytes32 b); + function foo(uint a) { Event(a, b); } + bytes32 b; + } would result in the JSON: @@ -377,11 +377,11 @@ As an example, the code :: - contract Test { - struct S { uint a; uint[] b; T[] c; } - struct T { uint x; uint y; } - function f(S s, T t, uint a) { } - } + contract Test { + struct S { uint a; uint[] b; T[] c; } + struct T { uint x; uint y; } + function f(S s, T t, uint a) { } + } would result in the JSON: diff --git a/docs/contracts.rst b/docs/contracts.rst index cdc92315..ef1e1be6 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -198,7 +198,6 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value function compute(uint a, uint b) internal returns (uint) { return a+b; } } - contract D { function readData() { C c = new C(); @@ -209,7 +208,6 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value } } - contract E is C { function g() { C c = new C(); @@ -238,7 +236,6 @@ be done at declaration. uint public data = 42; } - contract Caller { C c = new C(); function f() { @@ -321,7 +318,6 @@ inheritable properties of contracts and may be overridden by derived contracts. } } - contract mortal is owned { // This contract inherits the "onlyOwner"-modifier from // "owned" and applies it to the "close"-function, which @@ -332,7 +328,6 @@ inheritable properties of contracts and may be overridden by derived contracts. } } - contract priced { // Modifiers can receive arguments: modifier costs(uint price) { @@ -342,7 +337,6 @@ inheritable properties of contracts and may be overridden by derived contracts. } } - contract Register is priced, owned { mapping (address => bool) registeredAddresses; uint price; @@ -570,7 +564,6 @@ Please ensure you test your fallback function thoroughly to ensure the execution function() payable { } } - contract Caller { function callTest(Test test) { test.call(0xabcdef01); // hash does not exist @@ -687,12 +680,19 @@ as topics. The event call above can be performed in the same way as :: - log3( - msg.value, - 0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20, - msg.sender, - _id - ); + pragma solidity ^0.4.10; + + contract C { + function f() { + bytes32 _id = 0x420042; + log3( + bytes32(msg.value), + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(msg.sender), + _id + ); + } + } where the long hexadecimal number is equal to ``keccak256("Deposit(address,hash256,uint256)")``, the signature of the event. @@ -734,7 +734,6 @@ Details are given in the following example. address owner; } - // Use "is" to derive from another contract. Derived // contracts can access all non-private members including // internal functions and state variables. These cannot be @@ -745,7 +744,6 @@ Details are given in the following example. } } - // These abstract contracts are only provided to make the // interface known to the compiler. Note the function // without body. If a contract does not implement all @@ -754,13 +752,11 @@ Details are given in the following example. function lookup(uint id) returns (address adr); } - contract NameReg { function register(bytes32 name); function unregister(); } - // Multiple inheritance is possible. Note that "owned" is // also a base class of "mortal", yet there is only a single // instance of "owned" (as for virtual inheritance in C++). @@ -786,7 +782,6 @@ Details are given in the following example. } } - // If a constructor takes an argument, it needs to be // provided in the header (or modifier-invocation-style at // the constructor of the derived contract (see below)). @@ -821,12 +816,10 @@ seen in the following example:: function kill() { /* do cleanup 1 */ mortal.kill(); } } - contract Base2 is mortal { function kill() { /* do cleanup 2 */ mortal.kill(); } } - contract Final is Base1, Base2 { } @@ -848,7 +841,6 @@ derived override, but this function will bypass } } - contract Base1 is mortal { function kill() { /* do cleanup 1 */ super.kill(); } } @@ -858,7 +850,6 @@ derived override, but this function will bypass function kill() { /* do cleanup 2 */ super.kill(); } } - contract Final is Base2, Base1 { } @@ -888,7 +879,6 @@ the base constructors. This can be done in two ways:: function Base(uint _x) { x = _x; } } - contract Derived is Base(7) { function Derived(uint _y) Base(_y * _y) { } @@ -1081,7 +1071,6 @@ more advanced example to implement a set). } } - contract C { Set.Data knownValues; @@ -1157,7 +1146,6 @@ custom types without the overhead of external function calls: } } - contract C { using BigInt for BigInt.bigint; @@ -1250,7 +1238,6 @@ Let us rewrite the set example from the } } - contract C { using Set for Set.Data; // this is the crucial change Set.Data knownValues; @@ -1276,7 +1263,6 @@ It is also possible to extend elementary types in that way:: } } - contract C { using Search for uint[]; uint[] data; diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 0497365b..371c5b92 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -363,15 +363,19 @@ As a result, the following code is illegal and cause the compiler to throw an er In addition to this, if a variable is declared, it will be initialized at the beginning of the function to its default value. As a result, the following code is legal, despite being poorly written:: - function foo() returns (uint) { - // baz is implicitly initialized as 0 - uint bar = 5; - if (true) { - bar += baz; - } else { - uint baz = 10;// never executes + pragma solidity ^0.4.0; + + contract C { + function foo() returns (uint) { + // baz is implicitly initialized as 0 + uint bar = 5; + if (true) { + bar += baz; + } else { + uint baz = 10;// never executes + } + return bar;// returns 5 } - return bar;// returns 5 } .. index:: ! exception, ! throw, ! assert, ! require, ! revert diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index 6586cb5f..337a3d3f 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -55,18 +55,18 @@ complete contract): :: - pragma solidity ^0.4.0; - - // THIS CONTRACT CONTAINS A BUG - DO NOT USE - contract Fund { - /// Mapping of ether shares of the contract. - mapping(address => uint) shares; - /// Withdraw your share. - function withdraw() { - if (msg.sender.send(shares[msg.sender])) - shares[msg.sender] = 0; - } - } + pragma solidity ^0.4.0; + + // THIS CONTRACT CONTAINS A BUG - DO NOT USE + contract Fund { + /// Mapping of ether shares of the contract. + mapping(address => uint) shares; + /// Withdraw your share. + function withdraw() { + if (msg.sender.send(shares[msg.sender])) + shares[msg.sender] = 0; + } + } The problem is not too serious here because of the limited gas as part of ``send``, but it still exposes a weakness: Ether transfer always @@ -79,18 +79,18 @@ outlined further below: :: - pragma solidity ^0.4.11; + pragma solidity ^0.4.11; - contract Fund { - /// Mapping of ether shares of the contract. - mapping(address => uint) shares; - /// Withdraw your share. - function withdraw() { - var share = shares[msg.sender]; - shares[msg.sender] = 0; - msg.sender.transfer(share); - } - } + contract Fund { + /// Mapping of ether shares of the contract. + mapping(address => uint) shares; + /// Withdraw your share. + function withdraw() { + var share = shares[msg.sender]; + shares[msg.sender] = 0; + msg.sender.transfer(share); + } + } 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 @@ -179,7 +179,9 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like } } -Now someone tricks you into sending ether to the address of this attack wallet:: +Now someone tricks you into sending ether to the address of this attack wallet: + +:: pragma solidity ^0.4.11; diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst index 224eb368..0b554800 100644 --- a/docs/structure-of-a-contract.rst +++ b/docs/structure-of-a-contract.rst @@ -20,12 +20,12 @@ State variables are values which are permanently stored in contract storage. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.0; - contract SimpleStorage { - uint storedData; // State variable - // ... - } + contract SimpleStorage { + uint storedData; // State variable + // ... + } See the :ref:`types` section for valid state variable types and :ref:`visibility-and-getters` for possible choices for @@ -40,13 +40,13 @@ Functions are the executable units of code within a contract. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.0; - contract SimpleAuction { - function bid() payable { // Function - // ... - } - } + contract SimpleAuction { + function bid() payable { // Function + // ... + } + } :ref:`function-calls` can happen internally or externally and have different levels of visibility (:ref:`visibility-and-getters`) @@ -62,20 +62,20 @@ Function modifiers can be used to amend the semantics of functions in a declarat :: - pragma solidity ^0.4.11; + pragma solidity ^0.4.11; - contract Purchase { - address public seller; + contract Purchase { + address public seller; - modifier onlySeller() { // Modifier - require(msg.sender == seller); - _; - } + modifier onlySeller() { // Modifier + require(msg.sender == seller); + _; + } - function abort() onlySeller { // Modifier usage - // ... - } - } + function abort() onlySeller { // Modifier usage + // ... + } + } .. _structure-events: @@ -86,16 +86,16 @@ Events are convenience interfaces with the EVM logging facilities. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.0; - contract SimpleAuction { - event HighestBidIncreased(address bidder, uint amount); // Event + contract SimpleAuction { + event HighestBidIncreased(address bidder, uint amount); // Event - function bid() payable { - // ... - HighestBidIncreased(msg.sender, msg.value); // Triggering event - } - } + function bid() payable { + // ... + HighestBidIncreased(msg.sender, msg.value); // Triggering event + } + } See :ref:`events` in contracts section for information on how events are declared and can be used from within a dapp. @@ -110,16 +110,16 @@ Structs are custom defined types that can group several variables (see :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.0; - contract Ballot { - struct Voter { // Struct - uint weight; - bool voted; - address delegate; - uint vote; - } - } + contract Ballot { + struct Voter { // Struct + uint weight; + bool voted; + address delegate; + uint vote; + } + } .. _structure-enum-types: @@ -131,8 +131,8 @@ Enums can be used to create custom types with a finite set of values (see :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.0; - contract Purchase { - enum State { Created, Locked, Inactive } // Enum - } + contract Purchase { + enum State { Created, Locked, Inactive } // Enum + } -- cgit v1.2.3 From 744dea60a40e7e5cb25814c3f6f6eb01dc767698 Mon Sep 17 00:00:00 2001 From: Ezra Epstein Date: Fri, 3 Nov 2017 18:19:59 -0700 Subject: had "names" when "parameters" was meant --- docs/control-structures.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 371c5b92..bcb597cf 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -194,7 +194,7 @@ Omitted Function Parameter Names -------------------------------- The names of unused parameters (especially return parameters) can be omitted. -Those names will still be present on the stack, but they are inaccessible. +Those parameters will still be present on the stack, but they are inaccessible. :: -- cgit v1.2.3 From 88e1b68aafa72ad653d9d72d2c57cd4ed7d75e82 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 22 Nov 2017 10:57:52 +0100 Subject: More explanation about the packed encoding. --- docs/abi-spec.rst | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index 8bd6d1a7..c93ce25b 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -451,13 +451,18 @@ Non-standard Packed Mode Solidity supports a non-standard packed mode where: - no :ref:`function selector ` is encoded, -- short types are not zero padded and +- types shorter than 32 bytes are neither zero padded nor sign extended and - dynamic types are encoded in-place and without the length. -As an example encoding ``uint1, bytes1, uint8, string`` with values ``1, 0x42, 0x2424, "Hello, world!"`` results in :: +As an example encoding ``int1, bytes1, uint16, string`` with values ``-1, 0x42, 0x2424, "Hello, world!"`` results in :: - 0x0142242448656c6c6f2c20776f726c6421 - ^^ uint1(1) + 0xff42242448656c6c6f2c20776f726c6421 + ^^ int1(-1) ^^ bytes1(0x42) - ^^^^ uint8(0x2424) + ^^^^ uint16(0x2424) ^^^^^^^^^^^^^^^^^^^^^^^^^^ string("Hello, world!") without a length field + +More specifically, each statically-sized type takes as many bytes as its range has +and dynamically-sized types like ``string``, ``bytes`` or ``uint[]`` are encoded without +their length field. This means that the encoding is ambiguous as soon as there are two +dynamically-sized elements. -- cgit v1.2.3 From 4876c58f91336918cc6d6520ace6ee8fdedc834c Mon Sep 17 00:00:00 2001 From: Kwang Yul Seo Date: Wed, 22 Nov 2017 23:06:12 +0900 Subject: success(text) -> CHECK_SUCCESS(text) success(text) does not assert the result. Use CHECK_SUCCESS macro. --- test/libsolidity/SolidityNameAndTypeResolution.cpp | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 73c1660e..4f576e51 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -1038,7 +1038,7 @@ BOOST_AUTO_TEST_CASE(function_modifier_double_invocation) modifier mod(uint a) { if (a > 0) _; } } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(base_constructor_double_invocation) @@ -5690,7 +5690,7 @@ BOOST_AUTO_TEST_CASE(constructible_internal_constructor) function D() public { } } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(return_structs) @@ -5703,7 +5703,7 @@ BOOST_AUTO_TEST_CASE(return_structs) } } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(return_recursive_structs) @@ -5753,7 +5753,7 @@ BOOST_AUTO_TEST_CASE(address_checksum_type_deduction) } } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(invalid_address_checksum) @@ -5896,7 +5896,7 @@ BOOST_AUTO_TEST_CASE(interface) interface I { } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(interface_constructor) @@ -5917,7 +5917,7 @@ BOOST_AUTO_TEST_CASE(interface_functions) function f(); } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(interface_function_bodies) @@ -5939,7 +5939,7 @@ BOOST_AUTO_TEST_CASE(interface_function_external) function f() external; } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(interface_function_public) @@ -5980,7 +5980,7 @@ BOOST_AUTO_TEST_CASE(interface_events) event E(); } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(interface_inheritance) @@ -6023,7 +6023,7 @@ BOOST_AUTO_TEST_CASE(interface_function_parameters) function f(uint a) public returns (bool); } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(interface_enums) @@ -6047,7 +6047,7 @@ BOOST_AUTO_TEST_CASE(using_interface) } } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(using_interface_complex) @@ -6064,7 +6064,7 @@ BOOST_AUTO_TEST_CASE(using_interface_complex) } } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(warn_about_throw) @@ -6123,7 +6123,7 @@ BOOST_AUTO_TEST_CASE(pure_statement_check_for_regular_for_loop) } } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies) @@ -6239,7 +6239,7 @@ BOOST_AUTO_TEST_CASE(warn_unused_function_parameter) } } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(warn_unused_return_parameter) -- cgit v1.2.3 From 762d591a4755fbabfe8d2556bab10b77ec1d5bb5 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 22 Nov 2017 15:20:26 +0100 Subject: Introduce sorts for smt expressions. --- libsolidity/formal/SMTLib2Interface.cpp | 11 +----- libsolidity/formal/SMTLib2Interface.h | 8 ---- libsolidity/formal/SolverInterface.h | 66 +++++++++++++++++---------------- 3 files changed, 37 insertions(+), 48 deletions(-) diff --git a/libsolidity/formal/SMTLib2Interface.cpp b/libsolidity/formal/SMTLib2Interface.cpp index c627057a..0e00665a 100644 --- a/libsolidity/formal/SMTLib2Interface.cpp +++ b/libsolidity/formal/SMTLib2Interface.cpp @@ -64,8 +64,6 @@ void SMTLib2Interface::pop() Expression SMTLib2Interface::newFunction(string _name, Sort _domain, Sort _codomain) { - solAssert(!m_variables.count(_name), ""); - m_variables[_name] = SMTVariableType::Function; write( "(declare-fun |" + _name + @@ -80,16 +78,12 @@ Expression SMTLib2Interface::newFunction(string _name, Sort _domain, Sort _codom Expression SMTLib2Interface::newInteger(string _name) { - solAssert(!m_variables.count(_name), ""); - m_variables[_name] = SMTVariableType::Integer; write("(declare-const |" + _name + "| Int)"); return SolverInterface::newInteger(move(_name)); } Expression SMTLib2Interface::newBool(string _name) { - solAssert(!m_variables.count(_name), ""); - m_variables[_name] = SMTVariableType::Bool; write("(declare-const |" + _name + "| Bool)"); return SolverInterface::newBool(std::move(_name)); } @@ -151,9 +145,8 @@ string SMTLib2Interface::checkSatAndGetValuesCommand(vector const& _ for (size_t i = 0; i < _expressionsToEvaluate.size(); i++) { auto const& e = _expressionsToEvaluate.at(i); - solAssert(m_variables.count(e.name), ""); - solAssert(m_variables[e.name] == SMTVariableType::Integer, ""); - command += "(declare-const |EVALEXPR_" + to_string(i) + "| Int)\n"; + solAssert(e.sort == Sort::Int || e.sort == Sort::Bool, "Invalid sort for expression to evaluate."); + command += "(declare-const |EVALEXPR_" + to_string(i) + "| " + (e.sort == Sort::Int ? "Int" : "Bool") + "\n"; command += "(assert (= |EVALEXPR_" + to_string(i) + "| " + toSExpr(e) + "))\n"; } command += "(check-sat)\n"; diff --git a/libsolidity/formal/SMTLib2Interface.h b/libsolidity/formal/SMTLib2Interface.h index e827449f..63188acd 100644 --- a/libsolidity/formal/SMTLib2Interface.h +++ b/libsolidity/formal/SMTLib2Interface.h @@ -68,14 +68,6 @@ private: ReadCallback::Callback m_queryCallback; std::vector m_accumulatedOutput; - - enum class SMTVariableType { - Function, - Integer, - Bool - }; - - std::map m_variables; }; } diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index a69d19d5..ac722dad 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -44,7 +44,7 @@ enum class CheckResult enum class Sort { - Int, Bool + Int, Bool, IntIntFun }; /// C++ representation of an SMTLIB2 expression. @@ -52,10 +52,10 @@ class Expression { friend class SolverInterface; public: - explicit Expression(bool _v): name(_v ? "true" : "false") {} - Expression(size_t _number): name(std::to_string(_number)) {} - Expression(u256 const& _number): name(_number.str()) {} - Expression(bigint const& _number): name(_number.str()) {} + explicit Expression(bool _v): name(_v ? "true" : "false"), sort(Sort::Bool) {} + Expression(size_t _number): name(std::to_string(_number)), sort(Sort::Int) {} + Expression(u256 const& _number): name(_number.str()), sort(Sort::Int) {} + Expression(bigint const& _number): name(_number.str()), sort(Sort::Int) {} Expression(Expression const&) = default; Expression(Expression&&) = default; @@ -64,26 +64,27 @@ public: static Expression ite(Expression _condition, Expression _trueValue, Expression _falseValue) { + solAssert(_trueValue.sort == _falseValue.sort, ""); return Expression("ite", std::vector{ std::move(_condition), std::move(_trueValue), std::move(_falseValue) - }); + }, _trueValue.sort); } friend Expression operator!(Expression _a) { - return Expression("not", std::move(_a)); + return Expression("not", std::move(_a), Sort::Bool); } friend Expression operator&&(Expression _a, Expression _b) { - return Expression("and", std::move(_a), std::move(_b)); + return Expression("and", std::move(_a), std::move(_b), Sort::Bool); } friend Expression operator||(Expression _a, Expression _b) { - return Expression("or", std::move(_a), std::move(_b)); + return Expression("or", std::move(_a), std::move(_b), Sort::Bool); } friend Expression operator==(Expression _a, Expression _b) { - return Expression("=", std::move(_a), std::move(_b)); + return Expression("=", std::move(_a), std::move(_b), Sort::Bool); } friend Expression operator!=(Expression _a, Expression _b) { @@ -91,52 +92,54 @@ public: } friend Expression operator<(Expression _a, Expression _b) { - return Expression("<", std::move(_a), std::move(_b)); + return Expression("<", std::move(_a), std::move(_b), Sort::Bool); } friend Expression operator<=(Expression _a, Expression _b) { - return Expression("<=", std::move(_a), std::move(_b)); + return Expression("<=", std::move(_a), std::move(_b), Sort::Bool); } friend Expression operator>(Expression _a, Expression _b) { - return Expression(">", std::move(_a), std::move(_b)); + return Expression(">", std::move(_a), std::move(_b), Sort::Bool); } friend Expression operator>=(Expression _a, Expression _b) { - return Expression(">=", std::move(_a), std::move(_b)); + return Expression(">=", std::move(_a), std::move(_b), Sort::Bool); } friend Expression operator+(Expression _a, Expression _b) { - return Expression("+", std::move(_a), std::move(_b)); + return Expression("+", std::move(_a), std::move(_b), Sort::Int); } friend Expression operator-(Expression _a, Expression _b) { - return Expression("-", std::move(_a), std::move(_b)); + return Expression("-", std::move(_a), std::move(_b), Sort::Int); } friend Expression operator*(Expression _a, Expression _b) { - return Expression("*", std::move(_a), std::move(_b)); + return Expression("*", std::move(_a), std::move(_b), Sort::Int); } Expression operator()(Expression _a) const { + solAssert(sort == Sort::IntIntFun, "Attempted function application to non-function."); solAssert(arguments.empty(), "Attempted function application to non-function."); - return Expression(name, _a); + return Expression(name, _a, Sort::Int); } std::string const name; std::vector const arguments; + Sort sort; private: /// Manual constructor, should only be used by SolverInterface and this class itself. - Expression(std::string _name, std::vector _arguments): - name(std::move(_name)), arguments(std::move(_arguments)) {} - - explicit Expression(std::string _name): - Expression(std::move(_name), std::vector{}) {} - Expression(std::string _name, Expression _arg): - Expression(std::move(_name), std::vector{std::move(_arg)}) {} - Expression(std::string _name, Expression _arg1, Expression _arg2): - Expression(std::move(_name), std::vector{std::move(_arg1), std::move(_arg2)}) {} + Expression(std::string _name, std::vector _arguments, Sort _sort): + name(std::move(_name)), arguments(std::move(_arguments)), sort(_sort) {} + + explicit Expression(std::string _name, Sort _sort): + Expression(std::move(_name), std::vector{}, _sort) {} + Expression(std::string _name, Expression _arg, Sort _sort): + Expression(std::move(_name), std::vector{std::move(_arg)}, _sort) {} + Expression(std::string _name, Expression _arg1, Expression _arg2, Sort _sort): + Expression(std::move(_name), std::vector{std::move(_arg1), std::move(_arg2)}, _sort) {} }; DEV_SIMPLE_EXCEPTION(SolverError); @@ -149,20 +152,21 @@ public: virtual void push() = 0; virtual void pop() = 0; - virtual Expression newFunction(std::string _name, Sort /*_domain*/, Sort /*_codomain*/) + virtual Expression newFunction(std::string _name, Sort _domain, Sort _codomain) { + solAssert(_domain == Sort::Int && _codomain == Sort::Int, "Function sort not supported."); // Subclasses should do something here - return Expression(std::move(_name), {}); + return Expression(std::move(_name), {}, Sort::IntIntFun); } virtual Expression newInteger(std::string _name) { // Subclasses should do something here - return Expression(std::move(_name), {}); + return Expression(std::move(_name), {}, Sort::Int); } virtual Expression newBool(std::string _name) { // Subclasses should do something here - return Expression(std::move(_name), {}); + return Expression(std::move(_name), {}, Sort::Bool); } virtual void addAssertion(Expression const& _expr) = 0; -- cgit v1.2.3 From 6dbc34e16ee8bda0e156ccb20a3fb8cb6ff52c92 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 21 Nov 2017 13:36:41 +0100 Subject: If statement for Iulia / inline assembly. --- Changelog.md | 1 + docs/assembly.rst | 24 +++++++++++++++++++++--- libjulia/backends/evm/EVMCodeTransform.cpp | 13 +++++++++++++ libjulia/backends/evm/EVMCodeTransform.h | 1 + libsolidity/analysis/ViewPureChecker.cpp | 5 +++++ libsolidity/inlineasm/AsmAnalysis.cpp | 16 ++++++++++++++++ libsolidity/inlineasm/AsmAnalysis.h | 1 + libsolidity/inlineasm/AsmData.h | 2 ++ libsolidity/inlineasm/AsmDataForward.h | 3 ++- libsolidity/inlineasm/AsmParser.cpp | 12 +++++++++++- libsolidity/inlineasm/AsmPrinter.cpp | 5 +++++ libsolidity/inlineasm/AsmPrinter.h | 1 + libsolidity/inlineasm/AsmScopeFiller.cpp | 5 +++++ libsolidity/inlineasm/AsmScopeFiller.h | 1 + test/libsolidity/InlineAssembly.cpp | 17 ++++++++++++++++- test/libsolidity/SolidityEndToEndTest.cpp | 18 ++++++++++++++++++ 16 files changed, 119 insertions(+), 6 deletions(-) diff --git a/Changelog.md b/Changelog.md index a362138e..27e72838 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Features: * Syntax Checker: Turn the usage of ``callcode`` into an error as experimental 0.5.0 feature. * Type Checker: Improve address checksum warning. * Type Checker: More detailed errors for invalid array lengths (such as division by zero). + * Inline Assembly: ``if`` statement. Bugfixes: diff --git a/docs/assembly.rst b/docs/assembly.rst index 00bfb388..c233985b 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -26,6 +26,7 @@ arising when writing manual assembly by the following features: * access to external variables: ``function f(uint x) { assembly { x := sub(x, 1) } }`` * labels: ``let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))`` * loops: ``for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }`` +* if statements: ``if slt(x, 0) { x := sub(0, x) }`` * switch statements: ``switch x case 0 { y := mul(x, 2) } default { y := 0 }`` * function calls: ``function f(x) -> y { switch x case 0 { y := 1 } default { y := mul(x, f(sub(x, 1))) } }`` @@ -400,7 +401,7 @@ Labels Another problem in EVM assembly is that ``jump`` and ``jumpi`` use absolute addresses which can change easily. Solidity inline assembly provides labels to make the use of jumps easier. Note that labels are a low-level feature and it is possible to write -efficient assembly without labels, just using assembly functions, loops and switch instructions +efficient assembly without labels, just using assembly functions, loops, if and switch instructions (see below). The following code computes an element in the Fibonacci series. .. code:: @@ -523,6 +524,21 @@ is performed by replacing the variable's value on the stack by the new value. =: v // instruction style assignment, puts the result of sload(10) into v } +If +-- + +The if statement can be used for conditionally executing code. +There is no "else" part, consider using "switch" (see below) if +you need multiple alternatives. + +.. code:: + + { + if eq(value, 0) { revert(0, 0) } + } + +The curly braces for the body are required. + Switch ------ @@ -622,7 +638,7 @@ Things to Avoid --------------- Inline assembly might have a quite high-level look, but it actually is extremely -low-level. Function calls, loops and switches are converted by simple +low-level. Function calls, loops, ifs and switches are converted by simple rewriting rules and after that, the only thing the assembler does for you is re-arranging functional-style opcodes, managing jump labels, counting stack height for variable access and removing stack slots for assembly-local variables when the end @@ -669,7 +685,7 @@ for the Solidity compiler. In this form, it tries to achieve several goals: 3. Control flow should be easy to detect to help in formal verification and optimization. In order to achieve the first and last goal, assembly provides high-level constructs -like ``for`` loops, ``switch`` statements and function calls. It should be possible +like ``for`` loops, ``if`` and ``switch`` statements and function calls. It should be possible to write assembly programs that do not make use of explicit ``SWAP``, ``DUP``, ``JUMP`` and ``JUMPI`` statements, because the first two obfuscate the data flow and the last two obfuscate control flow. Furthermore, functional statements of @@ -875,6 +891,7 @@ Grammar:: FunctionalAssemblyAssignment | AssemblyAssignment | LabelDefinition | + AssemblyIf | AssemblySwitch | AssemblyFunctionDefinition | AssemblyFor | @@ -891,6 +908,7 @@ Grammar:: IdentifierList = Identifier ( ',' Identifier)* AssemblyAssignment = '=:' Identifier LabelDefinition = Identifier ':' + AssemblyIf = 'if' FunctionalAssemblyExpression AssemblyBlock AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase* ( 'default' AssemblyBlock )? AssemblyCase = 'case' FunctionalAssemblyExpression AssemblyBlock diff --git a/libjulia/backends/evm/EVMCodeTransform.cpp b/libjulia/backends/evm/EVMCodeTransform.cpp index 66f593e8..13d9d011 100644 --- a/libjulia/backends/evm/EVMCodeTransform.cpp +++ b/libjulia/backends/evm/EVMCodeTransform.cpp @@ -217,6 +217,19 @@ void CodeTransform::operator()(assembly::Instruction const& _instruction) checkStackHeight(&_instruction); } +void CodeTransform::operator()(If const& _if) +{ + visitExpression(*_if.condition); + m_assembly.setSourceLocation(_if.location); + m_assembly.appendInstruction(solidity::Instruction::ISZERO); + AbstractAssembly::LabelID end = m_assembly.newLabelId(); + m_assembly.appendJumpToIf(end); + (*this)(_if.body); + m_assembly.setSourceLocation(_if.location); + m_assembly.appendLabel(end); + checkStackHeight(&_if); +} + void CodeTransform::operator()(Switch const& _switch) { //@TODO use JUMPV in EVM1.5? diff --git a/libjulia/backends/evm/EVMCodeTransform.h b/libjulia/backends/evm/EVMCodeTransform.h index 951c8a50..387720a2 100644 --- a/libjulia/backends/evm/EVMCodeTransform.h +++ b/libjulia/backends/evm/EVMCodeTransform.h @@ -104,6 +104,7 @@ public: void operator()(solidity::assembly::StackAssignment const& _assignment); void operator()(solidity::assembly::Assignment const& _assignment); void operator()(solidity::assembly::VariableDeclaration const& _varDecl); + void operator()(solidity::assembly::If const& _if); void operator()(solidity::assembly::Switch const& _switch); void operator()(solidity::assembly::FunctionDefinition const&); void operator()(solidity::assembly::ForLoop const&); diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 7f28c7d2..7e41fc16 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -72,6 +72,11 @@ public: for (auto const& arg: _funCall.arguments) boost::apply_visitor(*this, arg); } + void operator()(assembly::If const& _if) + { + boost::apply_visitor(*this, *_if.condition); + (*this)(_if.body); + } void operator()(assembly::Switch const& _switch) { boost::apply_visitor(*this, *_switch.expression); diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index e5bdc90f..2804ddfc 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -286,6 +286,22 @@ bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall) return success; } +bool AsmAnalyzer::operator()(If const& _if) +{ + bool success = true; + + if (!expectExpression(*_if.condition)) + success = false; + m_stackHeight--; + + if (!(*this)(_if.body)) + success = false; + + m_info.stackHeightInfo[&_if] = m_stackHeight; + + return success; +} + bool AsmAnalyzer::operator()(Switch const& _switch) { bool success = true; diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h index 9b2a8f9c..e484b876 100644 --- a/libsolidity/inlineasm/AsmAnalysis.h +++ b/libsolidity/inlineasm/AsmAnalysis.h @@ -70,6 +70,7 @@ public: bool operator()(assembly::VariableDeclaration const& _variableDeclaration); bool operator()(assembly::FunctionDefinition const& _functionDefinition); bool operator()(assembly::FunctionCall const& _functionCall); + bool operator()(assembly::If const& _if); bool operator()(assembly::Switch const& _switch); bool operator()(assembly::ForLoop const& _forLoop); bool operator()(assembly::Block const& _block); diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h index b0dd85ca..a792a1b8 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -68,6 +68,8 @@ struct VariableDeclaration { SourceLocation location; TypedNameList variables; s struct Block { SourceLocation location; std::vector statements; }; /// Function definition ("function f(a, b) -> (d, e) { ... }") struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList arguments; TypedNameList returns; Block body; }; +/// Conditional execution without "else" part. +struct If { SourceLocation location; std::shared_ptr condition; Block body; }; /// Switch case or default case struct Case { SourceLocation location; std::shared_ptr value; Block body; }; /// Switch statement diff --git a/libsolidity/inlineasm/AsmDataForward.h b/libsolidity/inlineasm/AsmDataForward.h index 4ead7ff5..d627b41a 100644 --- a/libsolidity/inlineasm/AsmDataForward.h +++ b/libsolidity/inlineasm/AsmDataForward.h @@ -41,11 +41,12 @@ struct VariableDeclaration; struct FunctionalInstruction; struct FunctionDefinition; struct FunctionCall; +struct If; struct Switch; struct ForLoop; struct Block; -using Statement = boost::variant; +using Statement = boost::variant; } } diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 1f4df75b..8f171005 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -73,13 +73,23 @@ assembly::Statement Parser::parseStatement() return parseFunctionDefinition(); case Token::LBrace: return parseBlock(); + case Token::If: + { + assembly::If _if = createWithLocation(); + m_scanner->next(); + _if.condition = make_shared(parseExpression()); + if (_if.condition->type() == typeid(assembly::Instruction)) + fatalParserError("Instructions are not supported as conditions for if - try to append \"()\"."); + _if.body = parseBlock(); + return _if; + } case Token::Switch: { assembly::Switch _switch = createWithLocation(); m_scanner->next(); _switch.expression = make_shared(parseExpression()); if (_switch.expression->type() == typeid(assembly::Instruction)) - fatalParserError("Instructions are not supported as expressions for switch."); + fatalParserError("Instructions are not supported as expressions for switch - try to append \"()\"."); while (m_scanner->currentToken() == Token::Case) _switch.cases.emplace_back(parseCase()); if (m_scanner->currentToken() == Token::Default) diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index a5272808..0f183244 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -174,6 +174,11 @@ string AsmPrinter::operator()(assembly::FunctionCall const& _functionCall) ")"; } +string AsmPrinter::operator()(If const& _if) +{ + return "if " + boost::apply_visitor(*this, *_if.condition) + "\n" + (*this)(_if.body); +} + string AsmPrinter::operator()(Switch const& _switch) { string out = "switch " + boost::apply_visitor(*this, *_switch.expression); diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h index 66520632..eadf81d9 100644 --- a/libsolidity/inlineasm/AsmPrinter.h +++ b/libsolidity/inlineasm/AsmPrinter.h @@ -48,6 +48,7 @@ public: std::string operator()(assembly::VariableDeclaration const& _variableDeclaration); std::string operator()(assembly::FunctionDefinition const& _functionDefinition); std::string operator()(assembly::FunctionCall const& _functionCall); + std::string operator()(assembly::If const& _if); std::string operator()(assembly::Switch const& _switch); std::string operator()(assembly::ForLoop const& _forLoop); std::string operator()(assembly::Block const& _block); diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp index b70ae9ac..77ae9102 100644 --- a/libsolidity/inlineasm/AsmScopeFiller.cpp +++ b/libsolidity/inlineasm/AsmScopeFiller.cpp @@ -104,6 +104,11 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef) return success; } +bool ScopeFiller::operator()(If const& _if) +{ + return (*this)(_if.body); +} + bool ScopeFiller::operator()(Switch const& _switch) { bool success = true; diff --git a/libsolidity/inlineasm/AsmScopeFiller.h b/libsolidity/inlineasm/AsmScopeFiller.h index 80c03d2c..ed28abbf 100644 --- a/libsolidity/inlineasm/AsmScopeFiller.h +++ b/libsolidity/inlineasm/AsmScopeFiller.h @@ -59,6 +59,7 @@ public: bool operator()(assembly::VariableDeclaration const& _variableDeclaration); bool operator()(assembly::FunctionDefinition const& _functionDefinition); bool operator()(assembly::FunctionCall const&) { return true; } + bool operator()(assembly::If const& _if); bool operator()(assembly::Switch const& _switch); bool operator()(assembly::ForLoop const& _forLoop); bool operator()(assembly::Block const& _block); diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index da3522b4..8b7ba3b0 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -251,6 +251,21 @@ BOOST_AUTO_TEST_CASE(variable_use_before_decl) CHECK_PARSE_ERROR("{ let x := mul(2, x) }", DeclarationError, "Variable x used before it was declared."); } +BOOST_AUTO_TEST_CASE(if_statement) +{ + BOOST_CHECK(successParse("{ if 42 {} }")); + BOOST_CHECK(successParse("{ if 42 { let x := 3 } }")); + BOOST_CHECK(successParse("{ function f() -> x {} if f() { pop(f()) } }")); +} + +BOOST_AUTO_TEST_CASE(if_statement_invalid) +{ + CHECK_PARSE_ERROR("{ if calldatasize {}", ParserError, "Instructions are not supported as conditions for if"); + BOOST_CHECK("{ if calldatasize() {}"); + CHECK_PARSE_ERROR("{ if mstore(1, 1) {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); + CHECK_PARSE_ERROR("{ if 32 let x := 3 }", ParserError, "Expected token LBrace"); +} + BOOST_AUTO_TEST_CASE(switch_statement) { BOOST_CHECK(successParse("{ switch 42 default {} }")); @@ -275,7 +290,7 @@ BOOST_AUTO_TEST_CASE(switch_duplicate_case) BOOST_AUTO_TEST_CASE(switch_invalid_expression) { CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Literal, identifier or instruction expected."); - CHECK_PARSE_ERROR("{ switch calldatasize default {} }", ParserError, "Instructions are not supported as expressions for switch."); + CHECK_PARSE_ERROR("{ switch calldatasize default {} }", ParserError, "Instructions are not supported as expressions for switch"); CHECK_PARSE_ERROR("{ switch mstore(1, 1) default {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index c2f96aaa..05dc9ba3 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -8032,6 +8032,24 @@ BOOST_AUTO_TEST_CASE(inline_assembly_embedded_function_call) ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(4), u256(7), u256(0x10))); } +BOOST_AUTO_TEST_CASE(inline_assembly_if) +{ + char const* sourceCode = R"( + contract C { + function f(uint a) returns (uint b) { + assembly { + if gt(a, 1) { b := 2 } + } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2))); + ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(2))); +} + BOOST_AUTO_TEST_CASE(inline_assembly_switch) { char const* sourceCode = R"( -- cgit v1.2.3 From 6ed4e0632f18f4dc6d7c2db4c5ad6b0f64c9e225 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 21 Nov 2017 13:40:21 +0100 Subject: Use if statement in abi functions. --- libsolidity/codegen/ABIFunctions.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index d2cbac99..bb39cbbb 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -168,7 +168,7 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure) { size_t members = dynamic_cast(_type).numberOfMembers(); solAssert(members > 0, "empty enum should have caused a parser error."); - Whiskers w("switch lt(value, ) case 0 { } cleaned := value"); + Whiskers w("if iszero(lt(value, )) { } cleaned := value"); w("members", to_string(members)); if (_revertOnFailure) w("failure", "revert(0, 0)"); @@ -988,8 +988,8 @@ string ABIFunctions::copyToMemoryFunction(bool _fromCalldata) { mstore(add(dst, i), mload(add(src, i))) } - switch eq(i, length) - case 0 { + if gt(i, length) + { // clear end mstore(add(dst, length), 0) } -- cgit v1.2.3 From e15918d8b6fd106d147d00d0c3405df88ab39b64 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 22 Nov 2017 14:17:31 +0100 Subject: Add if statement to Julia specification. --- docs/julia.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index cf798363..014cd00b 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -15,7 +15,7 @@ future versions of the Solidity compiler will even use JULIA as intermediate language. It should also be easy to build high-level optimizer stages for JULIA. The core components of JULIA are functions, blocks, variables, literals, -for-loops, switch-statements, expressions and assignments to variables. +for-loops, if-statements, switch-statements, expressions and assignments to variables. JULIA is typed, both variables and literals must specify the type with postfix notation. The supported types are ``bool``, ``u8``, ``s8``, ``u32``, ``s32``, @@ -88,6 +88,8 @@ Grammar:: IdentifierList ':=' Expression Expression = FunctionCall | Identifier | Literal + If = + 'if' Expression Block Switch = 'switch' Expression Case* ( 'default' Block )? Case = @@ -248,8 +250,14 @@ We will use a destructuring notation for the AST nodes. G, L, break E(G, L, continue: BreakContinue) = G, L, continue + E(G, L, : If) = + let G0, L0, v = E(G, L, condition) + if v is true or non-zero: + E(G0, L0, body) + else: + G0, L0, regular E(G, L, : Switch) = - E(G, L, switch condition case l1:t1 st1 ... case ln:tn stn default {}) = + E(G, L, switch condition case l1:t1 st1 ... case ln:tn stn default {}) E(G, L, : Switch) = let G0, L0, v = E(G, L, condition) // i = 1 .. n -- cgit v1.2.3 From 9232cd2621113dac43829b7f0dd37f791526f0b4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 22 Nov 2017 16:24:59 +0100 Subject: Tests. --- test/libjulia/Parser.cpp | 13 +++++++++++++ test/libsolidity/InlineAssembly.cpp | 16 ++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/test/libjulia/Parser.cpp b/test/libjulia/Parser.cpp index f8c1aa4d..473a1d2c 100644 --- a/test/libjulia/Parser.cpp +++ b/test/libjulia/Parser.cpp @@ -269,6 +269,19 @@ BOOST_AUTO_TEST_CASE(multiple_assignment) BOOST_CHECK(successParse(text)); } +BOOST_AUTO_TEST_CASE(if_statement) +{ + BOOST_CHECK(successParse("{ if 42:u256 {} }")); + BOOST_CHECK(successParse("{ if 42:u256 { let x:u256 := 3:u256 } }")); + BOOST_CHECK(successParse("{ function f() -> x:u256 {} if f() { let b:u256 := f() } }")); +} + +BOOST_AUTO_TEST_CASE(if_statement_invalid) +{ + CHECK_ERROR("{ if let x:u256 {} }", ParserError, "Literal or identifier expected."); + CHECK_ERROR("{ if 32:u256 let x:u256 := 3:u256 }", ParserError, "Expected token LBrace"); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 8b7ba3b0..e9fb8431 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -258,6 +258,12 @@ BOOST_AUTO_TEST_CASE(if_statement) BOOST_CHECK(successParse("{ function f() -> x {} if f() { pop(f()) } }")); } +BOOST_AUTO_TEST_CASE(if_statement_scope) +{ + BOOST_CHECK(successParse("{ let x := 2 if 42 { x := 3 } }")); + CHECK_PARSE_ERROR("{ if 32 { let x := 3 } x := 2 }", DeclarationError, "Variable not found or variable not lvalue."); +} + BOOST_AUTO_TEST_CASE(if_statement_invalid) { CHECK_PARSE_ERROR("{ if calldatasize {}", ParserError, "Instructions are not supported as conditions for if"); @@ -502,6 +508,11 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode) parsePrintCompare(parsed); } +BOOST_AUTO_TEST_CASE(print_if) +{ + parsePrintCompare("{\n if 2\n {\n pop(mload(0))\n }\n}"); +} + BOOST_AUTO_TEST_CASE(print_switch) { parsePrintCompare("{\n switch 42\n case 1 {\n }\n case 2 {\n }\n default {\n }\n}"); @@ -643,6 +654,11 @@ BOOST_AUTO_TEST_CASE(for_statement) BOOST_CHECK(successAssemble("{ let x := calldatasize() for { let i := 0} lt(i, x) { i := add(i, 1) } { mstore(i, 2) } }")); } +BOOST_AUTO_TEST_CASE(if_statement) +{ + BOOST_CHECK(successAssemble("{ if 1 {} }")); + BOOST_CHECK(successAssemble("{ let x := 0 if eq(calldatasize(), 0) { x := 1 } mstore(0, x) }")); +} BOOST_AUTO_TEST_CASE(large_constant) { -- cgit v1.2.3 From 4c50d3f8e12bbc158d0d8cc39d709b5f00382b66 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 22 Nov 2017 15:41:07 +0000 Subject: Improve expected test errors for some old cases --- test/libsolidity/SolidityNameAndTypeResolution.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 73c1660e..59c84ecf 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -1392,7 +1392,7 @@ BOOST_AUTO_TEST_CASE(events_with_same_name) event A(uint i); } )"; - BOOST_CHECK(success(text)); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(events_with_same_name_unnamed_arguments) @@ -4033,7 +4033,7 @@ BOOST_AUTO_TEST_CASE(varM_disqualified_as_keyword) } } )"; - BOOST_CHECK(!success(text)); + CHECK_ERROR(text, DeclarationError, "Identifier not found or not unique."); } BOOST_AUTO_TEST_CASE(modifier_is_not_a_valid_typename) @@ -4074,7 +4074,7 @@ BOOST_AUTO_TEST_CASE(long_uint_variable_fails) } } )"; - BOOST_CHECK(!success(text)); + CHECK_ERROR(text, DeclarationError, "Identifier not found or not unique."); } BOOST_AUTO_TEST_CASE(bytes10abc_is_identifier) @@ -4113,7 +4113,7 @@ BOOST_AUTO_TEST_CASE(library_functions_do_not_have_value) } } )"; - BOOST_CHECK(!success(text)); + CHECK_ERROR(text, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup in function ()"); } BOOST_AUTO_TEST_CASE(invalid_fixed_types_0x7_mxn) -- cgit v1.2.3 From 3f8c14350655783fe762cf3fe9535afccc45e18b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 23 Nov 2017 12:11:12 +0000 Subject: Add SolidityLexer.pyc to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 114420c9..14c227d0 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ prerelease.txt build/ docs/_build docs/utils/__pycache__ +docs/utils/*.pyc # vim stuff *.swp -- cgit v1.2.3 From 2c3c5ad7c9ca3760319b7086ea9b1ac396950e4f Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 23 Nov 2017 12:12:34 +0000 Subject: Move reserved keywords from SolidityLexer --- docs/utils/SolidityLexer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/utils/SolidityLexer.py b/docs/utils/SolidityLexer.py index a828146f..fe5b7798 100644 --- a/docs/utils/SolidityLexer.py +++ b/docs/utils/SolidityLexer.py @@ -56,7 +56,7 @@ class SolidityLexer(RegexLexer): (r'[})\].]', Punctuation), (r'(anonymous|as|assembly|break|constant|continue|do|delete|else|external|for|hex|if|' r'indexed|internal|import|is|mapping|memory|new|payable|public|pragma|' - r'private|return|returns|storage|super|this|throw|using|while)\b', Keyword, 'slashstartsregex'), + r'private|pure|return|returns|storage|super|this|throw|using|view|while)\b', Keyword, 'slashstartsregex'), (r'(var|function|event|modifier|struct|enum|contract|library|interface)\b', Keyword.Declaration, 'slashstartsregex'), (r'(bytes|string|address|uint|int|bool|byte|' + '|'.join( @@ -67,8 +67,8 @@ class SolidityLexer(RegexLexer): ['fixed%dx%d' % ((i), (j + 8)) for i in range(0, 256, 8) for j in range(0, 256 - i, 8)] ) + r')\b', Keyword.Type, 'slashstartsregex'), (r'(wei|szabo|finney|ether|seconds|minutes|hours|days|weeks|years)\b', Keyword.Type, 'slashstartsregex'), - (r'(abstract|after|case|catch|default|final|in|inline|interface|let|match|' - r'null|of|pure|relocatable|static|switch|try|type|typeof|view)\b', Keyword.Reserved), + (r'(abstract|after|case|catch|default|final|in|inline|let|match|' + r'null|of|relocatable|static|switch|try|type|typeof)\b', Keyword.Reserved), (r'(true|false)\b', Keyword.Constant), (r'(block|msg|tx|now|suicide|selfdestruct|addmod|mulmod|sha3|keccak256|log[0-4]|' r'sha256|ecrecover|ripemd160|assert|revert|require)', Name.Builtin), -- cgit v1.2.3 From dcd55c5005468808607ebd92d84124c8949d9d17 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 23 Nov 2017 12:13:00 +0000 Subject: Add exponential notation to numbers in SolidityLexer --- docs/utils/SolidityLexer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/utils/SolidityLexer.py b/docs/utils/SolidityLexer.py index fe5b7798..50f51cf4 100644 --- a/docs/utils/SolidityLexer.py +++ b/docs/utils/SolidityLexer.py @@ -73,9 +73,9 @@ class SolidityLexer(RegexLexer): (r'(block|msg|tx|now|suicide|selfdestruct|addmod|mulmod|sha3|keccak256|log[0-4]|' r'sha256|ecrecover|ripemd160|assert|revert|require)', Name.Builtin), (r'[$a-zA-Z_][a-zA-Z0-9_]*', Name.Other), - (r'[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?', Number.Float), + (r'[0-9][0-9]*\.[0-9]+([eE][0-9]+)?', Number.Float), (r'0x[0-9a-fA-F]+', Number.Hex), - (r'[0-9]+', Number.Integer), + (r'[0-9]+([eE][0-9]+)?', Number.Integer), (r'"(\\\\|\\"|[^"])*"', String.Double), (r"'(\\\\|\\'|[^'])*'", String.Single), ] -- cgit v1.2.3 From d0be67f6b061a93ea014b430fdb04b5bc446c9c3 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 23 Nov 2017 18:43:19 +0100 Subject: Bugfix in blind auction --- docs/solidity-by-example.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index 59ab7962..9489665e 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -492,7 +492,7 @@ high or low invalid bids. if (amount > 0) { // It is important to set this to zero because the recipient // can call this function again as part of the receiving call - // before `send` returns (see the remark above about + // before `transfer` returns (see the remark above about // conditions -> effects -> interaction). pendingReturns[msg.sender] = 0; @@ -508,12 +508,11 @@ high or low invalid bids. require(!ended); AuctionEnded(highestBidder, highestBid); ended = true; - // We send all the money we have, because some - // of the refunds might have failed. - beneficiary.transfer(this.balance); + beneficiary.transfer(highestBid); } } + .. index:: purchase, remote purchase, escrow ******************** -- cgit v1.2.3 From 1d91b65b726d4757b866124d75834f28a9bc9eb9 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 23 Nov 2017 18:52:04 +0100 Subject: Force condition to be bool in iulia mode. --- docs/julia.rst | 2 +- test/libjulia/Parser.cpp | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/julia.rst b/docs/julia.rst index 014cd00b..309e6b36 100644 --- a/docs/julia.rst +++ b/docs/julia.rst @@ -252,7 +252,7 @@ We will use a destructuring notation for the AST nodes. G, L, continue E(G, L, : If) = let G0, L0, v = E(G, L, condition) - if v is true or non-zero: + if v is true: E(G0, L0, body) else: G0, L0, regular diff --git a/test/libjulia/Parser.cpp b/test/libjulia/Parser.cpp index 473a1d2c..9aa325a4 100644 --- a/test/libjulia/Parser.cpp +++ b/test/libjulia/Parser.cpp @@ -271,15 +271,17 @@ BOOST_AUTO_TEST_CASE(multiple_assignment) BOOST_AUTO_TEST_CASE(if_statement) { - BOOST_CHECK(successParse("{ if 42:u256 {} }")); - BOOST_CHECK(successParse("{ if 42:u256 { let x:u256 := 3:u256 } }")); - BOOST_CHECK(successParse("{ function f() -> x:u256 {} if f() { let b:u256 := f() } }")); + BOOST_CHECK(successParse("{ if true:bool {} }")); + BOOST_CHECK(successParse("{ if false:bool { let x:u256 := 3:u256 } }")); + BOOST_CHECK(successParse("{ function f() -> x:bool {} if f() { let b:bool := f() } }")); } BOOST_AUTO_TEST_CASE(if_statement_invalid) { CHECK_ERROR("{ if let x:u256 {} }", ParserError, "Literal or identifier expected."); - CHECK_ERROR("{ if 32:u256 let x:u256 := 3:u256 }", ParserError, "Expected token LBrace"); + CHECK_ERROR("{ if true:bool let x:u256 := 3:u256 }", ParserError, "Expected token LBrace"); + // TODO change this to an error once we check types. + BOOST_CHECK(successParse("{ if 42:u256 { } }")); } BOOST_AUTO_TEST_SUITE_END() -- cgit v1.2.3 From 0e2a9658d231f75e5ac89fd58c5c880b46c4f4dc Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 22 Nov 2017 17:12:22 +0100 Subject: Explain IntIntFun and merge assertion. --- libsolidity/formal/SolverInterface.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index ac722dad..c9adf863 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -44,7 +44,9 @@ enum class CheckResult enum class Sort { - Int, Bool, IntIntFun + Int, + Bool, + IntIntFun // Function of one Int returning a single Int }; /// C++ representation of an SMTLIB2 expression. @@ -120,8 +122,10 @@ public: } Expression operator()(Expression _a) const { - solAssert(sort == Sort::IntIntFun, "Attempted function application to non-function."); - solAssert(arguments.empty(), "Attempted function application to non-function."); + solAssert( + sort == Sort::IntIntFun && arguments.empty(), + "Attempted function application to non-function." + ); return Expression(name, _a, Sort::Int); } -- cgit v1.2.3 From 83f326525e20fe18ae69849b1f5604ef3e88954e Mon Sep 17 00:00:00 2001 From: Isaac van Bakel Date: Sat, 25 Nov 2017 21:51:27 +0000 Subject: Replaced solAssert in Jumpdest optimization This is the only use of solAssert in libevmasm, and the only dependency on libsolidity. There is no justification for why the illegal state is checked here. --- libevmasm/JumpdestRemover.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libevmasm/JumpdestRemover.cpp b/libevmasm/JumpdestRemover.cpp index b6016798..60493a99 100644 --- a/libevmasm/JumpdestRemover.cpp +++ b/libevmasm/JumpdestRemover.cpp @@ -21,8 +21,6 @@ #include "JumpdestRemover.h" -#include - #include using namespace std; @@ -45,7 +43,7 @@ bool JumpdestRemover::optimise(set const& _tagsReferencedFromOutside) if (_item.type() != Tag) return false; auto asmIdAndTag = _item.splitForeignPushTag(); - solAssert(asmIdAndTag.first == size_t(-1), "Sub-assembly tag used as label."); + assertThrow(asmIdAndTag.first == size_t(-1), OptimizerException, "Sub-assembly tag used as label."); size_t tag = asmIdAndTag.second; return !references.count(tag); } -- cgit v1.2.3 From f77480cd881a2689dc35515b6c80f4d34653db7c Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 27 Nov 2017 17:06:34 +0100 Subject: Patch truffle to ignore pre-release compiler warning. --- test/externalTests.sh | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/test/externalTests.sh b/test/externalTests.sh index 6ff2ebc5..1cc0af19 100755 --- a/test/externalTests.sh +++ b/test/externalTests.sh @@ -42,7 +42,55 @@ DIR=$(mktemp -d) git clone --depth 1 https://github.com/OpenZeppelin/zeppelin-solidity.git "$DIR" cd "$DIR" npm install - cp "$SOLJSON" ./node_modules/solc/soljson.js + find . -name soljson.js -exec cp "$SOLJSON" {} \; + + # This is a patch that lets truffle ignore the pre-release compiler warning + cat > truffle.patch < Date: Fri, 1 Sep 2017 13:59:21 +0200 Subject: ABI decoder. --- Changelog.md | 2 + libsolidity/codegen/ABIFunctions.cpp | 425 ++++++++++++++++++++++++++++++- libsolidity/codegen/ABIFunctions.h | 47 ++++ libsolidity/codegen/CompilerContext.cpp | 1 + libsolidity/codegen/CompilerUtils.cpp | 17 ++ libsolidity/codegen/CompilerUtils.h | 7 + libsolidity/codegen/ContractCompiler.cpp | 10 + 7 files changed, 505 insertions(+), 4 deletions(-) diff --git a/Changelog.md b/Changelog.md index 27e72838..f69a39ce 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,8 @@ Features: * Allow constant variables to be used as array length + * Code Generator: New ABI decoder which supports structs and arbitrarily nested + arrays and checks input size (activate using ``pragma experimental ABIEncoderV2;``. * Syntax Checker: Turn the usage of ``callcode`` into an error as experimental 0.5.0 feature. * Type Checker: Improve address checksum warning. * Type Checker: More detailed errors for invalid array lengths (such as division by zero). diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index bb39cbbb..c9a9ff57 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -22,9 +22,13 @@ #include +#include +#include + #include -#include +#include +#include using namespace std; using namespace dev; @@ -99,6 +103,73 @@ string ABIFunctions::tupleEncoder( }); } +string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) +{ + string functionName = string("abi_decode_tuple_"); + for (auto const& t: _types) + functionName += t->identifier(); + if (_fromMemory) + functionName += "_fromMemory"; + + return createFunction(functionName, [&]() { + solAssert(!_types.empty(), ""); + + TypePointers decodingTypes; + for (auto const& t: _types) + decodingTypes.emplace_back(t->decodingType()); + + Whiskers templ(R"( + function (headStart, dataEnd) -> { + switch slt(sub(dataEnd, headStart), ) case 1 { revert(0, 0) } + + } + )"); + templ("functionName", functionName); + templ("minimumSize", to_string(headSize(decodingTypes))); + + string decodeElements; + vector valueReturnParams; + size_t headPos = 0; + size_t stackPos = 0; + for (size_t i = 0; i < _types.size(); ++i) + { + solAssert(_types[i], ""); + solAssert(decodingTypes[i], ""); + size_t sizeOnStack = _types[i]->sizeOnStack(); + solAssert(sizeOnStack == decodingTypes[i]->sizeOnStack(), ""); + solAssert(sizeOnStack > 0, ""); + vector valueNamesLocal; + for (size_t j = 0; j < sizeOnStack; j++) + { + valueNamesLocal.push_back("value" + to_string(stackPos)); + valueReturnParams.push_back("value" + to_string(stackPos)); + stackPos++; + } + bool dynamic = decodingTypes[i]->isDynamicallyEncoded(); + Whiskers elementTempl(R"( + { + let offset := )" + string( + dynamic ? + "(add(headStart, ))" : + "" + ) + R"( + := (add(headStart, offset), dataEnd) + } + )"); + elementTempl("load", _fromMemory ? "mload" : "calldataload"); + elementTempl("values", boost::algorithm::join(valueNamesLocal, ", ")); + elementTempl("pos", to_string(headPos)); + elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true)); + decodeElements += elementTempl.render(); + headPos += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize(); + } + templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", ")); + templ("decodeElements", decodeElements); + + return templ.render(); + }); +} + string ABIFunctions::requestedFunctions() { string result; @@ -141,10 +212,9 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure) solUnimplemented("Fixed point types not implemented."); break; case Type::Category::Array: - solAssert(false, "Array cleanup requested."); - break; case Type::Category::Struct: - solAssert(false, "Struct cleanup requested."); + solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type."); + templ("body", "cleaned := value"); break; case Type::Category::FixedBytes: { @@ -367,6 +437,24 @@ string ABIFunctions::combineExternalFunctionIdFunction() }); } +string ABIFunctions::splitExternalFunctionIdFunction() +{ + string functionName = "split_external_function_id"; + return createFunction(functionName, [&]() { + return Whiskers(R"( + function (combined) -> addr, selector { + combined := (combined) + selector := and(combined, 0xffffffff) + addr := (combined) + } + )") + ("functionName", functionName) + ("shr32", shiftRightFunction(32, false)) + ("shr64", shiftRightFunction(64, false)) + .render(); + }); +} + string ABIFunctions::abiEncodingFunction( Type const& _from, Type const& _to, @@ -963,6 +1051,290 @@ string ABIFunctions::abiEncodingFunctionFunctionType( }); } +string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bool _forUseOnStack) +{ + TypePointer decodingType = _type.decodingType(); + solAssert(decodingType, ""); + + if (auto arrayType = dynamic_cast(decodingType.get())) + { + if (arrayType->dataStoredIn(DataLocation::CallData)) + { + solAssert(!_fromMemory, ""); + return abiDecodingFunctionCalldataArray(*arrayType); + } + else if (arrayType->isByteArray()) + return abiDecodingFunctionByteArray(*arrayType, _fromMemory); + else + return abiDecodingFunctionArray(*arrayType, _fromMemory); + } + else if (auto const* structType = dynamic_cast(decodingType.get())) + return abiDecodingFunctionStruct(*structType, _fromMemory); + else if (auto const* functionType = dynamic_cast(decodingType.get())) + return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack); + + solAssert(decodingType->sizeOnStack() == 1, ""); + solAssert(decodingType->isValueType(), ""); + solAssert(decodingType->calldataEncodedSize() == 32, ""); + solAssert(!decodingType->isDynamicallyEncoded(), ""); + + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + return createFunction(functionName, [&]() { + Whiskers templ(R"( + function (offset, end) -> value { + value := ((offset)) + } + )"); + templ("functionName", functionName); + templ("load", _fromMemory ? "mload" : "calldataload"); + // Cleanup itself should use the type and not decodingType, because e.g. + // the decoding type of an enum is a plain int. + templ("cleanup", cleanupFunction(_type, true)); + return templ.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + solAssert(!_type.isByteArray(), ""); + + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + + solAssert(!_type.dataStoredIn(DataLocation::Storage), ""); + + return createFunction(functionName, [&]() { + string load = _fromMemory ? "mload" : "calldataload"; + bool dynamicBase = _type.baseType()->isDynamicallyEncoded(); + Whiskers templ( + R"( + // + function (offset, end) -> array { + let length := + array := ((length)) + let dst := array + // might update offset and dst + let src := offset + + for { let i := 0 } lt(i, length) { i := add(i, 1) } + { + let elementPos := + + mstore(dst, (elementPos, end)) + dst := add(dst, 0x20) + src := add(src, ) + } + } + )" + ); + templ("functionName", functionName); + templ("readableTypeName", _type.toString(true)); + templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)"); + templ("allocate", allocationFunction()); + templ("allocationSize", arrayAllocationSizeFunction(_type)); + if (_type.isDynamicallySized()) + templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)"); + else + templ("storeLength", ""); + if (dynamicBase) + { + templ("staticBoundsCheck", ""); + // The dynamic bounds check might not be needed (because we have an additional check + // one level deeper), but we keep it in just in case. This at least prevents + // the part one level deeper from reading the length from an out of bounds position. + templ("dynamicBoundsCheck", "switch gt(elementPos, end) case 1 { revert(0, 0) }"); + templ("retrieveElementPos", "add(offset, " + load + "(src))"); + templ("baseEncodedSize", "0x20"); + } + else + { + string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize()); + templ("staticBoundsCheck", "switch gt(add(src, mul(length, " + baseEncodedSize + ")), end) case 1 { revert(0, 0) }"); + templ("dynamicBoundsCheck", ""); + templ("retrieveElementPos", "src"); + templ("baseEncodedSize", baseEncodedSize); + } + templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false)); + return templ.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) +{ + // This does not work with arrays of complex types - the array access + // is not yet implemented in Solidity. + solAssert(_type.dataStoredIn(DataLocation::CallData), ""); + if (!_type.isDynamicallySized()) + solAssert(_type.length() < u256("0xffffffffffffffff"), ""); + solAssert(!_type.baseType()->isDynamicallyEncoded(), ""); + solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), ""); + + string functionName = + "abi_decode_" + + _type.identifier(); + return createFunction(functionName, [&]() { + string templ; + if (_type.isDynamicallySized()) + templ = R"( + // + function (offset, end) -> arrayPos, length { + length := calldataload(offset) + switch gt(length, 0xffffffffffffffff) case 1 { revert(0, 0) } + arrayPos := add(offset, 0x20) + switch gt(add(arrayPos, mul(, )), end) case 1 { revert(0, 0) } + } + )"; + else + templ = R"( + // + function (offset, end) -> arrayPos { + arrayPos := offset + switch gt(add(arrayPos, mul(, )), end) case 1 { revert(0, 0) } + } + )"; + Whiskers w{templ}; + w("functionName", functionName); + w("readableTypeName", _type.toString(true)); + w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize())); + w("length", _type.isDynamicallyEncoded() ? "length" : toCompactHexWithPrefix(_type.length())); + return w.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + solAssert(_type.isByteArray(), ""); + + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + + return createFunction(functionName, [&]() { + Whiskers templ( + R"( + function (offset, end) -> array { + let length := (offset) + array := ((length)) + mstore(array, length) + let src := add(offset, 0x20) + let dst := add(array, 0x20) + switch gt(add(src, length), end) case 1 { revert(0, 0) } + (src, dst, length) + } + )" + ); + templ("functionName", functionName); + templ("load", _fromMemory ? "mload" : "calldataload"); + templ("allocate", allocationFunction()); + templ("allocationSize", arrayAllocationSizeFunction(_type)); + templ("copyToMemFun", copyToMemoryFunction(!_fromMemory)); + return templ.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory) +{ + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : ""); + + solUnimplementedAssert(!_type.dataStoredIn(DataLocation::CallData), ""); + + return createFunction(functionName, [&]() { + Whiskers templ(R"( + // + function (headStart, end) -> value { + switch slt(sub(end, headStart), ) case 1 { revert(0, 0) } + value := () + <#members> + { + // + + } + + } + )"); + templ("functionName", functionName); + templ("readableTypeName", _type.toString(true)); + templ("allocate", allocationFunction()); + solAssert(_type.memorySize() < u256("0xffffffffffffffff"), ""); + templ("memorySize", toCompactHexWithPrefix(_type.memorySize())); + size_t headPos = 0; + vector> members; + for (auto const& member: _type.members(nullptr)) + { + solAssert(member.type, ""); + solAssert(member.type->canLiveOutsideStorage(), ""); + auto decodingType = member.type->decodingType(); + solAssert(decodingType, ""); + bool dynamic = decodingType->isDynamicallyEncoded(); + Whiskers memberTempl(R"( + let offset := )" + string(dynamic ? "(add(headStart, ))" : "" ) + R"( + mstore(add(value, ), (add(headStart, offset), end)) + )"); + memberTempl("load", _fromMemory ? "mload" : "calldataload"); + memberTempl("pos", to_string(headPos)); + memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name))); + memberTempl("abiDecode", abiDecodingFunction(*member.type, _fromMemory, false)); + + members.push_back({}); + members.back()["decode"] = memberTempl.render(); + members.back()["memberName"] = member.name; + headPos += dynamic ? 0x20 : decodingType->calldataEncodedSize(); + } + templ("members", members); + templ("minimumSize", toCompactHexWithPrefix(headPos)); + return templ.render(); + }); +} + +string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack) +{ + solAssert(_type.kind() == FunctionType::Kind::External, ""); + + string functionName = + "abi_decode_" + + _type.identifier() + + (_fromMemory ? "_fromMemory" : "") + + (_forUseOnStack ? "_onStack" : ""); + + return createFunction(functionName, [&]() { + if (_forUseOnStack) + { + return Whiskers(R"( + function (offset, end) -> addr, function_selector { + addr, function_selector := ((offset)) + } + )") + ("functionName", functionName) + ("load", _fromMemory ? "mload" : "calldataload") + ("splitExtFun", splitExternalFunctionIdFunction()) + .render(); + } + else + { + return Whiskers(R"( + function (offset, end) -> fun { + fun := ((offset)) + } + )") + ("functionName", functionName) + ("load", _fromMemory ? "mload" : "calldataload") + ("cleanExtFun", cleanupCombinedExternalFunctionIdFunction()) + .render(); + } + }); +} + string ABIFunctions::copyToMemoryFunction(bool _fromCalldata) { string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory"; @@ -1098,6 +1470,33 @@ string ABIFunctions::arrayLengthFunction(ArrayType const& _type) }); } +string ABIFunctions::arrayAllocationSizeFunction(ArrayType const& _type) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + string functionName = "array_allocation_size_" + _type.identifier(); + return createFunction(functionName, [&]() { + Whiskers w(R"( + function (length) -> size { + // Make sure we can allocate memory without overflow + switch gt(length, 0xffffffffffffffff) case 1 { revert(0, 0) } + size := + + } + )"); + w("functionName", functionName); + if (_type.isByteArray()) + // Round up + w("allocationSize", "and(add(length, 0x1f), not(0x1f))"); + else + w("allocationSize", "mul(length, 0x20)"); + if (_type.isDynamicallySized()) + w("addLengthSlot", "size := add(size, 0x20)"); + else + w("addLengthSlot", ""); + return w.render(); + }); +} + string ABIFunctions::arrayDataAreaFunction(ArrayType const& _type) { string functionName = "array_dataslot_" + _type.identifier(); @@ -1189,6 +1588,24 @@ string ABIFunctions::nextArrayElementFunction(ArrayType const& _type) }); } +string ABIFunctions::allocationFunction() +{ + string functionName = "allocateMemory"; + return createFunction(functionName, [&]() { + return Whiskers(R"( + function (size) -> memPtr { + memPtr := mload() + let newFreePtr := add(memPtr, size) + switch lt(newFreePtr, memPtr) case 1 { revert(0, 0) } + mstore(, newFreePtr) + } + )") + ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)) + ("functionName", functionName) + .render(); + }); +} + string ABIFunctions::createFunction(string const& _name, function const& _creator) { if (!m_requestedFunctions.count(_name)) diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index e61f68bc..855b2a11 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -66,6 +66,16 @@ public: bool _encodeAsLibraryTypes = false ); + /// @returns name of an assembly function to ABI-decode values of @a _types + /// into memory. If @a _fromMemory is true, decodes from memory instead of + /// from calldata. + /// Can allocate memory. + /// Inputs: (layout reversed on stack) + /// Outputs: ... + /// The values represent stack slots. If a type occupies more or less than one + /// stack slot, it takes exactly that number of values. + std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false); + /// @returns concatenation of all generated functions. std::string requestedFunctions(); @@ -87,6 +97,10 @@ private: /// for use in the ABI. std::string combineExternalFunctionIdFunction(); + /// @returns a function that splits the address and selector from a single value + /// for use in the ABI. + std::string splitExternalFunctionIdFunction(); + /// @returns the name of the ABI encoding function with the given type /// and queues the generation of the function to the requested functions. /// @param _fromStack if false, the input value was just loaded from storage @@ -146,6 +160,29 @@ private: bool _fromStack ); + /// @returns the name of the ABI decodinf function for the given type + /// and queues the generation of the function to the requested functions. + /// The caller has to ensure that no out of bounds access (at least to the static + /// part) can happen inside this function. + /// @param _fromMemory if decoding from memory instead of from calldata + /// @param _forUseOnStack if the decoded value is stored on stack or in memory. + std::string abiDecodingFunction( + Type const& _Type, + bool _fromMemory, + bool _forUseOnStack + ); + + /// Part of @a abiDecodingFunction for "regular" array types. + std::string abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory); + /// Part of @a abiDecodingFunction for calldata array types. + std::string abiDecodingFunctionCalldataArray(ArrayType const& _type); + /// Part of @a abiDecodingFunction for byte array types. + std::string abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory); + /// Part of @a abiDecodingFunction for struct types. + std::string abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory); + /// Part of @a abiDecodingFunction for array types. + std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack); + /// @returns a function that copies raw bytes of dynamic length from calldata /// or memory to memory. /// Pads with zeros and might write more than exactly length. @@ -158,6 +195,10 @@ private: std::string roundUpFunction(); std::string arrayLengthFunction(ArrayType const& _type); + /// @returns the name of a function that computes the number of bytes required + /// to store an array in memory given its length (internally encoded, not ABI encoded). + /// The function reverts for too large lengthes. + std::string arrayAllocationSizeFunction(ArrayType const& _type); /// @returns the name of a function that converts a storage slot number /// or a memory pointer to the slot number / memory pointer for the data position of an array /// which is stored in that slot / memory area. @@ -166,6 +207,12 @@ private: /// Only works for memory arrays and storage arrays that store one item per slot. std::string nextArrayElementFunction(ArrayType const& _type); + /// @returns the name of a function that allocates memory. + /// Modifies the "free memory pointer" + /// Arguments: size + /// Return value: pointer + std::string allocationFunction(); + /// Helper function that uses @a _creator to create a function and add it to /// @a m_requestedFunctions if it has not been created yet and returns @a _name in both /// cases. diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index ce9c3b7f..35d763f1 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -318,6 +318,7 @@ void CompilerContext::appendInlineAssembly( ErrorList errors; ErrorReporter errorReporter(errors); +// cout << _assembly << endl; auto scanner = make_shared(CharStream(_assembly), "--CODEGEN--"); auto parserResult = assembly::Parser(errorReporter).parse(scanner); #ifdef SOL_OUTPUT_ASM diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 053bed6a..533aca5c 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -319,6 +319,23 @@ void CompilerUtils::abiEncodeV2( m_context << ret.tag(); } +void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory) +{ + // stack: + auto ret = m_context.pushNewTag(); + m_context << Instruction::SWAP1; + if (_fromMemory) + // TODO pass correct size for the memory case + m_context << (u256(1) << 63); + else + m_context << Instruction::CALLDATASIZE; + m_context << Instruction::SWAP1; + string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory); + m_context.appendJumpTo(m_context.namedTag(decoderName)); + m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3); + m_context << ret.tag(); +} + void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) { auto repeat = m_context.newTag(); diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index ad3989ad..3cde281b 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -146,6 +146,13 @@ public: bool _encodeAsLibraryTypes = false ); + /// Decodes data from ABI encoding into internal encoding. If @a _fromMemory is set to true, + /// the data is taken from memory instead of from calldata. + /// Can allocate memory. + /// Stack pre: + /// Stack post: ... + void abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory = false); + /// Zero-initialises (the data part of) an already allocated memory array. /// Length has to be nonzero! /// Stack pre: diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 74565ae4..1ae44165 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -322,6 +322,15 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter { // We do not check the calldata size, everything is zero-padded + if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)) + { + // Use the new JULIA-based decoding function + auto stackHeightBefore = m_context.stackHeight(); + CompilerUtils(m_context).abiDecodeV2(_typeParameters, _fromMemory); + solAssert(m_context.stackHeight() - stackHeightBefore == CompilerUtils(m_context).sizeOnStack(_typeParameters) - 1, ""); + return; + } + //@todo this does not yet support nested dynamic arrays // Retain the offset pointer as base_offset, the point from which the data offsets are computed. @@ -892,6 +901,7 @@ void ContractCompiler::appendMissingFunctions() } m_context.appendMissingLowLevelFunctions(); string abiFunctions = m_context.abiFunctions().requestedFunctions(); +// cout << abiFunctions << endl; if (!abiFunctions.empty()) m_context.appendInlineAssembly("{" + move(abiFunctions) + "}", {}, true); } -- cgit v1.2.3 From 98c38108e8ce01888ee4dbf98a332aa5ba41f722 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 13 Sep 2017 17:16:45 +0200 Subject: Decoder tests. --- test/ExecutionFramework.h | 16 +- test/boostTest.cpp | 1 + test/libsolidity/ABIDecoderTests.cpp | 790 +++++++++++++++++++++++++++++++++++ test/libsolidity/ABIEncoderTests.cpp | 50 +-- test/libsolidity/ABITestsCommon.h | 43 ++ 5 files changed, 866 insertions(+), 34 deletions(-) create mode 100644 test/libsolidity/ABIDecoderTests.cpp create mode 100644 test/libsolidity/ABITestsCommon.h diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 2c61c0a6..8aa99473 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -84,14 +84,24 @@ public: return callFallbackWithValue(0); } - template - bytes const& callContractFunctionWithValue(std::string _sig, u256 const& _value, Args const&... _arguments) + bytes const& callContractFunctionWithValueNoEncoding(std::string _sig, u256 const& _value, bytes const& _arguments) { FixedHash<4> hash(dev::keccak256(_sig)); - sendMessage(hash.asBytes() + encodeArgs(_arguments...), false, _value); + sendMessage(hash.asBytes() + _arguments, false, _value); return m_output; } + bytes const& callContractFunctionNoEncoding(std::string _sig, bytes const& _arguments) + { + return callContractFunctionWithValueNoEncoding(_sig, 0, _arguments); + } + + template + bytes const& callContractFunctionWithValue(std::string _sig, u256 const& _value, Args const&... _arguments) + { + return callContractFunctionWithValueNoEncoding(_sig, _value, encodeArgs(_arguments...)); + } + template bytes const& callContractFunction(std::string _sig, Args const&... _arguments) { diff --git a/test/boostTest.cpp b/test/boostTest.cpp index 7b452e06..a3cc51c5 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -57,6 +57,7 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) if (dev::test::Options::get().disableIPC) { for (auto suite: { + "ABIDecoderTest", "ABIEncoderTest", "SolidityAuctionRegistrar", "SolidityFixedFeeRegistrar", diff --git a/test/libsolidity/ABIDecoderTests.cpp b/test/libsolidity/ABIDecoderTests.cpp new file mode 100644 index 00000000..6a4b5723 --- /dev/null +++ b/test/libsolidity/ABIDecoderTests.cpp @@ -0,0 +1,790 @@ +/* + 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 . +*/ +/** + * Unit tests for Solidity's ABI decoder. + */ + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace std::placeholders; +using namespace dev::test; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +BOOST_FIXTURE_TEST_SUITE(ABIDecoderTest, SolidityExecutionFramework) + +BOOST_AUTO_TEST_CASE(BOTH_ENCODERS_macro) +{ + // This tests that the "both decoders macro" at least runs twice and + // modifies the source. + string sourceCode; + int runs = 0; + BOTH_ENCODERS(runs++;) + BOOST_CHECK(sourceCode == NewEncoderPragma); + BOOST_CHECK_EQUAL(runs, 2); +} + +BOOST_AUTO_TEST_CASE(value_types) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint16 b, uint24 c, int24 d, bytes3 x, bool e, C g) public returns (uint) { + if (a != 1) return 1; + if (b != 2) return 2; + if (c != 3) return 3; + if (d != 4) return 4; + if (x != "abc") return 5; + if (e != true) return 6; + if (g != this) return 7; + return 20; + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction( + "f(uint256,uint16,uint24,int24,bytes3,bool,address)", + 1, 2, 3, 4, string("abc"), true, u160(m_contractAddress) + ), encodeArgs(u256(20))); + ) +} + +BOOST_AUTO_TEST_CASE(enums) +{ + string sourceCode = R"( + contract C { + enum E { A, B } + function f(E e) public pure returns (uint x) { + assembly { x := e } + } + } + )"; + bool newDecoder = false; + BOTH_ENCODERS( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(uint8)", 0), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(uint8)", 1), encodeArgs(u256(1))); + // The old decoder was not as strict about enums + ABI_CHECK(callContractFunction("f(uint8)", 2), (newDecoder ? encodeArgs() : encodeArgs(2))); + ABI_CHECK(callContractFunction("f(uint8)", u256(-1)), (newDecoder? encodeArgs() : encodeArgs(u256(0xff)))); + newDecoder = true; + ) +} + +BOOST_AUTO_TEST_CASE(cleanup) +{ + string sourceCode = R"( + contract C { + function f(uint16 a, int16 b, address c, bytes3 d, bool e) + public pure returns (uint v, uint w, uint x, uint y, uint z) { + assembly { v := a w := b x := c y := d z := e} + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("f(uint16,int16,address,bytes3,bool)", 1, 2, 3, "a", true), + encodeArgs(u256(1), u256(2), u256(3), string("a"), true) + ); + ABI_CHECK( + callContractFunction( + "f(uint16,int16,address,bytes3,bool)", + u256(0xffffff), u256(0x1ffff), u256(-1), string("abcd"), u256(4) + ), + encodeArgs(u256(0xffff), u256(-1), (u256(1) << 160) - 1, string("abc"), true) + ); + ) +} + +BOOST_AUTO_TEST_CASE(fixed_arrays) +{ + string sourceCode = R"( + contract C { + function f(uint16[3] a, uint16[2][3] b, uint i, uint j, uint k) + public pure returns (uint, uint) { + return (a[i], b[j][k]); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 1, 2, 3, + 11, 12, + 21, 22, + 31, 32, + 1, 2, 1 + ); + ABI_CHECK( + callContractFunction("f(uint16[3],uint16[2][3],uint256,uint256,uint256)", args), + encodeArgs(u256(2), u256(32)) + ); + ) +} + +BOOST_AUTO_TEST_CASE(dynamic_arrays) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint16[] b, uint c) + public pure returns (uint, uint, uint) { + return (b.length, b[a], c); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 6, 0x60, 9, + 7, + 11, 12, 13, 14, 15, 16, 17 + ); + ABI_CHECK( + callContractFunction("f(uint256,uint16[],uint256)", args), + encodeArgs(u256(7), u256(17), u256(9)) + ); + ) +} + +BOOST_AUTO_TEST_CASE(dynamic_nested_arrays) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint16[][] b, uint[2][][3] c, uint d) + public pure returns (uint, uint, uint, uint, uint, uint, uint) { + return (a, b.length, b[1].length, b[1][1], c[1].length, c[1][1][1], d); + } + function test() view returns (uint, uint, uint, uint, uint, uint, uint) { + uint16[][] memory b = new uint16[][](3); + b[0] = new uint16[](2); + b[0][0] = 0x55; + b[0][1] = 0x56; + b[1] = new uint16[](4); + b[1][0] = 0x65; + b[1][1] = 0x66; + b[1][2] = 0x67; + b[1][3] = 0x68; + + uint[2][][3] memory c; + c[0] = new uint[2][](1); + c[0][0][1] = 0x75; + c[1] = new uint[2][](5); + c[1][1][1] = 0x85; + + return this.f(0x12, b, c, 0x13); + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 0x12, 4 * 0x20, 17 * 0x20, 0x13, + // b + 3, 3 * 0x20, 6 * 0x20, 11 * 0x20, + 2, 85, 86, + 4, 101, 102, 103, 104, + 0, + // c + 3 * 0x20, 6 * 0x20, 17 * 0x20, + 1, 0, 117, + 5, 0, 0, 0, 133, 0, 0, 0, 0, 0, 0, + 0 + ); + + bytes expectation = encodeArgs(0x12, 3, 4, 0x66, 5, 0x85, 0x13); + ABI_CHECK(callContractFunction("test()"), expectation); + ABI_CHECK(callContractFunction("f(uint256,uint16[][],uint256[2][][3],uint256)", args), expectation); + ) +} + +BOOST_AUTO_TEST_CASE(byte_arrays) +{ + string sourceCode = R"( + contract C { + function f(uint a, bytes b, uint c) + public pure returns (uint, uint, byte, uint) { + return (a, b.length, b[3], c); + } + + function f_external(uint a, bytes b, uint c) + external pure returns (uint, uint, byte, uint) { + return (a, b.length, b[3], c); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 6, 0x60, 9, + 7, "abcdefg" + ); + ABI_CHECK( + callContractFunction("f(uint256,bytes,uint256)", args), + encodeArgs(u256(6), u256(7), "d", 9) + ); + ABI_CHECK( + callContractFunction("f_external(uint256,bytes,uint256)", args), + encodeArgs(u256(6), u256(7), "d", 9) + ); + ) +} + +BOOST_AUTO_TEST_CASE(calldata_arrays_too_large) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint[] b, uint c) external pure returns (uint) { + return 7; + } + } + )"; + bool newEncoder = false; + BOTH_ENCODERS( + compileAndRun(sourceCode); + bytes args = encodeArgs( + 6, 0x60, 9, + (u256(1) << 255) + 2, 1, 2 + ); + ABI_CHECK( + callContractFunction("f(uint256,uint256[],uint256)", args), + newEncoder ? encodeArgs() : encodeArgs(7) + ); + newEncoder = true; + ) +} + +BOOST_AUTO_TEST_CASE(decode_from_memory_simple) +{ + string sourceCode = R"( + contract C { + uint public _a; + uint[] public _b; + function C(uint a, uint[] b) { + _a = a; + _b = b; + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "C", encodeArgs( + 7, 0x40, + // b + 3, 0x21, 0x22, 0x23 + )); + ABI_CHECK(callContractFunction("_a()"), encodeArgs(7)); + ABI_CHECK(callContractFunction("_b(uint256)", 0), encodeArgs(0x21)); + ABI_CHECK(callContractFunction("_b(uint256)", 1), encodeArgs(0x22)); + ABI_CHECK(callContractFunction("_b(uint256)", 2), encodeArgs(0x23)); + ABI_CHECK(callContractFunction("_b(uint256)", 3), encodeArgs()); + ) +} + +BOOST_AUTO_TEST_CASE(decode_function_type) +{ + string sourceCode = R"( + contract D { + function () external returns (uint) public _a; + function D(function () external returns (uint) a) { + _a = a; + } + } + contract C { + function f() returns (uint) { + return 3; + } + function g(function () external returns (uint) _f) returns (uint) { + return _f(); + } + // uses "decode from memory" + function test1() returns (uint) { + D d = new D(this.f); + return d._a()(); + } + // uses "decode from calldata" + function test2() returns (uint) { + return this.g(this.f); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("test1()"), encodeArgs(3)); + ABI_CHECK(callContractFunction("test2()"), encodeArgs(3)); + ) +} + +BOOST_AUTO_TEST_CASE(decode_function_type_array) +{ + string sourceCode = R"( + contract D { + function () external returns (uint)[] public _a; + function D(function () external returns (uint)[] a) { + _a = a; + } + } + contract E { + function () external returns (uint)[3] public _a; + function E(function () external returns (uint)[3] a) { + _a = a; + } + } + contract C { + function f1() public returns (uint) { + return 1; + } + function f2() public returns (uint) { + return 2; + } + function f3() public returns (uint) { + return 3; + } + function g(function () external returns (uint)[] _f, uint i) public returns (uint) { + return _f[i](); + } + function h(function () external returns (uint)[3] _f, uint i) public returns (uint) { + return _f[i](); + } + // uses "decode from memory" + function test1_dynamic() public returns (uint) { + var x = new function() external returns (uint)[](3); + x[0] = this.f1; + x[1] = this.f2; + x[2] = this.f3; + D d = new D(x); + return d._a(2)(); + } + function test1_static() public returns (uint) { + E e = new E([this.f1, this.f2, this.f3]); + return e._a(2)(); + } + // uses "decode from calldata" + function test2_dynamic() public returns (uint) { + var x = new function() external returns (uint)[](3); + x[0] = this.f1; + x[1] = this.f2; + x[2] = this.f3; + return this.g(x, 0); + } + function test2_static() public returns (uint) { + return this.h([this.f1, this.f2, this.f3], 0); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("test1_static()"), encodeArgs(3)); + ABI_CHECK(callContractFunction("test1_dynamic()"), encodeArgs(3)); + ABI_CHECK(callContractFunction("test2_static()"), encodeArgs(1)); + ABI_CHECK(callContractFunction("test2_dynamic()"), encodeArgs(1)); + ) +} + +BOOST_AUTO_TEST_CASE(decode_from_memory_complex) +{ + string sourceCode = R"( + contract C { + uint public _a; + uint[] public _b; + bytes[2] public _c; + function C(uint a, uint[] b, bytes[2] c) { + _a = a; + _b = b; + _c = c; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C", encodeArgs( + 7, 0x60, 7 * 0x20, + // b + 3, 0x21, 0x22, 0x23, + // c + 0x40, 0x80, + 8, string("abcdefgh"), + 52, string("ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ") + )); + ABI_CHECK(callContractFunction("_a()"), encodeArgs(7)); + ABI_CHECK(callContractFunction("_b(uint256)", 0), encodeArgs(0x21)); + ABI_CHECK(callContractFunction("_b(uint256)", 1), encodeArgs(0x22)); + ABI_CHECK(callContractFunction("_b(uint256)", 2), encodeArgs(0x23)); + ABI_CHECK(callContractFunction("_b(uint256)", 3), encodeArgs()); + ABI_CHECK(callContractFunction("_c(uint256)", 0), encodeArgs(0x20, 8, string("abcdefgh"))); + ABI_CHECK(callContractFunction("_c(uint256)", 1), encodeArgs(0x20, 52, string("ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"))); + ABI_CHECK(callContractFunction("_c(uint256)", 2), encodeArgs()); + ) +} + +BOOST_AUTO_TEST_CASE(short_input_value_type) +{ + string sourceCode = R"( + contract C { + function f(uint a, uint b) public pure returns (uint) { return a; } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(uint256,uint256)", 1, 2), encodeArgs(1)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(64, 0)), encodeArgs(0)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(63, 0)), encodeArgs()); + ) +} + +BOOST_AUTO_TEST_CASE(short_input_array) +{ + string sourceCode = R"( + contract C { + function f(uint[] a) public pure returns (uint) { return 7; } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1) + bytes(31, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1) + bytes(32, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 2, 5, 6)), encodeArgs(7)); + ) +} + +BOOST_AUTO_TEST_CASE(short_dynamic_input_array) +{ + string sourceCode = R"( + contract C { + function f(bytes[1] a) public pure returns (uint) { return 7; } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[1])", encodeArgs(0x20)), encodeArgs()); + ) +} + +BOOST_AUTO_TEST_CASE(short_input_bytes) +{ + string sourceCode = R"( + contract C { + function e(bytes a) public pure returns (uint) { return 7; } + function f(bytes[] a) public pure returns (uint) { return 7; } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(5, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(6, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(7, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(8, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(5, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(6, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(7, 0)), encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(8, 0)), encodeArgs(7)); + ) +} + +BOOST_AUTO_TEST_CASE(cleanup_int_inside_arrays) +{ + string sourceCode = R"( + contract C { + enum E { A, B } + function f(uint16[] a) public pure returns (uint r) { assembly { r := mload(add(a, 0x20)) } } + function g(int16[] a) public pure returns (uint r) { assembly { r := mload(add(a, 0x20)) } } + function h(E[] a) public pure returns (uint r) { assembly { r := mload(add(a, 0x20)) } } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, 7), encodeArgs(7)); + ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, 7), encodeArgs(7)); + ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, u256("0xffff")), encodeArgs(u256("0xffff"))); + ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, u256("0xffff")), encodeArgs(u256(-1))); + ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, u256("0x1ffff")), encodeArgs(u256("0xffff"))); + ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, u256("0x10fff")), encodeArgs(u256("0x0fff"))); + ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 0), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 1), encodeArgs(u256(1))); + ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 2), encodeArgs()); + ) +} + +BOOST_AUTO_TEST_CASE(storage_ptr) +{ + string sourceCode = R"( + library L { + struct S { uint x; uint y; } + function f(uint[] storage r, S storage s) public returns (uint, uint, uint, uint) { + r[2] = 8; + s.x = 7; + return (r[0], r[1], s.x, s.y); + } + } + contract C { + uint8 x = 3; + L.S s; + uint[] r; + function f() public returns (uint, uint, uint, uint, uint, uint) { + r.length = 6; + r[0] = 1; + r[1] = 2; + r[2] = 3; + s.x = 11; + s.y = 12; + var (a, b, c, d) = L.f(r, s); + return (r[2], s.x, a, b, c, d); + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode, 0, "L"); + compileAndRun(sourceCode, 0, "C", bytes(), map{{"L", m_contractAddress}}); + ABI_CHECK(callContractFunction("f()"), encodeArgs(8, 7, 1, 2, 7, 12)); + ) +} + +BOOST_AUTO_TEST_CASE(struct_simple) +{ + string sourceCode = R"( + contract C { + struct S { uint a; uint8 b; uint8 c; bytes2 d; } + function f(S s) public pure returns (uint a, uint b, uint c, uint d) { + a = s.a; + b = s.b; + c = s.c; + d = uint(s.d); + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f((uint256,uint8,uint8,bytes2))", 1, 2, 3, "ab"), encodeArgs(1, 2, 3, 'a' * 0x100 + 'b')); + ) +} + +BOOST_AUTO_TEST_CASE(struct_cleanup) +{ + string sourceCode = R"( + contract C { + struct S { int16 a; uint8 b; bytes2 c; } + function f(S s) public pure returns (uint a, uint b, uint c) { + assembly { + a := mload(s) + b := mload(add(s, 0x20)) + c := mload(add(s, 0x40)) + } + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK( + callContractFunction("f((int16,uint8,bytes2))", 0xff010, 0xff0002, "abcd"), + encodeArgs(u256("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010"), 2, "ab") + ); + ) +} + +BOOST_AUTO_TEST_CASE(struct_short) +{ + string sourceCode = R"( + contract C { + struct S { int a; uint b; bytes16 c; } + function f(S s) public pure returns (S q) { + q = s; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK( + callContractFunction("f((int256,uint256,bytes16))", 0xff010, 0xff0002, "abcd"), + encodeArgs(0xff010, 0xff0002, "abcd") + ); + ABI_CHECK( + callContractFunctionNoEncoding("f((int256,uint256,bytes16))", encodeArgs(0xff010, 0xff0002) + bytes(32, 0)), + encodeArgs(0xff010, 0xff0002, 0) + ); + ABI_CHECK( + callContractFunctionNoEncoding("f((int256,uint256,bytes16))", encodeArgs(0xff010, 0xff0002) + bytes(31, 0)), + encodeArgs() + ); + ) +} + +BOOST_AUTO_TEST_CASE(struct_function) +{ + string sourceCode = R"( + contract C { + struct S { function () external returns (uint) f; uint b; } + function f(S s) public returns (uint, uint) { + return (s.f(), s.b); + } + function test() public returns (uint, uint) { + return this.f(S(this.g, 3)); + } + function g() public returns (uint) { return 7; } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("test()"), encodeArgs(7, 3)); + ) +} + +BOOST_AUTO_TEST_CASE(empty_struct) +{ + string sourceCode = R"( + contract C { + struct S { } + function f(uint a, S s, uint b) public pure returns (uint x, uint y) { + assembly { x := a y := b } + } + function g() public returns (uint, uint) { + return this.f(7, S(), 8); + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256,(),uint256)", 7, 8), encodeArgs(7, 8)); + ABI_CHECK(callContractFunction("g()"), encodeArgs(7, 8)); + ) +} + +BOOST_AUTO_TEST_CASE(mediocre_struct) +{ + string sourceCode = R"( + contract C { + struct S { C c; } + function f(uint a, S[2] s1, uint b) public returns (uint r1, C r2, uint r3) { + r1 = a; + r2 = s1[0].c; + r3 = b; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + string sig = "f(uint256,(address)[2],uint256)"; + ABI_CHECK(callContractFunction(sig, + 7, u256(u160(m_contractAddress)), 0, 8 + ), encodeArgs(7, u256(u160(m_contractAddress)), 8)); + ) +} + +BOOST_AUTO_TEST_CASE(mediocre2_struct) +{ + string sourceCode = R"( + contract C { + struct S { C c; uint[] x; } + function f(uint a, S[2] s1, uint b) public returns (uint r1, C r2, uint r3) { + r1 = a; + r2 = s1[0].c; + r3 = b; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + string sig = "f(uint256,(address,uint256[])[2],uint256)"; + ABI_CHECK(callContractFunction(sig, + 7, 0x60, 8, + 0x40, 7 * 0x20, + u256(u160(m_contractAddress)), 0x40, + 2, 0x11, 0x12, + 0x99, 0x40, + 4, 0x31, 0x32, 0x34, 0x35 + ), encodeArgs(7, u256(u160(m_contractAddress)), 8)); + ) +} + +BOOST_AUTO_TEST_CASE(complex_struct) +{ + string sourceCode = R"( + contract C { + enum E {A, B, C} + struct T { uint x; E e; uint8 y; } + struct S { C c; T[] t;} + function f(uint a, S[2] s1, S[] s2, uint b) public returns + (uint r1, C r2, uint r3, uint r4, C r5, uint r6, E r7, uint8 r8) { + r1 = a; + r2 = s1[0].c; + r3 = b; + r4 = s2.length; + r5 = s2[1].c; + r6 = s2[1].t.length; + r7 = s2[1].t[1].e; + r8 = s2[1].t[1].y; + } + } + )"; + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + string sig = "f(uint256,(address,(uint256,uint8,uint8)[])[2],(address,(uint256,uint8,uint8)[])[],uint256)"; + bytes args = encodeArgs( + 7, 0x80, 0x1e0, 8, + // S[2] s1 + 0x40, + 0x100, + // S s1[0] + u256(u160(m_contractAddress)), + 0x40, + // T s1[0].t + 1, // length + // s1[0].t[0] + 0x11, 1, 0x12, + // S s1[1] + 0, 0x40, + // T s1[1].t + 0, + // S[] s2 (0x1e0) + 2, // length + 0x40, 0xa0, + // S s2[0] + 0, 0x40, 0, + // S s2[1] + 0x1234, 0x40, + // s2[1].t + 3, // length + 0, 0, 0, + 0x21, 2, 0x22, + 0, 0, 0 + ); + ABI_CHECK(callContractFunction(sig, args), encodeArgs(7, u256(u160(m_contractAddress)), 8, 2, 0x1234, 3, 2, 0x22)); + // invalid enum value + args.data()[0x20 * 28] = 3; + ABI_CHECK(callContractFunction(sig, args), encodeArgs()); + ) +} + + + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces diff --git a/test/libsolidity/ABIEncoderTests.cpp b/test/libsolidity/ABIEncoderTests.cpp index af51edcc..49db9ce1 100644 --- a/test/libsolidity/ABIEncoderTests.cpp +++ b/test/libsolidity/ABIEncoderTests.cpp @@ -25,6 +25,8 @@ #include #include +#include + using namespace std; using namespace std::placeholders; using namespace dev::test; @@ -42,20 +44,6 @@ namespace test BOOST_CHECK_EQUAL(toHex(m_logs[0].data), toHex(DATA)); \ } while (false) -static string const NewEncoderPragma = "pragma experimental ABIEncoderV2;\n"; - -#define NEW_ENCODER(CODE) \ -{ \ - sourceCode = NewEncoderPragma + sourceCode; \ - { CODE } \ -} - -#define BOTH_ENCODERS(CODE) \ -{ \ - { CODE } \ - NEW_ENCODER(CODE) \ -} - BOOST_FIXTURE_TEST_SUITE(ABIEncoderTest, SolidityExecutionFramework) BOOST_AUTO_TEST_CASE(both_encoders_macro) @@ -74,7 +62,7 @@ BOOST_AUTO_TEST_CASE(value_types) string sourceCode = R"( contract C { event E(uint a, uint16 b, uint24 c, int24 d, bytes3 x, bool, C); - function f() { + function f() public { bytes6 x = hex"1bababababa2"; bool b; assembly { b := 7 } @@ -98,7 +86,7 @@ BOOST_AUTO_TEST_CASE(string_literal) string sourceCode = R"( contract C { event E(string, bytes20, string); - function f() { + function f() public { E("abcdef", "abcde", "abcdefabcdefgehabcabcasdfjklabcdefabcedefghabcabcasdfjklabcdefabcdefghabcabcasdfjklabcdeefabcdefghabcabcasdefjklabcdefabcdefghabcabcasdfjkl"); } } @@ -120,7 +108,7 @@ BOOST_AUTO_TEST_CASE(enum_type_cleanup) string sourceCode = R"( contract C { enum E { A, B } - function f(uint x) returns (E en) { + function f(uint x) public returns (E en) { assembly { en := x } } } @@ -138,7 +126,7 @@ BOOST_AUTO_TEST_CASE(conversion) string sourceCode = R"( contract C { event E(bytes4, bytes4, uint16, uint8, int16, int8); - function f() { + function f() public { bytes2 x; assembly { x := 0xf1f2f3f400000000000000000000000000000000000000000000000000000000 } uint8 a; uint16 b = 0x1ff; @@ -164,7 +152,7 @@ BOOST_AUTO_TEST_CASE(memory_array_one_dim) string sourceCode = R"( contract C { event E(uint a, int16[] b, uint c); - function f() { + function f() public { int16[] memory x = new int16[](3); assembly { for { let i := 0 } lt(i, 3) { i := add(i, 1) } { @@ -191,7 +179,7 @@ BOOST_AUTO_TEST_CASE(memory_array_two_dim) string sourceCode = R"( contract C { event E(uint a, int16[][2] b, uint c); - function f() { + function f() public { int16[][2] memory x; x[0] = new int16[](3); x[1] = new int16[](2); @@ -216,7 +204,7 @@ BOOST_AUTO_TEST_CASE(memory_byte_array) string sourceCode = R"( contract C { event E(uint a, bytes[] b, uint c); - function f() { + function f() public { bytes[] memory x = new bytes[](2); x[0] = "abcabcdefghjklmnopqrsuvwabcdefgijklmnopqrstuwabcdefgijklmnoprstuvw"; x[1] = "abcdefghijklmnopqrtuvwabcfghijklmnopqstuvwabcdeghijklmopqrstuvw"; @@ -243,7 +231,7 @@ BOOST_AUTO_TEST_CASE(storage_byte_array) bytes short; bytes long; event E(bytes s, bytes l); - function f() { + function f() public { short = "123456789012345678901234567890a"; long = "ffff123456789012345678901234567890afffffffff123456789012345678901234567890a"; E(short, long); @@ -267,7 +255,7 @@ BOOST_AUTO_TEST_CASE(storage_array) contract C { address[3] addr; event E(address[3] a); - function f() { + function f() public { assembly { sstore(0, sub(0, 1)) sstore(1, sub(0, 2)) @@ -290,7 +278,7 @@ BOOST_AUTO_TEST_CASE(storage_array_dyn) contract C { address[] addr; event E(address[] a); - function f() { + function f() public { addr.push(1); addr.push(2); addr.push(3); @@ -311,7 +299,7 @@ BOOST_AUTO_TEST_CASE(storage_array_compact) contract C { int72[] x; event E(int72[]); - function f() { + function f() public { x.push(-1); x.push(2); x.push(-3); @@ -339,7 +327,7 @@ BOOST_AUTO_TEST_CASE(external_function) contract C { event E(function(uint) external returns (uint), function(uint) external returns (uint)); function(uint) external returns (uint) g; - function f(uint) returns (uint) { + function f(uint) public returns (uint) { g = this.f; E(this.f, g); } @@ -347,7 +335,7 @@ BOOST_AUTO_TEST_CASE(external_function) )"; BOTH_ENCODERS( compileAndRun(sourceCode); - callContractFunction("f(uint256)"); + callContractFunction("f(uint256)", u256(0)); string functionIdF = asString(m_contractAddress.ref()) + asString(FixedHash<4>(dev::keccak256("f(uint256)")).ref()); REQUIRE_LOG_DATA(encodeArgs(functionIdF, functionIdF)); ) @@ -360,7 +348,7 @@ BOOST_AUTO_TEST_CASE(external_function_cleanup) event E(function(uint) external returns (uint), function(uint) external returns (uint)); // This test relies on the fact that g is stored in slot zero. function(uint) external returns (uint) g; - function f(uint) returns (uint) { + function f(uint) public returns (uint) { function(uint) external returns (uint)[1] memory h; assembly { sstore(0, sub(0, 1)) mstore(h, sub(0, 1)) } E(h[0], g); @@ -369,7 +357,7 @@ BOOST_AUTO_TEST_CASE(external_function_cleanup) )"; BOTH_ENCODERS( compileAndRun(sourceCode); - callContractFunction("f(uint256)"); + callContractFunction("f(uint256)", u256(0)); REQUIRE_LOG_DATA(encodeArgs(string(24, char(-1)), string(24, char(-1)))); ) } @@ -404,7 +392,7 @@ BOOST_AUTO_TEST_CASE(function_name_collision) // and by the ABI encoder string sourceCode = R"( contract C { - function f(uint x) returns (uint) { + function f(uint x) public returns (uint) { assembly { function abi_encode_t_uint256_to_t_uint256() { mstore(0, 7) @@ -432,7 +420,7 @@ BOOST_AUTO_TEST_CASE(structs) struct T { uint64[2] x; } S s; event e(uint16, S); - function f() returns (uint, S) { + function f() public returns (uint, S) { uint16 x = 7; s.a = 8; s.b = 9; diff --git a/test/libsolidity/ABITestsCommon.h b/test/libsolidity/ABITestsCommon.h new file mode 100644 index 00000000..2ef555f3 --- /dev/null +++ b/test/libsolidity/ABITestsCommon.h @@ -0,0 +1,43 @@ +/* + 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 . +*/ + +#include + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +static std::string const NewEncoderPragma = "pragma experimental ABIEncoderV2;\n"; + +#define NEW_ENCODER(CODE) \ +{ \ + sourceCode = NewEncoderPragma + sourceCode; \ + { CODE } \ +} + +#define BOTH_ENCODERS(CODE) \ +{ \ + { CODE } \ + NEW_ENCODER(CODE) \ +} + +} +} +} // end namespaces -- cgit v1.2.3 From 5a3dbb0269b3ff6b443a3cb4ccfc4f00eaba26b4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 13 Oct 2017 17:29:09 +0200 Subject: Cleanup and overflow checks for data pointers. --- libsolidity/codegen/ABIFunctions.cpp | 64 ++++++++++++++++++++++---------- libsolidity/codegen/ABIFunctions.h | 4 +- libsolidity/codegen/CompilerContext.cpp | 1 - libsolidity/codegen/ContractCompiler.cpp | 1 - test/libsolidity/ABIDecoderTests.cpp | 2 +- 5 files changed, 48 insertions(+), 24 deletions(-) diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index c9a9ff57..6648be06 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -111,9 +111,9 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) if (_fromMemory) functionName += "_fromMemory"; - return createFunction(functionName, [&]() { - solAssert(!_types.empty(), ""); + solAssert(!_types.empty(), ""); + return createFunction(functionName, [&]() { TypePointers decodingTypes; for (auto const& t: _types) decodingTypes.emplace_back(t->decodingType()); @@ -146,16 +146,22 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) stackPos++; } bool dynamic = decodingTypes[i]->isDynamicallyEncoded(); - Whiskers elementTempl(R"( + Whiskers elementTempl( + dynamic ? + R"( { - let offset := )" + string( - dynamic ? - "(add(headStart, ))" : - "" - ) + R"( + let offset := (add(headStart, )) + switch gt(offset, 0xffffffffffffffff) case 1 { revert(0, 0) } := (add(headStart, offset), dataEnd) } - )"); + )" : + R"( + { + let offset := + := (add(headStart, offset), dataEnd) + } + )" + ); elementTempl("load", _fromMemory ? "mload" : "calldataload"); elementTempl("values", boost::algorithm::join(valueNamesLocal, ", ")); elementTempl("pos", to_string(headPos)); @@ -1053,6 +1059,10 @@ string ABIFunctions::abiEncodingFunctionFunctionType( string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bool _forUseOnStack) { + // The decoding function has to perform bounds checks unless it decodes a value type. + // Conversely, bounds checks have to be performed before the decoding function + // of a value type is called. + TypePointer decodingType = _type.decodingType(); solAssert(decodingType, ""); @@ -1072,7 +1082,14 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo return abiDecodingFunctionStruct(*structType, _fromMemory); else if (auto const* functionType = dynamic_cast(decodingType.get())) return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack); + else + return abiDecodingFunctionValueType(_type, _fromMemory); +} +string ABIFunctions::abiDecodingFunctionValueType(const Type& _type, bool _fromMemory) +{ + TypePointer decodingType = _type.decodingType(); + solAssert(decodingType, ""); solAssert(decodingType->sizeOnStack() == 1, ""); solAssert(decodingType->isValueType(), ""); solAssert(decodingType->calldataEncodedSize() == 32, ""); @@ -1095,6 +1112,7 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo templ("cleanup", cleanupFunction(_type, true)); return templ.render(); }); + } string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory) @@ -1116,6 +1134,7 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from R"( // function (offset, end) -> array { + switch slt(add(offset, 0x1f), end) case 0 { revert(0, 0) } let length := array := ((length)) let dst := array @@ -1125,7 +1144,6 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from for { let i := 0 } lt(i, length) { i := add(i, 1) } { let elementPos := - mstore(dst, (elementPos, end)) dst := add(dst, 0x20) src := add(src, ) @@ -1145,10 +1163,6 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from if (dynamicBase) { templ("staticBoundsCheck", ""); - // The dynamic bounds check might not be needed (because we have an additional check - // one level deeper), but we keep it in just in case. This at least prevents - // the part one level deeper from reading the length from an out of bounds position. - templ("dynamicBoundsCheck", "switch gt(elementPos, end) case 1 { revert(0, 0) }"); templ("retrieveElementPos", "add(offset, " + load + "(src))"); templ("baseEncodedSize", "0x20"); } @@ -1156,7 +1170,6 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from { string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize()); templ("staticBoundsCheck", "switch gt(add(src, mul(length, " + baseEncodedSize + ")), end) case 1 { revert(0, 0) }"); - templ("dynamicBoundsCheck", ""); templ("retrieveElementPos", "src"); templ("baseEncodedSize", baseEncodedSize); } @@ -1184,6 +1197,7 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) templ = R"( // function (offset, end) -> arrayPos, length { + switch slt(add(offset, 0x1f), end) case 0 { revert(0, 0) } length := calldataload(offset) switch gt(length, 0xffffffffffffffff) case 1 { revert(0, 0) } arrayPos := add(offset, 0x20) @@ -1221,6 +1235,7 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _ Whiskers templ( R"( function (offset, end) -> array { + switch slt(add(offset, 0x1f), end) case 0 { revert(0, 0) } let length := (offset) array := ((length)) mstore(array, length) @@ -1277,10 +1292,18 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr auto decodingType = member.type->decodingType(); solAssert(decodingType, ""); bool dynamic = decodingType->isDynamicallyEncoded(); - Whiskers memberTempl(R"( - let offset := )" + string(dynamic ? "(add(headStart, ))" : "" ) + R"( - mstore(add(value, ), (add(headStart, offset), end)) - )"); + Whiskers memberTempl( + dynamic ? + R"( + let offset := (add(headStart, )) + switch gt(offset, 0xffffffffffffffff) case 1 { revert(0, 0) } + mstore(add(value, ), (add(headStart, offset), end)) + )" : + R"( + let offset := + mstore(add(value, ), (add(headStart, offset), end)) + )" + ); memberTempl("load", _fromMemory ? "mload" : "calldataload"); memberTempl("pos", to_string(headPos)); memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name))); @@ -1596,7 +1619,8 @@ string ABIFunctions::allocationFunction() function (size) -> memPtr { memPtr := mload() let newFreePtr := add(memPtr, size) - switch lt(newFreePtr, memPtr) case 1 { revert(0, 0) } + // protect against overflow + switch or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) case 1 { revert(0, 0) } mstore(, newFreePtr) } )") diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index 855b2a11..2b582e84 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -160,7 +160,7 @@ private: bool _fromStack ); - /// @returns the name of the ABI decodinf function for the given type + /// @returns the name of the ABI decoding function for the given type /// and queues the generation of the function to the requested functions. /// The caller has to ensure that no out of bounds access (at least to the static /// part) can happen inside this function. @@ -172,6 +172,8 @@ private: bool _forUseOnStack ); + /// Part of @a abiDecodingFunction for value types. + std::string abiDecodingFunctionValueType(Type const& _type, bool _fromMemory); /// Part of @a abiDecodingFunction for "regular" array types. std::string abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory); /// Part of @a abiDecodingFunction for calldata array types. diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 35d763f1..ce9c3b7f 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -318,7 +318,6 @@ void CompilerContext::appendInlineAssembly( ErrorList errors; ErrorReporter errorReporter(errors); -// cout << _assembly << endl; auto scanner = make_shared(CharStream(_assembly), "--CODEGEN--"); auto parserResult = assembly::Parser(errorReporter).parse(scanner); #ifdef SOL_OUTPUT_ASM diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 1ae44165..a81ba518 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -901,7 +901,6 @@ void ContractCompiler::appendMissingFunctions() } m_context.appendMissingLowLevelFunctions(); string abiFunctions = m_context.abiFunctions().requestedFunctions(); -// cout << abiFunctions << endl; if (!abiFunctions.empty()) m_context.appendInlineAssembly("{" + move(abiFunctions) + "}", {}, true); } diff --git a/test/libsolidity/ABIDecoderTests.cpp b/test/libsolidity/ABIDecoderTests.cpp index 6a4b5723..c0c017e4 100644 --- a/test/libsolidity/ABIDecoderTests.cpp +++ b/test/libsolidity/ABIDecoderTests.cpp @@ -40,7 +40,7 @@ namespace test BOOST_FIXTURE_TEST_SUITE(ABIDecoderTest, SolidityExecutionFramework) -BOOST_AUTO_TEST_CASE(BOTH_ENCODERS_macro) +BOOST_AUTO_TEST_CASE(both_encoders_macro) { // This tests that the "both decoders macro" at least runs twice and // modifies the source. -- cgit v1.2.3 From 9d8e3ff395006c0c6285f70c13bd470e9374bda3 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 23 Nov 2017 19:14:35 +0100 Subject: Also test short input for old decoder. --- test/libsolidity/ABIDecoderTests.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/libsolidity/ABIDecoderTests.cpp b/test/libsolidity/ABIDecoderTests.cpp index c0c017e4..15c04b37 100644 --- a/test/libsolidity/ABIDecoderTests.cpp +++ b/test/libsolidity/ABIDecoderTests.cpp @@ -449,11 +449,13 @@ BOOST_AUTO_TEST_CASE(short_input_value_type) function f(uint a, uint b) public pure returns (uint) { return a; } } )"; - NEW_ENCODER( + bool newDecoder = false; + BOTH_ENCODERS( compileAndRun(sourceCode); ABI_CHECK(callContractFunction("f(uint256,uint256)", 1, 2), encodeArgs(1)); ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(64, 0)), encodeArgs(0)); - ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(63, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(63, 0)), newDecoder ? encodeArgs() : encodeArgs(0)); + newDecoder = true; ) } @@ -464,13 +466,15 @@ BOOST_AUTO_TEST_CASE(short_input_array) function f(uint[] a) public pure returns (uint) { return 7; } } )"; - NEW_ENCODER( + bool newDecoder = false; + BOTH_ENCODERS( compileAndRun(sourceCode); ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 0)), encodeArgs(7)); - ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1)), encodeArgs()); - ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1) + bytes(31, 0)), encodeArgs()); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1)), newDecoder ? encodeArgs() : encodeArgs(7)); + ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1) + bytes(31, 0)), newDecoder ? encodeArgs() : encodeArgs(7)); ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1) + bytes(32, 0)), encodeArgs(7)); ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 2, 5, 6)), encodeArgs(7)); + newDecoder = true; ) } -- cgit v1.2.3 From 2ebc9953e42b40995f049e95e08343e7c59098ee Mon Sep 17 00:00:00 2001 From: wbt Date: Wed, 29 Nov 2017 14:21:21 -0500 Subject: Minor update in contracts Fix typos and prevent example code from needing horizontal scroll bar --- docs/contracts.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contracts.rst b/docs/contracts.rst index ef1e1be6..2b0956bb 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -211,7 +211,7 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value contract E is C { function g() { C c = new C(); - uint val = compute(3, 5); // acces to internal member (from derivated to parent contract) + uint val = compute(3, 5); // access to internal member (from derived to parent contract) } } -- cgit v1.2.3 From d37e6ba1c7ade678644b5669b120bd76b72f39ea Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 30 Mar 2017 11:16:28 +0100 Subject: Add target selection helpers to StandardCompiler --- libsolidity/interface/StandardCompiler.cpp | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 430739ac..9aec7abb 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -131,6 +131,62 @@ StringMap createSourceList(Json::Value const& _input) return sources; } +bool isTargetRequired(Json::Value const& _targets, string const& _target) +{ + for (auto const& target: _targets) + /// @TODO support sub-matching, e.g "evm" matches "evm.assembly" + if (target == "*" || target == _target) + return true; + return false; +} + +/// +/// @a _targets is a JSON object containining a two-level hashmap, where the first level is the filename, +/// the second level is the contract name and the value is an array of target names to be requested for that contract. +/// @a _file is the current file +/// @a _contract is the current contract +/// @a _target is the current target name +/// +/// @returns true if the @a _targets has a match for the requested target in the specific file / contract. +/// +/// In @a _targets the use of '*' as a wildcard is permitted. +/// +/// @TODO optimise this. Perhaps flatten the structure upfront. +/// +bool isTargetRequired(Json::Value const& _targets, string const& _file, string const& _contract, string const& _target) +{ + if (!_targets.isObject()) + return false; + + for (auto const& file: { _file, string("*") }) + if (_targets.isMember(file) && _targets[file].isObject()) + { + if (_contract.empty()) + { + /// Special case for SourceUnit-level targets (such as AST) + if ( + _targets[file].isMember("") && + _targets[file][""].isArray() && + isTargetRequired(_targets[file][""], _target) + ) + return true; + } + else + { + /// Regular case for Contract-level targets + for (auto const& contract: { _contract, string("*") }) + if ( + _targets[file].isMember(contract) && + _targets[file][contract].isArray() && + isTargetRequired(_targets[file][contract], _target) + ) + return true; + } + } + + return false; +} + Json::Value formatLinkReferences(std::map const& linkReferences) { Json::Value ret(Json::objectValue); -- cgit v1.2.3 From 8da245cca3228be3e8d7908f184a327dcaf16b29 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 12 Apr 2017 10:37:04 +0100 Subject: Limit output according to the selected targets in StandardCompiler --- Changelog.md | 1 + libsolidity/interface/StandardCompiler.cpp | 52 ++++++++++++++++++------------ 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/Changelog.md b/Changelog.md index f69a39ce..8f610a53 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ Features: * Allow constant variables to be used as array length * Code Generator: New ABI decoder which supports structs and arbitrarily nested arrays and checks input size (activate using ``pragma experimental ABIEncoderV2;``. + * Standard JSON: Support the ``outputSelection`` field for selective compilation of target artifacts. * Syntax Checker: Turn the usage of ``callcode`` into an error as experimental 0.5.0 feature. * Type Checker: Improve address checksum warning. * Type Checker: More detailed errors for invalid array lengths (such as division by zero). diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 9aec7abb..8a4e81e4 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -452,8 +452,10 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) { Json::Value sourceResult = Json::objectValue; sourceResult["id"] = sourceIndex++; - sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); - sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); + if (isTargetRequired(outputSelection, sourceName, "", "ast")) + sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); + if (isTargetRequired(outputSelection, sourceName, "", "legacyAST")) + sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); output["sources"][sourceName] = sourceResult; } @@ -467,28 +469,38 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) // ABI, documentation and metadata Json::Value contractData(Json::objectValue); - contractData["abi"] = m_compilerStack.contractABI(contractName); - contractData["metadata"] = m_compilerStack.metadata(contractName); - contractData["userdoc"] = m_compilerStack.natspecUser(contractName); - contractData["devdoc"] = m_compilerStack.natspecDev(contractName); + if (isTargetRequired(outputSelection, file, name, "abi")) + contractData["abi"] = m_compilerStack.contractABI(contractName); + if (isTargetRequired(outputSelection, file, name, "metadata")) + contractData["metadata"] = m_compilerStack.metadata(contractName); + if (isTargetRequired(outputSelection, file, name, "userdoc")) + contractData["userdoc"] = m_compilerStack.natspecUser(contractName); + if (isTargetRequired(outputSelection, file, name, "devdoc")) + contractData["devdoc"] = m_compilerStack.natspecDev(contractName); // EVM Json::Value evmData(Json::objectValue); // @TODO: add ir - evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input)); - evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input)); - evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName); - evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName); - - evmData["bytecode"] = collectEVMObject( - m_compilerStack.object(contractName), - m_compilerStack.sourceMapping(contractName) - ); - - evmData["deployedBytecode"] = collectEVMObject( - m_compilerStack.runtimeObject(contractName), - m_compilerStack.runtimeSourceMapping(contractName) - ); + if (isTargetRequired(outputSelection, file, name, "evm.assembly")) + evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input)); + if (isTargetRequired(outputSelection, file, name, "evm.legacyAssembly")) + evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input)); + if (isTargetRequired(outputSelection, file, name, "evm.methodIdentifiers")) + evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName); + if (isTargetRequired(outputSelection, file, name, "evm.gasEstimates")) + evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName); + + if (isTargetRequired(outputSelection, file, name, "evm.bytecode")) + evmData["bytecode"] = collectEVMObject( + m_compilerStack.object(contractName), + m_compilerStack.sourceMapping(contractName) + ); + + if (isTargetRequired(outputSelection, file, name, "evm.deployedBytecode")) + evmData["deployedBytecode"] = collectEVMObject( + m_compilerStack.runtimeObject(contractName), + m_compilerStack.runtimeSourceMapping(contractName) + ); contractData["evm"] = evmData; -- cgit v1.2.3 From 73d25c883fddaa961a8276f1e892f935c3f6b2db Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 20 Apr 2017 23:34:12 +0100 Subject: Specify output selection in tests --- test/libsolidity/StandardCompiler.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 4504946b..091207e8 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -179,6 +179,14 @@ BOOST_AUTO_TEST_CASE(basic_compilation) "fileA": { "content": "contract A { }" } + }, + "settings": { + "outputSelection": { + "fileA": { + "A": [ "abi", "devdoc", "userdoc", "evm.bytecode", "evm.assembly", "evm.gasEstimates", "metadata" ], + "": [ "legacyAST" ] + } + } } } )"; -- cgit v1.2.3 From 123d85a19e7f20f06d07fd9a0e4201a3d38ce252 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 10 Aug 2017 21:34:21 +0100 Subject: Request all outputs in JSONCompiler --- solc/jsonCompiler.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index 7e797a62..23feaa2a 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -128,6 +128,11 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback input["settings"]["optimizer"]["enabled"] = _optimize; input["settings"]["optimizer"]["runs"] = 200; + // Enable all SourceUnit-level outputs. + input["settings"]["outputSelection"]["*"][""][0] = "*"; + // Enable all Contract-level outputs. + input["settings"]["outputSelection"]["*"]["*"][0] = "*"; + StandardCompiler compiler(wrapReadCallback(_readCallback)); Json::Value ret = compiler.compile(input); -- cgit v1.2.3 From bbcec95bac84314d947814ace7da85f69168df94 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 19 Oct 2017 14:09:40 +0100 Subject: Add workaround for bytecode/deployedBytecode selection --- libsolidity/interface/StandardCompiler.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 8a4e81e4..d9ad0759 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -187,6 +187,14 @@ bool isTargetRequired(Json::Value const& _targets, string const& _file, string c return false; } +bool isTargetRequired(Json::Value const& _targets, string const& _file, string const& _contract, vector const& _requested) +{ + for (auto const& requested: _requested) + if (isTargetRequired(_targets, _file, _contract, requested)) + return true; + return false; +} + Json::Value formatLinkReferences(std::map const& linkReferences) { Json::Value ret(Json::objectValue); @@ -490,13 +498,23 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) if (isTargetRequired(outputSelection, file, name, "evm.gasEstimates")) evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName); - if (isTargetRequired(outputSelection, file, name, "evm.bytecode")) + if (isTargetRequired( + outputSelection, + file, + name, + { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" } + )) evmData["bytecode"] = collectEVMObject( m_compilerStack.object(contractName), m_compilerStack.sourceMapping(contractName) ); - if (isTargetRequired(outputSelection, file, name, "evm.deployedBytecode")) + if (isTargetRequired( + outputSelection, + file, + name, + { "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" } + )) evmData["deployedBytecode"] = collectEVMObject( m_compilerStack.runtimeObject(contractName), m_compilerStack.runtimeSourceMapping(contractName) -- cgit v1.2.3 From 59bed63dbcad9c813f621ec1f49eb9207286fd33 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 19 Oct 2017 14:24:33 +0100 Subject: Update standard json documentation --- docs/using-the-compiler.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 7f82df70..c12750c8 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -138,7 +138,7 @@ Input Description // ewasm.wasm - eWASM binary format (not supported atm) // // Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select every - // target part of that output. + // target part of that output. Additionally, `*` can be used as a wildcard to request everything. // outputSelection: { // Enable the metadata and bytecode outputs of every single contract. -- cgit v1.2.3 From b2023196a295a0e41bdc5c2b848b5c09fc581691 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 22 Nov 2017 12:55:44 +0000 Subject: Rename target selection to use the word artifact --- libsolidity/interface/StandardCompiler.cpp | 64 +++++++++++++++--------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index d9ad0759..6fb70584 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -131,43 +131,43 @@ StringMap createSourceList(Json::Value const& _input) return sources; } -bool isTargetRequired(Json::Value const& _targets, string const& _target) +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact) { - for (auto const& target: _targets) + for (auto const& artifact: _outputSelection) /// @TODO support sub-matching, e.g "evm" matches "evm.assembly" - if (target == "*" || target == _target) + if (artifact == "*" || artifact == _artifact) return true; return false; } /// -/// @a _targets is a JSON object containining a two-level hashmap, where the first level is the filename, -/// the second level is the contract name and the value is an array of target names to be requested for that contract. +/// @a _outputSelection is a JSON object containining a two-level hashmap, where the first level is the filename, +/// the second level is the contract name and the value is an array of artifact names to be requested for that contract. /// @a _file is the current file /// @a _contract is the current contract -/// @a _target is the current target name +/// @a _artifact is the current artifact name /// -/// @returns true if the @a _targets has a match for the requested target in the specific file / contract. +/// @returns true if the @a _outputSelection has a match for the requested target in the specific file / contract. /// -/// In @a _targets the use of '*' as a wildcard is permitted. +/// In @a _outputSelection the use of '*' as a wildcard is permitted. /// /// @TODO optimise this. Perhaps flatten the structure upfront. /// -bool isTargetRequired(Json::Value const& _targets, string const& _file, string const& _contract, string const& _target) +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact) { - if (!_targets.isObject()) + if (!_outputSelection.isObject()) return false; for (auto const& file: { _file, string("*") }) - if (_targets.isMember(file) && _targets[file].isObject()) + if (_outputSelection.isMember(file) && _outputSelection[file].isObject()) { if (_contract.empty()) { /// Special case for SourceUnit-level targets (such as AST) if ( - _targets[file].isMember("") && - _targets[file][""].isArray() && - isTargetRequired(_targets[file][""], _target) + _outputSelection[file].isMember("") && + _outputSelection[file][""].isArray() && + isArtifactRequested(_outputSelection[file][""], _artifact) ) return true; } @@ -176,9 +176,9 @@ bool isTargetRequired(Json::Value const& _targets, string const& _file, string c /// Regular case for Contract-level targets for (auto const& contract: { _contract, string("*") }) if ( - _targets[file].isMember(contract) && - _targets[file][contract].isArray() && - isTargetRequired(_targets[file][contract], _target) + _outputSelection[file].isMember(contract) && + _outputSelection[file][contract].isArray() && + isArtifactRequested(_outputSelection[file][contract], _artifact) ) return true; } @@ -187,10 +187,10 @@ bool isTargetRequired(Json::Value const& _targets, string const& _file, string c return false; } -bool isTargetRequired(Json::Value const& _targets, string const& _file, string const& _contract, vector const& _requested) +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector const& _artifacts) { - for (auto const& requested: _requested) - if (isTargetRequired(_targets, _file, _contract, requested)) + for (auto const& artifact: _artifacts) + if (isArtifactRequested(_outputSelection, _file, _contract, artifact)) return true; return false; } @@ -460,9 +460,9 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) { Json::Value sourceResult = Json::objectValue; sourceResult["id"] = sourceIndex++; - if (isTargetRequired(outputSelection, sourceName, "", "ast")) + if (isArtifactRequested(outputSelection, sourceName, "", "ast")) sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); - if (isTargetRequired(outputSelection, sourceName, "", "legacyAST")) + if (isArtifactRequested(outputSelection, sourceName, "", "legacyAST")) sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); output["sources"][sourceName] = sourceResult; } @@ -477,28 +477,28 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) // ABI, documentation and metadata Json::Value contractData(Json::objectValue); - if (isTargetRequired(outputSelection, file, name, "abi")) + if (isArtifactRequested(outputSelection, file, name, "abi")) contractData["abi"] = m_compilerStack.contractABI(contractName); - if (isTargetRequired(outputSelection, file, name, "metadata")) + if (isArtifactRequested(outputSelection, file, name, "metadata")) contractData["metadata"] = m_compilerStack.metadata(contractName); - if (isTargetRequired(outputSelection, file, name, "userdoc")) + if (isArtifactRequested(outputSelection, file, name, "userdoc")) contractData["userdoc"] = m_compilerStack.natspecUser(contractName); - if (isTargetRequired(outputSelection, file, name, "devdoc")) + if (isArtifactRequested(outputSelection, file, name, "devdoc")) contractData["devdoc"] = m_compilerStack.natspecDev(contractName); // EVM Json::Value evmData(Json::objectValue); // @TODO: add ir - if (isTargetRequired(outputSelection, file, name, "evm.assembly")) + if (isArtifactRequested(outputSelection, file, name, "evm.assembly")) evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input)); - if (isTargetRequired(outputSelection, file, name, "evm.legacyAssembly")) + if (isArtifactRequested(outputSelection, file, name, "evm.legacyAssembly")) evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input)); - if (isTargetRequired(outputSelection, file, name, "evm.methodIdentifiers")) + if (isArtifactRequested(outputSelection, file, name, "evm.methodIdentifiers")) evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName); - if (isTargetRequired(outputSelection, file, name, "evm.gasEstimates")) + if (isArtifactRequested(outputSelection, file, name, "evm.gasEstimates")) evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName); - if (isTargetRequired( + if (isArtifactRequested( outputSelection, file, name, @@ -509,7 +509,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) m_compilerStack.sourceMapping(contractName) ); - if (isTargetRequired( + if (isArtifactRequested( outputSelection, file, name, -- cgit v1.2.3 From 3576ccf5b36f41d36898919eee7316b9c7c49d41 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 22 Nov 2017 13:35:01 +0000 Subject: Simplify target selection code --- libsolidity/interface/StandardCompiler.cpp | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 6fb70584..ad01821e 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -161,27 +161,18 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _fil for (auto const& file: { _file, string("*") }) if (_outputSelection.isMember(file) && _outputSelection[file].isObject()) { - if (_contract.empty()) - { - /// Special case for SourceUnit-level targets (such as AST) + /// For SourceUnit-level targets (such as AST) only allow empty name, otherwise + /// for Contract-level targets try both contract name and wildcard + vector contracts{ _contract }; + if (!_contract.empty()) + contracts.push_back("*"); + for (auto const& contract: contracts) if ( - _outputSelection[file].isMember("") && - _outputSelection[file][""].isArray() && - isArtifactRequested(_outputSelection[file][""], _artifact) + _outputSelection[file].isMember(contract) && + _outputSelection[file][contract].isArray() && + isArtifactRequested(_outputSelection[file][contract], _artifact) ) return true; - } - else - { - /// Regular case for Contract-level targets - for (auto const& contract: { _contract, string("*") }) - if ( - _outputSelection[file].isMember(contract) && - _outputSelection[file][contract].isArray() && - isArtifactRequested(_outputSelection[file][contract], _artifact) - ) - return true; - } } return false; -- cgit v1.2.3 From f7538daaf4fde2cbad2d0856f13a4ad773df9c85 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 30 Nov 2017 01:23:22 +0100 Subject: Preparation for 0.4.19 release. --- Changelog.md | 8 ++++---- docs/bugs_by_version.json | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Changelog.md b/Changelog.md index 8f610a53..9a30986a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,14 +1,14 @@ -### 0.4.19 (unreleased) +### 0.4.19 (2017-11-30) Features: - * Allow constant variables to be used as array length * Code Generator: New ABI decoder which supports structs and arbitrarily nested - arrays and checks input size (activate using ``pragma experimental ABIEncoderV2;``. + arrays and checks input size (activate using ``pragma experimental ABIEncoderV2;``). + * General: Allow constant variables to be used as array length. + * Inline Assembly: ``if`` statement. * Standard JSON: Support the ``outputSelection`` field for selective compilation of target artifacts. * Syntax Checker: Turn the usage of ``callcode`` into an error as experimental 0.5.0 feature. * Type Checker: Improve address checksum warning. * Type Checker: More detailed errors for invalid array lengths (such as division by zero). - * Inline Assembly: ``if`` statement. Bugfixes: diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index cca45428..3a8ff9a1 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -397,6 +397,10 @@ "bugs": [], "released": "2017-10-18" }, + "0.4.19": { + "bugs": [], + "released": "2017-11-30" + }, "0.4.2": { "bugs": [ "ZeroFunctionSelector", -- cgit v1.2.3 From 8d551a3ae411ed56bea6f35fca8c792b8b0d32c3 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 30 Nov 2017 14:29:16 +0100 Subject: Store bytecode in any case. --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a2301c42..708d3620 100644 --- a/.travis.yml +++ b/.travis.yml @@ -184,10 +184,8 @@ before_script: script: - test $SOLC_EMSCRIPTEN != On || (scripts/test_emscripten.sh) - - test $? == 0 || SOLC_STOREBYTECODE=Off - test $SOLC_DOCS != On || (scripts/docs.sh) - test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh) - - test $? == 0 || SOLC_STOREBYTECODE=Off - test $SOLC_STOREBYTECODE != On || (cd $TRAVIS_BUILD_DIR && scripts/bytecodecompare/storebytecode.sh) deploy: -- cgit v1.2.3