diff --git a/solarkraft/package-lock.json b/solarkraft/package-lock.json index 943a1a27..55494e55 100644 --- a/solarkraft/package-lock.json +++ b/solarkraft/package-lock.json @@ -9,6 +9,8 @@ "version": "0.0.1", "license": "Apache-2.0", "dependencies": { + "@stellar/stellar-base": "^11.0.1", + "@stellar/stellar-sdk": "^11.3.0", "@sweet-monads/either": "^3.3.1", "axios": "^1.6.7", "chalk": "^5.3.0", @@ -206,6 +208,41 @@ "node": ">= 8" } }, + "node_modules/@stellar/js-xdr": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.1.1.tgz", + "integrity": "sha512-3gnPjAz78htgqsNEDkEsKHKosV2BF2iZkoHCNxpmZwUxiPsw+2VaXMed8RRMe0rGk3d5GZe7RrSba8zV80J3Ag==" + }, + "node_modules/@stellar/stellar-base": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-11.0.1.tgz", + "integrity": "sha512-VQh+1KEtFjegD6spx08+lENt8tQOkQQQZoLtqExjpRXyWlqDhEe+bXMlBTYKDc5MIynHyD42RPEib27UG17trA==", + "dependencies": { + "@stellar/js-xdr": "^3.1.1", + "base32.js": "^0.1.0", + "bignumber.js": "^9.1.2", + "buffer": "^6.0.3", + "sha.js": "^2.3.6", + "tweetnacl": "^1.0.3" + }, + "optionalDependencies": { + "sodium-native": "^4.0.10" + } + }, + "node_modules/@stellar/stellar-sdk": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-11.3.0.tgz", + "integrity": "sha512-i+heopibJNRA7iM8rEPz0AXphBPYvy2HDo8rxbDwWpozwCfw8kglP9cLkkhgJe8YicgLrdExz/iQZaLpqLC+6w==", + "dependencies": { + "@stellar/stellar-base": "^11.0.1", + "axios": "^1.6.8", + "bignumber.js": "^9.1.2", + "eventsource": "^2.0.2", + "randombytes": "^2.1.0", + "toml": "^3.0.0", + "urijs": "^1.19.1" + } + }, "node_modules/@sweet-monads/either": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@sweet-monads/either/-/either-3.3.1.tgz", @@ -660,6 +697,33 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base32.js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz", + "integrity": "sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/bignumber.js": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", @@ -706,6 +770,29 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1249,6 +1336,14 @@ "node": ">=0.10.0" } }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1603,6 +1698,25 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -2263,6 +2377,17 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/noms": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", @@ -2488,7 +2613,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -2584,7 +2708,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -2629,6 +2752,18 @@ "randombytes": "^2.1.0" } }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2658,6 +2793,16 @@ "node": ">=8" } }, + "node_modules/sodium-native": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.1.1.tgz", + "integrity": "sha512-LXkAfRd4FHtkQS4X6g+nRcVaN7mWVNepV06phIsC6+IZFvGh1voW5TNQiQp2twVaMf05gZqQjuS+uWLM6gHhNQ==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.8.0" + } + }, "node_modules/sort-object-keys": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-1.1.3.tgz", @@ -2842,6 +2987,11 @@ "node": ">=8.0" } }, + "node_modules/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -2906,6 +3056,11 @@ "node": ">=0.3.1" } }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2992,6 +3147,11 @@ "punycode": "^2.1.0" } }, + "node_modules/urijs": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -3261,6 +3421,39 @@ "fastq": "^1.6.0" } }, + "@stellar/js-xdr": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.1.1.tgz", + "integrity": "sha512-3gnPjAz78htgqsNEDkEsKHKosV2BF2iZkoHCNxpmZwUxiPsw+2VaXMed8RRMe0rGk3d5GZe7RrSba8zV80J3Ag==" + }, + "@stellar/stellar-base": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-11.0.1.tgz", + "integrity": "sha512-VQh+1KEtFjegD6spx08+lENt8tQOkQQQZoLtqExjpRXyWlqDhEe+bXMlBTYKDc5MIynHyD42RPEib27UG17trA==", + "requires": { + "@stellar/js-xdr": "^3.1.1", + "base32.js": "^0.1.0", + "bignumber.js": "^9.1.2", + "buffer": "^6.0.3", + "sha.js": "^2.3.6", + "sodium-native": "^4.0.10", + "tweetnacl": "^1.0.3" + } + }, + "@stellar/stellar-sdk": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-11.3.0.tgz", + "integrity": "sha512-i+heopibJNRA7iM8rEPz0AXphBPYvy2HDo8rxbDwWpozwCfw8kglP9cLkkhgJe8YicgLrdExz/iQZaLpqLC+6w==", + "requires": { + "@stellar/stellar-base": "^11.0.1", + "axios": "^1.6.8", + "bignumber.js": "^9.1.2", + "eventsource": "^2.0.2", + "randombytes": "^2.1.0", + "toml": "^3.0.0", + "urijs": "^1.19.1" + } + }, "@sweet-monads/either": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@sweet-monads/either/-/either-3.3.1.tgz", @@ -3580,6 +3773,16 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "base32.js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz", + "integrity": "sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, "bignumber.js": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", @@ -3614,6 +3817,15 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4005,6 +4217,11 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==" + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4264,6 +4481,11 @@ "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", "dev": true }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, "ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -4780,6 +5002,12 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "optional": true + }, "noms": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", @@ -4931,7 +5159,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -4993,8 +5220,7 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "secure-keys": { "version": "1.0.0", @@ -5019,6 +5245,15 @@ "randombytes": "^2.1.0" } }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5039,6 +5274,15 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, + "sodium-native": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.1.1.tgz", + "integrity": "sha512-LXkAfRd4FHtkQS4X6g+nRcVaN7mWVNepV06phIsC6+IZFvGh1voW5TNQiQp2twVaMf05gZqQjuS+uWLM6gHhNQ==", + "optional": true, + "requires": { + "node-gyp-build": "^4.8.0" + } + }, "sort-object-keys": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-1.1.3.tgz", @@ -5191,6 +5435,11 @@ "is-number": "^7.0.0" } }, + "toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==" + }, "ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -5227,6 +5476,11 @@ } } }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5279,6 +5533,11 @@ "punycode": "^2.1.0" } }, + "urijs": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==" + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/solarkraft/package.json b/solarkraft/package.json index 3e04400c..3db5af4c 100644 --- a/solarkraft/package.json +++ b/solarkraft/package.json @@ -40,6 +40,8 @@ "test": "mocha --loader=ts-node/esm --exclude test/e2e/**/*.test.ts test/**/*.test.ts" }, "dependencies": { + "@stellar/stellar-base": "^11.0.1", + "@stellar/stellar-sdk": "^11.3.0", "@sweet-monads/either": "^3.3.1", "axios": "^1.6.7", "chalk": "^5.3.0", @@ -66,4 +68,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} diff --git a/solarkraft/src/io/itf.ts b/solarkraft/src/io/itf.ts new file mode 100644 index 00000000..6652335d --- /dev/null +++ b/solarkraft/src/io/itf.ts @@ -0,0 +1,85 @@ +/** + * @license + * [Apache-2.0](https://github.com/freespek/solarkraft/blob/main/LICENSE) + */ +import { byte, getFullType, Value } from '../state/value.js' +import { State } from '../state/state.js' + +const META_FIELD: string = '#meta' +const VAR_TYPES_FIELD: string = 'varTypes' +const VARS_FIELD: string = 'vars' +const DESCRIPTION_FIELD: string = 'description' +const FORMAT_FIELD: string = 'format' +const FORMAT_DESCRIPTION_FIELD: string = 'format-description' +const BIG_INT_FIELD: string = '#bigint' +const MAP_FIELD: string = '#map' +const UNSERIALIZABLE_FIELD: string = '#unserializable' +const STATES_FIELD: string = 'states' + +const UNSERIALIZABLE = { [UNSERIALIZABLE_FIELD]: true } + +// Wraps bigint or byte literals in the ITF #bigint wrapper +function numWrap(b: bigint | byte) { + return { [BIG_INT_FIELD]: b.toString() } +} + +// Transforms the internal representation into the Apalache ITF format for serialization +export function valToITF(v: Value) { + switch (v.type) { + case 'bool': + return v.val + case 'u32': + case 'i32': + case 'u64': + case 'i64': + case 'u128': + case 'i128': + return numWrap(v.val) + case 'symb': + case 'addr': + return v.val + case 'arr': + // array of numbers, must be wrapped + return v.val.map(numWrap) + case 'vec': + return v.val.map(valToITF) + case 'map': + return { + [MAP_FIELD]: Array.from(v.val.entries()).map( + ([mapkey, mapval]) => [valToITF(mapkey), valToITF(mapval)] + ), + } + default: + return UNSERIALIZABLE + } +} + +export function stateToITF(state: State): object { + const desc = `Created by Solarkraft on ${new Date().toUTCString()}` + + const formatDesc = + 'https://apalache.informal.systems/docs/adr/015adr-trace.html' + + const meta: object = { + [FORMAT_FIELD]: 'ITF', + [DESCRIPTION_FIELD]: desc, + [FORMAT_DESCRIPTION_FIELD]: formatDesc, + } + + const varTypes = {} + const varNames = [] + const stateObj = {} + + for (const [varName, varValue] of state) { + varTypes[varName] = getFullType(varValue) + varNames.push(varName) + stateObj[varName] = valToITF(varValue) + } + meta[VAR_TYPES_FIELD] = varTypes + + return { + [META_FIELD]: meta, + [VARS_FIELD]: varNames, + [STATES_FIELD]: [stateObj], + } +} diff --git a/solarkraft/src/state/state.ts b/solarkraft/src/state/state.ts new file mode 100644 index 00000000..82033cdc --- /dev/null +++ b/solarkraft/src/state/state.ts @@ -0,0 +1,8 @@ +/** + * @license + * [Apache-2.0](https://github.com/freespek/solarkraft/blob/main/LICENSE) + */ +import { OrderedMap } from 'immutable' +import { Value } from './value.js' + +export type State = OrderedMap diff --git a/solarkraft/src/state/value.ts b/solarkraft/src/state/value.ts index a47fd4c2..e2aa2627 100644 --- a/solarkraft/src/state/value.ts +++ b/solarkraft/src/state/value.ts @@ -16,7 +16,100 @@ export type Value = { | MapValue ) -export function isValid(v: Value): boolean { +const EMPTY_COLLECTION_TYPE: string = 'T' +const UNKNOWN_TYPE: string = 'UNKNOWN' + +// Helper function, takes an array of strings (expected: types of child values for a Vec or Map Value) +// and returns: +// - an empty array, iff the input array is empty +// - an array with a single element, iff every element within the input array is equal to that element +// - the original array, if two or more elements in the input array are different +// whenever the function returns a singleton array, we can consider the original collection to be homogeneous +// and its contents to be of the type contained in the singleton return. +function computeSingleTypeRep(ts: string[]): string[] { + if (ts.length === 0) return [] + else { + const t0 = ts[0] + const hom = !ts.some((t) => t !== t0) + if (hom) return [t0] + else return ts + } +} + +// Returns the full type annotation of the provided (possibly complex) Value `v`. +// It uses Apalache-style type notation, with Soroban type primitives. +// For heterogeneous collections (Vec/Map), the type annotation is distinct, and should be considered to lie +// outside of the type system. +export function getFullType(v: Value): string { + switch (v.type) { + case 'bool': + case 'u32': + case 'i32': + case 'u64': + case 'i64': + case 'u128': + case 'i128': + case 'symb': + case 'addr': + return v.type + case 'arr': + // Bytes is not a generic collection, but always an array of integers, so we treat it as a + // type literal. Vec is the generic collection type. + return 'Bytes' + case 'vec': { + const childTypes = v.val.map(getFullType) + + const tRep = computeSingleTypeRep(childTypes) + + switch (tRep.length) { + case 0: + // We return a placeholder if empty + return `Vec(${EMPTY_COLLECTION_TYPE})` + case 1: + // expected case + return `Vec(${tRep[0]})` + default: + // If we have nonhomogeneous vectors, we treat them as tuples + // Note that tuples don't exist in the Soroban type system, and we should treat them as + // an indicator that the data is flawed. + return `<${childTypes.join(', ')}>` + } + } + case 'map': { + const childKeyTypes = [...v.val.keys()].map(getFullType) + const childValTypes = [...v.val.values()].map(getFullType) + + const keyTrep = computeSingleTypeRep(childKeyTypes) + const valTrep = computeSingleTypeRep(childValTypes) + + // Switch over [a, b] doesn't work + if (keyTrep.length === 0 && valTrep.length === 0) { + // they're either both empty, or neither is + return `(${EMPTY_COLLECTION_TYPE}1 -> ${EMPTY_COLLECTION_TYPE}2)` + } else if (keyTrep.length === 1 && valTrep.length === 1) { + // expected case: + return `(${keyTrep[0]} -> ${valTrep[0]})` + } else { + // If we have nonhomogeneous maps, we treat them as disjoin unions of homogeneous maps + // Note that such a type doesn't exist in the Soroban type system, and we should treat it as + // an indicator that the data is flawed. + const zipped = childKeyTypes.map((k, i) => [ + k, + childValTypes[i], + ]) + const maps = zipped.map(([kk, vv]) => `(${kk} -> ${vv})`) + return `(${maps.join(' | ')})` + } + } + default: + return UNKNOWN_TYPE + } +} + +export function isValid( + v: Value, + allow_nonhmogenoeous: boolean = false +): boolean { switch (v.type) { // Integers are valid iff their values lie within the [0, 2^n) or [-2^{n-1},2^{n-1}) intervals case 'u32': { @@ -55,16 +148,47 @@ export function isValid(v: Value): boolean { } // Fixed-length byte arrays are valid only if their declared length matches their actual length case 'arr': { - return typeof v.len === 'undefined' || v.val.length === v.len + const val = v.val + return ( + !val.some((x) => 0 > x && x >= 2 ** 8) && + (typeof v.len === 'undefined' || v.val.length === v.len) + ) } // Vectors are valid iff their elements are all valid. case 'vec': { - return !v.val.some((elem) => !isValid(elem)) + // If we enforce homogeneous collections, we chack all child types match + let homCheck = true + if (!allow_nonhmogenoeous && v.val.length !== 0) { + const t0 = getFullType(v.val[0]) + homCheck = !v.val.some((elem) => getFullType(elem) !== t0) + } + + return ( + homCheck && + !v.val.some((elem) => !isValid(elem, allow_nonhmogenoeous)) + ) } // Maps are valid iff their keys and values are all valid. case 'map': { - return ![...v.val.entries()].some( - ([key, value]) => !isValid(key) || !isValid(value) + const asArr = [...v.val.entries()] + // If we enforce homogeneous collections, we chack all child types match + let homCheck = true + if (!allow_nonhmogenoeous && asArr.length !== 0) { + const key_t0 = getFullType(asArr[0][0]) + const val_t0 = getFullType(asArr[0][1]) + homCheck = !asArr.some( + ([x, y]) => + getFullType(x) !== key_t0 || getFullType(y) != val_t0 + ) + } + + return ( + homCheck && + !asArr.some( + ([key, value]) => + !isValid(key, allow_nonhmogenoeous) || + !isValid(value, allow_nonhmogenoeous) + ) ) } // Booleans are always valid, under TS type constraints. @@ -180,7 +304,7 @@ export function addr(s: string): AddrValue { return obj } -export type byte = 0 | 1 +export type byte = number // Byte arrays (Bytes, BytesN) // The `len` field is present iff the length is fixed (i.e. for BytesN) @@ -209,34 +333,42 @@ export type VecValue = { } // Safe constructor for vec-typed `Value`s. Throws a `TypeError` if `v` contains an invalid value. -export function vec(v: Value[]): VecValue { +export function vec( + v: Value[], + allow_nonhmogenoeous: boolean = false +): VecValue { const obj: VecValue = { type: 'vec', val: v } - if (!isValid(obj)) { + if (!isValid(obj, allow_nonhmogenoeous)) { throw new TypeError(`Some element of ${v} is not valid.`) } return obj } +export type MapT = OrderedMap + // Soroban Map is an ordered key-value dictionary (note that JS maps are in principle unordered, but will iterate in insertion order). // Maps have at most one entry per key. Setting a value for a key in the map that already has a value for that key replaces the value. <-- docs export type MapValue = { - val: OrderedMap + val: MapT type: 'map' } export type KeyValuePair = [Value, Value] // Safe constructor for map-typed `Value`s. Throws a `TypeError` if `v` contains an invalid key or value. -export function map(v: OrderedMap): MapValue { +export function map(v: MapT, allow_nonhmogenoeous: boolean = false): MapValue { const obj: MapValue = { type: 'map', val: v } - if (!isValid(obj)) { + if (!isValid(obj, allow_nonhmogenoeous)) { throw new TypeError(`Some key or value of ${v} is not valid.`) } return obj } // Safe constructor for vec-typed `Value`s. Throws a `TypeError` if `v` contains an invalid key or value, or if it contains duplicate keys. -export function mapFromKV(a: KeyValuePair[]): MapValue { +export function mapFromKV( + a: KeyValuePair[], + allow_nonhmogenoeous: boolean = false +): MapValue { let partialMap = OrderedMap() for (const [k, v] of a) { @@ -247,7 +379,7 @@ export function mapFromKV(a: KeyValuePair[]): MapValue { } partialMap = partialMap.set(k, v) } - return map(partialMap) + return map(partialMap, allow_nonhmogenoeous) } // Helper function, returns the contents of the map as an array of key-value pairs for serialization diff --git a/solarkraft/test/io/itf.test.ts b/solarkraft/test/io/itf.test.ts new file mode 100644 index 00000000..86e1e6e6 --- /dev/null +++ b/solarkraft/test/io/itf.test.ts @@ -0,0 +1,62 @@ +// an example unit test to copy from + +import { assert } from 'chai' +import { OrderedMap } from 'immutable' +import { describe, it } from 'mocha' +import { Value, i32, u128, addr, map } from '../../src/state/value.js' +import { stateToITF } from '../../src/io/itf.js' +import { State } from '../../src/state/state.js' + +describe('itf tests', () => { + const aliceAddr = 'ALICE000000000000000000000000000000000000000000000000000' + const bobAddr = 'BOB00000000000000000000000000000000000000000000000000000' + + const state: State = OrderedMap() + .set( + 'balances', + map( + OrderedMap() + .set(addr(aliceAddr), u128(10n ** 20n)) + .set(addr(bobAddr), u128(10n ** 10n)) + ) + ) + .set('COUNTER', i32(-50n)) + + const itf = stateToITF(state) + + it('outputs metadata and type information correctly', () => { + assert(itf['vars'].length === 2) + assert(itf['vars'][0] === 'balances') + assert(itf['vars'][1] === 'COUNTER') + + assert(itf['#meta']['format'] === 'ITF') + assert(itf['#meta']['varTypes']['balances'] === '(addr -> u128)') + assert(itf['#meta']['varTypes']['COUNTER'] === 'i32') + + assert(itf['states'].length === 1) + }) + + it('serializes the contract state correctly', () => { + const s = itf['states'][0] + + const expectedBalances = [ + [aliceAddr, { '#bigint': '1' + '0'.repeat(20) }], + [bobAddr, { '#bigint': '1' + '0'.repeat(10) }], + ] + + const expectedCounter = { '#bigint': '-50' } + + const bmap = s['balances']['#map'] + + assert(bmap.length == 2) + assert(bmap[0].length == 2) + assert(bmap[1].length == 2) + + assert(bmap[0][0] === expectedBalances[0][0]) + assert(bmap[0][1]['#bigint'] === expectedBalances[0][1]['#bigint']) + assert(bmap[1][0] === expectedBalances[1][0]) + assert(bmap[1][1]['#bigint'] === expectedBalances[1][1]['#bigint']) + + assert[s['COUNTER']['#bigint'] === expectedCounter['#bigint']] + }) +}) diff --git a/solarkraft/test/state/value.test.ts b/solarkraft/test/state/value.test.ts index 9b81991a..9d4d0f79 100644 --- a/solarkraft/test/state/value.test.ts +++ b/solarkraft/test/state/value.test.ts @@ -22,6 +22,8 @@ import { toArr, KeyValuePair, mapFromKV, + MapT, + getFullType, } from '../../src/state/value.js' describe('Integer tests', () => { @@ -172,8 +174,12 @@ describe('Collection tests', () => { vec(vecWithInvalid) }, TypeError) + assert.throws(() => { + vec(heterogeneousArr) + }, TypeError) + const homVec = vec(homogeneousArr) - const hetVec = vec(heterogeneousArr) + const hetVec = vec(heterogeneousArr, true) assert(homVec.val.length == homogeneousArr.length) assert(hetVec.val.length == heterogeneousArr.length) @@ -189,18 +195,26 @@ describe('Collection tests', () => { 'BOB00000000000000000000000000000000000000000000000000000' ) - const mapValid: OrderedMap = OrderedMap() + const mapValid: MapT = OrderedMap() .set(k0, alice) .set(k1, bob) - const mapInvalidVal: OrderedMap = OrderedMap< - Value, - Value - >().set(k0, { type: 'addr', val: 'ALICE' }) - const mapInvalidKey: OrderedMap = OrderedMap< - Value, - Value - >().set({ type: 'u32', val: -1n }, bob) + const mapHetKey: MapT = OrderedMap() + .set(k0, alice) + .set(u64(1n), bob) + + const mapHetVal: MapT = OrderedMap() + .set(k0, alice) + .set(k1, symb('BOB')) + + const mapInvalidVal: MapT = OrderedMap().set(k0, { + type: 'addr', + val: 'ALICE', + }) + const mapInvalidKey: MapT = OrderedMap().set( + { type: 'u32', val: -1n }, + bob + ) assert.throws(() => { map(mapInvalidKey) @@ -209,6 +223,13 @@ describe('Collection tests', () => { map(mapInvalidVal) }, TypeError) + assert.throws(() => { + map(mapHetKey) + }, TypeError) + assert.throws(() => { + map(mapHetVal) + }, TypeError) + const valdiMap = map(mapValid) const asArr = toArr(valdiMap) @@ -220,6 +241,15 @@ describe('Collection tests', () => { assert(asArr[0][1] === alice) assert(asArr[1][0] === k1) assert(asArr[1][1] === bob) + + // check for no throws + const hetMapKey = map(mapHetKey, true) + const keyTypes = [...hetMapKey.val.keys()].map((x) => x.type) + assert(keyTypes[0] !== keyTypes[1]) + + const hetMapVal = map(mapHetVal, true) + const valTypes = [...hetMapVal.val.values()].map((x) => x.type) + assert(valTypes[0] !== valTypes[1]) }) it('asserts map array constructors properly assert child validity', () => { @@ -269,3 +299,132 @@ describe('Collection tests', () => { assert(asArr[1][1] === arrValid[1][1]) }) }) + +describe('Type tests', () => { + it("Checks basic types' type tag is their full type", () => { + const vals = [ + u32(0n), + i32(0n), + u64(0n), + i64(0n), + u128(0n), + i128(0n), + bool(true), + addr('ALICE000000000000000000000000000000000000000000000000000'), + symb('BOB'), + ] + + assert(!vals.some((v) => v.type != getFullType(v))) + }) + + it('Checks byte array type is Bytes', () => { + const bytesNoN = bytes([0, 1, 0, 129]) + const bytesWithN = bytesN([1, 0, 1, 0]) + + assert(getFullType(bytesNoN) === 'Bytes') + assert(getFullType(bytesWithN) === 'Bytes') + }) + + it('Checks vector type is Vec and heterogeneous vectors are distinctly typed', () => { + const emptyVec = vec([], false) + const homVec = vec([u32(0n), u32(1n)], false) + const hetVec = vec([u32(0n), i32(0n), u32(0n)], true) + const nestedHomVec = vec( + [vec([u32(0n), u32(1n)], false), vec([u32(2n), u32(3n)], false)], + false + ) + const nestedHetVec = vec( + [vec([u32(0n), u32(1n)], false), vec([u32(2n), i32(3n)], true)], + true + ) + + assert(getFullType(emptyVec) === `Vec(T)`) + assert(getFullType(homVec) === `Vec(u32)`) + assert(getFullType(hetVec) === ``) + assert(getFullType(nestedHomVec) === `Vec(Vec(u32))`) + assert(getFullType(nestedHetVec) === `>`) + }) + + it('Checks map type is Map and heterogeneous maps are distinclty typed', () => { + const emptyMap = map(OrderedMap(), false) + + const k0 = u32(0n) + const k1 = u32(1n) + const alice = symb('ALICE') + const bob = symb('BOB') + + const homMap = map( + OrderedMap().set(k0, alice).set(k1, bob), + false + ) + + const hetMapK = map( + OrderedMap().set(k0, alice).set(i32(1n), bob), + true + ) + + const hetMapV = map( + OrderedMap() + .set( + k0, + addr( + 'ALICE000000000000000000000000000000000000000000000000000' + ) + ) + .set(k1, bob), + true + ) + + const nestedHomMap = map( + OrderedMap() + .set( + k0, + map( + OrderedMap().set(k0, alice).set(k1, bob), + false + ) + ) + .set( + k1, + map( + OrderedMap() + .set(u32(2n), symb('CHARLIE')) + .set(u32(3n), symb('DELTA')), + false + ) + ), + false + ) + + const nestedHetMap = map( + OrderedMap() + .set( + k0, + map( + OrderedMap().set(k0, alice).set(k1, bob), + false + ) + ) + .set( + k1, + map( + OrderedMap() + .set(u32(2n), symb('CHARLIE')) + .set(i32(3n), symb('DELTA')), + true + ) + ), + true + ) + + assert(getFullType(emptyMap) === `(T1 -> T2)`) + assert(getFullType(homMap) === `(u32 -> symb)`) + assert(getFullType(hetMapK) === `((u32 -> symb) | (i32 -> symb))`) + assert(getFullType(hetMapV) === `((u32 -> addr) | (u32 -> symb))`) + assert(getFullType(nestedHomMap) === `(u32 -> (u32 -> symb))`) + assert( + getFullType(nestedHetMap) === + `((u32 -> (u32 -> symb)) | (u32 -> ((u32 -> symb) | (i32 -> symb))))` + ) + }) +})