mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #6233 from ethereum/metadata-tests
Improve metadata tests (add CBOR parser)
This commit is contained in:
commit
6718b62f94
@ -21,6 +21,8 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <libdevcore/Assertions.h>
|
||||||
|
#include <libdevcore/CommonData.h>
|
||||||
#include <libdevcore/JSON.h>
|
#include <libdevcore/JSON.h>
|
||||||
#include <test/Metadata.h>
|
#include <test/Metadata.h>
|
||||||
|
|
||||||
@ -31,7 +33,7 @@ namespace dev
|
|||||||
namespace test
|
namespace test
|
||||||
{
|
{
|
||||||
|
|
||||||
bytes bytecodeSansMetadata(bytes const& _bytecode)
|
bytes onlyMetadata(bytes const& _bytecode)
|
||||||
{
|
{
|
||||||
unsigned size = _bytecode.size();
|
unsigned size = _bytecode.size();
|
||||||
if (size < 5)
|
if (size < 5)
|
||||||
@ -43,6 +45,14 @@ bytes bytecodeSansMetadata(bytes const& _bytecode)
|
|||||||
unsigned char firstByte = _bytecode[size - metadataSize - 2];
|
unsigned char firstByte = _bytecode[size - metadataSize - 2];
|
||||||
if (firstByte != 0xa1 && firstByte != 0xa2)
|
if (firstByte != 0xa1 && firstByte != 0xa2)
|
||||||
return bytes{};
|
return bytes{};
|
||||||
|
return bytes(_bytecode.end() - metadataSize - 2, _bytecode.end() - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes bytecodeSansMetadata(bytes const& _bytecode)
|
||||||
|
{
|
||||||
|
unsigned metadataSize = onlyMetadata(_bytecode).size();
|
||||||
|
if (metadataSize == 0)
|
||||||
|
return bytes{};
|
||||||
return bytes(_bytecode.begin(), _bytecode.end() - metadataSize - 2);
|
return bytes(_bytecode.begin(), _bytecode.end() - metadataSize - 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +61,116 @@ string bytecodeSansMetadata(string const& _bytecode)
|
|||||||
return toHex(bytecodeSansMetadata(fromHex(_bytecode, WhenError::Throw)));
|
return toHex(bytecodeSansMetadata(fromHex(_bytecode, WhenError::Throw)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEV_SIMPLE_EXCEPTION(CBORException);
|
||||||
|
|
||||||
|
class TinyCBORParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit TinyCBORParser(bytes const& _metadata): m_pos(0), m_metadata(_metadata)
|
||||||
|
{
|
||||||
|
assertThrow((m_pos + 1) < _metadata.size(), CBORException, "Input too short.");
|
||||||
|
}
|
||||||
|
unsigned mapItemCount()
|
||||||
|
{
|
||||||
|
assertThrow(nextType() == MajorType::Map, CBORException, "Fixed-length map expected.");
|
||||||
|
return readLength();
|
||||||
|
}
|
||||||
|
string readKey()
|
||||||
|
{
|
||||||
|
return readString();
|
||||||
|
}
|
||||||
|
string readValue()
|
||||||
|
{
|
||||||
|
switch(nextType())
|
||||||
|
{
|
||||||
|
case MajorType::ByteString:
|
||||||
|
return toHex(readBytes(readLength()));
|
||||||
|
case MajorType::TextString:
|
||||||
|
return readString();
|
||||||
|
case MajorType::SimpleData:
|
||||||
|
{
|
||||||
|
unsigned value = nextImmediate();
|
||||||
|
m_pos++;
|
||||||
|
if (value == 20)
|
||||||
|
return "false";
|
||||||
|
else if (value == 21)
|
||||||
|
return "true";
|
||||||
|
else
|
||||||
|
assertThrow(false, CBORException, "Unsupported simple value (not a boolean).");
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
assertThrow(false, CBORException, "Unsupported value type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
enum class MajorType
|
||||||
|
{
|
||||||
|
ByteString,
|
||||||
|
TextString,
|
||||||
|
Map,
|
||||||
|
SimpleData
|
||||||
|
};
|
||||||
|
MajorType nextType() const
|
||||||
|
{
|
||||||
|
unsigned value = (m_metadata.at(m_pos) >> 5) & 0x7;
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case 2: return MajorType::ByteString;
|
||||||
|
case 3: return MajorType::TextString;
|
||||||
|
case 5: return MajorType::Map;
|
||||||
|
case 7: return MajorType::SimpleData;
|
||||||
|
default: assertThrow(false, CBORException, "Unsupported major type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsigned nextImmediate() const { return m_metadata.at(m_pos) & 0x1f; }
|
||||||
|
unsigned readLength()
|
||||||
|
{
|
||||||
|
unsigned length = m_metadata.at(m_pos++) & 0x1f;
|
||||||
|
if (length < 24)
|
||||||
|
return length;
|
||||||
|
if (length == 24)
|
||||||
|
return m_metadata.at(m_pos++);
|
||||||
|
// Unsupported length kind. (Only by this parser.)
|
||||||
|
assertThrow(false, CBORException, string("Unsupported length ") + to_string(length));
|
||||||
|
}
|
||||||
|
bytes readBytes(unsigned length)
|
||||||
|
{
|
||||||
|
bytes ret{m_metadata.begin() + m_pos, m_metadata.begin() + m_pos + length};
|
||||||
|
m_pos += length;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
string readString()
|
||||||
|
{
|
||||||
|
// Expect a text string.
|
||||||
|
assertThrow(nextType() == MajorType::TextString, CBORException, "String expected.");
|
||||||
|
bytes tmp{readBytes(readLength())};
|
||||||
|
return string{tmp.begin(), tmp.end()};
|
||||||
|
}
|
||||||
|
unsigned m_pos;
|
||||||
|
bytes const& m_metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
boost::optional<map<string, string>> parseCBORMetadata(bytes const& _metadata)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TinyCBORParser parser(_metadata);
|
||||||
|
map<string, string> ret;
|
||||||
|
unsigned count = parser.mapItemCount();
|
||||||
|
for (unsigned i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
string key = parser.readKey();
|
||||||
|
string value = parser.readValue();
|
||||||
|
ret[move(key)] = move(value);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (CBORException const&)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool isValidMetadata(string const& _metadata)
|
bool isValidMetadata(string const& _metadata)
|
||||||
{
|
{
|
||||||
Json::Value metadata;
|
Json::Value metadata;
|
||||||
|
@ -20,6 +20,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <libdevcore/CommonData.h>
|
#include <libdevcore/CommonData.h>
|
||||||
|
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace dev
|
namespace dev
|
||||||
@ -27,6 +31,9 @@ namespace dev
|
|||||||
namespace test
|
namespace test
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/// Returns only the CBOR metadata.
|
||||||
|
bytes onlyMetadata(bytes const& _bytecode);
|
||||||
|
|
||||||
/// Returns the bytecode with the metadata hash stripped out.
|
/// Returns the bytecode with the metadata hash stripped out.
|
||||||
bytes bytecodeSansMetadata(bytes const& _bytecode);
|
bytes bytecodeSansMetadata(bytes const& _bytecode);
|
||||||
|
|
||||||
@ -34,6 +41,15 @@ bytes bytecodeSansMetadata(bytes const& _bytecode);
|
|||||||
/// Throws exception on invalid hex string.
|
/// Throws exception on invalid hex string.
|
||||||
std::string bytecodeSansMetadata(std::string const& _bytecode);
|
std::string bytecodeSansMetadata(std::string const& _bytecode);
|
||||||
|
|
||||||
|
/// Parse CBOR metadata into a map. Expects the input CBOR to be a
|
||||||
|
/// fixed length map, with each key being a string. The values
|
||||||
|
/// are parsed as follows:
|
||||||
|
/// - strings into strings
|
||||||
|
/// - bytes into hex strings
|
||||||
|
/// - booleans into "true"/"false" strings
|
||||||
|
/// - everything else is invalid
|
||||||
|
boost::optional<std::map<std::string, std::string>> parseCBORMetadata(bytes const& _metadata);
|
||||||
|
|
||||||
/// Expects a serialised metadata JSON and returns true if the
|
/// Expects a serialised metadata JSON and returns true if the
|
||||||
/// content is valid metadata.
|
/// content is valid metadata.
|
||||||
bool isValidMetadata(std::string const& _metadata);
|
bool isValidMetadata(std::string const& _metadata);
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
#include <libdevcore/SwarmHash.h>
|
#include <libdevcore/SwarmHash.h>
|
||||||
#include <libdevcore/JSON.h>
|
#include <libdevcore/JSON.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
namespace dev
|
namespace dev
|
||||||
{
|
{
|
||||||
namespace solidity
|
namespace solidity
|
||||||
@ -32,6 +34,18 @@ namespace solidity
|
|||||||
namespace test
|
namespace test
|
||||||
{
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
map<string, string> requireParsedCBORMetadata(bytes const& _bytecode)
|
||||||
|
{
|
||||||
|
bytes cborMetadata = dev::test::onlyMetadata(_bytecode);
|
||||||
|
BOOST_REQUIRE(!cborMetadata.empty());
|
||||||
|
boost::optional<map<string, string>> tmp = dev::test::parseCBORMetadata(cborMetadata);
|
||||||
|
BOOST_REQUIRE(tmp);
|
||||||
|
return *tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Metadata)
|
BOOST_AUTO_TEST_SUITE(Metadata)
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(metadata_stamp)
|
BOOST_AUTO_TEST_CASE(metadata_stamp)
|
||||||
@ -54,11 +68,10 @@ BOOST_AUTO_TEST_CASE(metadata_stamp)
|
|||||||
BOOST_CHECK(dev::test::isValidMetadata(metadata));
|
BOOST_CHECK(dev::test::isValidMetadata(metadata));
|
||||||
bytes hash = dev::swarmHash(metadata).asBytes();
|
bytes hash = dev::swarmHash(metadata).asBytes();
|
||||||
BOOST_REQUIRE(hash.size() == 32);
|
BOOST_REQUIRE(hash.size() == 32);
|
||||||
BOOST_REQUIRE(bytecode.size() >= 2);
|
auto const cborMetadata = requireParsedCBORMetadata(bytecode);
|
||||||
size_t metadataCBORSize = (size_t(bytecode.end()[-2]) << 8) + size_t(bytecode.end()[-1]);
|
BOOST_CHECK(cborMetadata.size() == 1);
|
||||||
BOOST_REQUIRE(metadataCBORSize < bytecode.size() - 2);
|
BOOST_CHECK(cborMetadata.count("bzzr0") == 1);
|
||||||
bytes expectation = bytes{0xa1, 0x65, 'b', 'z', 'z', 'r', '0', 0x58, 0x20} + hash;
|
BOOST_CHECK(cborMetadata.at("bzzr0") == toHex(hash));
|
||||||
BOOST_CHECK(std::equal(expectation.begin(), expectation.end(), bytecode.end() - metadataCBORSize - 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(metadata_stamp_experimental)
|
BOOST_AUTO_TEST_CASE(metadata_stamp_experimental)
|
||||||
@ -81,14 +94,12 @@ BOOST_AUTO_TEST_CASE(metadata_stamp_experimental)
|
|||||||
BOOST_CHECK(dev::test::isValidMetadata(metadata));
|
BOOST_CHECK(dev::test::isValidMetadata(metadata));
|
||||||
bytes hash = dev::swarmHash(metadata).asBytes();
|
bytes hash = dev::swarmHash(metadata).asBytes();
|
||||||
BOOST_REQUIRE(hash.size() == 32);
|
BOOST_REQUIRE(hash.size() == 32);
|
||||||
BOOST_REQUIRE(bytecode.size() >= 2);
|
auto const cborMetadata = requireParsedCBORMetadata(bytecode);
|
||||||
size_t metadataCBORSize = (size_t(bytecode.end()[-2]) << 8) + size_t(bytecode.end()[-1]);
|
BOOST_CHECK(cborMetadata.size() == 2);
|
||||||
BOOST_REQUIRE(metadataCBORSize < bytecode.size() - 2);
|
BOOST_CHECK(cborMetadata.count("bzzr0") == 1);
|
||||||
bytes expectation =
|
BOOST_CHECK(cborMetadata.at("bzzr0") == toHex(hash));
|
||||||
bytes{0xa2, 0x65, 'b', 'z', 'z', 'r', '0', 0x58, 0x20} +
|
BOOST_CHECK(cborMetadata.count("experimental") == 1);
|
||||||
hash +
|
BOOST_CHECK(cborMetadata.at("experimental") == "true");
|
||||||
bytes{0x6c, 'e', 'x', 'p', 'e', 'r', 'i', 'm', 'e', 'n', 't', 'a', 'l', 0xf5};
|
|
||||||
BOOST_CHECK(std::equal(expectation.begin(), expectation.end(), bytecode.end() - metadataCBORSize - 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(metadata_relevant_sources)
|
BOOST_AUTO_TEST_CASE(metadata_relevant_sources)
|
||||||
|
Loading…
Reference in New Issue
Block a user