Merge pull request #10936 from ethereum/customErrorsABI

ABI and Natspec for errors
This commit is contained in:
chriseth 2021-03-30 23:04:09 +02:00 committed by GitHub
commit 15fe07bebe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 330 additions and 26 deletions

View File

@ -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));

View File

@ -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;
}

View File

@ -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"
// }
// ]

View File

@ -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"
// }
// ]

View File

@ -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"(