From 517cd17a6f6aded75256f8d0b70a97fe113dc25b Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 2 Feb 2021 12:34:58 +0100 Subject: [PATCH 1/2] Add errors to the ABI. --- libsolidity/interface/ABI.cpp | 17 ++++ test/libsolidity/ABIJson/errors.sol | 93 ++++++++++++++++++ .../libsolidity/ABIJson/errors_referenced.sol | 94 +++++++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 test/libsolidity/ABIJson/errors.sol create mode 100644 test/libsolidity/ABIJson/errors_referenced.sol diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp index 960d4270e..c4a2dfe5c 100644 --- a/libsolidity/interface/ABI.cpp +++ b/libsolidity/interface/ABI.cpp @@ -121,6 +121,23 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) abi.emplace(std::move(event)); } + for (ErrorDefinition const* error: _contractDef.interfaceErrors()) + { + Json::Value errorJson; + errorJson["type"] = "error"; + errorJson["name"] = error->name(); + errorJson["inputs"] = Json::arrayValue; + for (auto const& p: error->parameters()) + { + Type const* type = p->annotation().type->interfaceType(false); + solAssert(type, ""); + errorJson["inputs"].append( + formatType(p->name(), *type, *p->annotation().type, false) + ); + } + abi.emplace(move(errorJson)); + } + Json::Value abiJson{Json::arrayValue}; for (auto& f: abi) abiJson.append(std::move(f)); diff --git a/test/libsolidity/ABIJson/errors.sol b/test/libsolidity/ABIJson/errors.sol new file mode 100644 index 000000000..91c76ce6f --- /dev/null +++ b/test/libsolidity/ABIJson/errors.sol @@ -0,0 +1,93 @@ +error FreeError(); +contract X { + error E(uint); + error E2(uint a, uint b); + error E4(uint a, bytes[][] c); + struct S { uint x; } + error E5(S t); + function f() public pure { + revert FreeError(); + } +} +// ---- +// :X +// [ +// { +// "inputs": +// [ +// { +// "internalType": "uint256", +// "name": "", +// "type": "uint256" +// } +// ], +// "name": "E", +// "type": "error" +// }, +// { +// "inputs": +// [ +// { +// "internalType": "uint256", +// "name": "a", +// "type": "uint256" +// }, +// { +// "internalType": "uint256", +// "name": "b", +// "type": "uint256" +// } +// ], +// "name": "E2", +// "type": "error" +// }, +// { +// "inputs": +// [ +// { +// "internalType": "uint256", +// "name": "a", +// "type": "uint256" +// }, +// { +// "internalType": "bytes[][]", +// "name": "c", +// "type": "bytes[][]" +// } +// ], +// "name": "E4", +// "type": "error" +// }, +// { +// "inputs": +// [ +// { +// "components": +// [ +// { +// "internalType": "uint256", +// "name": "x", +// "type": "uint256" +// } +// ], +// "internalType": "struct X.S", +// "name": "t", +// "type": "tuple" +// } +// ], +// "name": "E5", +// "type": "error" +// }, +// { +// "inputs": [], +// "name": "FreeError", +// "type": "error" +// }, +// { +// "inputs": [], +// "name": "f", +// "outputs": [], +// "stateMutability": "pure", +// "type": "function" +// } +// ] diff --git a/test/libsolidity/ABIJson/errors_referenced.sol b/test/libsolidity/ABIJson/errors_referenced.sol new file mode 100644 index 000000000..ecb5b1003 --- /dev/null +++ b/test/libsolidity/ABIJson/errors_referenced.sol @@ -0,0 +1,94 @@ +error E1(); +function f() { + revert E1(); +} +contract C { + // unreferenced but inherited + error E2(); +} +contract D { + // referenced + error E3(uint); +} +contract X is C { + // unreferenced but defined + error E3(); + function g(uint x) public { + if (x == 0) + f(); + else if (x == 2) + revert D.E3(1); + } +} +// ---- +// :C +// [ +// { +// "inputs": [], +// "name": "E2", +// "type": "error" +// } +// ] +// +// +// :D +// [ +// { +// "inputs": +// [ +// { +// "internalType": "uint256", +// "name": "", +// "type": "uint256" +// } +// ], +// "name": "E3", +// "type": "error" +// } +// ] +// +// +// :X +// [ +// { +// "inputs": [], +// "name": "E1", +// "type": "error" +// }, +// { +// "inputs": [], +// "name": "E2", +// "type": "error" +// }, +// { +// "inputs": +// [ +// { +// "internalType": "uint256", +// "name": "", +// "type": "uint256" +// } +// ], +// "name": "E3", +// "type": "error" +// }, +// { +// "inputs": [], +// "name": "E3", +// "type": "error" +// }, +// { +// "inputs": +// [ +// { +// "internalType": "uint256", +// "name": "x", +// "type": "uint256" +// } +// ], +// "name": "g", +// "outputs": [], +// "stateMutability": "nonpayable", +// "type": "function" +// } +// ] From 79d7466e191a076be00ded120f01f0620e844952 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 3 Feb 2021 13:53:39 +0100 Subject: [PATCH 2/2] NatSpec for errors. --- libsolidity/interface/Natspec.cpp | 50 ++++++----- test/libsolidity/SolidityNatspecJSON.cpp | 102 +++++++++++++++++++++++ 2 files changed, 126 insertions(+), 26 deletions(-) diff --git a/libsolidity/interface/Natspec.cpp b/libsolidity/interface/Natspec.cpp index d70cb3494..1251fcb8d 100644 --- a/libsolidity/interface/Natspec.cpp +++ b/libsolidity/interface/Natspec.cpp @@ -38,11 +38,10 @@ using namespace solidity::frontend; Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef) { Json::Value doc; - Json::Value methods(Json::objectValue); - Json::Value events(Json::objectValue); doc["version"] = Json::Value(c_natspecVersion); doc["kind"] = Json::Value("user"); + doc["methods"] = Json::objectValue; auto constructorDefinition(_contractDef.constructor()); if (constructorDefinition) @@ -53,7 +52,7 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef) // add the constructor, only if we have any documentation to add Json::Value user; user["notice"] = Json::Value(value); - methods["constructor"] = user; + doc["methods"]["constructor"] = user; } } @@ -75,24 +74,27 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef) } if (!value.empty()) - { - Json::Value user; // since @notice is the only user tag if missing function should not appear - user["notice"] = Json::Value(value); - methods[it.second->externalSignature()] = user; - } + doc["methods"][it.second->externalSignature()]["notice"] = value; } for (auto const& event: _contractDef.interfaceEvents()) { string value = extractDoc(event->annotation().docTags, "notice"); if (!value.empty()) - events[event->functionType(true)->externalSignature()]["notice"] = value; + doc["events"][event->functionType(true)->externalSignature()]["notice"] = value; } - doc["methods"] = methods; - if (!events.empty()) - doc["events"] = events; + for (auto const& error: _contractDef.interfaceErrors()) + { + string value = extractDoc(error->annotation().docTags, "notice"); + if (!value.empty()) + { + Json::Value errorDoc; + errorDoc["notice"] = value; + doc["errors"][error->functionType(true)->externalSignature()].append(move(errorDoc)); + } + } return doc; } @@ -100,7 +102,6 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef) Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef) { Json::Value doc = extractCustomDoc(_contractDef.annotation().docTags); - Json::Value methods(Json::objectValue); doc["version"] = Json::Value(c_natspecVersion); doc["kind"] = Json::Value("dev"); @@ -115,13 +116,14 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef) if (!dev.empty()) doc["details"] = Json::Value(dev); + doc["methods"] = Json::objectValue; auto constructorDefinition(_contractDef.constructor()); if (constructorDefinition) { Json::Value constructor(devDocumentation(constructorDefinition->annotation().docTags)); if (!constructor.empty()) // add the constructor, only if we have any documentation to add - methods["constructor"] = constructor; + doc["methods"]["constructor"] = constructor; } for (auto const& it: _contractDef.interfaceFunctions()) @@ -135,34 +137,30 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef) Json::Value jsonReturn = extractReturnParameterDocs(fun->annotation().docTags, *fun); if (!jsonReturn.empty()) - method["returns"] = jsonReturn; + method["returns"] = move(jsonReturn); if (!method.empty()) - methods[it.second->externalSignature()] = method; + doc["methods"][it.second->externalSignature()] = move(method); } } - Json::Value stateVariables(Json::objectValue); for (VariableDeclaration const* varDecl: _contractDef.stateVariables()) { if (auto devDoc = devDocumentation(varDecl->annotation().docTags); !devDoc.empty()) - stateVariables[varDecl->name()] = devDoc; + doc["stateVariables"][varDecl->name()] = devDoc; solAssert(varDecl->annotation().docTags.count("return") <= 1, ""); if (varDecl->annotation().docTags.count("return") == 1) - stateVariables[varDecl->name()]["return"] = extractDoc(varDecl->annotation().docTags, "return"); + doc["stateVariables"][varDecl->name()]["return"] = extractDoc(varDecl->annotation().docTags, "return"); } - Json::Value events(Json::objectValue); for (auto const& event: _contractDef.events()) if (auto devDoc = devDocumentation(event->annotation().docTags); !devDoc.empty()) - events[event->functionType(true)->externalSignature()] = devDoc; + doc["events"][event->functionType(true)->externalSignature()] = devDoc; - doc["methods"] = methods; - if (!stateVariables.empty()) - doc["stateVariables"] = stateVariables; - if (!events.empty()) - doc["events"] = events; + for (auto const& error: _contractDef.interfaceErrors()) + if (auto devDoc = devDocumentation(error->annotation().docTags); !devDoc.empty()) + doc["errors"][error->functionType(true)->externalSignature()].append(devDoc); return doc; } diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index a7d596b37..53b377988 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -2247,6 +2247,108 @@ BOOST_AUTO_TEST_CASE(dev_return_name_no_description) checkNatspec(sourceCode, "B", natspec2, false); } +BOOST_AUTO_TEST_CASE(error) +{ + char const* sourceCode = R"( + contract test { + /// Something failed. + /// @dev an error. + /// @param a first parameter + /// @param b second parameter + error E(uint a, uint b); + } + )"; + + char const* devdoc = R"X({ + "errors":{ + "E(uint256,uint256)": [{ + "details" : "an error.", + "params" : + { + "a" : "first parameter", + "b" : "second parameter" + } + }] + }, + "methods": {} + })X"; + + checkNatspec(sourceCode, "test", devdoc, false); + + char const* userdoc = R"X({ + "errors":{ + "E(uint256,uint256)": [{ + "notice" : "Something failed." + }] + }, + "methods": {} + })X"; + checkNatspec(sourceCode, "test", userdoc, true); +} + +BOOST_AUTO_TEST_CASE(error_multiple) +{ + char const* sourceCode = R"( + contract A { + /// Something failed. + /// @dev an error. + /// @param x first parameter + /// @param y second parameter + error E(uint x, uint y); + } + contract test { + /// X Something failed. + /// @dev X an error. + /// @param a X first parameter + /// @param b X second parameter + error E(uint a, uint b); + function f(bool a) public pure { + if (a) + revert E(1, 2); + else + revert A.E(5, 6); + } + } + )"; + + char const* devdoc = R"X({ + "methods": {}, + "errors": { + "E(uint256,uint256)": [ + { + "details" : "an error.", + "params" : + { + "x" : "first parameter", + "y" : "second parameter" + } + }, + { + "details" : "X an error.", + "params" : + { + "a" : "X first parameter", + "b" : "X second parameter" + } + } + ] + } + })X"; + + checkNatspec(sourceCode, "test", devdoc, false); + + char const* userdoc = R"X({ + "errors":{ + "E(uint256,uint256)": [ + { "notice" : "Something failed." }, + { "notice" : "X Something failed." } + ] + }, + "methods": {} + })X"; + checkNatspec(sourceCode, "test", userdoc, true); +} + BOOST_AUTO_TEST_CASE(custom) { char const* sourceCode = R"(