diff options
author | Alex Beregszaszi <alex@rtfs.hu> | 2017-01-26 22:42:34 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-26 22:42:34 +0800 |
commit | 102fd7ee5daeb7d7a7bb1254cf0ce71a23ad1220 (patch) | |
tree | 0ad00aad0ed6e5dfb382b4a7443772319ec15d34 | |
parent | 024061b8280d51a11d91dc3347603c9281224705 (diff) | |
parent | f610ba77a493f8a6f270c72222d34eedfb183722 (diff) | |
download | dexon-solidity-102fd7ee5daeb7d7a7bb1254cf0ce71a23ad1220.tar dexon-solidity-102fd7ee5daeb7d7a7bb1254cf0ce71a23ad1220.tar.gz dexon-solidity-102fd7ee5daeb7d7a7bb1254cf0ce71a23ad1220.tar.bz2 dexon-solidity-102fd7ee5daeb7d7a7bb1254cf0ce71a23ad1220.tar.lz dexon-solidity-102fd7ee5daeb7d7a7bb1254cf0ce71a23ad1220.tar.xz dexon-solidity-102fd7ee5daeb7d7a7bb1254cf0ce71a23ad1220.tar.zst dexon-solidity-102fd7ee5daeb7d7a7bb1254cf0ce71a23ad1220.zip |
Merge pull request #1604 from ethereum/checksums
Warn about invalid checksums of addresses.
-rw-r--r-- | Changelog.md | 1 | ||||
-rw-r--r-- | docs/types.rst | 13 | ||||
-rw-r--r-- | libdevcore/CommonData.cpp | 40 | ||||
-rw-r--r-- | libdevcore/CommonData.h | 5 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.cpp | 10 | ||||
-rw-r--r-- | libsolidity/ast/AST.cpp | 23 | ||||
-rw-r--r-- | libsolidity/ast/AST.h | 5 | ||||
-rw-r--r-- | libsolidity/ast/Types.cpp | 8 | ||||
-rw-r--r-- | libsolidity/ast/Types.h | 2 | ||||
-rw-r--r-- | libsolidity/codegen/ExpressionCompiler.cpp | 1 | ||||
-rwxr-xr-x | scripts/tests.sh | 16 | ||||
-rw-r--r-- | solc/CommandLineInterface.cpp | 5 | ||||
-rwxr-xr-x | test/cmdlineTests.sh | 51 | ||||
-rw-r--r-- | test/libdevcore/Checksum.cpp | 83 | ||||
-rw-r--r-- | test/libsolidity/SolidityNameAndTypeResolution.cpp | 49 |
15 files changed, 294 insertions, 18 deletions
diff --git a/Changelog.md b/Changelog.md index a8b66abc..71a1e096 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Features: * Compiler interface: Report source location for "stack too deep" errors. * AST: Use deterministic node identifiers. * Type system: Introduce type identifier strings. + * Type checker: Warn about invalid checksum for addresses and deduce type from valid ones. * Metadata: Do not include platform in the version number. * Metadata: Add option to store sources as literal content. * Code generator: Extract array utils into low-level functions. diff --git a/docs/types.rst b/docs/types.rst index 6b67e684..3fb1db95 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -171,6 +171,19 @@ Fixed Point Numbers **COMING SOON...** +.. index:: address, literal;address + +.. _address_literals: + +Address Literals +---------------- + +Hexadecimal literals that pass the address checksum test, for example +``0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF`` are of ``address`` type. +Hexadecimal literals that are between 39 and 41 digits +long and do not pass the checksum test produce +a warning and are treated as regular rational number literals. + .. index:: literal, literal;rational .. _rational_literals: diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index 062d1b29..a53d9628 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -19,8 +19,12 @@ * @date 2014 */ -#include "CommonData.h" -#include "Exceptions.h" +#include <libdevcore/CommonData.h> +#include <libdevcore/Exceptions.h> +#include <libdevcore/SHA3.h> + +#include <boost/algorithm/string.hpp> + using namespace std; using namespace dev; @@ -95,3 +99,35 @@ bytes dev::fromHex(std::string const& _s, WhenError _throw) } return ret; } + + +bool dev::passesAddressChecksum(string const& _str, bool _strict) +{ + string s = _str.substr(0, 2) == "0x" ? _str.substr(2) : _str; + + if (s.length() != 40) + return false; + + if (!_strict && ( + _str.find_first_of("abcdef") == string::npos || + _str.find_first_of("ABCDEF") == string::npos + )) + return true; + + h256 hash = keccak256(boost::algorithm::to_lower_copy(s, std::locale::classic())); + for (size_t i = 0; i < 40; ++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; + } + return true; +} diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 5ffcdcca..e0a6d221 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -179,4 +179,9 @@ bool contains(T const& _t, V const& _v) return std::end(_t) != std::find(std::begin(_t), std::end(_t), _v); } +/// @returns true iff @a _str passess the hex address checksum test. +/// @param _strict if false, hex strings with only uppercase or only lowercase letters +/// are considered valid. +bool passesAddressChecksum(std::string const& _str, bool _strict); + } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index adb3d54f..533c787b 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1565,6 +1565,16 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) void TypeChecker::endVisit(Literal const& _literal) { + if (_literal.looksLikeAddress()) + { + if (_literal.passesAddressChecksum()) + { + _literal.annotation().type = make_shared<IntegerType>(0, IntegerType::Modifier::Address); + return; + } + else + warning(_literal.location(), "This looks like an address but has an invalid checksum."); + } _literal.annotation().type = Type::forLiteral(_literal); if (!_literal.annotation().type) fatalTypeError(_literal.location(), "Invalid literal value."); diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 8a43c3f7..616de54e 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -20,8 +20,6 @@ * Solidity abstract syntax tree. */ -#include <algorithm> -#include <functional> #include <libsolidity/interface/Utils.h> #include <libsolidity/ast/AST.h> #include <libsolidity/ast/ASTVisitor.h> @@ -30,6 +28,11 @@ #include <libdevcore/SHA3.h> +#include <boost/algorithm/string.hpp> + +#include <algorithm> +#include <functional> + using namespace std; using namespace dev; using namespace dev::solidity; @@ -522,3 +525,19 @@ IdentifierAnnotation& Identifier::annotation() const m_annotation = new IdentifierAnnotation(); return static_cast<IdentifierAnnotation&>(*m_annotation); } + +bool Literal::looksLikeAddress() const +{ + if (subDenomination() != SubDenomination::None) + return false; + + string lit = value(); + return lit.substr(0, 2) == "0x" && abs(int(lit.length()) - 42) <= 1; +} + +bool Literal::passesAddressChecksum() const +{ + string lit = value(); + solAssert(lit.substr(0, 2) == "0x", "Expected hex prefix"); + return dev::passesAddressChecksum(lit, true); +} diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 4c75f7ea..743fdaa1 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -1584,6 +1584,11 @@ public: SubDenomination subDenomination() const { return m_subDenomination; } + /// @returns true if this looks like a checksummed address. + bool looksLikeAddress() const; + /// @returns true if it passes the address checksum test. + bool passesAddressChecksum() const; + private: Token::Value m_token; ASTPointer<ASTString> m_value; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index cefd0603..971e1f18 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -405,6 +405,14 @@ string IntegerType::toString(bool) const return prefix + dev::toString(m_bits); } +u256 IntegerType::literalValue(Literal const* _literal) const +{ + solAssert(m_modifier == Modifier::Address, ""); + solAssert(_literal, ""); + solAssert(_literal->value().substr(0, 2) == "0x", ""); + return u256(_literal->value()); +} + TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const { if ( diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 1e94631e..770cbb30 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -314,6 +314,8 @@ public: virtual std::string toString(bool _short) const override; + virtual u256 literalValue(Literal const* _literal) const override; + virtual TypePointer encodingType() const override { return shared_from_this(); } virtual TypePointer interfaceType(bool) const override { return shared_from_this(); } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index fe0eeb1c..bda4e04d 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1308,6 +1308,7 @@ void ExpressionCompiler::endVisit(Literal const& _literal) { case Type::Category::RationalNumber: case Type::Category::Bool: + case Type::Category::Integer: m_context << type->literalValue(&_literal); break; case Type::Category::StringLiteral: diff --git a/scripts/tests.sh b/scripts/tests.sh index dfbda734..cf18cb26 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -30,20 +30,8 @@ set -e REPO_ROOT="$(dirname "$0")"/.. - # Compile all files in std and examples. - -for f in "$REPO_ROOT"/std/*.sol -do - echo "Compiling $f..." - set +e - output=$("$REPO_ROOT"/build/solc/solc "$f" 2>&1) - 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 -done +echo "Running commandline tests..." +"$REPO_ROOT/test/cmdlineTests.sh" # This conditional is only needed because we don't have a working Homebrew # install for `eth` at the time of writing, so we unzip the ZIP file locally diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 3e3053ce..2c1f0644 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -436,6 +436,11 @@ bool CommandLineInterface::parseLibraryOption(string const& _input) string addrString(lib.begin() + colon + 1, lib.end()); boost::trim(libName); boost::trim(addrString); + if (!passesAddressChecksum(addrString, false)) + { + cerr << "Invalid checksum on library address \"" << libName << "\": " << addrString << endl; + return false; + } bytes binAddr = fromHex(addrString); h160 address(binAddr, h160::AlignRight); if (binAddr.size() > 20 || address == h160()) diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh new file mode 100755 index 00000000..fc48654a --- /dev/null +++ b/test/cmdlineTests.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +#------------------------------------------------------------------------------ +# Bash script to run commandline Solidity tests. +# +# The documentation for solidity is hosted at: +# +# https://solidity.readthedocs.org +# +# ------------------------------------------------------------------------------ +# 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 <http://www.gnu.org/licenses/> +# +# (c) 2016 solidity contributors. +#------------------------------------------------------------------------------ + +set -e + +REPO_ROOT="$(dirname "$0")"/.. +SOLC="$REPO_ROOT/build/solc/solc" + + # Compile all files in std and examples. + +for f in "$REPO_ROOT"/std/*.sol +do + echo "Compiling $f..." + set +e + output=$("$SOLC" "$f" 2>&1) + 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 +done + +# Test library checksum +echo 'contact C {}' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222 +! echo 'contract C {}' | "$SOLC" --link --libraries a:0x80f20564390eAe531E810af625A22f51385Cd222 2>/dev/null diff --git a/test/libdevcore/Checksum.cpp b/test/libdevcore/Checksum.cpp new file mode 100644 index 00000000..32260888 --- /dev/null +++ b/test/libdevcore/Checksum.cpp @@ -0,0 +1,83 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Unit tests for the address checksum. + */ + +#include <libdevcore/CommonData.h> + +#include "../TestHelper.h" + +using namespace std; + +namespace dev +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(Checksum) + +BOOST_AUTO_TEST_CASE(regular) +{ + BOOST_CHECK(passesAddressChecksum("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", true)); + BOOST_CHECK(passesAddressChecksum("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true)); + BOOST_CHECK(passesAddressChecksum("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true)); + BOOST_CHECK(passesAddressChecksum("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true)); +} + +BOOST_AUTO_TEST_CASE(regular_negative) +{ + BOOST_CHECK(!passesAddressChecksum("0x6aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", true)); + BOOST_CHECK(!passesAddressChecksum("0xeB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true)); + BOOST_CHECK(!passesAddressChecksum("0xebF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true)); + BOOST_CHECK(!passesAddressChecksum("0xE1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true)); +} + +BOOST_AUTO_TEST_CASE(regular_invalid_length) +{ + BOOST_CHECK(passesAddressChecksum("0x9426cbfc57389778d313268E7F85F1CDc2fdad60", true)); + BOOST_CHECK(!passesAddressChecksum("0x9426cbfc57389778d313268E7F85F1CDc2fdad6", true)); + BOOST_CHECK(passesAddressChecksum("0x08A61851FFa4637dE289D630Ae8c5dFb0ff9171F", true)); + BOOST_CHECK(!passesAddressChecksum("0x8A61851FFa4637dE289D630Ae8c5dFb0ff9171F", true)); + BOOST_CHECK(passesAddressChecksum("0x00c40cC30cb4675673c9ee382de805c19734986A", true)); + BOOST_CHECK(!passesAddressChecksum("0xc40cC30cb4675673c9ee382de805c19734986A", true)); + BOOST_CHECK(passesAddressChecksum("0xC40CC30cb4675673C9ee382dE805c19734986a00", true)); + BOOST_CHECK(!passesAddressChecksum("0xC40CC30cb4675673C9ee382dE805c19734986a", true)); +} + +BOOST_AUTO_TEST_CASE(homocaps_valid) +{ + BOOST_CHECK(passesAddressChecksum("0x52908400098527886E0F7030069857D2E4169EE7", true)); + BOOST_CHECK(passesAddressChecksum("0x8617E340B3D01FA5F11F306F4090FD50E238070D", true)); + BOOST_CHECK(passesAddressChecksum("0xde709f2102306220921060314715629080e2fb77", true)); + BOOST_CHECK(passesAddressChecksum("0x27b1fdb04752bbc536007a920d24acb045561c26", true)); +} + +BOOST_AUTO_TEST_CASE(homocaps_invalid) +{ + string upper = "0x00AA0000000012400000000DDEEFF000000000BB"; + BOOST_CHECK(passesAddressChecksum(upper, false)); + BOOST_CHECK(!passesAddressChecksum(upper, true)); + string lower = "0x11aa000000000000000d00cc00000000000000bb"; + BOOST_CHECK(passesAddressChecksum(lower, false)); + BOOST_CHECK(!passesAddressChecksum(lower, true)); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index ce241c78..b6067ea5 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4983,6 +4983,55 @@ BOOST_AUTO_TEST_CASE(constructible_internal_constructor) success(text); } +BOOST_AUTO_TEST_CASE(address_checksum_type_deduction) +{ + char const* text = R"( + contract C { + function f() { + var x = 0xfA0bFc97E48458494Ccd857e1A85DC91F7F0046E; + x.send(2); + } + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(invalid_address_checksum) +{ + char const* text = R"( + contract C { + function f() { + var x = 0xFA0bFc97E48458494Ccd857e1A85DC91F7F0046E; + } + } + )"; + CHECK_WARNING(text, "checksum"); +} + +BOOST_AUTO_TEST_CASE(invalid_address_no_checksum) +{ + char const* text = R"( + contract C { + function f() { + var x = 0xfa0bfc97e48458494ccd857e1a85dc91f7f0046e; + } + } + )"; + CHECK_WARNING(text, "checksum"); +} + +BOOST_AUTO_TEST_CASE(invalid_address_length) +{ + char const* text = R"( + contract C { + function f() { + var x = 0xA0bFc97E48458494Ccd857e1A85DC91F7F0046E; + } + } + )"; + CHECK_WARNING(text, "checksum"); +} + BOOST_AUTO_TEST_SUITE_END() } |