diff --git a/__tests__/Obi_test.res b/__tests__/Obi_test.res new file mode 100644 index 00000000..06bf7215 --- /dev/null +++ b/__tests__/Obi_test.res @@ -0,0 +1,324 @@ +open Jest +open Obi2 +open Expect + +describe("Expect Obi to extract fields correctly", () => { + test("should be able to extract fields from bytes correctly", () => { + expect(extractFields(`{symbol:string,multiplier:u64}/{volume:u64}`, Input))->toEqual( + Some([ + {fieldName: "symbol", fieldType: "string"}, + {fieldName: "multiplier", fieldType: "u64"}, + ]), + ) + }) +}) + +describe("Expect Obi encode correctly", () => { + test("should be able to encode input (string, int) correctly", () => { + expect( + encode( + `{symbol: string,multiplier: u64}/{price: u64,sources: [{ name: string, time: u64 }]}`, + Input, + [ + {fieldName: "symbol", fieldValue: "\"BTC\""}, + {fieldName: "multiplier", fieldValue: "1000000000"}, + ], + ), + )->toEqual(Some("00000003425443000000003b9aca00"->JsBuffer.fromHex)) + }) + + test("should be able to encode input (bytes) correctly", () => { + expect( + encode( + `{symbol: bytes}/{price: u64}`, + Input, + [ + { + fieldName: "symbol", + fieldValue: "\"2242544322\"", + }, + ], + ), + )->toEqual(Some("0000000a32323432353434333232"->JsBuffer.fromHex)) + }) + + test("should be able to encode nested input correctly", () => { + expect( + encode( + `{list: [{symbol: {name: [string]}}]}/{price: u64}`, + Input, + [{fieldName: "list", fieldValue: `[{"symbol": {"name": ["XXX", "YYY"]}}]`}], + ), + )->toEqual(Some("0x00000001000000020000000358585800000003595959"->JsBuffer.fromHex)) + }) + + test("should be able to encode output correctly", () => { + expect( + encode( + `{list: [{symbol: {name: [string]}}]}/{price: [u64]}`, + Output, + [{fieldName: "price", fieldValue: `[120, 323]`}], + ), + )->toEqual(Some("0x0000000200000000000000780000000000000143"->JsBuffer.fromHex)) + }) + + test("should return None if invalid data", () => { + expect( + encode( + `{symbol: string,multiplier: u64}/{price: u64,sources: [{ name: string, time: u64 }]}`, + Input, + [{fieldName: "symbol", fieldValue: "BTC"}], + ), + )->toEqual(None) + }) + + test("should return None if invalid input schema", () => { + expect( + encode( + `{symbol: string}/{price: u64,sources: [{ name: string, time: u64 }]}`, + Input, + [ + {fieldName: "symbol", fieldValue: "BTC"}, + {fieldName: "multiplier", fieldValue: "1000000000"}, + ], + ), + )->toEqual(None) + }) + + test("should return None if invalid output schema", () => { + expect(encode(`{symbol: string}`, Output, [{fieldName: "symbol", fieldValue: "BTC"}]))->toEqual( + None, + ) + }) +}) + +describe("Expect Obi decode correctly", () => { + test("should be able to decode from bytes correctly", () => { + expect( + decode( + `{symbol: string,multiplier: u64}/{price: u64,sources: [{ name: string, time: u64 }]}`, + Input, + "0x00000003425443000000003b9aca00"->JsBuffer.fromHex, + ), + )->toEqual( + Some([ + {fieldName: "symbol", fieldValue: "\"BTC\""}, + {fieldName: "multiplier", fieldValue: "1000000000"}, + ]), + ) + }) + + test("should be able to decode from bytes correctly (nested)", () => { + expect( + decode( + `{list: [{symbol: {name: [string]}}]}/{price: u64}`, + Input, + "0x00000001000000020000000358585800000003595959"->JsBuffer.fromHex, + ), + )->toEqual( + Some([{fieldName: "list", fieldValue: "[{\"symbol\":{\"name\":[\"XXX\",\"YYY\"]}}]"}]), + ) + }) + + test("should be able to decode from bytes correctly (bytes)", () => { + expect( + decode(`{symbol: bytes}/{price: u64}`, Input, "0x0000000455555555"->JsBuffer.fromHex), + )->toEqual(Some([{fieldName: "symbol", fieldValue: "0x55555555"}])) + }) + + test("should return None if invalid schema", () => { + expect( + decode( + `{symbol: string}/{price: u64,sources: [{ name: string, time: u64 }]}`, + Input, + "0x00000003425443000000003b9aca00"->JsBuffer.fromHex, + ), + )->toEqual(None) + }) +}) + +describe("should be able to generate solidity correctly", () => { + test("should be able to generate solidity", () => { + expect(generateDecoderSolidity(`{symbol:string,multiplier:u64}/{px:u64}`))->toEqual( + Some(`pragma solidity ^0.5.0; + +import "./Obi.sol"; + +library ParamsDecoder { + using Obi for Obi.Data; + + struct Params { + uint64 multiplier; + string symbol; + } + + function decodeParams(bytes memory _data) + internal + pure + returns (Params memory result) + { + Obi.Data memory data = Obi.from(_data); + result.multiplier = data.decodeU64(); + result.symbol = string(data.decodeBytes()); + } +} + +library ResultDecoder { + using Obi for Obi.Data; + + struct Result { + uint64 px; + } + + function decodeResult(bytes memory _data) + internal + pure + returns (Result memory result) + { + Obi.Data memory data = Obi.from(_data); + result.px = data.decodeU64(); + } +} + +`), + ) + }) + + test("should be able to generate solidity when parameter is array", () => { + expect(generateDecoderSolidity(`{symbols:[string],multiplier:u64}/{rates:[u64]}`))->toEqual( + Some(`pragma solidity ^0.5.0; + +import "./Obi.sol"; + +library ParamsDecoder { + using Obi for Obi.Data; + + struct Params { + uint64 multiplier; + string[] symbols; + } + + function decodeParams(bytes memory _data) + internal + pure + returns (Params memory result) + { + Obi.Data memory data = Obi.from(_data); + result.multiplier = data.decodeU64(); + uint32 length = data.decodeU32(); + string[] memory symbols = new string[](length); + for (uint256 i = 0; i < length; i++) { + symbols[i] = string(data.decodeBytes()); + } + result.symbols = symbols + } +} + +library ResultDecoder { + using Obi for Obi.Data; + + struct Result { + uint64[] rates; + } + + function decodeResult(bytes memory _data) + internal + pure + returns (Result memory result) + { + Obi.Data memory data = Obi.from(_data); + uint32 length = data.decodeU32(); + uint64[] memory rates = new uint64[](length); + for (uint256 i = 0; i < length; i++) { + rates[i] = data.decodeU64(); + } + result.rates = rates + } +} + +`), + ) + }) +}) + +describe("should be able to generate go code correctly", () => { + // TODO: Change to real generated code once golang ParamsDecode is implemented + test("should be able to generate go code 1", () => { + expect( + generateDecoderGo("main", `{symbol:string,multiplier:u64}/{px:u64}`, Obi2.Input), + )->toEqual(Some(`Code is not available.`)) + }) + test("should be able to generate go code 2", () => { + expect( + generateDecoderGo("test", `{symbol:string,multiplier:u64}/{px:u64}`, Obi2.Output), + )->toEqual( + Some(`package test + +import "github.com/bandchain/chain/pkg/obi" + +type Result struct { + Px uint64 +} + +func DecodeResult(data []byte) (Result, error) { + decoder := obi.NewObiDecoder(data) + + px, err := decoder.DecodeU64() + if err !== nil { + return Result{}, err + } + + if !decoder.Finished() { + return Result{}, errors.New("Obi: bytes left when decode result") + } + + return Result{ + Px: px + }, nil +}`), + ) + }) +}) + +describe("should be able to generate encode go code correctly", () => { + test("should be able to generate encode go code 1", () => { + expect(generateEncodeGo("main", `{symbol:string,multiplier:u64}/{px:u64}`, Input))->toEqual( + Some(`package main + +import "github.com/bandchain/chain/pkg/obi" + +type Result struct { + Multiplier uint64 + Symbol string +} + +func(result *Result) EncodeResult() []byte { + encoder := obi.NewObiEncoder() + + encoder.EncodeU64(result.multiplier) + encoder.EncodeString(result.symbol) + + return encoder.GetEncodedData() +}`), + ) + }) + test("should be able to generate encode go code 2", () => { + expect(generateEncodeGo("test", `{symbol:string,multiplier:u64}/{px:u64}`, Output))->toEqual( + Some(`package test + +import "github.com/bandchain/chain/pkg/obi" + +type Result struct { + Px uint64 +} + +func(result *Result) EncodeResult() []byte { + encoder := obi.NewObiEncoder() + + encoder.EncodeU64(result.px) + + return encoder.GetEncodedData() +}`), + ) + }) +}) diff --git a/src/components/msg/tx-index/RenderMsgDetails.res b/src/components/msg/tx-index/RenderMsgDetails.res index a7d78098..05011bd2 100644 --- a/src/components/msg/tx-index/RenderMsgDetails.res +++ b/src/components/msg/tx-index/RenderMsgDetails.res @@ -31,7 +31,7 @@ module Calldata = { data={calldata->JsBuffer.toHex(~with0x=false)} title="Copy as bytes" width=125 /> - {Obi.decode(schema, "input", calldata)->Belt.Option.mapWithDefault(failed, calldataKVs => + {Obi2.decode(schema, Obi2.Input, calldata)->Belt.Option.mapWithDefault(failed, calldataKVs => Belt.Array.map(({fieldName, fieldValue}) => [ KVTable.Value(fieldName), diff --git a/src/components/page/oracle-script/OracleScriptBridgeCode.res b/src/components/page/oracle-script/OracleScriptBridgeCode.res index 2bdfaa08..2ee806dc 100644 --- a/src/components/page/oracle-script/OracleScriptBridgeCode.res +++ b/src/components/page/oracle-script/OracleScriptBridgeCode.res @@ -140,7 +140,7 @@ module LanguageIcon = { } let getFileNameFromLanguage = (~language, ~dataType) => { - let dataTypeString = dataType->Obi.dataTypeToString + let dataTypeString = dataType->Obi2.dataTypeToString switch language { | Solidity => "Decoders.sol" | Go => j`$(dataTypeString)Decoder.go` @@ -149,8 +149,8 @@ let getFileNameFromLanguage = (~language, ~dataType) => { let getCodeFromSchema = (~schema, ~language, ~dataType) => { switch language { - | Solidity => Obi.generateDecoderSolidity(schema) - | Go => Obi.generateDecoderGo("main", schema, dataType) + | Solidity => Obi2.generateDecoderSolidity(schema) + | Go => Obi2.generateDecoderGo("main", schema, dataType) } } @@ -270,6 +270,6 @@ let make = (~schema) => {
{schema->renderCode}
- + } diff --git a/src/components/page/oracle-script/OracleScriptExecute.res b/src/components/page/oracle-script/OracleScriptExecute.res index b64ca1ec..424af157 100644 --- a/src/components/page/oracle-script/OracleScriptExecute.res +++ b/src/components/page/oracle-script/OracleScriptExecute.res @@ -101,7 +101,7 @@ module ConnectPanel = { module ParameterInput = { @react.component - let make = (~params: Obi.field_key_type_t, ~index, ~setCallDataArr) => { + let make = (~params: Obi2.field_key_type_t, ~index, ~setCallDataArr) => { let fieldType = params.fieldType let fieldName = params.fieldName->Js.String2.replaceByRe(%re(`/[_]/g`), " ") let ({ThemeContext.theme: theme}, _) = React.useContext(ThemeContext.context) @@ -323,7 +323,7 @@ module ExecutionPart = { let make = ( ~id: ID.OracleScript.t, ~schema: string, - ~paramsInput: array, + ~paramsInput: array, ) => { let isMobile = Media.isMobile() @@ -453,17 +453,16 @@ module ExecutionPart = { style={Styles.button(theme, result == Loading)} onClick={_ => if result !== Loading { - switch Obi.encode( - schema, - "input", + let inputDataEncode = paramsInput ->Belt.Array.map(({fieldName}) => fieldName) ->Belt.Array.zip(callDataArr) ->Belt.Array.map(((fieldName, fieldValue)) => { - open Obi + open Obi2 {fieldName, fieldValue} - }), - ) { + }) + + switch Obi2.encode(schema, Obi2.Input, inputDataEncode) { | Some(encoded) => setResult(_ => Loading) let _ = TxCreator.sendTransaction( @@ -536,7 +535,7 @@ module ExecutionPart = { @react.component let make = (~id: ID.OracleScript.t, ~schema: string) => { - let paramsInput = schema->Obi.extractFields("input")->Belt.Option.getExn + let paramsInput = schema->Obi2.extractFields(Input)->Belt.Option.getExn Some() }->Belt.Option.getWithDefault( diff --git a/src/components/page/oracle-script/OracleScriptExecuteResponse.res b/src/components/page/oracle-script/OracleScriptExecuteResponse.res index 2812a88b..e55af0b4 100644 --- a/src/components/page/oracle-script/OracleScriptExecuteResponse.res +++ b/src/components/page/oracle-script/OracleScriptExecuteResponse.res @@ -95,7 +95,7 @@ let make = (~txResponse: TxCreator.tx_response_t, ~schema: string) => {switch requestOpt { | Some({resolveStatus: Success, result: Some(result), id}) => - let outputKVsOpt = Obi.decode(schema, "output", result) + let outputKVsOpt = Obi2.decode(schema, Obi2.Output, result) switch outputKVsOpt { | Some(outputKVs) => <> diff --git a/src/components/reusable/layout/InfoMobileCard.res b/src/components/reusable/layout/InfoMobileCard.res index c0c451ee..4385d16c 100644 --- a/src/components/reusable/layout/InfoMobileCard.res +++ b/src/components/reusable/layout/InfoMobileCard.res @@ -26,8 +26,8 @@ type t = | RequestStatus(RequestSub.resolve_status_t, string) | ProgressBar(request_count_t) | Float(float, option) - | KVTableReport(array, array) - | KVTableRequest(option>) + | KVTableReport(array, array) + | KVTableRequest(option>) | CopyButton(JsBuffer.t) | Percentage(float, option) | Timestamp(MomentRe.Moment.t) diff --git a/src/pages/Example.res b/src/pages/Example.res deleted file mode 100644 index 93fab1e3..00000000 --- a/src/pages/Example.res +++ /dev/null @@ -1,398 +0,0 @@ -// module Styles = { -// open CssJs - -// let root = style(. [ -// backgroundColor(black), -// color(white), -// padding2(~v=px(10), ~h=px(20)), -// marginTop(Spacing.lg), -// Media.mobile([backgroundColor(pink)]), -// ]) - -// let padding = style(. [padding(px(20))]) -// } - -// @react.component -// let make = () => { -// let ({ThemeContext.isDarkMode: isDarkMode, theme}, toggle) = React.useContext( -// ThemeContext.context, -// ) -// let (_, dispatchModal) = React.useContext(ModalContext.context) - -// let currentTime = -// React.useContext(TimeContext.context) -> MomentRe.Moment.format(Config.timestampUseFormat) - -// Js.log(currentTime) - -// let send = () => { -// None->SubmitMsg.Send->SubmitTx->OpenModal->dispatchModal -// } - -// let pageSize = 5 -// let (value, setValue) = React.useState(_ => 0.) -// let (preValue, setPreValue) = React.useState(_ => 0.) -// let (page, setPage) = React.useState(_ => 1) -// let blockSub = BlockSub.getList(~page, ~pageSize, ()) -// let run = () => -// AxiosRequest.execute( -// AxiosRequest.t( -// ~executable="IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwppbXBvcnQgcmVxdWVzdHMKaW1wb3J0IHN5cwoKVVJMID0gImh0dHBzOi8vYXNpYS1zb3V0aGVhc3QyLXByaWNlLWNhY2hpbmcuY2xvdWRmdW5jdGlvbnMubmV0L3F1ZXJ5LXByaWNlIgpIRUFERVJTID0geyJDb250ZW50LVR5cGUiOiAiYXBwbGljYXRpb24vanNvbiJ9CgoKZGVmIG1haW4oc3ltYm9scyk6CiAgICBwYXlsb2FkID0geyJzb3VyY2UiOiAiY3J5cHRvX2NvbXBhcmUiLCAic3ltYm9scyI6IHN5bWJvbHN9CiAgICByID0gcmVxdWVzdHMucG9zdChVUkwsIGhlYWRlcnM9SEVBREVSUywganNvbj1wYXlsb2FkKQogICAgci5yYWlzZV9mb3Jfc3RhdHVzKCkKCiAgICBweHMgPSByLmpzb24oKQoKICAgIGlmIGxlbihweHMpICE9IGxlbihzeW1ib2xzKToKICAgICAgICByYWlzZSBFeGNlcHRpb24oIlNMVUdfQU5EX1NZTUJPTF9MRU5fTk9UX01BVENIIikKCiAgICByZXR1cm4gIiwiLmpvaW4ocHhzKQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICB0cnk6CiAgICAgICAgcHJpbnQobWFpbihzeXMuYXJndlsxOl0pKQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgIHByaW50KHN0cihlKSwgZmlsZT1zeXMuc3RkZXJyKQogICAgICAgIHN5cy5leGl0KDEpCg==", -// ~calldata="BTC", -// ~timeout=5000, -// ), -// ) -// ->Promise.then(res => { -// Js.log(res) -// Promise.resolve() -// }) -// ->Promise.catch(err => { -// Js.log(err) -// Promise.resolve() -// }) -// ->ignore - -// // let pageSize = 5 -// // let (value, setValue) = React.useState(_ => 0.) -// // let (preValue, setPreValue) = React.useState(_ => 0.) -// // let blockSub = BlockSub.get(~height=10030000, ()) - -// // let markDown = "### Hello Word ``` code ``` **strong**" - -// // let update = () => setValue(prev => prev +. 20.) - -// // let capitalizedName = "hello world" -> ChangeCase.pascalCase - -// // let keyword = "testing" - -// // let client = BandChainJS.createClient("https://api-gm-lb.bandchain.org") -// // let _ = -// // client -// // ->BandChainJS.getReferenceData(["BAND/USD", "BAND/BTC"]) -// // ->Promise.then(result => { -// // Js.log(result) -// // Promise.resolve() -// // }) - -// // Js.log(capitalizedName) -// // Js.log(Theme.get(Theme.Day)) -// // let identity = "94C57647B928FAF1" -// // let useTest = AxiosHooks.use(j`https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=$identity&fields=pictures`) -// // Js.log2("use", useTest) - -// // let ( -// // data, -// // reload, -// // ) = AxiosHooks.useWithReload(j`https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=$identity&fields=pictures`) - -// // Js.log2("useWithReloadData", data) - -// // React.useEffect1(() => { -// // let handleKey = event => -// // if ReactEvent.Keyboard.keyCode(event) == 27 { -// // Js.log("trigger") -// // } -// // Document.addKeyboardEventListener("keydown", handleKey) -// // Some(() => Document.removeKeyboardEventListener("keydown", handleKey)) -// // }, []) -// // LocalStorage.setItem(keyword, "Hello World") -// // Js.log(Semver.gte("5.2.3", "3.2.1")) - -// // // address -// // let mnemonic = "absurd exhibit garbage gun flush gown basic chicken east image chimney stand skill own bracket" -// // let bandChain = CosmosJS.network("rpcUrl", "chainID") -// // bandChain->CosmosJS.setPath("m/44'/494'/0'/0/0") -// // bandChain->CosmosJS.setBech32MainPrefix("band") -// // Js.log2( -// // "private", -// // bandChain -> CosmosJS.getECPairPriv(_, mnemonic) -> JsBuffer.toHex(~with0x=false), -// // ) -// // Js.log2("address", bandChain -> CosmosJS.getAddress(_, mnemonic)) - -// // let countUp = CountUp.context( -// // CountUp.props( -// // ~start=preValue, -// // ~end=value, -// // ~delay=0, -// // ~decimals=6, -// // ~duration=4, -// // ~useEasing=false, -// // ~separator=",", -// // ~ref="counter", -// // ), -// // ) - -// Js.log(Env.rpc) - -// let chainID = "band-laozi-testnet2" - -// let (accountOpt, dispatchAccount) = React.useContext(AccountContext.context) - -// let connectMnemonic = () => { -// let wallet = Wallet.createFromMnemonic("s") -// let _ = -// wallet -// ->Wallet.getAddressAndPubKey -// ->Promise.then(((address, pubKey)) => { -// dispatchAccount(Connect(wallet, address, pubKey, chainID)) -// Js.log(accountOpt) -// Promise.resolve() -// }) -// ->Promise.catch(err => { -// Js.Console.log(err) -// Promise.resolve() -// }) -// } - -// let connectLedger = () => { -// let wallet = Wallet.createFromMnemonic("s") -// let _ = -// wallet -// ->Wallet.getAddressAndPubKey -// ->Promise.then(((address, pubKey)) => { -// dispatchAccount(Connect(wallet, address, pubKey, chainID)) -// Js.log(accountOpt) -// Promise.resolve() -// }) -// ->Promise.catch(err => { -// Js.Console.log(err) -// Promise.resolve() -// }) -// } - -// let connectLedger = () => { -// let _ = -// Wallet.createFromLedger(Ledger.Cosmos, 0) -// ->Promise.then(wallet => { -// wallet -// ->Wallet.getAddressAndPubKey -// ->Promise.then(((address, pubKey)) => { -// dispatchAccount(Connect(wallet, address, pubKey, chainID)) -// Promise.resolve() -// }) -// }) -// ->Promise.catch(err => { -// Js.Console.log(err) -// Promise.resolve() -// }) -// } - -// let disconnect = () => { -// dispatchAccount(Disconnect) -// } - -// let infoSub = React.useContext(GlobalContext.context) - -// <> -// React.string} -// placement="bottom" -// arrow=true -// leaveDelay=0 -// leaveTouchDelay=3000 -// enterTouchDelay=0> -// {React.string("Hello World")} -// -// -//
{ -// Copy.copy("Hello World") -// }}> -// {"Copy" -> React.string} -//
-//
-// {switch blockSub { -// | Data(blocks) => { -// Js.log2("Data is ", blocks) -// React.null -// } -// | _ => { -// Js.log("Loading...") -// "Loading...." -> React.string -// } -// }} -//
-// -// -// -// -// -// {switch accountOpt { -// | Some({address}) => address -> Address.toBech32 -> React.string -// | None => "not connected" -> React.string -// }} -// -// -// {"let x = hello world; console.log(x);" -> React.string} -// -// { -// let dict = Js.Dict.empty() -// Js.Dict.set(dict, "name", Js.Json.string("John Doe")) -// let src = Js.Json.object_(dict) - -// -// } -// {switch infoSub { -// | Data({financial}) => { -// Js.log(financial) -// React.null -// } -// | _ => React.null -// }} -// -// Address.fromBech32, -// 149, -// #account, -// ), -// ), -// ("XXXX", InfoMobileCard.Height(ID.Block.ID(1299))), -// ] -// idx={"aksdaksdkasd"} -// /> -// -// // let pageSize = 5 -// // let (value, setValue) = React.useState(_ => 0.) -// // let (preValue, setPreValue) = React.useState(_ => 0.) -// // let blockSub = BlockSub.get(~height=10030000, ()) - -// // let markDown = "### Hello Word ``` code ``` **strong**" - -// // let update = () => setValue(prev => prev +. 20.) - -// // let capitalizedName = "hello world" -> ChangeCase.pascalCase - -// // let keyword = "testing" - -// // let client = BandChainJS.createClient("https://api-gm-lb.bandchain.org") -// // let _ = -// // client -// // ->BandChainJS.getReferenceData(["BAND/USD", "BAND/BTC"]) -// // ->Promise.then(result => { -// // Js.log(result) -// // Promise.resolve() -// // }) - -// // Js.log(capitalizedName) -// // Js.log(Theme.get(Theme.Day)) -// // let identity = "94C57647B928FAF1" -// // let useTest = AxiosHooks.use(j`https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=$identity&fields=pictures`) -// // Js.log2("use", useTest) - -// // let ( -// // data, -// // reload, -// // ) = AxiosHooks.useWithReload(j`https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=$identity&fields=pictures`) - -// // Js.log2("useWithReloadData", data) - -// // React.useEffect1(() => { -// // let handleKey = event => -// // if ReactEvent.Keyboard.keyCode(event) == 27 { -// // Js.log("trigger") -// // } -// // Document.addKeyboardEventListener("keydown", handleKey) -// // Some(() => Document.removeKeyboardEventListener("keydown", handleKey)) -// // }, []) -// // LocalStorage.setItem(keyword, "Hello World") -// // Js.log(Semver.gte("5.2.3", "3.2.1")) - -// // // address -// // let mnemonic = "absurd exhibit garbage gun flush gown basic chicken east image chimney stand skill own bracket" -// // let bandChain = CosmosJS.network("rpcUrl", "chainID") -// // bandChain->CosmosJS.setPath("m/44'/494'/0'/0/0") -// // bandChain->CosmosJS.setBech32MainPrefix("band") -// // Js.log2( -// // "private", -// // bandChain -> CosmosJS.getECPairPriv(_, mnemonic) -> JsBuffer.toHex(~with0x=false), -// // ) -// // Js.log2("address", bandChain -> CosmosJS.getAddress(_, mnemonic)) - -// // let countUp = CountUp.context( -// // CountUp.props( -// // ~start=preValue, -// // ~end=value, -// // ~delay=0, -// // ~decimals=6, -// // ~duration=4, -// // ~useEasing=false, -// // ~separator=",", -// // ~ref="counter", -// // ), -// // ) - -// // React.useEffect1(_ => { -// // countUp.update(value) -// // let timeoutId = Js.Global.setTimeout(() => setPreValue(_ => value), 800) -// // Some(() => Js.Global.clearTimeout(timeoutId)) -// // }, [value]) - -// // Js.log(LocalStorage.getItem(keyword)) - -// // <> -// // React.string} -// // placement="bottom" -// // arrow=true -// // leaveDelay=0 -// // leaveTouchDelay=3000 -// // enterTouchDelay=0> -// // {React.string("Hello World")} -// // -// // -// // -// //
{ -// // Copy.copy("Hello World") -// // }}> -// // {"Copy" -> React.string} -// //
-// // -// // -// // -// // -// // {"let x = hello world; console.log(x);" -> React.string} -// // -// //
-// // {switch blockSub { -// // | {data: Some({blocks_by_pk}), loading: false} => { -// // Js.log2("Data is ", blocks_by_pk) -// // React.null -// // } -// // | {loading: true, data: Some(x)} => { -// // Js.log2("Loading with Some", x) -// //
{"Loading with Some" -> React.string}
-// // } -// // | {loading: true, data: None} => { -// // Js.log("Loading with None") -// //
{"Loading with None" -> React.string}
-// // } -// // | {error: Some(error)} => { -// // Js.log(error) -// // React.null -// // } -// // | {loading: false, data: None, error: None} => { -// // Js.log("No data") -// //
{"No data" -> React.string}
-// // } -// // }} -// //
-// // { -// // let dict = Js.Dict.empty() -// // Js.Dict.set(dict, "name", Js.Json.string("John Doe")) -// // let src = Js.Json.object_(dict) - -// // -// // } -// // -// } - diff --git a/src/pages/RequestDetailsPage.res b/src/pages/RequestDetailsPage.res index 99a0edc1..38e319fd 100644 --- a/src/pages/RequestDetailsPage.res +++ b/src/pages/RequestDetailsPage.res @@ -132,7 +132,7 @@ module KVTableContainer = { | Some(decodes) => isMobile ? decodes - ->Belt.Array.map(({Obi.fieldName: fieldName, fieldValue}) => + ->Belt.Array.map(({Obi2.fieldName: fieldName, fieldValue}) => {decodes - ->Belt.Array.map(({Obi.fieldName: fieldName, fieldValue}) => { + ->Belt.Array.map(({Obi2.fieldName: fieldName, fieldValue}) => { @@ -314,18 +314,6 @@ let make = (~reqID) => { {switch requestSub { | Data({transactionOpt}) => - // switch transactionOpt { - // | Some({gasFee}) => - // Coin.getBandAmountFromCoins - // ->Format.fPretty(~digits=6) ++ " BAND"} - // size=Text.Body1 - // color={theme.neutral_600} - // /> - // | None => - // } { {switch requestSub { - | Data({transactionOpt}) => - // switch transactionOpt { - // | Some({blockHeight}) => - // | None => - // } - + | Data({transactionOpt}) => | _ => }} @@ -573,7 +556,7 @@ let make = (~reqID) => {
{switch requestSub { | Data({oracleScript: {schema}, calldata}) => - let decodesOpt = Obi.decode(schema, "input", calldata) + let decodesOpt = Obi2.decode(schema, Obi2.Input, calldata) | _ => }} @@ -604,7 +587,7 @@ let make = (~reqID) => { | Data({oracleScript: {schema}, result: resultOpt, resolveStatus}) => switch (resolveStatus, resultOpt) { | (RequestSub.Success, Some(result)) => - let decodesOpt = Obi.decode(schema, "output", result) + let decodesOpt = Obi2.decode(schema, Obi2.Output, result) | (Pending, _) => diff --git a/src/pages/Test.res b/src/pages/Test.res deleted file mode 100644 index b4b95aee..00000000 --- a/src/pages/Test.res +++ /dev/null @@ -1,6 +0,0 @@ -@react.component -let make = () => { -
-

{"Test"->React.string}

-
-} diff --git a/src/utils/Obi2.res b/src/utils/Obi2.res index 45c877d2..f1871871 100644 --- a/src/utils/Obi2.res +++ b/src/utils/Obi2.res @@ -18,46 +18,48 @@ let flowToString = x => | Output => "output" } -// TODO -let extractFields: (string, string) => option> = %raw(` - function(schema, t) { - try { - const normalizedSchema = schema.replace(/\s+/g, '') - const tokens = normalizedSchema.split('/') - let val - if (t === 'input') { - val = tokens[0] - } else if (t === 'output') { - val = tokens[1] - } else { - return undefined - } - let specs = val.slice(1, val.length - 1).split(',') - return specs.map((spec) => { - let x = spec.split(':') - return {fieldName: x[0], fieldType: x[1]} - }) - } catch { - return undefined +let dataTypeToString = dataType => + switch dataType { + | Input => "Params" + | Output => "Result" + } + +let extractFields = (schema, flow) => { + try { + let normalizedSchema = schema->Js.String2.replaceByRe(%re("/\s+/g"), "") + + let tokens = normalizedSchema->Js.String2.split("/") + let val = switch flow { + | Input => tokens[0] + | Output => tokens[1] } + let specs = val->Js.String2.slice(~from=1, ~to_=val->String.length - 1)->Js.String2.split(",") + Some( + specs->Belt.Array.map(spec => { + let x = spec->Js.String2.split(":") + {fieldName: x[0], fieldType: x[1]} + }), + ) + } catch { + | _ => None } -`) +} let encode = (schema, flow, valuePairs) => { open BandChainJS.Obi + switch { - let typePairs = extractFields(schema, flow->flowToString)->Belt.Option.getExn + let typePairs = extractFields(schema, flow)->Belt.Option.getExn + let dataPairs = typePairs->Belt.Array.map(({fieldName, fieldType}) => { let value = valuePairs ->Belt.Array.keepMap(each => fieldName == each.fieldName ? Some(each.fieldValue) : None) ->Belt.Array.getExn(0) - Js.log(value) - - // TODO: parse follow fieldType let parsed = value->Js.Json.parseExn (fieldName, parsed) }) + let data = Js.Json.object_(Js.Dict.fromArray(dataPairs)) let obi = create(schema) switch flow { @@ -71,3 +73,304 @@ let encode = (schema, flow, valuePairs) => { | encoded => Some(encoded) } } + +let stringify: string => string = %raw(`function(data) { + if (Array.isArray(data)) { + return "[" + [...data].map(stringify).join(",") + "]" + } else if (typeof(data) === "bigint") { + return data.toString() + } else if (Buffer.isBuffer(data)) { + return "0x" + data.toString('hex') + } else if (typeof(data) === "object") { + return "{" + Object.entries(data).map(([k,v]) => JSON.stringify(k)+ ":" + stringify(v)).join(",") + "}" + } else { + return JSON.stringify(data) + } + } + `) + +let decode = (schema, flow, data) => { + open BandChainJS.Obi + + switch { + let obi = create(schema) + + let rawResult = switch flow { + | Input => obi->decodeInput(data)->Js.Dict.entries + | Output => obi->decodeOutput(data)->Js.Dict.entries + } + + rawResult->Belt.Array.map(((k, v)) => { + {fieldName: k, fieldValue: stringify(v)} + }) + } { + | exception err => + Js.Console.error({`Error decode`}) // For debug + None + | decoded => Some(decoded) + } +} + +type primitive_t = + | String + | U64 + | U32 + | U8 + +type variable_t = + | Single(primitive_t) + | Array(primitive_t) + +type field_t = { + name: string, + varType: variable_t, +} + +let parse = ({fieldName, fieldType}) => { + let v = { + switch fieldType { + | "string" => Some(Single(String)) + | "u64" => Some(Single(U64)) + | "u32" => Some(Single(U32)) + | "u8" => Some(Single(U8)) + | "[string]" => Some(Array(String)) + | "[u64]" => Some(Array(U64)) + | "[u32]" => Some(Array(U32)) + | "[u8]" => Some(Array(U8)) + | _ => None + } + } + + v->Belt.Option.map(varType' => {name: fieldName, varType: varType'}) +} + +let declarePrimitiveSol = primitive => + switch primitive { + | String => "string" + | U64 => "uint64" + | U32 => "uint32" + | U8 => "uint8" + } + +let declareSolidity = ({name, varType}) => { + let type_ = switch varType { + | Single(x) => declarePrimitiveSol(x) + | Array(x) => { + let declareType = declarePrimitiveSol(x) + `${declareType}[]` + } + } + `${type_} ${name};` +} + +let assignSolidity = ({name, varType}) => { + let decode = primitive => + switch primitive { + | String => "string(data.decodeBytes());" + | U64 => "data.decodeU64();" + | U32 => "data.decodeU32();" + | U8 => "data.decodeU8();" + } + + switch varType { + | Single(x) => { + let decodeFunction = decode(x) + `result.${name} = ${decodeFunction}` + } + + | Array(x) => { + let type_ = declarePrimitiveSol(x) + let decodeFunction = decode(x) + + `uint32 length = data.decodeU32(); + ${type_}[] memory ${name} = new ${type_}[](length); + for (uint256 i = 0; i < length; i++) { + ${name}[i] = ${decodeFunction} + } + result.${name} = ${name}` + } + } +} + +// TODO: abstract it out +let optionsAll = options => + options->Belt.Array.reduce(_, Some([]), (acc, obj) => { + switch (acc, obj) { + | (Some(acc'), Some(obj')) => Some(acc'->Js.Array.concat([obj'])) + | (_, _) => None + } + }) + +let generateDecodeLibSolidity = (schema, dataType) => { + let dataTypeString = dataType->dataTypeToString + let name = dataType + let template = (structs, functions) => + `library ${dataTypeString}Decoder { + using Obi for Obi.Data; + + struct ${dataTypeString} { + ${structs} + } + + function decode${dataTypeString}(bytes memory _data) + internal + pure + returns (${dataTypeString} memory result) + { + Obi.Data memory data = Obi.from(_data); + ${functions} + } +} + +` + + let fieldPairsOpt = extractFields(schema, name) + + fieldPairsOpt->Belt.Option.flatMap(fieldsPairs => { + let fieldsOpt = fieldsPairs->Belt.Array.map(parse)->optionsAll + fieldsOpt->Belt.Option.flatMap(fields => { + let indent = "\n " + Some( + template( + fields->Belt.Array.map(declareSolidity)->Js.Array.joinWith(indent, _), + fields->Belt.Array.map(assignSolidity)->Js.Array.joinWith(indent, _), + ), + ) + }) + }) +} + +let generateDecoderSolidity = schema => { + let template = `pragma solidity ^0.5.0; + +import "./Obi.sol"; + +` + let paramsCodeOpt = generateDecodeLibSolidity(schema, Input) + let resultCodeOpt = generateDecodeLibSolidity(schema, Output) + + paramsCodeOpt->Belt.Option.flatMap(paramsCode => { + resultCodeOpt->Belt.Option.flatMap(resultCode => Some(template ++ paramsCode ++ resultCode)) + }) +} + +// TODO: revisit when using this. +let declareGo = ({name, varType}) => { + let capitalizedName = name->ChangeCase.pascalCase + let type_ = switch varType { + | Single(String) => "string" + | Single(U64) => "uint64" + | Single(U32) => "uint32" + | Single(U8) => "uint8" + | Array(String) => "[]string" + | Array(U64) => "[]uint64" + | Array(U32) => "[]uint32" + | Array(U8) => "[]uint8" + } + j`$capitalizedName $type_` +} + +let assignGo = ({name, varType}) => { + switch varType { + | Single(String) => j`$name, err := decoder.DecodeString() + if err !== nil { + return Result{}, err + }` + | Single(U64) => j`$name, err := decoder.DecodeU64() + if err !== nil { + return Result{}, err + }` + | Single(U32) => j`$name, err := decoder.DecodeU32() + if err !== nil { + return Result{}, err + }` + | Single(U8) => j`$name, err := decoder.DecodeU8() + if err !== nil { + return Result{}, err + }` + | _ => "// TODO: implement later" + } +} + +let resultGo = ({name}) => { + let capitalizedName = name->ChangeCase.pascalCase + j`$capitalizedName: $name` +} + +let generateDecoderGo = (packageName, schema, dataType) => { + switch dataType { + | Input => Some("Code is not available.") + | Output => + let name = dataType + let template = (structs, functions, results) => + j`package $packageName + +import "github.com/bandchain/chain/pkg/obi" + +type Result struct { +\t$structs +} + +func DecodeResult(data []byte) (Result, error) { +\tdecoder := obi.NewObiDecoder(data) + +\t$functions + +\tif !decoder.Finished() { +\t\treturn Result{}, errors.New("Obi: bytes left when decode result") +\t} + +\treturn Result{ +\t\t$results +\t}, nil +}` + + let fieldsPair = extractFields(schema, name)->Belt.Option.getExn + let fields = fieldsPair->Belt.Array.map(parse)->optionsAll->Belt.Option.getExn + Some( + template( + fields->Belt.Array.map(declareGo)->Js.Array.joinWith("\n\t", _), + fields->Belt.Array.map(assignGo)->Js.Array.joinWith("\n\t", _), + fields->Belt.Array.map(resultGo)->Js.Array.joinWith("\n\t\t", _), + ), + ) + } +} + +let encodeStructGo = ({name, varType}) => { + switch varType { + | Single(U8) => j`encoder.EncodeU8(result.$name)` + | Single(U32) => j`encoder.EncodeU32(result.$name)` + | Single(U64) => j`encoder.EncodeU64(result.$name)` + | Single(String) => j`encoder.EncodeString(result.$name)` + | _ => "//TODO: implement later" + } +} + +let generateEncodeGo = (packageName, schema, name) => { + let template = (structs, functions) => + j`package $packageName + +import "github.com/bandchain/chain/pkg/obi" + +type Result struct { +\t$structs +} + +func(result *Result) EncodeResult() []byte { +\tencoder := obi.NewObiEncoder() + +\t$functions + +\treturn encoder.GetEncodedData() +}` + + let fieldsPair = extractFields(schema, name)->Belt.Option.getExn + let fields = fieldsPair->Belt.Array.map(parse)->optionsAll->Belt.Option.getExn + Some( + template( + fields->Belt.Array.map(declareGo)->Js.Array.joinWith("\n\t", _), + fields->Belt.Array.map(encodeStructGo)->Js.Array.joinWith("\n\t", _), + ), + ) +} diff --git a/src/utils/proof/NonEVMProof.res b/src/utils/proof/NonEVMProof.res new file mode 100644 index 00000000..6b5c4bfc --- /dev/null +++ b/src/utils/proof/NonEVMProof.res @@ -0,0 +1,399 @@ +type request_t = + | Request(RequestSub.t) + | RequestMini(RequestSub.Mini.t) + +type request_packet_t = { + clientID: string, + oracleScriptID: int, + calldata: JsBuffer.t, + askCount: int, + minCount: int, +} + +type response_packet_t = { + clientID: string, + requestID: int, + ansCount: int, + requestTime: int, + resolveTime: int, + resolveStatus: int, + result: JsBuffer.t, +} + +type iavl_merkle_path_t = { + isDataOnRight: bool, + subtreeHeight: int, + subtreeSize: int, + subtreeVersion: int, + siblingHash: JsBuffer.t, +} + +type oracle_data_proof_t = { + requestPacket: request_packet_t, + responsePacket: response_packet_t, + version: int, + iavlMerklePaths: list, +} + +type multi_store_proof_t = { + accToGovStoresMerkleHash: JsBuffer.t, + mainAndMintStoresMerkleHash: JsBuffer.t, + oracleIAVLStateHash: JsBuffer.t, + paramsStoresMerkleHash: JsBuffer.t, + slashingToUpgradeStoresMerkleHash: JsBuffer.t, +} + +type block_header_merkle_parts_t = { + versionAndChainIdHash: JsBuffer.t, + timeSecond: int, + timeNanoSecond: int, + lastBlockIDAndOther: JsBuffer.t, + nextValidatorHashAndConsensusHash: JsBuffer.t, + lastResultsHash: JsBuffer.t, + evidenceAndProposerHash: JsBuffer.t, +} + +type tm_signature_t = { + r: JsBuffer.t, + s: JsBuffer.t, + v: int, + signedDataPrefix: JsBuffer.t, + signedDataSuffix: JsBuffer.t, +} + +type block_relay_proof_t = { + multiStoreProof: multi_store_proof_t, + blockHeaderMerkleParts: block_header_merkle_parts_t, + signatures: list, +} + +type proof_t = { + blockHeight: int, + oracleDataProof: oracle_data_proof_t, + blockRelayProof: block_relay_proof_t, +} + +let decodeRequestPacket = { + open JsonUtils.Decode + object(fields => { + clientID: fields.optional(. "client_id", string)->Belt.Option.getWithDefault(""), + oracleScriptID: fields.required(. "oracle_script_id", intstr), + calldata: fields.required(. "calldata", bufferFromBase64), + askCount: fields.required(. "ask_count", intstr), + minCount: fields.required(. "min_count", intstr), + }) +} + +let decodeResponsePacket = { + open JsonUtils.Decode + object(fields => { + clientID: fields.optional(. "client_id", string)->Belt.Option.getWithDefault(""), + requestID: fields.required(. "request_id", intstr), + ansCount: fields.required(. "ans_count", intstr), + requestTime: fields.required(. "request_time", intstr), + resolveTime: fields.required(. "resolve_time", intstr), + resolveStatus: fields.required(. "resolve_status", int), + result: fields.required(. "result", bufferFromBase64), + }) +} + +let decodeIAVLMerklePath = { + open JsonUtils.Decode + object(fields => { + isDataOnRight: fields.required(. "isDataOnRight", bool), + subtreeHeight: fields.required(. "subtreeHeight", int), + subtreeSize: fields.required(. "subtreeSize", intstr), + subtreeVersion: fields.required(. "subtreeVersion", intstr), + siblingHash: fields.required(. "siblingHash", bufferFromHex), + }) +} + +let decodeOracleDataProof = { + open JsonUtils.Decode + object(fields => { + requestPacket: fields.required(. "requestPacket", decodeRequestPacket), + responsePacket: fields.required(. "responsePacket", decodeResponsePacket), + version: fields.required(. "version", intstr), + iavlMerklePaths: fields.required(. "merklePaths", list(decodeIAVLMerklePath)), + }) +} + +let decodeMultiStoreProof = { + open JsonUtils.Decode + object(fields => { + accToGovStoresMerkleHash: fields.required(. "accToGovStoresMerkleHash", bufferFromHex), + mainAndMintStoresMerkleHash: fields.required(. "mainAndMintStoresMerkleHash", bufferFromHex), + oracleIAVLStateHash: fields.required(. "oracleIAVLStateHash", bufferFromHex), + paramsStoresMerkleHash: fields.required(. "paramsStoresMerkleHash", bufferFromHex), + slashingToUpgradeStoresMerkleHash: fields.required(. + "slashingToUpgradeStoresMerkleHash", + bufferFromHex, + ), + }) +} + +let decodeBlockHeaderMerkleParts = { + open JsonUtils.Decode + object(fields => { + versionAndChainIdHash: fields.required(. "versionAndChainIdHash", bufferFromHex), + timeSecond: fields.required(. "timeSecond", intstr), + timeNanoSecond: fields.required(. "timeNanoSecond", int), + lastBlockIDAndOther: fields.required(. "lastBlockIDAndOther", bufferFromHex), + nextValidatorHashAndConsensusHash: fields.required(. + "nextValidatorHashAndConsensusHash", + bufferFromHex, + ), + lastResultsHash: fields.required(. "lastResultsHash", bufferFromHex), + evidenceAndProposerHash: fields.required(. "evidenceAndProposerHash", bufferFromHex), + }) +} + +let decodeTMSignature = { + open JsonUtils.Decode + object(fields => { + r: fields.required(. "r", bufferFromHex), + s: fields.required(. "s", bufferFromHex), + v: fields.required(. "v", int), + signedDataPrefix: fields.required(. "signedDataPrefix", bufferFromHex), + signedDataSuffix: fields.required(. "signedDataSuffix", bufferFromHex), + }) +} + +let decodeBlockRelayProof = { + open JsonUtils.Decode + object(fields => { + multiStoreProof: fields.required(. "multiStoreProof", decodeMultiStoreProof), + blockHeaderMerkleParts: fields.required(. + "blockHeaderMerkleParts", + decodeBlockHeaderMerkleParts, + ), + signatures: fields.required(. "signatures", list(decodeTMSignature)), + }) +} + +let decodeProof = { + open JsonUtils.Decode + object(fields => { + blockHeight: fields.required(. "blockHeight", intstr), + oracleDataProof: fields.required(. "oracleDataProof", decodeOracleDataProof), + blockRelayProof: fields.required(. "blockRelayProof", decodeBlockRelayProof), + }) +} +let obi_encode_int = (i, n) => + Obi2.encode( + "{x: " ++ n ++ "}/{_:u64}", + Obi2.Input, + [{fieldName: "x", fieldValue: i->Belt.Int.toString}], + )->Belt.Option.getExn + +type variant_of_proof_t = + | RequestPacket(request_packet_t) + | ResponsePacket(response_packet_t) + | IAVLMerklePath(iavl_merkle_path_t) + | IAVLMerklePaths(list) + | MultiStoreProof(multi_store_proof_t) + | BlockHeaderMerkleParts(block_header_merkle_parts_t) + | Signature(tm_signature_t) + | Signatures(list) + | Proof(proof_t) + +let rec encode = x => + switch x { + | RequestPacket({clientID, oracleScriptID, calldata, askCount, minCount}) => + Obi2.encode( + "{clientID: string, oracleScriptID: u64, calldata: bytes, askCount: u64, minCount: u64}/{_:u64}", + Obi2.Input, + [ + {fieldName: "clientID", fieldValue: clientID}, + {fieldName: "oracleScriptID", fieldValue: oracleScriptID->Belt.Int.toString}, + {fieldName: "calldata", fieldValue: calldata->JsBuffer.toHex(~with0x=true)}, + {fieldName: "askCount", fieldValue: askCount->Belt.Int.toString}, + {fieldName: "minCount", fieldValue: minCount->Belt.Int.toString}, + ], + ) + + | ResponsePacket({ + clientID, + requestID, + ansCount, + requestTime, + resolveTime, + resolveStatus, + result, + }) => + Obi2.encode( + "{clientID: string, requestID: u64, ansCount: u64, requestTime: u64, resolveTime: u64, resolveStatus: u32, result: bytes}/{_:u64}", + Obi2.Input, + [ + {fieldName: "clientID", fieldValue: clientID}, + {fieldName: "requestID", fieldValue: requestID->Belt.Int.toString}, + {fieldName: "ansCount", fieldValue: ansCount->Belt.Int.toString}, + {fieldName: "requestTime", fieldValue: requestTime->Belt.Int.toString}, + {fieldName: "resolveTime", fieldValue: resolveTime->Belt.Int.toString}, + {fieldName: "resolveStatus", fieldValue: resolveStatus->Belt.Int.toString}, + {fieldName: "result", fieldValue: result->JsBuffer.toHex(~with0x=true)}, + ], + ) + + | IAVLMerklePath({isDataOnRight, subtreeHeight, subtreeSize, subtreeVersion, siblingHash}) => + Obi2.encode( + "{isDataOnRight: u8, subtreeHeight: u8, subtreeSize: u64, subtreeVersion: u64, siblingHash: bytes}/{_:u64}", + Obi2.Input, + [ + {fieldName: "isDataOnRight", fieldValue: isDataOnRight ? "1" : "0"}, + {fieldName: "subtreeHeight", fieldValue: subtreeHeight->Belt.Int.toString}, + {fieldName: "subtreeSize", fieldValue: subtreeSize->Belt.Int.toString}, + {fieldName: "subtreeVersion", fieldValue: subtreeVersion->Belt.Int.toString}, + {fieldName: "siblingHash", fieldValue: siblingHash->JsBuffer.toHex(~with0x=true)}, + ], + ) + + | IAVLMerklePaths(iavl_merkle_paths) => + iavl_merkle_paths + ->Belt.List.map(_, x => encode(IAVLMerklePath(x))) + ->Belt.List.reduce(_, Some(JsBuffer.from([])), (a, b) => + switch (a, b) { + | (Some(acc), Some(elem)) => Some(JsBuffer.concat([acc, elem])) + | _ => None + } + ) + ->Belt.Option.map(_, x => + JsBuffer.concat([obi_encode_int(iavl_merkle_paths->Belt.List.length, "u32"), x]) + ) + + | MultiStoreProof({ + accToGovStoresMerkleHash, + mainAndMintStoresMerkleHash, + oracleIAVLStateHash, + paramsStoresMerkleHash, + slashingToUpgradeStoresMerkleHash, + }) => + Some( + JsBuffer.concat([ + accToGovStoresMerkleHash, + mainAndMintStoresMerkleHash, + oracleIAVLStateHash, + paramsStoresMerkleHash, + slashingToUpgradeStoresMerkleHash, + ]), + ) + + | BlockHeaderMerkleParts({ + versionAndChainIdHash, + timeSecond, + timeNanoSecond, + lastBlockIDAndOther, + nextValidatorHashAndConsensusHash, + lastResultsHash, + evidenceAndProposerHash, + }) => + Obi2.encode( + "{versionAndChainIdHash: bytes, timeSecond: u64, timeNanoSecond: u32, lastBlockIDAndOther: bytes, nextValidatorHashAndConsensusHash: bytes, lastResultsHash: bytes, evidenceAndProposerHash: bytes}/{_:u64}", + Obi2.Input, + [ + { + fieldName: "versionAndChainIdHash", + fieldValue: versionAndChainIdHash->JsBuffer.toHex(~with0x=true), + }, + {fieldName: "timeSecond", fieldValue: timeSecond->Belt.Int.toString}, + {fieldName: "timeNanoSecond", fieldValue: timeNanoSecond->Belt.Int.toString}, + { + fieldName: "lastBlockIDAndOther", + fieldValue: lastBlockIDAndOther->JsBuffer.toHex(~with0x=true), + }, + { + fieldName: "nextValidatorHashAndConsensusHash", + fieldValue: nextValidatorHashAndConsensusHash->JsBuffer.toHex(~with0x=true), + }, + { + fieldName: "lastResultsHash", + fieldValue: lastResultsHash->JsBuffer.toHex(~with0x=true), + }, + { + fieldName: "evidenceAndProposerHash", + fieldValue: evidenceAndProposerHash->JsBuffer.toHex(~with0x=true), + }, + ], + ) + + | Signature({r, s, v, signedDataPrefix, signedDataSuffix}) => + Obi2.encode( + "{r: bytes, s: bytes, v: u8, signedDataPrefix: bytes, signedDataSuffix: bytes}/{_:u64}", + Obi2.Input, + [ + {fieldName: "r", fieldValue: r->JsBuffer.toHex(~with0x=true)}, + {fieldName: "s", fieldValue: s->JsBuffer.toHex(~with0x=true)}, + {fieldName: "v", fieldValue: v->Belt.Int.toString}, + { + fieldName: "signedDataPrefix", + fieldValue: signedDataPrefix->JsBuffer.toHex(~with0x=true), + }, + { + fieldName: "signedDataSuffix", + fieldValue: signedDataSuffix->JsBuffer.toHex(~with0x=true), + }, + ], + ) + + | Signatures(tm_signatures) => + tm_signatures + ->Belt.List.map(_, x => encode(Signature(x))) + ->Belt.List.reduce(_, Some(JsBuffer.from([])), (a, b) => + switch (a, b) { + | (Some(acc), Some(elem)) => Some(JsBuffer.concat([acc, elem])) + | _ => None + } + ) + ->Belt.Option.map(_, x => + JsBuffer.concat([obi_encode_int(tm_signatures->Belt.List.length, "u32"), x]) + ) + + | Proof({ + blockHeight, + oracleDataProof: {requestPacket, responsePacket, version, iavlMerklePaths}, + blockRelayProof: {multiStoreProof, blockHeaderMerkleParts, signatures}, + }) => { + let encodeMultiStore = encode(MultiStoreProof(multiStoreProof))->Belt.Option.getExn + let encodeBlockHeaderMerkleParts = + encode(BlockHeaderMerkleParts(blockHeaderMerkleParts))->Belt.Option.getExn + let encodeSignatures = encode(Signatures(signatures))->Belt.Option.getExn + let encodeReq = encode(RequestPacket(requestPacket))->Belt.Option.getExn + let encodeRes = encode(ResponsePacket(responsePacket))->Belt.Option.getExn + let encodeIAVLMerklePaths = encode(IAVLMerklePaths(iavlMerklePaths))->Belt.Option.getExn + Obi2.encode( + "{blockHeight: u64, multiStore: bytes, blockMerkleParts: bytes, signatures: bytes, packet: bytes, version: u64, iavlPaths: bytes}/{_:u64}", + Obi2.Input, + [ + {fieldName: "blockHeight", fieldValue: blockHeight->Belt.Int.toString}, + { + fieldName: "multiStore", + fieldValue: encodeMultiStore->JsBuffer.toHex(~with0x=true), + }, + { + fieldName: "blockMerkleParts", + fieldValue: encodeBlockHeaderMerkleParts->JsBuffer.toHex(~with0x=true), + }, + { + fieldName: "signatures", + fieldValue: encodeSignatures->JsBuffer.toHex(~with0x=true), + }, + { + fieldName: "packet", + fieldValue: JsBuffer.concat([encodeReq, encodeRes])->JsBuffer.toHex(~with0x=true), + }, + {fieldName: "version", fieldValue: version->Belt.Int.toString}, + { + fieldName: "iavlPaths", + fieldValue: encodeIAVLMerklePaths->JsBuffer.toHex(~with0x=true), + }, + ], + ) + } + } + +let createProofFromJson = proof => { + switch Proof(proof->JsonUtils.Decode.mustDecode(decodeProof)) { + | result => result->encode + | exception _ => None + } +}