From 4c26429f1265b6c6e4301c843435e8faef1be443 Mon Sep 17 00:00:00 2001 From: as-iotex Date: Thu, 17 Mar 2022 18:07:48 +0000 Subject: [PATCH] feat: add readContract api endpoint --- src/api/rpc.cpp | 38 +++++++++++++ src/api/rpc.h | 2 + src/api/wallet/wallets.cpp | 23 ++++++++ src/api/wallet/wallets.h | 17 ++++++ src/protobuf/pb_api.cpp | 108 +++++++++++++++++++++++++++++++++++++ src/protobuf/pb_api.h | 25 +++++++++ 6 files changed, 213 insertions(+) diff --git a/src/api/rpc.cpp b/src/api/rpc.cpp index 53cd871..b7c9e27 100644 --- a/src/api/rpc.cpp +++ b/src/api/rpc.cpp @@ -163,6 +163,44 @@ RpcCallData Wallets::sendExecution(Host& host, const responsetypes::ActionCore_E sprintf(body + strlen(body), "%s", base64Signature); sprintf(body + strlen(body), R"(","encoding": "IOTEX_PROTOBUF"}})"); + ret.body = body; + return ret; +} + +RpcCallData Wallets::readContract(Host& host, + const responsetypes::Execution execution, + uint64_t gasLimit, + const IotexString callerAddress) +{ + RpcCallData ret; + + uint8_t data[execution.data.length() / 2]; + memset(data, 0, sizeof(data)); + char base64Data[sizeof(data) * 2]; // Double the size is enough for a base64 encoded message + signer.str2hex(execution.data.c_str(), data, sizeof(data)); + encoder.base64_encode(data, sizeof(data), base64Data); + + // Url + ret.url.reserve(URL_MAX_LEN); + ret.url += host.toString().c_str(); + ret.url += "ReadContract"; + + // Body + char body[1024 + sizeof(base64Data)] = {0}; + sprintf(body + strlen(body), R"({"execution": {"amount": ")"); + sprintf(body + strlen(body), "%s", execution.amount); + sprintf(body + strlen(body), R"(")"); + sprintf(body + strlen(body), R"(,"contract": ")"); + sprintf(body + strlen(body), "%s", execution.contract); + sprintf(body + strlen(body), R"(","data": ")"); + sprintf(body + strlen(body), "%s", base64Data); + sprintf(body + strlen(body), R"("})"); + sprintf(body + strlen(body), R"(,"callerAddress": ")"); + sprintf(body + strlen(body), "%s", callerAddress.c_str()); + sprintf(body + strlen(body), R"(","gasLimit":")"); + sprintf(body + strlen(body), "%lu", gasLimit); + sprintf(body + strlen(body), R"("})"); + ret.body = body; return ret; } \ No newline at end of file diff --git a/src/api/rpc.h b/src/api/rpc.h index 8d34a91..018faab 100644 --- a/src/api/rpc.h +++ b/src/api/rpc.h @@ -39,6 +39,8 @@ class Wallets const responsetypes::ActionCore_Execution execution, const uint8_t senderPubKey[IOTEX_PUBLIC_KEY_SIZE], const uint8_t signature[IOTEX_SIGNATURE_SIZE]); + static RpcCallData readContract(Host& host, const responsetypes::Execution execution, + uint64_t gasLimit, const IotexString callerAddress); }; } // namespace rpc diff --git a/src/api/wallet/wallets.cpp b/src/api/wallet/wallets.cpp index cf6f313..26bce9e 100644 --- a/src/api/wallet/wallets.cpp +++ b/src/api/wallet/wallets.cpp @@ -133,5 +133,28 @@ ResultCode Wallets::sendExecution(const uint8_t senderPubKey[IOTEX_PUBLIC_KEY_SI IOTEX_DEBUG(logModule, "Sent execution request. Action hash: %s", response.hash); + return ret; +} + +ResultCode Wallets::readContract(const responsetypes::Execution& execution, + const IotexString callerAddress, + uint64_t gasLimit, + ReadContractResponse* response) +{ + ResultCode ret; + rpc::RpcCallData callData = + rpc::Wallets::readContract(this->host_, execution, gasLimit, callerAddress); + IotexString rspBody; + + IOTEX_DEBUG(logModule, "Sending readContract request with body: %s", callData.body.c_str()); + + ret = http_->post(callData.url.c_str(), callData.body.c_str(), rspBody); + if(ret != ResultCode::SUCCESS) + return ret; + + ret = response->fromJson(rspBody); + if(ret != ResultCode::SUCCESS) + return ret; + return ret; } \ No newline at end of file diff --git a/src/api/wallet/wallets.h b/src/api/wallet/wallets.h index 5061bc2..48da1cc 100644 --- a/src/api/wallet/wallets.h +++ b/src/api/wallet/wallets.h @@ -93,6 +93,19 @@ class IWallets : public Base const responsetypes::ActionCore_Execution& transfer, uint8_t hash[IOTEX_HASH_SIZE]) = 0; + /** + * @brief Reads a contract + * + * @param execution The execution data + * @param callerAddress The caller address + * @param gasLimit The gas limit + * @param[out] response The abi encoded response if successful + * @return ResultCode Success or an error code + */ + virtual ResultCode readContract(const responsetypes::Execution& execution, + const IotexString callerAddress, uint64_t gasLimit, + responsetypes::ReadContractResponse* response) = 0; + protected: IWallets(Host& host, IHTTP& http) : Base(host, http) { @@ -130,6 +143,10 @@ class Wallets : public IWallets const uint8_t signature[IOTEX_SIGNATURE_SIZE], const responsetypes::ActionCore_Execution& transfer, uint8_t hash[IOTEX_HASH_SIZE]) override; + + virtual ResultCode readContract(const responsetypes::Execution& execution, + const IotexString callerAddress, uint64_t gasLimit, + responsetypes::ReadContractResponse* response) override; }; // class Wallets : public IWallets } // namespace api diff --git a/src/protobuf/pb_api.cpp b/src/protobuf/pb_api.cpp index 0533a44..706b149 100644 --- a/src/protobuf/pb_api.cpp +++ b/src/protobuf/pb_api.cpp @@ -667,5 +667,113 @@ ResultCode SendExecutionResponse::fromJson(IotexString jsonString) cJSON_Delete(data); + return ret; +} + +ResultCode ReadContractResponse::fromJson(IotexString jsonString) +{ + ResultCode ret = ResultCode::SUCCESS; + cJSON* data = cJSON_Parse(jsonString.c_str()); + if(data == NULL) + { + cJSON_Delete(data); + return ResultCode::ERROR_JSON_PARSE; + } + + const cJSON* rspData = cJSON_GetObjectItemCaseSensitive(data, "data"); + ret = SetValueFromJsonObject(rspData, CppType::STRING, (void*)&(this->data)); + if(ret != ResultCode::SUCCESS) + { + cJSON_Delete(data); + return ret; + } + + // Receipt + const cJSON* receipt = cJSON_GetObjectItemCaseSensitive(data, "receipt"); + // The server serializes the values as string even if they are integers + // cJSON fails if trying to parse a stringified int as an int + // So we use a tmp string to convert them + IotexString tmp; + + // Status + const cJSON* status = cJSON_GetObjectItemCaseSensitive(receipt, "status"); + ret = SetValueFromJsonObject(status, CppType::STRING, &tmp); + if(ret != ResultCode::SUCCESS) + { + cJSON_Delete(data); + return ret; + } + this->receipt.status = atol(tmp.c_str()); + + // blkHeight + const cJSON* blkHeight = cJSON_GetObjectItemCaseSensitive(receipt, "blkHeight"); + ret = SetValueFromJsonObject(blkHeight, CppType::STRING, &tmp); + if(ret != ResultCode::SUCCESS) + { + cJSON_Delete(data); + return ret; + } + this->receipt.blkHeight = atol(tmp.c_str()); + + // actHash + const cJSON* hash = cJSON_GetObjectItemCaseSensitive(receipt, "actHash"); + memset(this->receipt.actHash, 0, sizeof(this->receipt.actHash)); + ret = SetValueFromJsonObject(hash, CppType::C_STRING, (void*)&(this->receipt.actHash), IOTEX_HASH_STRLEN); + if(ret != ResultCode::SUCCESS) + { + cJSON_Delete(data); + return ret; + } + + // gasConsumed + const cJSON* gasConsumed = cJSON_GetObjectItemCaseSensitive(receipt, "gasConsumed"); + ret = SetValueFromJsonObject(gasConsumed, CppType::STRING, &tmp); + if(ret != ResultCode::SUCCESS) + { + cJSON_Delete(data); + return ret; + } + this->receipt.gasConsumed = atol(tmp.c_str()); + + // Following fields are optional + // contractAddress + const cJSON* contractAddress = cJSON_GetObjectItemCaseSensitive(receipt, "contractAddress"); + if (contractAddress) + { + memset(this->receipt.contractAddress, 0, sizeof(this->receipt.contractAddress)); + ret = SetValueFromJsonObject(contractAddress, CppType::C_STRING, (void*)&(this->receipt.contractAddress), IOTEX_ADDRESS_STRLEN); + if(ret != ResultCode::SUCCESS) + { + cJSON_Delete(data); + return ret; + } + } + + // executionRevertMsg + const cJSON* executionRevertMsg = cJSON_GetObjectItemCaseSensitive(receipt, "executionRevertMsg"); + if (executionRevertMsg) + { + ret = SetValueFromJsonObject(executionRevertMsg, CppType::STRING, (void*)&(this->receipt.executionRevertMsg)); + if(ret != ResultCode::SUCCESS) + { + cJSON_Delete(data); + return ret; + } + } + + // gasConsumed + const cJSON* txIndex = cJSON_GetObjectItemCaseSensitive(receipt, "txIndex"); + if (txIndex) + { + ret = SetValueFromJsonObject(txIndex, CppType::UINT32, (void*)&(this->receipt.txIndex)); + if(ret != ResultCode::SUCCESS) + { + cJSON_Delete(data); + return ret; + } + } + + cJSON_Delete(data); + return ret; } \ No newline at end of file diff --git a/src/protobuf/pb_api.h b/src/protobuf/pb_api.h index 5aa9bbf..cb3df9d 100644 --- a/src/protobuf/pb_api.h +++ b/src/protobuf/pb_api.h @@ -174,6 +174,31 @@ struct SendExecutionResponse char hash[IOTEX_HASH_C_STRING_SIZE]; }; +struct Receipt +{ + public: + ResultCode fromJson(IotexString jsonString); + + public: + uint64_t status; + uint64_t blkHeight; + char actHash[IOTEX_HASH_C_STRING_SIZE]; + uint64_t gasConsumed; + char contractAddress[IOTEX_ADDRESS_C_STRING_SIZE]; + IotexString executionRevertMsg; + uint32_t txIndex; +}; + +struct ReadContractResponse +{ + public: + ResultCode fromJson(IotexString jsonString); + + public: + IotexString data; + Receipt receipt; +}; + // JSON Objects - unused struct AccountMetaJsonObject