Skip to content

Commit

Permalink
Add a tool to convert Zil API state JSON to Scilla format (#988)
Browse files Browse the repository at this point in the history
* Add a tool to convert Zil API state JSON to Scilla format

* Minor grammer fixes to README

* Mention that init JSON must be provided to scilla-checker
  • Loading branch information
vaivaswatha authored May 21, 2021
1 parent 869c869 commit ce0a248
Show file tree
Hide file tree
Showing 6 changed files with 1,187 additions and 0 deletions.
3 changes: 3 additions & 0 deletions scripts/StateJsonConvert/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
convert.exe : ScillaStateJsonConvert.cpp
g++ -o convert.exe ScillaStateJsonConvert.cpp -ljsoncpp -lboost_program_options

61 changes: 61 additions & 0 deletions scripts/StateJsonConvert/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Convert State JSON from Zilliqa API Format to Scilla Format

The [Zilliqa API]((https://dev.zilliqa.com/docs/apis/api-contract-get-smartcontract-state))
to fetch the state of a contract produces a JSON in the format

```
{
"_balance": "0",
"admins": {
"0xdfa89866ae86632b36361d53b76c1373448c28fa": {
"argtypes": [],
"arguments": [],
"constructor": "True"
}
}
}
```

This program converts it into a format that Scilla understands. Since
there is some loss of information in the API output (the type of a field,
for example), this program also requires the `-contractinfo` output of
scilla-checker as an additional input to find this missing information.
The init JSON of the contract must be provided as input to `scilla-checker`
(via the `-init` option) so that names are disambiguated correctly in the
output contract info JSON of `scilla-checker`.

The output JSON for the above snippet would look something like this

```
[
{
"vname" : "_balance"
"type" : "Uint128",
"value" : "0"
},
{
"vname" : "admins",
"type" : "Map ByStr20 Bool",
"value" : [
{
"key" : "0xdfa89866ae86632b36361d53b76c1373448c28fa",
"val" : {
"argtypes": [],
"arguments": [],
"constructor": "True"
}
}
]
}
]
```

## Build
Install `libjsoncpp-dev`and `libboost-program-options-dev` system packages.
The command `make` in this directory should now succeed in building the
executable `convert.exe`.

## Use
Using the example inputs provided in `tests`, here's how the tool can be used:

```./convert.exe -s tests/ZilSwap/state.json -c tests/ZilSwap/contractinfo.json```
163 changes: 163 additions & 0 deletions scripts/StateJsonConvert/ScillaStateJsonConvert.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#include <iostream>
#include <fstream>
#include <memory>
#include <unordered_map>
#include <jsoncpp/json/reader.h>
#include <jsoncpp/json/value.h>

#include <boost/program_options.hpp>

namespace po = boost::program_options;

void exitMsg(const std::string &Msg) {
std::cerr << Msg;
exit(EXIT_FAILURE);
}

void parseCLIArgs(int argc, char *argv[], po::variables_map &VM) {
auto UsageString =
"Usage: " + std::string(argv[0]) +
" -c contract_info.json -s state.json"
"\nSupported options";
po::options_description Desc(UsageString);

// clang-format off
Desc.add_options()
("state,s", po::value<std::string>(), "Specify the JSON to use as initial state")
("contract-info,c", po::value<std::string>(), "Specify the contract info JSON from checker")
("help,h", "Print help message")
("version,v", "Print version")
;
// clang-format on

try {
po::store(po::command_line_parser(argc, argv).options(Desc).run(), VM);
po::notify(VM);
} catch (std::exception &e) {
std::cerr << e.what() << "\n";
std::cerr << Desc << "\n";
exit(EXIT_FAILURE);
}

if (VM.count("help")) {
std::cerr << Desc << "\n";
exit(EXIT_SUCCESS);
}

// Ensure that an input file is provided.
if (!VM.count("state") || !VM.count("contract-info")) {
std::cerr << "Missing mandatory command line arguments\n" << Desc << "\n";
exit(EXIT_FAILURE);
}
}

std::string readFile(const std::string &Filename) {
std::ifstream IfsFile(Filename);
std::string FileStr((std::istreambuf_iterator<char>(IfsFile)),
(std::istreambuf_iterator<char>()));
return FileStr;
}

Json::Value parseJSONString(const std::string &JS) {
Json::Value Ret;
Json::CharReaderBuilder ReadBuilder;
std::unique_ptr<Json::CharReader> Reader(ReadBuilder.newCharReader());
std::string Error;
try {
Reader->parse(JS.c_str(), JS.c_str() + JS.length(), &Ret, &Error);
} catch (const std::exception &e) {
exitMsg(std::string(e.what()) + ": " + Error);
}

return Ret;
}

Json::Value parseJSONFile(const std::string &Filename) {

return parseJSONString(readFile(Filename));
}

Json::Value convertValue(const Json::Value &IJ, int Depth) {
if (Depth == 0) {
return IJ;
}

if (IJ.isNull()) {
return Json::arrayValue;
}

if (!IJ.isObject()) {
exitMsg("Expected JSON object at depth " + std::to_string(Depth));
}

Json::Value OJ = Json::arrayValue;
for (Json::Value::const_iterator Itr = IJ.begin();
Itr != IJ.end(); Itr++)
{
Json::Value J = Json::objectValue;
J["key"] = Itr.name();
J["val"] = convertValue(*Itr, Depth - 1);
OJ.append(J);
}

return OJ;
}

int main(int argc, char *argv[])
{
po::variables_map VM;
parseCLIArgs(argc, argv, VM);

// Process the contract info JSON first.
std::string ContrInfoFilename = VM["contract-info"].as<std::string>();
Json::Value ContrInfoJ = parseJSONFile(ContrInfoFilename);

Json::Value &Fields = ContrInfoJ["contract_info"]["fields"];
if (Fields.isNull()) {
exitMsg("fields not found in " + ContrInfoFilename);
}

std::unordered_map<std::string, int> DepthMap;
std::unordered_map<std::string, std::string> TypeMap;
DepthMap["_balance"] = 0;
TypeMap["_balance"] = "Uint128";

for (const auto &Field : Fields) {
if (!Field["vname"].isString() || !Field["type"].isString()
|| !Field["depth"].isInt())
{
exitMsg("Incorrect field entry.\n" + Field.toStyledString());
}
TypeMap[Field["vname"].asString()] = Field["type"].asString();
DepthMap[Field["vname"].asString()] = Field["depth"].asInt();
}

std::string InputStateFilename = VM["state"].as<std::string>();
Json::Value InputState = parseJSONFile(InputStateFilename);

Json::Value OutputState = Json::arrayValue;
if (!InputState.isObject()) {
exitMsg("Expected input state to be a JSON object");
}

for (Json::Value::const_iterator Itr = InputState.begin();
Itr != InputState.end(); Itr++)
{
const std::string &VName = Itr.name();
const Json::Value &ValJ = *Itr;
const auto &Type = TypeMap.find(VName);
const auto &Depth = DepthMap.find(VName);
if (Type == TypeMap.end() || Depth == DepthMap.end()) {
exitMsg("Not found: type or depth for " + VName);
}
Json::Value OJ;
OJ["vname"] = VName;
OJ["type"] = Type->second;
OJ["value"] = convertValue(ValJ, Depth->second);
OutputState.append(OJ);
}

std::cout << OutputState.toStyledString();

return 0;
}
Loading

0 comments on commit ce0a248

Please sign in to comment.