diff --git a/README.md b/README.md index b45f7213..9122d6d6 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ See the [Vulcan Book](https://nomoixyz.github.io/vulcan/) for detailed usage inf ## Why Vulcan? Our goal is to provide: - + - Better naming for VM functionality (no more `prank`, `roll`, `warp`, ...) - A testing framework with better readability and a familiar syntax - Improved ergonomics @@ -84,3 +84,10 @@ contract TestSomething is Test { ## Contributing At this stage we are looking for all kinds of feedback, as the general direction of the project is not fully defined yet. If you have any ideas to improve naming, ergonomics, or anything else, please open an issue or a PR. + +## Acknowledgments + +Some of the ideas and code in Vulcan are directly inspired by or adapted from the following projects: + +- [forge-std](https://github.com/foundry-rs/forge-std/) +- [memester-xyz/surl](https://github.com/memester-xyz/surl) diff --git a/src/_modules/Json.sol b/src/_modules/Json.sol index 7091cce0..75100c24 100644 --- a/src/_modules/Json.sol +++ b/src/_modules/Json.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.13 <0.9.0; +import {Error, Result, LibResult, Ok} from "./Result.sol"; import "./Accounts.sol"; import "./Vulcan.sol"; @@ -9,22 +10,47 @@ struct JsonObject { string serialized; } -library json { - /// @dev Parses a json object string by key and returns an ABI encoded value. - /// @param jsonStr The json string. - /// @param key The key from the `jsonStr` to parse. - /// @return abiEncodedData The ABI encoded tuple representing the value of the provided key. - function getObject(string memory jsonStr, string memory key) internal pure returns (bytes memory abiEncodedData) { - return vulcan.hevm.parseJson(jsonStr, key); +struct JsonResult { + Result _inner; +} + +library JsonError { + bytes32 constant IMMUTABLE_JSON = keccak256("IMMUTABLE_JSON"); + + function immutableJson() internal pure returns (JsonResult memory res) { + return JsonResult(Error(IMMUTABLE_JSON, "Json object is immutable").toResult()); } +} - /// @dev Parses a json object string and returns an ABI encoded tuple. - /// @param jsonStr The json string. - /// @return abiEncodedData The ABI encoded tuple representing the json object. - function parse(string memory jsonStr) internal pure returns (bytes memory abiEncodedData) { - return vulcan.hevm.parseJson(jsonStr); +library LibJsonResult { + function isOk(JsonResult memory self) internal pure returns (bool) { + return self._inner.isOk(); + } + + function isError(JsonResult memory self) internal pure returns (bool) { + return self._inner.isError(); } + function unwrap(JsonResult memory self) internal pure returns (JsonObject memory) { + return abi.decode(self._inner.unwrap(), (JsonObject)); + } + + function expect(JsonResult memory self, string memory err) internal pure returns (JsonObject memory) { + return abi.decode(self._inner.expect(err), (JsonObject)); + } + + function toError(JsonResult memory self) internal pure returns (Error memory) { + return self._inner.toError(); + } +} + +function Ok(JsonObject memory value) pure returns (JsonResult memory) { + return JsonResult(Ok(abi.encode(value))); +} + +library json { + using json for JsonObject; + /// @dev Parses a json object struct by key and returns an ABI encoded value. /// @param jsonObj The json object struct. /// @param key The key from the `jsonObject` to parse. @@ -45,115 +71,115 @@ library json { } /// @dev Parses the value of the `key` contained on `jsonStr` as uint256. - /// @param jsonStr The json string. + /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. /// @return The uint256 value. - function getUint(string memory jsonStr, string memory key) internal pure returns (uint256) { - return vulcan.hevm.parseJsonUint(jsonStr, key); + function getUint(JsonObject memory obj, string memory key) internal pure returns (uint256) { + return vulcan.hevm.parseJsonUint(obj.serialized, key); } /// @dev Parses the value of the `key` contained on `jsonStr` as uint256[]. - /// @param jsonStr The json string. + /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. /// @return The uint256[] value. - function getUintArray(string memory jsonStr, string memory key) internal pure returns (uint256[] memory) { - return vulcan.hevm.parseJsonUintArray(jsonStr, key); + function getUintArray(JsonObject memory obj, string memory key) internal pure returns (uint256[] memory) { + return vulcan.hevm.parseJsonUintArray(obj.serialized, key); } /// @dev Parses the value of the `key` contained on `jsonStr` as int256. - /// @param jsonStr The json string. + /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. /// @return The int256 value. - function getInt(string memory jsonStr, string memory key) internal pure returns (int256) { - return vulcan.hevm.parseJsonInt(jsonStr, key); + function getInt(JsonObject memory obj, string memory key) internal pure returns (int256) { + return vulcan.hevm.parseJsonInt(obj.serialized, key); } /// @dev Parses the value of the `key` contained on `jsonStr` as int256[]. - /// @param jsonStr The json string. + /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. /// @return The int256[] value. - function getIntArray(string memory jsonStr, string memory key) internal pure returns (int256[] memory) { - return vulcan.hevm.parseJsonIntArray(jsonStr, key); + function getIntArray(JsonObject memory obj, string memory key) internal pure returns (int256[] memory) { + return vulcan.hevm.parseJsonIntArray(obj.serialized, key); } /// @dev Parses the value of the `key` contained on `jsonStr` as bool. - /// @param jsonStr The json string. + /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. /// @return The bool value. - function getBool(string memory jsonStr, string memory key) internal pure returns (bool) { - return vulcan.hevm.parseJsonBool(jsonStr, key); + function getBool(JsonObject memory obj, string memory key) internal pure returns (bool) { + return vulcan.hevm.parseJsonBool(obj.serialized, key); } /// @dev Parses the value of the `key` contained on `jsonStr` as bool[]. - /// @param jsonStr The json string. + /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. /// @return The bool[] value. - function getBoolArray(string memory jsonStr, string memory key) internal pure returns (bool[] memory) { - return vulcan.hevm.parseJsonBoolArray(jsonStr, key); + function getBoolArray(JsonObject memory obj, string memory key) internal pure returns (bool[] memory) { + return vulcan.hevm.parseJsonBoolArray(obj.serialized, key); } /// @dev Parses the value of the `key` contained on `jsonStr` as address. - /// @param jsonStr The json string. + /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. /// @return The address value. - function getAddress(string memory jsonStr, string memory key) internal pure returns (address) { - return vulcan.hevm.parseJsonAddress(jsonStr, key); + function getAddress(JsonObject memory obj, string memory key) internal pure returns (address) { + return vulcan.hevm.parseJsonAddress(obj.serialized, key); } /// @dev Parses the value of the `key` contained on `jsonStr` as address. - /// @param jsonStr The json string. + /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. /// @return The address value. - function getAddressArray(string memory jsonStr, string memory key) internal pure returns (address[] memory) { - return vulcan.hevm.parseJsonAddressArray(jsonStr, key); + function getAddressArray(JsonObject memory obj, string memory key) internal pure returns (address[] memory) { + return vulcan.hevm.parseJsonAddressArray(obj.serialized, key); } /// @dev Parses the value of the `key` contained on `jsonStr` as string. - /// @param jsonStr The json string. + /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. /// @return The string value. - function getString(string memory jsonStr, string memory key) internal pure returns (string memory) { - return vulcan.hevm.parseJsonString(jsonStr, key); + function getString(JsonObject memory obj, string memory key) internal pure returns (string memory) { + return vulcan.hevm.parseJsonString(obj.serialized, key); } /// @dev Parses the value of the `key` contained on `jsonStr` as string[]. - /// @param jsonStr The json string. + /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. /// @return The string[] value. - function getStringArray(string memory jsonStr, string memory key) internal pure returns (string[] memory) { - return vulcan.hevm.parseJsonStringArray(jsonStr, key); + function getStringArray(JsonObject memory obj, string memory key) internal pure returns (string[] memory) { + return vulcan.hevm.parseJsonStringArray(obj.serialized, key); } /// @dev Parses the value of the `key` contained on `jsonStr` as bytes. - /// @param jsonStr The json string. + /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. /// @return The bytes value. - function getBytes(string memory jsonStr, string memory key) internal pure returns (bytes memory) { - return vulcan.hevm.parseJsonBytes(jsonStr, key); + function getBytes(JsonObject memory obj, string memory key) internal pure returns (bytes memory) { + return vulcan.hevm.parseJsonBytes(obj.serialized, key); } /// @dev Parses the value of the `key` contained on `jsonStr` as bytes[]. - /// @param jsonStr The json string. + /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. /// @return The bytes[] value. - function getBytesArray(string memory jsonStr, string memory key) internal pure returns (bytes[] memory) { - return vulcan.hevm.parseJsonBytesArray(jsonStr, key); + function getBytesArray(JsonObject memory obj, string memory key) internal pure returns (bytes[] memory) { + return vulcan.hevm.parseJsonBytesArray(obj.serialized, key); } /// @dev Parses the value of the `key` contained on `jsonStr` as bytes32. - /// @param jsonStr The json string. + /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. /// @return The bytes32 value. - function getBytes32(string memory jsonStr, string memory key) internal pure returns (bytes32) { - return vulcan.hevm.parseJsonBytes32(jsonStr, key); + function getBytes32(JsonObject memory obj, string memory key) internal pure returns (bytes32) { + return vulcan.hevm.parseJsonBytes32(obj.serialized, key); } /// @dev Parses the value of the `key` contained on `jsonStr` as bytes32[]. - /// @param jsonStr The json string. + /// @param obj The json object. /// @param key The key from the `jsonStr` to parse. /// @return The bytes32[] value. - function getBytes32Array(string memory jsonStr, string memory key) internal pure returns (bytes32[] memory) { - return vulcan.hevm.parseJsonBytes32Array(jsonStr, key); + function getBytes32Array(JsonObject memory obj, string memory key) internal pure returns (bytes32[] memory) { + return vulcan.hevm.parseJsonBytes32Array(obj.serialized, key); } /// @dev Creates a new JsonObject struct. @@ -163,175 +189,236 @@ library json { return JsonObject({id: id, serialized: ""}); } + /// @dev Creates a new JsonObject struct. + /// @return The JsonObject struct. + function create(string memory obj) internal pure returns (JsonObject memory) { + return JsonObject({id: "", serialized: obj}); + } + /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param value The value of `key`. - /// @return The modified JsonObject struct. - function set(JsonObject memory obj, string memory key, bool value) internal returns (JsonObject memory) { + /// @return res The modified JsonObject struct. + function set(JsonObject memory obj, string memory key, bool value) internal returns (JsonResult memory res) { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } + obj.serialized = vulcan.hevm.serializeBool(obj.id, key, value); - return obj; + return Ok(obj); } /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param value The value of `key`. - /// @return The modified JsonObject struct. - function set(JsonObject memory obj, string memory key, uint256 value) internal returns (JsonObject memory) { + /// @return res The modified JsonObject struct. + function set(JsonObject memory obj, string memory key, uint256 value) internal returns (JsonResult memory res) { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } obj.serialized = vulcan.hevm.serializeUint(obj.id, key, value); - return obj; + return Ok(obj); } /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param value The value of `key`. - /// @return The modified JsonObject struct. - function set(JsonObject memory obj, string memory key, int256 value) internal returns (JsonObject memory) { + /// @return res The modified JsonObject struct. + function set(JsonObject memory obj, string memory key, int256 value) internal returns (JsonResult memory res) { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } obj.serialized = vulcan.hevm.serializeInt(obj.id, key, value); - return obj; + return Ok(obj); } /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param value The value of `key`. - /// @return The modified JsonObject struct. - function set(JsonObject memory obj, string memory key, address value) internal returns (JsonObject memory) { + /// @return res The modified JsonObject struct. + function set(JsonObject memory obj, string memory key, address value) internal returns (JsonResult memory res) { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } obj.serialized = vulcan.hevm.serializeAddress(obj.id, key, value); - return obj; + return Ok(obj); } /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param value The value of `key`. - /// @return The modified JsonObject struct. - function set(JsonObject memory obj, string memory key, bytes32 value) internal returns (JsonObject memory) { + /// @return res The modified JsonObject struct. + function set(JsonObject memory obj, string memory key, bytes32 value) internal returns (JsonResult memory res) { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } obj.serialized = vulcan.hevm.serializeBytes32(obj.id, key, value); - return obj; + return Ok(obj); } /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param value The value of `key`. - /// @return The modified JsonObject struct. - function set(JsonObject memory obj, string memory key, string memory value) internal returns (JsonObject memory) { + /// @return res The modified JsonObject struct. + function set(JsonObject memory obj, string memory key, string memory value) + internal + returns (JsonResult memory res) + { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } obj.serialized = vulcan.hevm.serializeString(obj.id, key, value); - return obj; + return Ok(obj); } /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param value The value of `key`. - /// @return The modified JsonObject struct. - function set(JsonObject memory obj, string memory key, bytes memory value) internal returns (JsonObject memory) { + /// @return res The modified JsonObject struct. + function set(JsonObject memory obj, string memory key, bytes memory value) + internal + returns (JsonResult memory res) + { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } obj.serialized = vulcan.hevm.serializeBytes(obj.id, key, value); - return obj; + return Ok(obj); } /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param values The value of `key`. - /// @return The modified JsonObject struct. - function set(JsonObject memory obj, string memory key, bool[] memory values) internal returns (JsonObject memory) { + /// @return res The modified JsonObject struct. + function set(JsonObject memory obj, string memory key, bool[] memory values) + internal + returns (JsonResult memory res) + { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } obj.serialized = vulcan.hevm.serializeBool(obj.id, key, values); - return obj; + return Ok(obj); } /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param values The value of `key`. - /// @return The modified JsonObject struct. + /// @return res The modified JsonObject struct. function set(JsonObject memory obj, string memory key, uint256[] memory values) internal - returns (JsonObject memory) + returns (JsonResult memory res) { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } obj.serialized = vulcan.hevm.serializeUint(obj.id, key, values); - return obj; + return Ok(obj); } /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param values The value of `key`. - /// @return The modified JsonObject struct. + /// @return res The modified JsonObject struct. function set(JsonObject memory obj, string memory key, int256[] memory values) internal - returns (JsonObject memory) + returns (JsonResult memory res) { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } obj.serialized = vulcan.hevm.serializeInt(obj.id, key, values); - return obj; + return Ok(obj); } /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param values The value of `key`. - /// @return The modified JsonObject struct. + /// @return res The modified JsonObject struct. function set(JsonObject memory obj, string memory key, address[] memory values) internal - returns (JsonObject memory) + returns (JsonResult memory res) { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } obj.serialized = vulcan.hevm.serializeAddress(obj.id, key, values); - return obj; + return Ok(obj); } /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param values The value of `key`. - /// @return The modified JsonObject struct. + /// @return res The modified JsonObject struct. function set(JsonObject memory obj, string memory key, bytes32[] memory values) internal - returns (JsonObject memory) + returns (JsonResult memory res) { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } obj.serialized = vulcan.hevm.serializeBytes32(obj.id, key, values); - return obj; + return Ok(obj); } /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param values The value of `key`. - /// @return The modified JsonObject struct. + /// @return res The modified JsonObject struct. function set(JsonObject memory obj, string memory key, string[] memory values) internal - returns (JsonObject memory) + returns (JsonResult memory res) { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } obj.serialized = vulcan.hevm.serializeString(obj.id, key, values); - return obj; + return Ok(obj); } /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param values The value of `key`. - /// @return The modified JsonObject struct. + /// @return res The modified JsonObject struct. function set(JsonObject memory obj, string memory key, bytes[] memory values) internal - returns (JsonObject memory) + returns (JsonResult memory res) { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } obj.serialized = vulcan.hevm.serializeBytes(obj.id, key, values); - return obj; + return Ok(obj); } /// @dev Serializes and sets the key and value for the provided json object. /// @param obj The json object to modify. /// @param key The key that will hold the `value`. /// @param value The value of `key`. - /// @return The modified JsonObject struct. + /// @return res The modified JsonObject struct. function set(JsonObject memory obj, string memory key, JsonObject memory value) internal - returns (JsonObject memory) + returns (JsonResult memory res) { + if (obj.isImmutable()) { + return JsonError.immutableJson(); + } obj.serialized = vulcan.hevm.serializeString(obj.id, key, value.serialized); - return obj; + return Ok(obj); } /// @dev Writes a JsonObject struct to a file. @@ -349,6 +436,10 @@ library json { vulcan.hevm.writeJson(obj.serialized, path, key); } + function isImmutable(JsonObject memory obj) internal pure returns (bool) { + return bytes(obj.id).length == 0; + } + function _incrementId() private returns (uint256 count) { bytes32 slot = keccak256("vulcan.json.id.counter"); @@ -360,3 +451,4 @@ library json { } using json for JsonObject global; +using LibJsonResult for JsonResult global; diff --git a/src/_modules/Result.sol b/src/_modules/Result.sol new file mode 100644 index 00000000..90d01463 --- /dev/null +++ b/src/_modules/Result.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.13 <0.9.0; + +struct Error { + bytes32 id; + string message; +} + +enum ResultType { + Error, + Ok +} + +struct Result { + ResultType _type; + bytes _data; +} + +struct StringResult { + Result _inner; +} + +library LibResult { + function isError(Result memory self) internal pure returns (bool) { + return self._type == ResultType.Error; + } + + function isOk(Result memory self) internal pure returns (bool) { + return self._type == ResultType.Ok; + } + + function toError(Result memory self) internal pure returns (Error memory) { + return abi.decode(self._data, (Error)); + } + + function toValue(Result memory self) internal pure returns (bytes memory) { + return self._data; + } + + function unwrap(Result memory self) internal pure returns (bytes memory) { + if (self.isError()) { + revert(self.toError().message); + } + + return self._data; + } + + function expect(Result memory self, string memory err) internal pure returns (bytes memory) { + if (self.isError()) { + revert(err); + } + + return self._data; + } + + function toResult(Error memory error) internal pure returns (Result memory) { + return Result({_type: ResultType.Error, _data: abi.encode(error)}); + } +} + +library LibStringResult { + function isOk(StringResult memory self) internal pure returns (bool) { + return self._inner.isOk(); + } + + function isError(StringResult memory self) internal pure returns (bool) { + return self._inner.isError(); + } + + function unwrap(StringResult memory self) internal pure returns (string memory) { + return string(self._inner.unwrap()); + } + + function expect(StringResult memory self, string memory err) internal pure returns (string memory) { + return string(self._inner.expect(err)); + } + + function toError(StringResult memory self) internal pure returns (Error memory) { + return self._inner.toError(); + } + + function toValue(StringResult memory self) internal pure returns (string memory) { + return string(self._inner.toValue()); + } +} + +function Ok(bytes memory value) pure returns (Result memory) { + return Result({_type: ResultType.Ok, _data: value}); +} + +function Ok(string memory value) pure returns (StringResult memory) { + return StringResult(Ok(bytes(value))); +} + +using LibStringResult for StringResult global; +using LibResult for Result global; +using LibResult for Error global; diff --git a/src/_modules/experimental/Request.sol b/src/_modules/experimental/Request.sol new file mode 100644 index 00000000..7e9367d5 --- /dev/null +++ b/src/_modules/experimental/Request.sol @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Command, CommandResult, commands} from "../Commands.sol"; +import {JsonObject, json as jsonModule, JsonResult, Ok} from "../Json.sol"; + +import {Result, Error, StringResult, Ok} from "../Result.sol"; + +enum Method { + GET, + POST, + PUT, + PATCH, + DELETE +} + +struct Header { + string key; + string value; +} + +struct RequestClient { + // Default headers, not used yet + Header[] headers; +} + +struct RequestBuilder { + RequestClient client; + RequestResult request; +} + +struct Request { + Method method; + string url; + Header[] headers; + bytes body; +} + +struct RequestResult { + Result _inner; +} + +// TODO: headers, etc +struct Response { + string url; + uint256 status; + bytes body; +} + +struct ResponseResult { + Result _inner; +} + +library request { + using request for *; + + // Return an empty client + function create() internal pure returns (RequestClient memory) {} + + function get(string memory url) internal returns (ResponseResult memory) { + return create().get(url).send(); + } +} + +library RequestError { + bytes32 constant COMMAND_FAILED = keccak256("COMMAND_FAILED"); + + function commandFailed() public pure returns (ResponseResult memory) { + return ResponseResult(Error(COMMAND_FAILED, "Command failed").toResult()); + } +} + +library LibRequestResult { + function isOk(RequestResult memory self) internal pure returns (bool) { + return self._inner.isOk(); + } + + function isError(RequestResult memory self) internal pure returns (bool) { + return self._inner.isError(); + } + + function unwrap(RequestResult memory self) internal pure returns (Request memory) { + return abi.decode(self._inner.unwrap(), (Request)); + } + + function expect(RequestResult memory self, string memory err) internal pure returns (Request memory) { + return abi.decode(self._inner.expect(err), (Request)); + } + + function toError(RequestResult memory self) internal pure returns (Error memory) { + return self._inner.toError(); + } + + function toValue(RequestResult memory self) internal pure returns (Request memory) { + return abi.decode(self._inner.toValue(), (Request)); + } +} + +library LibResponseResult { + function isOk(ResponseResult memory self) internal pure returns (bool) { + return self._inner.isOk(); + } + + function isError(ResponseResult memory self) internal pure returns (bool) { + return self._inner.isError(); + } + + function unwrap(ResponseResult memory self) internal pure returns (Response memory) { + return abi.decode(self._inner.unwrap(), (Response)); + } + + function expect(ResponseResult memory self, string memory err) internal pure returns (Response memory) { + return abi.decode(self._inner.expect(err), (Response)); + } + + function toError(ResponseResult memory self) internal pure returns (Error memory) { + return self._inner.toError(); + } + + function toValue(ResponseResult memory self) internal pure returns (Response memory) { + return abi.decode(self._inner.toValue(), (Response)); + } +} + +library LibRequestClient { + function get(RequestClient memory self, string memory url) internal pure returns (RequestBuilder memory) { + return LibRequestBuilder.create(self, Method.GET, url); + } + + function del(RequestClient memory self, string memory url) internal pure returns (RequestBuilder memory) { + return LibRequestBuilder.create(self, Method.DELETE, url); + } + + function patch(RequestClient memory self, string memory url) internal pure returns (RequestBuilder memory) { + return LibRequestBuilder.create(self, Method.PATCH, url); + } + + function post(RequestClient memory self, string memory url) internal pure returns (RequestBuilder memory) { + return LibRequestBuilder.create(self, Method.POST, url); + } + + function put(RequestClient memory self, string memory url) internal pure returns (RequestBuilder memory) { + return LibRequestBuilder.create(self, Method.PUT, url); + } +} + +library LibRequestBuilder { + using request for *; + + function create(RequestClient memory client, Method method, string memory url) + internal + pure + returns (RequestBuilder memory builder) + { + builder.client = client; + builder.request = Ok(Request({method: method, url: url, headers: new Header[](0), body: new bytes(0)})); + } + + function send(RequestBuilder memory self) internal returns (ResponseResult memory) { + RequestResult memory reqResult = self.build(); + + if (reqResult.isError()) { + return ResponseResult(reqResult.toError().toResult()); + } + + Request memory req = reqResult.toValue(); + + (string(req.toCommand().toString())); + + CommandResult memory result = req.toCommand().run(); + + if (result.isError()) { + return RequestError.commandFailed(); + } + + (uint256 status, bytes memory _body) = abi.decode(result.stdout, (uint256, bytes)); + + return Ok(Response({url: req.url, status: status, body: _body})); + } + + function build(RequestBuilder memory self) internal pure returns (RequestResult memory) { + return self.request; + } + + function body(RequestBuilder memory self, string memory _body) internal pure returns (RequestBuilder memory) { + if (self.request.isOk()) { + Request memory req = self.request.toValue(); + req.body = bytes(_body); + self.request = Ok(req); + } + return self; + } + + function basicAuth(RequestBuilder memory self, string memory username, string memory password) + internal + pure + returns (RequestBuilder memory) + { + // "Authorization: Basic $(base64 <<<"joeuser:secretpass")" + return self.header("Authorization", string.concat('Basic $(echo -n "', username, ":", password, '" | base64)')); + } + + function bearerAuth(RequestBuilder memory self, string memory token) + internal + pure + returns (RequestBuilder memory) + { + return self.header("Authorization", string.concat("Bearer ", token)); + } + + function header(RequestBuilder memory self, string memory key, string memory value) + internal + pure + returns (RequestBuilder memory) + { + if (self.request.isOk()) { + Request memory req = self.request.toValue(); + uint256 len = req.headers.length; + req.headers = new Header[](len + 1); + for (uint256 i = 0; i < len; i++) { + req.headers[i] = req.headers[i]; + } + req.headers[len] = Header({key: key, value: value}); + self.request = Ok(req); + } + return self; + } + + function json(RequestBuilder memory self, JsonObject memory obj) internal pure returns (RequestBuilder memory) { + return self.json(obj.serialized); + } + + function json(RequestBuilder memory self, string memory serialized) internal pure returns (RequestBuilder memory) { + // TODO: parse json and set error if it fails + return self.header("Content-Type", "application/json").body(serialized); + } +} + +library LibRequest { + function toCommand(Request memory self) internal pure returns (Command memory) { + // Adapted from https://github.com/memester-xyz/surl/blob/034c912ae9b5e707a5afd21f145b452ad8e800df/src/Surl.sol#L90 + string memory script = + string.concat('response=$(curl -s -w "\\n%{http_code}" ', self.url, " -X ", toString(self.method)); + + for (uint256 i = 0; i < self.headers.length; i++) { + script = string.concat(script, " -H ", '"', self.headers[i].key, ": ", self.headers[i].value, '"'); + } + + if (self.body.length > 0) { + script = string.concat(script, " -d ", "'", string(self.body), "'"); + } + + script = string.concat( + script, + ');body=$(sed "$ d" <<< "$response" | tr -d "\\n");code=$(tail -n 1 <<< "$response");cast abi-encode "response(uint256,string)" "$code" "$body";' + ); + + return commands.create("bash").arg("-c").arg(script); + } + + function toString(Method method) internal pure returns (string memory) { + if (method == Method.GET) { + return "GET"; + } else if (method == Method.POST) { + return "POST"; + } else if (method == Method.PUT) { + return "PUT"; + } else if (method == Method.PATCH) { + return "PATCH"; + } else if (method == Method.DELETE) { + return "DELETE"; + } + + // Should never happen + revert("Invalid method"); + } +} + +library LibResponse { + // TODO: validate response and return error if there are issues + function json(Response memory self) internal pure returns (JsonResult memory) { + return Ok(jsonModule.create(string(self.body))); + } + + function text(Response memory self) internal pure returns (StringResult memory) { + return Ok(string(self.body)); + } + + // function asBytes(Response memory self) internal pure returns (BytesResult memory) { + // return BytesResult({value: self.body, ok: true, error: ""}); + // } +} + +function Ok(Request memory value) pure returns (RequestResult memory) { + return RequestResult(Ok(abi.encode(value))); +} + +function Ok(Response memory value) pure returns (ResponseResult memory) { + return ResponseResult(Ok(abi.encode(value))); +} + +using LibRequestClient for RequestClient global; +using LibRequest for Request global; +using LibResponse for Response global; +using LibRequestBuilder for RequestBuilder global; +using LibRequestResult for RequestResult global; +using LibResponseResult for ResponseResult global; diff --git a/src/test.sol b/src/test.sol index a50f1abc..18dd34d2 100644 --- a/src/test.sol +++ b/src/test.sol @@ -15,7 +15,7 @@ import {fs, FsMetadata} from "./_modules/Fs.sol"; import {gas} from "./_modules/Gas.sol"; import {huff, Huffc} from "./_modules/Huff.sol"; import {InvariantsBase, invariants} from "./_modules/Invariants.sol"; -import {json, JsonObject} from "./_modules/Json.sol"; +import {json, JsonObject, Ok} from "./_modules/Json.sol"; import {strings} from "./_modules/Strings.sol"; import {watchers, Watcher} from "./_modules/Watchers.sol"; import {config, Rpc} from "./_modules/Config.sol"; @@ -25,6 +25,7 @@ import {format} from "./_utils/format.sol"; import {println} from "./_utils/println.sol"; import {bound} from "./_utils/bound.sol"; import {formatError} from "./_utils/formatError.sol"; +import {Ok} from "./_modules/Result.sol"; // @dev Main entry point to Vulcan tests contract Test is InvariantsBase { diff --git a/test/_modules/Json.t.sol b/test/_modules/Json.t.sol index 25d03b99..7c3b5c06 100644 --- a/test/_modules/Json.t.sol +++ b/test/_modules/Json.t.sol @@ -1,6 +1,6 @@ pragma solidity >=0.8.13 <0.9.0; -import {Test, expect, console, json, JsonObject, vulcan} from "../../src/test.sol"; +import {Test, expect, println, json, JsonObject, vulcan} from "../../src/test.sol"; contract JsonTest is Test { using vulcan for *; @@ -9,87 +9,92 @@ contract JsonTest is Test { string foo; } - function testParse() external { - string memory jsonStr = '{"foo":"bar"}'; - Foo memory obj = abi.decode(json.parse(jsonStr), (Foo)); + function testParseImmutable() external { + Foo memory obj = abi.decode(json.create('{"foo":"bar"}').parse(), (Foo)); expect(obj.foo).toEqual("bar"); } - function testParseStruct() external { - JsonObject memory jsonObject = json.create().set("foo", string("bar")); - Foo memory obj = abi.decode(json.parse(jsonObject), (Foo)); + function testParse() external { + JsonObject memory jsonObject = json.create().set("foo", string("bar")).unwrap(); + Foo memory obj = abi.decode(jsonObject.parse(), (Foo)); expect(obj.foo).toEqual("bar"); } function testGetUint() external { - expect(json.getUint('{"foo":123}', ".foo")).toEqual(123); + expect(json.create('{"foo":123}').getUint(".foo")).toEqual(123); } function testGetUintArray() external { - uint256[] memory arr = json.getUintArray('{"foo":[123]}', ".foo"); + uint256[] memory arr = json.create('{"foo":[123]}').getUintArray(".foo"); expect(arr.length).toEqual(1); expect(arr[0]).toEqual(123); } function testGetInt() external { - expect(json.getInt('{"foo":-123}', ".foo")).toEqual(-123); + expect(json.create('{"foo":-123}').getInt(".foo")).toEqual(-123); } function testGetIntArray() external { - int256[] memory arr = json.getIntArray('{"foo":[-123]}', ".foo"); + int256[] memory arr = json.create('{"foo":[-123]}').getIntArray(".foo"); expect(arr.length).toEqual(1); expect(arr[0]).toEqual(-123); } function testGetBool() external { - expect(json.getBool('{"foo":true}', ".foo")).toEqual(true); + expect(json.create('{"foo":true}').getBool(".foo")).toEqual(true); } function testGetBoolArray() external { - bool[] memory arr = json.getBoolArray('{"foo":[true]}', ".foo"); + bool[] memory arr = json.create('{"foo":[true]}').getBoolArray(".foo"); expect(arr.length).toEqual(1); expect(arr[0]).toEqual(true); } function testGetAddress() external { - expect(json.getAddress('{"foo":"0x0000000000000000000000000000000000000001"}', ".foo")).toEqual(address(1)); + expect(json.create('{"foo":"0x0000000000000000000000000000000000000001"}').getAddress(".foo")).toEqual( + address(1) + ); } function testGetAddressArray() external { - address[] memory arr = json.getAddressArray('{"foo":["0x0000000000000000000000000000000000000001"]}', ".foo"); + address[] memory arr = + json.create('{"foo":["0x0000000000000000000000000000000000000001"]}').getAddressArray(".foo"); expect(arr.length).toEqual(1); expect(arr[0]).toEqual(address(1)); } function testGetString() external { - expect(json.getString('{"foo":"bar"}', ".foo")).toEqual("bar"); + expect(json.create('{"foo":"bar"}').getString(".foo")).toEqual("bar"); } function testGetStringArray() external { - string[] memory arr = json.getStringArray('{"foo":["bar"]}', ".foo"); + string[] memory arr = json.create('{"foo":["bar"]}').getStringArray(".foo"); expect(arr.length).toEqual(1); expect(arr[0]).toEqual("bar"); } function testGetBytes() external { - expect(json.getBytes('{"foo":"0x1234"}', ".foo")).toEqual(hex"1234"); + expect(json.create('{"foo":"0x1234"}').getBytes(".foo")).toEqual(hex"1234"); } function testGetBytesArray() external { - bytes[] memory arr = json.getBytesArray('{"foo":["0x1234"]}', ".foo"); + bytes[] memory arr = json.create('{"foo":["0x1234"]}').getBytesArray(".foo"); expect(arr.length).toEqual(1); expect(arr[0]).toEqual(hex"1234"); } function testGetBytes32() external { - expect(json.getBytes32('{"foo":"0x0000000000000000000000000000000000000000000000000000000000000001"}', ".foo")) - .toEqual(bytes32(uint256(1))); + expect( + json.create('{"foo":"0x0000000000000000000000000000000000000000000000000000000000000001"}').getBytes32( + ".foo" + ) + ).toEqual(bytes32(uint256(1))); } function testGetBytes32Array() external { - bytes32[] memory arr = json.getBytes32Array( - '{"foo":["0x0000000000000000000000000000000000000000000000000000000000000001"]}', ".foo" - ); + bytes32[] memory arr = json.create( + '{"foo":["0x0000000000000000000000000000000000000000000000000000000000000001"]}' + ).getBytes32Array(".foo"); expect(arr.length).toEqual(1); expect(arr[0]).toEqual(bytes32(uint256(1))); } diff --git a/test/_modules/Request.t.sol b/test/_modules/Request.t.sol new file mode 100644 index 00000000..ad118e71 --- /dev/null +++ b/test/_modules/Request.t.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.13 <0.9.0; + +import {Test, expect, println, json, JsonObject, vulcan, commands} from "../../src/test.sol"; + +import { + request, + RequestResult, + RequestClient, + Response, + ResponseResult +} from "../../src/_modules/experimental/Request.sol"; + +contract RequestTest is Test { + using vulcan for *; + + function testBasicAuth() external { + RequestClient memory client = request.create(); + + Response memory res = + client.get("https://httpbin.org/basic-auth/user/passwd").basicAuth("user", "passwd").send().unwrap(); + + expect(res.status).toEqual(200); + + JsonObject memory obj = res.json().unwrap(); + + expect(obj.getString(".authenticated")).toEqual("true"); + } + + function testRequestFail() external { + Response memory res = request.get("https://httpbin.org/404").unwrap(); + expect(res.status).toEqual(404); + } + + function testRequestGet() external { + RequestClient memory client = request.create(); + + Response memory res = client.get("https://httpbin.org/get").send().unwrap(); + expect(res.status).toEqual(200); + } + + function testRequestPost() external { + RequestClient memory client = request.create(); + Response memory res = client.post("https://httpbin.org/post").json('{ "foo": "bar" }').send().unwrap(); + // { ... "json": { "foo": "bar" } ... } + expect(res.json().unwrap().getString(".json.foo")).toEqual("bar"); + expect(res.status).toEqual(200); + } + + function testRequestJsonDecode() external { + JsonObject memory obj = request.get("https://httpbin.org/ip").unwrap().json().unwrap(); + + expect(bytes(obj.getString(".origin")).length).toBeGreaterThan(0); + } + + struct HttpBinIpResponse { + string origin; + } + + function testRequestJsonParse() external { + JsonObject memory obj = request.get("https://httpbin.org/ip").unwrap().json().unwrap(); + + HttpBinIpResponse memory res = abi.decode(obj.parse(), (HttpBinIpResponse)); + + expect(bytes(res.origin).length).toBeGreaterThan(0); + } +}