diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..c4e0d4a --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,25 @@ +name: Run test suite + +on: + push: + branches: + - '*' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + submodules: recursive + - name: setup node + uses: actions/setup-node@v3 + with: + node-version-file: .nvmrc + - name: run tests + run: | + npm install + npm run build + npm run test diff --git a/.github/workflows/tidy.yml b/.github/workflows/tidy.yml new file mode 100644 index 0000000..90845d5 --- /dev/null +++ b/.github/workflows/tidy.yml @@ -0,0 +1,25 @@ +name: Code style and lint + +on: + pull_request: + branches: + - master + + workflow_dispatch: + +jobs: + tidy: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + submodules: recursive + - name: setup node + uses: actions/setup-node@v3 + with: + node-version-file: .nvmrc + - name: run tidy + run: | + npm install + npm run tidy-check diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..74d9ace --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20.6 diff --git a/.tidyrc.json b/.tidyrc.json new file mode 100644 index 0000000..e7de9f0 --- /dev/null +++ b/.tidyrc.json @@ -0,0 +1,8 @@ +{ + "indent": 2, + "operatorsFile": null, + "ribbon": 1, + "typeArrowPlacement": "first", + "unicode": "never", + "width": null +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c75a30a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: node_js -dist: focal -sudo: required -node_js: stable -install: - - npm install -script: - - npm run build -after_success: -- >- - test $TRAVIS_TAG && - echo $GITHUB_TOKEN | pulp login && - echo y | pulp publish --no-push diff --git a/package.json b/package.json index d959fa9..8ab378f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,10 @@ { "private": true, "scripts": { - "build": "spago build" + "build": "spago build", + "test": "spago -x test.dhall test", + "tidy": "purs-tidy format-in-place \"src/**/*.purs\" \"test/**/*.purs\"", + "tidy-check": "purs-tidy check \"src/**/*.purs\" \"test/**/*.purs\"" }, "dependencies": { "bn.js": "^4.11.0", @@ -11,11 +14,12 @@ "pulp": "^15.0.0", "rlp": "^2.0.0", "secp256k1": "^3.0.1", - "solc": "^0.5" + "solc": "^0.8" }, "devDependencies": { + "purescript": "^0.15.8", "purescript-psa": "^0.8.2", - "purescript": "^0.14.2", - "spago": "^0.20.3" + "spago": "^0.20.9", + "purs-tidy": "^0.10.0" } } diff --git a/packages.dhall b/packages.dhall index 9623df3..d5f7a8d 100644 --- a/packages.dhall +++ b/packages.dhall @@ -1,81 +1,9 @@ let upstream = - https://github.com/purescript/package-sets/releases/download/psc-0.14.2-20210629/packages.dhall sha256:534c490bb73cae75adb5a39871142fd8db5c2d74c90509797a80b8bb0d5c3f7b + https://raw.githubusercontent.com/f-o-a-m/package-sets/purs-0.15/purs-0.15.7-web3.dhall + sha256:2d868539460c47c2bf5ecf4c6b68c3ea3162849f2da9cd3f263b740299448d8f let overrides = {=} -let additions = - { web3 = - { dependencies = - [ "aff" - , "avar" - , "console" - , "coroutines" - , "coroutine-transducers" - , "debug" - , "effect" - , "errors" - , "eth-core" - , "foreign" - , "foreign-generic" - , "fork" - , "free" - , "heterogeneous" - , "identity" - , "parsing" - , "partial" - , "profunctor-lenses" - , "psci-support" - , "tagged" - , "transformers" - , "typelevel-prelude" - , "variant" - ] - , repo = "https://github.com/f-o-a-m/purescript-web3" - , version = "v4.0.0" - } - , eth-core = - { dependencies = - [ "argonaut" - , "bytestrings" - , "console" - , "debug" - , "effect" - , "foreign-generic" - , "ordered-collections" - , "parsing" - , "prelude" - , "psci-support" - , "ring-modules" - , "simple-json" - ] - , repo = - "https://github.com/f-o-a-m/purescript-eth-core.git" - , version = - "v7.0.0" - } - , coroutine-transducers = - { dependencies = - [ "aff" - , "coroutines" - , "effect" - , "maybe" - , "psci-support" - ] - , repo = - "https://github.com/blinky3713/purescript-coroutine-transducers" - , version = - "v1.0.0" - } - , tagged = - { dependencies = - [ "identity" - , "profunctor" - ] - , repo = - "https://github.com/kejace/purescript-tagged" - , version = - "v0.14" - } - } +let additions = {=} in upstream // overrides // additions diff --git a/spago.dhall b/spago.dhall index d3dbbba..772c10a 100644 --- a/spago.dhall +++ b/spago.dhall @@ -21,7 +21,6 @@ You can edit this file as you like. , "newtype" , "node-path" , "prelude" - , "psci-support" , "strings" , "transformers" , "tuples" diff --git a/src/Language/Solidity/Compiler.js b/src/Language/Solidity/Compiler.js index 3198709..224ec1e 100644 --- a/src/Language/Solidity/Compiler.js +++ b/src/Language/Solidity/Compiler.js @@ -1,6 +1,6 @@ "use strict"; -const solcMod = require('solc'); +import solcMod from "solc"; function stringify(input) { if (typeof input !== 'string') { @@ -16,34 +16,79 @@ function objectify(input) { return JSON.parse(input); } -exports.defaultCompiler = solcMod; +export const defaultCompiler = solcMod; -exports.version = function(solc) { +export const version = function(solc) { return solc.version(); } -exports.useCompiler = function(source) { - const requireFromString = function(str) { - const filename = "__solc_useCompiler"; - const Module = module.constructor; - var m = new Module(filename, module); - m.filename = filename - m.paths = module.paths; - m._compile(source, "__solc_useCompiler"); - return m.exports; +// Because PureScript 0.15+ forces all its modules to be ES modules, we can't just +// use require() like in a CJS module. Nor do we have the CommonJS `Module` API, where we can +// synthesize CJS modules at runtime. This is how useCompiler previously worked. +// import()ing from a data URI will make Node's loader assume we're trying to load an ES module, and there's +// no way to actually tell it to treat it as a CJS module -- `await import(..., { assert: { type: 'commonjs' }})` +// is actually an unsupported assertion (only `import assert { type: 'json' }` is supported by node). +// +// All of this is really unfortunate since solc is shipped as CommonJS. To get around this, we create a loader hook +// that forces Node to treat certain URLs as CommonJS. +// +// This unfortunately requires Node v20.6+ +// +// Because we are a PureScript package and can't assume anything about where any relative +// "pure" JS files will be, but we do know our own module's URL, we keep this function here +// and in _useCompiler, we register this file (output/Language.Solidity.Compiler/foreign.js) as a Node loader. +const __DATA_JS_BASE64 = "data:text/javascript;base64,"; +const __SOLC_CJS_MARKER = "/solc_cjs"; +export function load(spec, context, next) { + if (typeof spec === 'string' && spec.startsWith(__DATA_JS_BASE64) && spec.endsWith(__SOLC_CJS_MARKER)) { + const cleanedSpec = spec.substring(__DATA_JS_BASE64.length, spec.length - __SOLC_CJS_MARKER.length); + return { + format: 'commonjs', + shortCircuit: true, + source: atob(cleanedSpec), + }; + } else { + return next(spec, context); } - return solcMod.setupMethods(requireFromString(source)); } -exports.callbackSuccess = function (contents) { +export const _useCompiler = function(source) { + return function (onError, onSuccess) { + const mkMod = async () => { + try { + // todo: this is obv. unusable in a browser. `purescript-solc` only really exists to + // to support Chanterelle, so this is fine for now... + const NodeModule = await import("node:module"); + NodeModule.register(import.meta.url); + const url = __DATA_JS_BASE64 + btoa(source) + __SOLC_CJS_MARKER; + const mod = await import(url); + return mod.default; + } catch(e) { + console.error(e); + throw e; + } + }; + + const cancel = mkMod().then(m => onSuccess(solcMod.setupMethods(m)), e => { + onError(e); + }); + + return function(cancelError, onCancelerError, onCancelerSuccess) { + cancel(); + onCancelerSuccess(); + }; + }; +} + +export const callbackSuccess = function (contents) { return { "contents": contents } }; -exports.callbackFailure = function (error) { +export const callbackFailure = function (error) { return { "error": error } }; -exports._loadRemoteVersion = function(version) { +export const _loadRemoteVersion = function(version) { return function (onError, onSuccess) { var cancel = solcMod.loadRemoteVersion(version, function(err, solcSnapshot) { if (err) { @@ -59,7 +104,7 @@ exports._loadRemoteVersion = function(version) { } }; -exports._compile = function (solc, input, readCallback) { +export const _compile = function (solc, input, readCallback) { return function() { // support different versions of solc-js // to understand what's going on here, keep this in mind: @@ -156,4 +201,4 @@ exports._compile = function (solc, input, readCallback) { return readCallback(requestedFile)(); })); } -}; +}; \ No newline at end of file diff --git a/src/Language/Solidity/Compiler.purs b/src/Language/Solidity/Compiler.purs index b81c12c..08338a2 100644 --- a/src/Language/Solidity/Compiler.purs +++ b/src/Language/Solidity/Compiler.purs @@ -28,7 +28,7 @@ foreign import callbackSuccess :: String -> SolcReadFileCallbackResult foreign import callbackFailure :: String -> SolcReadFileCallbackResult foreign import defaultCompiler :: SolidityCompiler foreign import version :: SolidityCompiler -> String -foreign import useCompiler :: String -> SolidityCompiler +foreign import _useCompiler :: String -> EffectFnAff SolidityCompiler foreign import _loadRemoteVersion :: String -> EffectFnAff SolidityCompiler foreign import _compile :: Fn3 SolidityCompiler Json (FilePath -> Effect SolcReadFileCallbackResult) (Effect Json) @@ -42,7 +42,8 @@ compile compile solc input readFile = liftEffect $ map (lmap printJsonDecodeError) $ A.decodeJson <$> runFn3 _compile solc (encodeJson input) liftedCallback - where liftedCallback = map (either callbackFailure callbackSuccess) <<< readFile + where + liftedCallback = map (either callbackFailure callbackSuccess) <<< readFile loadRemoteVersion :: forall m @@ -50,3 +51,10 @@ loadRemoteVersion => String -> m SolidityCompiler loadRemoteVersion = liftAff <<< fromEffectFnAff <<< _loadRemoteVersion + +useCompiler + :: forall m + . MonadAff m + => String + -> m SolidityCompiler +useCompiler = liftAff <<< fromEffectFnAff <<< _useCompiler \ No newline at end of file diff --git a/src/Language/Solidity/Compiler/Releases.js b/src/Language/Solidity/Compiler/Releases.js index 791d83e..86b7bd1 100644 --- a/src/Language/Solidity/Compiler/Releases.js +++ b/src/Language/Solidity/Compiler/Releases.js @@ -1,16 +1,16 @@ "use strict"; -const http = require('http'); -const https = require('https'); -const MemoryStream = require('memorystream'); +import http from "http"; +import https from "https"; +import MemoryStream from "memorystream"; -exports._getURL = function(url) { +export const _getURL = function(url) { const httpImpl = url.startsWith("https:") ? https : http; return function(onError, onSuccess) { var cancel = httpImpl.get(url, function (res) { var error; if (res.statusCode != 200) { - error = new Error("Request failed, status code " + statusCode); + error = new Error("Request failed to " + url + " status code " + res.statusCode); } if (error) { diff --git a/src/Language/Solidity/Compiler/Releases.purs b/src/Language/Solidity/Compiler/Releases.purs index b9f9118..e9f8c6d 100644 --- a/src/Language/Solidity/Compiler/Releases.purs +++ b/src/Language/Solidity/Compiler/Releases.purs @@ -1,4 +1,4 @@ -module Language.Solidity.Compiler.Releases +module Language.Solidity.Compiler.Releases ( Build(..) , ReleaseList(..) , BuildR @@ -17,46 +17,60 @@ import Data.Argonaut (class DecodeJson, class EncodeJson, decodeJson, encodeJson import Data.Argonaut.Decode.Error (printJsonDecodeError) import Data.Bifunctor (lmap) import Data.Either (Either(..), note) +import Data.Generic.Rep (class Generic) import Data.Maybe (Maybe(..)) +import Data.Show.Generic (genericShow) import Data.String (toLower) import Effect.Aff.Class (class MonadAff, liftAff) import Effect.Aff.Compat (EffectFnAff, fromEffectFnAff) import Foreign.Object as FO type BuildR a = Record - ( path :: String - , version :: String - , build :: String - , longVersion :: String - , keccak256 :: String - , urls :: Array String + ( path :: String + , version :: String + , build :: String + , longVersion :: String + , keccak256 :: String + , urls :: Array String | a ) -data Build = Stable (BuildR ()) - | Prerelease (BuildR (prerelease :: String)) +data Build + = Stable (BuildR ()) + | Prerelease (BuildR (prerelease :: String)) + +derive instance genericBuild :: Generic Build _ +instance showBuild :: Show Build where + show = genericShow instance decodeJsonBuild :: DecodeJson Build where decodeJson j = Prerelease <$> decodeJson j <|> Stable <$> decodeJson j instance encodeJsonBuild :: EncodeJson Build where - encodeJson (Stable s) = encodeJson s + encodeJson (Stable s) = encodeJson s encodeJson (Prerelease s) = encodeJson s -newtype ReleaseList = - ReleaseList { builds :: Array Build - , releases :: FO.Object String - , latestRelease :: String - } +newtype ReleaseList = + ReleaseList + { builds :: Array Build + , releases :: FO.Object String + , latestRelease :: String + } + +derive instance genericReleaseList :: Generic ReleaseList _ derive newtype instance decodeJsonReleaseList :: DecodeJson ReleaseList derive newtype instance encodeJsonReleaseList :: EncodeJson ReleaseList +instance showReleaseList :: Show ReleaseList where + show = genericShow -newtype ReleaseRepo = - ReleaseRepo { base :: String - , listFile :: String - } +newtype ReleaseRepo = + ReleaseRepo + { base :: String + , listFile :: String + } foreign import _getURL :: String -> EffectFnAff String + getURL :: forall m . MonadAff m @@ -66,7 +80,7 @@ getURL u = liftAff $ (map Right <<< fromEffectFnAff $ _getURL u) `catchError` (p defaultReleaseRepo :: ReleaseRepo defaultReleaseRepo = ReleaseRepo - { base: "https://ethereum.github.io/solc-bin/bin" + { base: "https://binaries.soliditylang.org/bin" , listFile: "list.json" } @@ -83,8 +97,8 @@ getReleaseList (ReleaseRepo repo) = runExceptT do lookupLatestRelease :: ReleaseList -> Either String String -lookupLatestRelease (ReleaseList list) = - note "repo's latest release was not in the repo's releases list" $ +lookupLatestRelease (ReleaseList list) = + note "repo's latest release was not in the repo's releases list" $ FO.lookup list.latestRelease list.releases getReleaseSource @@ -101,6 +115,6 @@ getReleaseSource rr@(ReleaseRepo repo) release = runExceptT do releaseFileName <- except (lookupLatestRelease rl) fetch releaseFileName _ -> case FO.lookup release list.releases of - Nothing -> fetch release - Just releaseFilename -> fetch releaseFilename - + Nothing -> fetch release + Just releaseFilename -> fetch releaseFilename + diff --git a/src/Language/Solidity/Compiler/Types.purs b/src/Language/Solidity/Compiler/Types.purs index 5e358d8..5b12438 100644 --- a/src/Language/Solidity/Compiler/Types.purs +++ b/src/Language/Solidity/Compiler/Types.purs @@ -1,54 +1,57 @@ -module Language.Solidity.Compiler.Types +module Language.Solidity.Compiler.Types ( module Common , module Input , module Output ) where -import Language.Solidity.Compiler.Types.Common ( ContractMapped - , FileMapped - ) as Common -import Language.Solidity.Compiler.Types.Input ( class IsSelection - , Remapping(..) - , CompilerSettings(..) - , OptimizerDetails(..) - , YulOptimizerDetails(..) - , OptimizerSettings(..) - , EvmVersion(..) - , MetadataSettings(..) - , Library(..) - , Libraries(..) - , FileLevelSelection(..) - , EvmBytecodeOutput(..) - , EvmOutputSelection(..) - , EwasmOutputSelection(..) - , ContractLevelSelection(..) - , OutputSelection(..) - , OutputSelections(..) - , SourceLanguage(..) - , Source(..) - , Sources(..) - , CompilerInput(..) - , decodeJsonSelection - , encodeJsonSelection - , fromSelection - , toSelection - ) as Input -import Language.Solidity.Compiler.Types.Output ( ErrorType(..) - , ErrorSeverity(..) - , SourceLocation(..) - , CompilationError(..) - , SourceLevelOutput(..) - , BytecodeObject(..) - , LinkReference(..) - , LinkReferences(..) - , BytecodeOutput(..) - , MethodIdentifiers(..) - , GasEstimate(..) - , GasEstimates(..) - , EvmOutput(..) - , EwasmOutput(..) - , ContractLevelOutput(..) - , CompilerOutput(..) - , mkBytecodeObject - , unBytecodeObject - ) as Output \ No newline at end of file +import Language.Solidity.Compiler.Types.Common + ( ContractMapped + , FileMapped + ) as Common +import Language.Solidity.Compiler.Types.Input + ( class IsSelection + , Remapping(..) + , CompilerSettings(..) + , OptimizerDetails(..) + , YulOptimizerDetails(..) + , OptimizerSettings(..) + , EvmVersion(..) + , MetadataSettings(..) + , Library(..) + , Libraries(..) + , FileLevelSelection(..) + , EvmBytecodeOutput(..) + , EvmOutputSelection(..) + , EwasmOutputSelection(..) + , ContractLevelSelection(..) + , OutputSelection(..) + , OutputSelections(..) + , SourceLanguage(..) + , Source(..) + , Sources(..) + , CompilerInput(..) + , decodeJsonSelection + , encodeJsonSelection + , fromSelection + , toSelection + ) as Input +import Language.Solidity.Compiler.Types.Output + ( ErrorType(..) + , ErrorSeverity(..) + , SourceLocation(..) + , CompilationError(..) + , SourceLevelOutput(..) + , BytecodeObject(..) + , LinkReference(..) + , LinkReferences(..) + , BytecodeOutput(..) + , MethodIdentifiers(..) + , GasEstimate(..) + , GasEstimates(..) + , EvmOutput(..) + , EwasmOutput(..) + , ContractLevelOutput(..) + , CompilerOutput(..) + , mkBytecodeObject + , unBytecodeObject + ) as Output \ No newline at end of file diff --git a/src/Language/Solidity/Compiler/Types/Common.purs b/src/Language/Solidity/Compiler/Types/Common.purs index 7cca3b3..3e7d828 100644 --- a/src/Language/Solidity/Compiler/Types/Common.purs +++ b/src/Language/Solidity/Compiler/Types/Common.purs @@ -16,7 +16,7 @@ import Data.Newtype (class Newtype) import Foreign.Object as FO --- Some readability sugar for nested object syntaxes -type FileMapped a = FO.Object a +type FileMapped a = FO.Object a type ContractMapped a = FO.Object a --- Some fields are arrays, and can be omitted entirely if empty @@ -26,19 +26,20 @@ flattenOptionalArray rs = if Array.null rs then Nothing else Just rs --- Some fields are strings which are really JSON newtype Strung a = Strung a -derive newtype instance eqStrung :: Eq a => Eq (Strung a) -derive newtype instance ordStrung :: Ord a => Ord (Strung a) +derive newtype instance eqStrung :: Eq a => Eq (Strung a) +derive newtype instance ordStrung :: Ord a => Ord (Strung a) derive newtype instance semigroupStrung :: Semigroup a => Semigroup (Strung a) -derive newtype instance monoidStrung :: Monoid a => Monoid (Strung a) +derive newtype instance monoidStrung :: Monoid a => Monoid (Strung a) derive instance newtypeStrung :: Newtype (Strung a) _ instance decodeJsonStrung :: DecodeJson a => DecodeJson (Strung a) where - decodeJson j = - decodeJson j >>= - (lmap TypeMismatch <<< jsonParser) >>= - decodeJson >>= - pure <<< Strung + decodeJson j = + decodeJson j + >>= (lmap TypeMismatch <<< jsonParser) + >>= decodeJson + >>= + pure <<< Strung instance encodeJsonStrung :: EncodeJson a => EncodeJson (Strung a) where encodeJson (Strung a) = fromString <<< stringify $ encodeJson a diff --git a/src/Language/Solidity/Compiler/Types/Input.purs b/src/Language/Solidity/Compiler/Types/Input.purs index 6e1bb01..c14e319 100644 --- a/src/Language/Solidity/Compiler/Types/Input.purs +++ b/src/Language/Solidity/Compiler/Types/Input.purs @@ -1,4 +1,4 @@ -module Language.Solidity.Compiler.Types.Input +module Language.Solidity.Compiler.Types.Input ( SourceLanguage(..) , Source(..) , Sources(..) @@ -21,44 +21,47 @@ import Language.Solidity.Compiler.Types.Settings (class IsSelection, CompilerSet -------------------------------------------------- --- "language" field of input data SourceLanguage = Solidity | Yul + derive instance eqSourceLanguage :: Eq SourceLanguage derive instance ordSourceLanguage :: Ord SourceLanguage instance decodeJsonSourceLanguage :: DecodeJson SourceLanguage where decodeJson j = decodeJson j >>= case _ of "Solidity" -> pure Solidity - "Yul" -> pure Yul - x -> Left $ Named ("Unknown source language " <> x) $ UnexpectedValue j + "Yul" -> pure Yul + x -> Left $ Named ("Unknown source language " <> x) $ UnexpectedValue j instance encodeJsonSourceLanguage :: EncodeJson SourceLanguage where encodeJson = A.fromString <<< case _ of Solidity -> "Solidity" - Yul -> "Yul" + Yul -> "Yul" -------------------------------------------------- --- "sources" field of input -data Source = - FromURLs +data Source + = FromURLs { keccak256 :: Maybe HexString -- todo: enforce 256 bit size? - , urls :: Array String + , urls :: Array String } | FromContent { keccak256 :: Maybe HexString -- todo: enforce 256 bit size? - , content :: String + , content :: String } -derive instance eqSource :: Eq Source + +derive instance eqSource :: Eq Source derive instance ordSource :: Ord Source instance encodeJsonSource :: EncodeJson Source where encodeJson (FromURLs u) = - "urls" := u.urls - ~> "keccak256" :=? u.keccak256 + "urls" := u.urls + ~> "keccak256" :=? u.keccak256 encodeJson (FromContent c) = - "content" := c.content - ~> "keccak256" :=? c.keccak256 + "content" := c.content + ~> "keccak256" :=? c.keccak256 newtype Sources = Sources (FO.Object Source) + derive instance newtypeSources :: Newtype Sources _ derive newtype instance encodeJsonSources :: EncodeJson Sources derive newtype instance eqSources :: Eq Sources @@ -69,15 +72,16 @@ derive newtype instance ordSources :: Ord Sources newtype CompilerInput = CompilerInput { language :: SourceLanguage - , sources :: Sources + , sources :: Sources , settings :: Maybe CompilerSettings } -derive instance eqCompilerInput :: Eq CompilerInput + +derive instance eqCompilerInput :: Eq CompilerInput derive instance ordCompilerInput :: Ord CompilerInput instance encodeJsonCompilerInput :: EncodeJson CompilerInput where encodeJson (CompilerInput i) = - "language" := i.language - ~> "sources" := i.sources - ~> "settings" :=? i.settings - ~>? jsonEmptyObject + "language" := i.language + ~> "sources" := i.sources + ~> "settings" :=? i.settings + ~>? jsonEmptyObject diff --git a/src/Language/Solidity/Compiler/Types/Output.purs b/src/Language/Solidity/Compiler/Types/Output.purs index 514a472..dff0208 100644 --- a/src/Language/Solidity/Compiler/Types/Output.purs +++ b/src/Language/Solidity/Compiler/Types/Output.purs @@ -36,36 +36,38 @@ import Network.Ethereum.Types (HexString, mkHexString, unHex) -------------------------------------------------- --- "errors[].type" field of output -data ErrorType = JSONError - | IOError - | ParserError - | DocstringParsingError - | SyntaxError - | DeclarationError - | TypeError - | UnimplementedFeatureError - | InternalCompilerError - | Exception - | CompilerError - | FatalError - | Warning -derive instance eqErrorType :: Eq ErrorType +data ErrorType + = JSONError + | IOError + | ParserError + | DocstringParsingError + | SyntaxError + | DeclarationError + | TypeError + | UnimplementedFeatureError + | InternalCompilerError + | Exception + | CompilerError + | FatalError + | Warning + +derive instance eqErrorType :: Eq ErrorType derive instance ordErrorType :: Ord ErrorType instance showErrorType :: Show ErrorType where - show JSONError = "JSON Error" - show IOError = "IO Error" - show ParserError = "Parser Error" - show DocstringParsingError = "Docstring Parsing Error" - show SyntaxError = "Syntax Error" - show DeclarationError = "Declaration Error" - show TypeError = "Type Error" + show JSONError = "JSON Error" + show IOError = "IO Error" + show ParserError = "Parser Error" + show DocstringParsingError = "Docstring Parsing Error" + show SyntaxError = "Syntax Error" + show DeclarationError = "Declaration Error" + show TypeError = "Type Error" show UnimplementedFeatureError = "Unimplemented Feature Error" - show InternalCompilerError = "Internal Compiler Error" - show Exception = "Compiler Exception" - show CompilerError = "Compiler Error" - show FatalError = "Fatal Error" - show Warning = "Warning" + show InternalCompilerError = "Internal Compiler Error" + show Exception = "Compiler Exception" + show CompilerError = "Compiler Error" + show FatalError = "Fatal Error" + show Warning = "Warning" instance decodeJsonErrorType :: DecodeJson ErrorType where decodeJson j = decodeJson j >>= case _ of @@ -88,75 +90,82 @@ instance decodeJsonErrorType :: DecodeJson ErrorType where --- "errors[].severity" field of output data ErrorSeverity = SeverityError | SeverityWarning -derive instance eqErrorSeverity :: Eq ErrorSeverity + +derive instance eqErrorSeverity :: Eq ErrorSeverity derive instance ordErrorSeverity :: Ord ErrorSeverity instance decodeJsonErrorSeverity :: DecodeJson ErrorSeverity where decodeJson o = decodeJson o >>= case _ of - "error" -> pure SeverityError + "error" -> pure SeverityError "warning" -> pure SeverityWarning - x -> Left $ Named ("Unexpected ErrorSeverity " <> x) $ UnexpectedValue o + x -> Left $ Named ("Unexpected ErrorSeverity " <> x) $ UnexpectedValue o -------------------------------------------------- --- "errors[].sourceLocation" and ".secondarySourceLocations" field of output newtype SourceLocation = SourceLocation - { file :: String - , start :: Int - , end :: Int + { file :: String + , start :: Int + , end :: Int , message :: Maybe String } + derive instance eqSourceLocation :: Eq SourceLocation derive instance ordSourceLocation :: Ord SourceLocation instance showSourceLocation :: Show SourceLocation where show (SourceLocation sl) = - let msg = maybe "" (append ": ") sl.message - in sl.file <> ":" <> show sl.start <> "-" <> show sl.end <> msg + let + msg = maybe "" (append ": ") sl.message + in + sl.file <> ":" <> show sl.start <> "-" <> show sl.end <> msg instance decodeJsonSourceLocation :: DecodeJson SourceLocation where decodeJson j = do o <- decodeJson j - file <- o .: "file" - start <- o .: "start" - end <- o .: "end" + file <- o .: "file" + start <- o .: "start" + end <- o .: "end" message <- o .:? "message" pure $ SourceLocation { file, start, end, message } -------------------------------------------------- --- "errors" field of output -data CompilationError = SimpleCompilationError String - | FullCompilationError - { type :: ErrorType - , component :: String - , severity :: ErrorSeverity - , message :: String - , formattedMessage :: Maybe String - , sourceLocation :: Maybe SourceLocation - , secondarySourceLocations :: Array SourceLocation - } -derive instance eqCompilationError :: Eq CompilationError +data CompilationError + = SimpleCompilationError String + | FullCompilationError + { type :: ErrorType + , component :: String + , severity :: ErrorSeverity + , message :: String + , formattedMessage :: Maybe String + , sourceLocation :: Maybe SourceLocation + , secondarySourceLocations :: Array SourceLocation + } + +derive instance eqCompilationError :: Eq CompilationError derive instance ordCompilationError :: Ord CompilationError instance decodeJsonCompilationError :: DecodeJson CompilationError where decodeJson j = (SimpleCompilationError <$> decodeJson j) <|> (decodeAsObject =<< decodeJson j) - where decodeAsObject o = do - ty <- o .: "type" - component <- o .: "component" - severity <- o .: "severity" - message <- o .: "message" - formattedMessage <- o .:? "formattedMessage" - sourceLocation <- o .:? "sourceLocation" - secondarySourceLocations <- o .:? "secondarySourceLocations" .!= [] - pure $ FullCompilationError - { type: ty - , component - , severity - , message - , formattedMessage - , sourceLocation - , secondarySourceLocations - } + where + decodeAsObject o = do + ty <- o .: "type" + component <- o .: "component" + severity <- o .: "severity" + message <- o .: "message" + formattedMessage <- o .:? "formattedMessage" + sourceLocation <- o .:? "sourceLocation" + secondarySourceLocations <- o .:? "secondarySourceLocations" .!= [] + pure $ FullCompilationError + { type: ty + , component + , severity + , message + , formattedMessage + , sourceLocation + , secondarySourceLocations + } -------------------------------------------------- --- "sources{}" field of output @@ -166,7 +175,8 @@ newtype SourceLevelOutput = SourceLevelOutput , ast :: Maybe A.Json , legacyAST :: Maybe A.Json } -derive instance eqSourceLevelOutput :: Eq SourceLevelOutput + +derive instance eqSourceLevelOutput :: Eq SourceLevelOutput derive instance ordSourceLevelOutput :: Ord SourceLevelOutput instance decodeJsonSourceLevelOutput :: DecodeJson SourceLevelOutput where @@ -180,7 +190,8 @@ instance decodeJsonSourceLevelOutput :: DecodeJson SourceLevelOutput where -------------------------------------------------- --- "contracts{}{}.evm.{deployedBytecode, bytecode}.object" field of output data BytecodeObject = BytecodeHexString HexString | BytecodeUnlinked String -derive instance eqBytecodeObject :: Eq BytecodeObject + +derive instance eqBytecodeObject :: Eq BytecodeObject derive instance ordBytecodeObject :: Ord BytecodeObject instance decodeJsonBytecodeObject :: DecodeJson BytecodeObject where @@ -194,34 +205,36 @@ mkBytecodeObject s = maybe (BytecodeUnlinked s) BytecodeHexString (mkHexString s unBytecodeObject :: BytecodeObject -> String unBytecodeObject (BytecodeHexString s) = unHex s -unBytecodeObject (BytecodeUnlinked u) = u +unBytecodeObject (BytecodeUnlinked u) = u -------------------------------------------------- --- "contracts{}{}.evm.{deployedBytecode, bytecode}.linkReferences" field of output data LinkReference = LinkReference - { start :: Int + { start :: Int , length :: Int } -derive instance eqLinkReference :: Eq LinkReference + +derive instance eqLinkReference :: Eq LinkReference derive instance ordLinkReference :: Ord LinkReference instance decodeJsonLinkReference :: DecodeJson LinkReference where decodeJson j = do o <- decodeJson j - start <- o .: "start" + start <- o .: "start" length <- o .: "length" pure $ LinkReference { start, length } instance encodeJsonLinkReference :: EncodeJson LinkReference where encodeJson (LinkReference { start, length }) = - "start" := start - ~> "length" := length - ~> jsonEmptyObject + "start" := start + ~> "length" := length + ~> jsonEmptyObject newtype LinkReferences = LinkReferences (FileMapped (ContractMapped (Array LinkReference))) + derive instance newtypeLinkReferences :: Newtype LinkReferences _ -derive newtype instance eqLinkReferences :: Eq LinkReferences +derive newtype instance eqLinkReferences :: Eq LinkReferences derive newtype instance ordLinkReferences :: Ord LinkReferences derive newtype instance decodeJsonLinkReferences :: DecodeJson LinkReferences @@ -233,7 +246,8 @@ newtype BytecodeOutput = BytecodeOutput , sourceMapping :: Maybe (Strung Json) , linkReferences :: Maybe LinkReferences } -derive instance eqBytecodeOutput :: Eq BytecodeOutput + +derive instance eqBytecodeOutput :: Eq BytecodeOutput derive instance ordBytecodeOutput :: Ord BytecodeOutput instance decodeJsonBytecodeOutput :: DecodeJson BytecodeOutput where @@ -248,8 +262,9 @@ instance decodeJsonBytecodeOutput :: DecodeJson BytecodeOutput where -------------------------------------------------- --- "contracts{}{}.evm.methodIdentifiers" field of output newtype MethodIdentifiers = MethodIdentifiers (FO.Object HexString) + derive instance newtypeMethodIdentifiers :: Newtype MethodIdentifiers _ -derive newtype instance eqMethodIdentifiers :: Eq MethodIdentifiers +derive newtype instance eqMethodIdentifiers :: Eq MethodIdentifiers derive newtype instance ordMethodIdentifiers :: Ord MethodIdentifiers derive newtype instance decodeJsonMethodIdentifiers :: DecodeJson MethodIdentifiers @@ -257,7 +272,8 @@ derive newtype instance decodeJsonMethodIdentifiers :: DecodeJson MethodIdentifi --- "contracts{}{}.evm.gasEstimates.*" values of output data GasEstimate = InfiniteGas | GasCount BigNumber -derive instance eqGasEstimate :: Eq GasEstimate + +derive instance eqGasEstimate :: Eq GasEstimate derive instance ordGasEstimate :: Ord GasEstimate instance decodeJsonGasEstimate :: DecodeJson GasEstimate where @@ -266,24 +282,26 @@ instance decodeJsonGasEstimate :: DecodeJson GasEstimate where x -> note (Named "invalid BigNumber" $ UnexpectedValue j) $ GasCount <$> parseBigNumber Int.decimal x newtype GasEstimates = GasEstimates (FO.Object GasEstimate) + derive newtype instance eqGasEstimates :: Eq GasEstimates derive newtype instance ordGasEstimates :: Ord GasEstimates derive newtype instance decodeJsonGasEstimates :: DecodeJson GasEstimates newtype CreationGasEstimates = CreationGasEstimates { codeDepositCost :: Maybe GasEstimate - , executionCost :: Maybe GasEstimate - , totalCost :: Maybe GasEstimate + , executionCost :: Maybe GasEstimate + , totalCost :: Maybe GasEstimate } -derive instance eqCreationGasEstimates :: Eq CreationGasEstimates + +derive instance eqCreationGasEstimates :: Eq CreationGasEstimates derive instance ordCreationGasEstimates :: Ord CreationGasEstimates instance decodeJsonCreationGasEstimates :: DecodeJson CreationGasEstimates where decodeJson j = do o <- decodeJson j codeDepositCost <- o .:? "codeDepositCost" - executionCost <- o .:? "executionCost" - totalCost <- o .:? "totalCost" + executionCost <- o .:? "executionCost" + totalCost <- o .:? "totalCost" pure $ CreationGasEstimates { codeDepositCost, executionCost, totalCost } -------------------------------------------------- @@ -293,6 +311,7 @@ newtype ContractGasEstimates = ContractGasEstimates , external :: Maybe (FO.Object GasEstimate) , internal :: Maybe (FO.Object GasEstimate) } + derive instance eqContractGasEstimates :: Eq ContractGasEstimates derive instance ordContractGasEstimates :: Ord ContractGasEstimates @@ -315,6 +334,7 @@ newtype EvmOutput = EvmOutput , methodIdentifiers :: Maybe MethodIdentifiers , gasEstimates :: Maybe GasEstimates } + derive instance eqEvmOutput :: Eq EvmOutput derive instance ordEvmOutput :: Ord EvmOutput @@ -342,6 +362,7 @@ newtype EwasmOutput = EwasmOutput { wast :: Maybe String , wasm :: Maybe HexString } + derive instance eqEwasmOutput :: Eq EwasmOutput derive instance ordEwasmOutput :: Ord EwasmOutput @@ -364,7 +385,8 @@ newtype ContractLevelOutput = ContractLevelOutput , evm :: Maybe EvmOutput , ewasm :: Maybe EwasmOutput } -derive instance eqContractLevelOutput :: Eq ContractLevelOutput + +derive instance eqContractLevelOutput :: Eq ContractLevelOutput derive instance ordContractLevelOutput :: Ord ContractLevelOutput instance decodeJsonContractLevelOutput :: DecodeJson ContractLevelOutput where @@ -386,13 +408,14 @@ newtype CompilerOutput = CompilerOutput , sources :: FileMapped SourceLevelOutput , contracts :: FileMapped (ContractMapped ContractLevelOutput) } -derive instance eqCompilerOutput :: Eq CompilerOutput + +derive instance eqCompilerOutput :: Eq CompilerOutput derive instance ordCompilerOutput :: Ord CompilerOutput instance decodeJsonCompilerOutput :: DecodeJson CompilerOutput where decodeJson j = do o <- decodeJson j - errors <- o .:? "errors" .!= [] - sources <- o .:? "sources" .!= FO.empty + errors <- o .:? "errors" .!= [] + sources <- o .:? "sources" .!= FO.empty contracts <- o .:? "contracts" .!= FO.empty pure $ CompilerOutput { errors, sources, contracts } diff --git a/src/Language/Solidity/Compiler/Types/Settings.purs b/src/Language/Solidity/Compiler/Types/Settings.purs index 5d84d72..1b5b703 100644 --- a/src/Language/Solidity/Compiler/Types/Settings.purs +++ b/src/Language/Solidity/Compiler/Types/Settings.purs @@ -1,4 +1,4 @@ -module Language.Solidity.Compiler.Types.Settings +module Language.Solidity.Compiler.Types.Settings ( class IsSelection , Remapping(..) , CompilerSettings(..) @@ -43,16 +43,17 @@ import Node.Path (FilePath) --- "remappings" field of "settings" field --- NB: this is a single remapping -data Remapping = GlobalRemapping { to :: FilePath} - | Remapping { from :: FilePath, to :: FilePath } +data Remapping + = GlobalRemapping { to :: FilePath } + | Remapping { from :: FilePath, to :: FilePath } -derive instance eqRemapping :: Eq Remapping +derive instance eqRemapping :: Eq Remapping derive instance ordRemapping :: Ord Remapping instance encodeJsonRemapping :: EncodeJson Remapping where encodeJson = A.fromString <<< case _ of GlobalRemapping g -> ":g=" <> g.to - Remapping r -> r.from <> "=" <> r.to + Remapping r -> r.from <> "=" <> r.to -------------------------------------------------- --- "settings.optimizer.yulDetails" @@ -78,14 +79,14 @@ instance encodeJsonYulOptimizerDetails :: EncodeJson YulOptimizerDetails where --- "settings.optimizer.details" newtype OptimizerDetails = OptimizerDetails - { peephole :: Maybe Boolean - , jumpdestRemove :: Maybe Boolean - , orderLiterals :: Maybe Boolean - , deduplicate :: Maybe Boolean - , cse :: Maybe Boolean + { peephole :: Maybe Boolean + , jumpdestRemove :: Maybe Boolean + , orderLiterals :: Maybe Boolean + , deduplicate :: Maybe Boolean + , cse :: Maybe Boolean , constantOptimizer :: Maybe Boolean - , yul :: Maybe Boolean - , yulDetails :: Maybe YulOptimizerDetails + , yul :: Maybe Boolean + , yulDetails :: Maybe YulOptimizerDetails } derive instance eqOptimizerDetails :: Eq OptimizerDetails @@ -94,14 +95,14 @@ derive instance ordOptimizerDetails :: Ord OptimizerDetails instance decodeJsonOptimizerDetails :: DecodeJson OptimizerDetails where decodeJson j = do o <- decodeJson j - peephole <- o .:! "peephole" - jumpdestRemove <- o .:! "jumpdestRemove" - orderLiterals <- o .:! "orderLiterals" - deduplicate <- o .:! "deduplicate" - cse <- o .:! "cse" + peephole <- o .:! "peephole" + jumpdestRemove <- o .:! "jumpdestRemove" + orderLiterals <- o .:! "orderLiterals" + deduplicate <- o .:! "deduplicate" + cse <- o .:! "cse" constantOptimizer <- o .:! "constantOptimizer" - yul <- o .:! "yul" - yulDetails <- o .:! "yulDetails" + yul <- o .:! "yul" + yulDetails <- o .:! "yulDetails" pure $ OptimizerDetails { peephole , jumpdestRemove @@ -115,22 +116,22 @@ instance decodeJsonOptimizerDetails :: DecodeJson OptimizerDetails where instance encodeJsonOptimizerDetails :: EncodeJson OptimizerDetails where encodeJson (OptimizerDetails o) = - "peephole" :=? o.peephole - ~>? "jumpdestRemove" :=? o.jumpdestRemove - ~>? "orderLiterals" :=? o.orderLiterals - ~>? "deduplicate" :=? o.deduplicate - ~>? "cse" :=? o.cse - ~>? "constantOptimizer" :=? o.constantOptimizer - ~>? "yul" :=? o.yul - ~>? "yulDetails" :=? o.yulDetails - ~>? jsonEmptyObject + "peephole" :=? o.peephole + ~>? "jumpdestRemove" :=? o.jumpdestRemove + ~>? "orderLiterals" :=? o.orderLiterals + ~>? "deduplicate" :=? o.deduplicate + ~>? "cse" :=? o.cse + ~>? "constantOptimizer" :=? o.constantOptimizer + ~>? "yul" :=? o.yul + ~>? "yulDetails" :=? o.yulDetails + ~>? jsonEmptyObject -------------------------------------------------- --- "settings.optimizer" newtype OptimizerSettings = OptimizerSettings { enabled :: Maybe Boolean - , runs :: Maybe Int + , runs :: Maybe Int , details :: Maybe OptimizerDetails } @@ -141,48 +142,49 @@ instance decodeJsonOptimizerSettings :: DecodeJson OptimizerSettings where decodeJson j = do o <- decodeJson j enabled <- o .:! "enabled" - runs <- o .:! "runs" + runs <- o .:! "runs" details <- o .:! "details" pure $ OptimizerSettings { enabled, runs, details } instance encodeJsonOptimizerSettings :: EncodeJson OptimizerSettings where encodeJson (OptimizerSettings o) = - "enabled" :=? o.enabled - ~>? "runs" :=? o.runs - ~>? "details" :=? o.details - ~>? jsonEmptyObject + "enabled" :=? o.enabled + ~>? "runs" :=? o.runs + ~>? "details" :=? o.details + ~>? jsonEmptyObject -------------------------------------------------- --- "settings.evmVersion" -data EvmVersion = Homestead -- at 1150000 - | TangerineWhistle -- aka EIP-150, at 2463000 - | SpuriousDragon -- aka EIP-607, at 2675000 - | Byzantium -- aka EIP-609, at 4370000 - | Constantinople -- aka EIP-1013, at 7280000 - | Petersburg -- aka EIP-1014, at 7280000 +data EvmVersion + = Homestead -- at 1150000 + | TangerineWhistle -- aka EIP-150, at 2463000 + | SpuriousDragon -- aka EIP-607, at 2675000 + | Byzantium -- aka EIP-609, at 4370000 + | Constantinople -- aka EIP-1013, at 7280000 + | Petersburg -- aka EIP-1014, at 7280000 derive instance eqEvmVersion :: Eq EvmVersion derive instance ordEvmVersion :: Ord EvmVersion instance decodeJsonEvmVersion :: DecodeJson EvmVersion where decodeJson j = decodeJson j >>= case _ of - "homestead" -> pure Homestead + "homestead" -> pure Homestead "tangerineWhistle" -> pure TangerineWhistle - "spuriousDragon" -> pure SpuriousDragon - "byzantium" -> pure Byzantium - "constantinople" -> pure Constantinople - "petersburg" -> pure Petersburg - x -> Left $ Named ("Unknown EVM version " <> x) $ UnexpectedValue j + "spuriousDragon" -> pure SpuriousDragon + "byzantium" -> pure Byzantium + "constantinople" -> pure Constantinople + "petersburg" -> pure Petersburg + x -> Left $ Named ("Unknown EVM version " <> x) $ UnexpectedValue j instance encodeJsonEvmVersion :: EncodeJson EvmVersion where encodeJson = A.fromString <<< case _ of - Homestead -> "homestead" + Homestead -> "homestead" TangerineWhistle -> "tangerineWhistle" - SpuriousDragon -> "spuriousDragon" - Byzantium -> "byzantium" - Constantinople -> "constantinople" - Petersburg -> "petersburg" + SpuriousDragon -> "spuriousDragon" + Byzantium -> "byzantium" + Constantinople -> "constantinople" + Petersburg -> "petersburg" -------------------------------------------------- --- "settings.metadata" @@ -191,7 +193,7 @@ newtype MetadataSettings = MetadataSettings { useLiteralContent :: Boolean } -derive instance eqMetadataSettings :: Eq MetadataSettings +derive instance eqMetadataSettings :: Eq MetadataSettings derive instance ordMetadataSettings :: Ord MetadataSettings instance decodeJsonMetadataSettings :: DecodeJson MetadataSettings where @@ -201,7 +203,7 @@ instance decodeJsonMetadataSettings :: DecodeJson MetadataSettings where pure $ MetadataSettings { useLiteralContent } instance encodeJsonMetadataSettings :: EncodeJson MetadataSettings where - encodeJson (MetadataSettings ms) = + encodeJson (MetadataSettings ms) = jsonSingletonObject "useLiteralContent" $ A.fromBoolean ms.useLiteralContent -------------------------------------------------- @@ -209,8 +211,9 @@ instance encodeJsonMetadataSettings :: EncodeJson MetadataSettings where newtype Library = Library { libraryName :: String - , address :: Address + , address :: Address } + derive instance eqLibrary :: Eq Library derive instance ordLibrary :: Ord Library @@ -219,6 +222,7 @@ instance encodeJsonLibrary :: EncodeJson Library where jsonSingletonObject l.libraryName (encodeJson l.address) newtype Libraries = Libraries (FileMapped Library) + derive newtype instance eqLibraries :: Eq Libraries derive newtype instance ordLibraries :: Ord Libraries derive newtype instance encodeJsonLibraries :: EncodeJson Libraries @@ -227,14 +231,15 @@ derive newtype instance encodeJsonLibraries :: EncodeJson Libraries --- "settings.outputSelection" class IsSelection a where - toSelection :: a -> Array String + toSelection :: a -> Array String fromSelection :: Array String -> Maybe a decodeJsonSelection :: forall a. IsSelection a => Json -> Either String a decodeJsonSelection j = do s <- lmap printJsonDecodeError $ decodeJson j - let splits = split (Pattern ".") s - sels = fromSelection splits + let + splits = split (Pattern ".") s + sels = fromSelection splits note ("Unknown output selection \"" <> s <> "\"") sels decodeJsonSelection' :: forall a. IsSelection a => Json -> Either JsonDecodeError a @@ -243,127 +248,138 @@ decodeJsonSelection' j = lmap (\e -> Named e $ UnexpectedValue j) $ decodeJsonSe encodeJsonSelection :: forall a. IsSelection a => a -> Json encodeJsonSelection = fromString <<< joinWith "." <<< toSelection -mapFromSelectionNullable :: forall a b. IsSelection a => (Maybe a -> b) -> Array String -> Maybe b +mapFromSelectionNullable :: forall a b. IsSelection a => (Maybe a -> b) -> Array String -> Maybe b mapFromSelectionNullable f [] = Just (f Nothing) mapFromSelectionNullable f xs = (f <<< Just) <$> fromSelection xs instance isSelectionMaybe :: IsSelection a => IsSelection (Maybe a) where - toSelection Nothing = [] + toSelection Nothing = [] toSelection (Just a) = toSelection a fromSelection [] = Nothing fromSelection xs = fromSelection xs -data FileLevelSelection = AST - | LegacyAST -derive instance eqFileLevelSelection :: Eq FileLevelSelection +data FileLevelSelection + = AST + | LegacyAST + +derive instance eqFileLevelSelection :: Eq FileLevelSelection derive instance ordFileLevelSelection :: Ord FileLevelSelection instance isSelectionFileLevel :: IsSelection FileLevelSelection where - toSelection AST = ["ast"] - toSelection LegacyAST = ["legacyAST"] - - fromSelection ["ast"] = Just AST - fromSelection ["legacyAST"] = Just LegacyAST - fromSelection _ = Nothing - -data EvmBytecodeOutput = BytecodeObject - | BytecodeOpcodes - | BytecodeSourceMap - | BytecodeLinkReferences -derive instance eqEvmBytecodeOutput :: Eq EvmBytecodeOutput + toSelection AST = [ "ast" ] + toSelection LegacyAST = [ "legacyAST" ] + + fromSelection [ "ast" ] = Just AST + fromSelection [ "legacyAST" ] = Just LegacyAST + fromSelection _ = Nothing + +data EvmBytecodeOutput + = BytecodeObject + | BytecodeOpcodes + | BytecodeSourceMap + | BytecodeLinkReferences + +derive instance eqEvmBytecodeOutput :: Eq EvmBytecodeOutput derive instance ordEvmBytecodeOutput :: Ord EvmBytecodeOutput instance isSelectionBytecode :: IsSelection EvmBytecodeOutput where - toSelection BytecodeObject = ["object"] - toSelection BytecodeOpcodes = ["opcodes"] - toSelection BytecodeSourceMap = ["sourceMap"] - toSelection BytecodeLinkReferences = ["linkReferences"] - - fromSelection ["object"] = Just BytecodeObject - fromSelection ["opcodes"] = Just BytecodeOpcodes - fromSelection ["sourceMap"] = Just BytecodeSourceMap - fromSelection ["linkReferences"] = Just BytecodeLinkReferences - fromSelection _ = Nothing - -data EvmOutputSelection = AssemblySelection - | LegacyAssemblySelection - | BytecodeSelection (Maybe EvmBytecodeOutput) - | DeployedBytecodeSelection (Maybe EvmBytecodeOutput) - | MethodIdentifiersSelection - | GasEstimatesSelection -derive instance eqEvmOutputSelection :: Eq EvmOutputSelection + toSelection BytecodeObject = [ "object" ] + toSelection BytecodeOpcodes = [ "opcodes" ] + toSelection BytecodeSourceMap = [ "sourceMap" ] + toSelection BytecodeLinkReferences = [ "linkReferences" ] + + fromSelection [ "object" ] = Just BytecodeObject + fromSelection [ "opcodes" ] = Just BytecodeOpcodes + fromSelection [ "sourceMap" ] = Just BytecodeSourceMap + fromSelection [ "linkReferences" ] = Just BytecodeLinkReferences + fromSelection _ = Nothing + +data EvmOutputSelection + = AssemblySelection + | LegacyAssemblySelection + | BytecodeSelection (Maybe EvmBytecodeOutput) + | DeployedBytecodeSelection (Maybe EvmBytecodeOutput) + | MethodIdentifiersSelection + | GasEstimatesSelection + +derive instance eqEvmOutputSelection :: Eq EvmOutputSelection derive instance ordEvmOutputSelection :: Ord EvmOutputSelection instance isSelectionEvmOutput :: IsSelection EvmOutputSelection where toSelection = case _ of - AssemblySelection -> ["assembly"] - LegacyAssemblySelection -> ["legacyAssembly"] - BytecodeSelection bc -> ["bytecode"] <> toSelection bc - DeployedBytecodeSelection dbc -> ["deployedBytecode"] <> toSelection dbc - MethodIdentifiersSelection -> ["methodIdentifiers"] - GasEstimatesSelection -> ["gasEstimates"] - - fromSelection ["assembly"] = Just AssemblySelection - fromSelection ["legacyAssembly"] = Just LegacyAssemblySelection - fromSelection ["methodIdentifiers"] = Just MethodIdentifiersSelection - fromSelection ["gasEstimates"] = Just GasEstimatesSelection - fromSelection xs = uncons xs >>= \{head, tail} -> case head of - "bytecode" -> mapFromSelectionNullable BytecodeSelection tail + AssemblySelection -> [ "assembly" ] + LegacyAssemblySelection -> [ "legacyAssembly" ] + BytecodeSelection bc -> [ "bytecode" ] <> toSelection bc + DeployedBytecodeSelection dbc -> [ "deployedBytecode" ] <> toSelection dbc + MethodIdentifiersSelection -> [ "methodIdentifiers" ] + GasEstimatesSelection -> [ "gasEstimates" ] + + fromSelection [ "assembly" ] = Just AssemblySelection + fromSelection [ "legacyAssembly" ] = Just LegacyAssemblySelection + fromSelection [ "methodIdentifiers" ] = Just MethodIdentifiersSelection + fromSelection [ "gasEstimates" ] = Just GasEstimatesSelection + fromSelection xs = uncons xs >>= \{ head, tail } -> case head of + "bytecode" -> mapFromSelectionNullable BytecodeSelection tail "deployedBytecode" -> mapFromSelectionNullable DeployedBytecodeSelection tail - _ -> Nothing + _ -> Nothing + +data EwasmOutputSelection + = Wast + | Wasm -data EwasmOutputSelection = Wast - | Wasm -derive instance eqEwasmOutputSelection :: Eq EwasmOutputSelection +derive instance eqEwasmOutputSelection :: Eq EwasmOutputSelection derive instance ordEwasmOutputSelection :: Ord EwasmOutputSelection instance isSelectionEwasmOutput :: IsSelection EwasmOutputSelection where toSelection = case _ of - Wast -> ["wast"] - Wasm -> ["wasm"] - - fromSelection ["wast"] = Just Wast - fromSelection ["wasm"] = Just Wasm - fromSelection _ = Nothing - -data ContractLevelSelection = ABI - | DevDoc - | UserDoc - | Metadata - | IR - | IROptimized - | EvmOutputSelection (Maybe EvmOutputSelection) - | EwasmOutputSelection (Maybe EwasmOutputSelection) -derive instance eqContractLevelSelection :: Eq ContractLevelSelection + Wast -> [ "wast" ] + Wasm -> [ "wasm" ] + + fromSelection [ "wast" ] = Just Wast + fromSelection [ "wasm" ] = Just Wasm + fromSelection _ = Nothing + +data ContractLevelSelection + = ABI + | DevDoc + | UserDoc + | Metadata + | IR + | IROptimized + | EvmOutputSelection (Maybe EvmOutputSelection) + | EwasmOutputSelection (Maybe EwasmOutputSelection) + +derive instance eqContractLevelSelection :: Eq ContractLevelSelection derive instance ordContractLevelSelection :: Ord ContractLevelSelection instance isSelectionContractLevel :: IsSelection ContractLevelSelection where - toSelection ABI = ["abi"] - toSelection DevDoc = ["devdoc"] - toSelection UserDoc = ["userdoc"] - toSelection Metadata = ["metadata"] - toSelection IR = ["ir"] - toSelection IROptimized = ["irOptimized"] - toSelection (EvmOutputSelection o) = ["evm"] <> toSelection o - toSelection (EwasmOutputSelection o) = ["ewasm"] <> toSelection o - - fromSelection ["abi"] = Just ABI - fromSelection ["devdoc"] = Just DevDoc - fromSelection ["userdoc"] = Just UserDoc - fromSelection ["metadata"] = Just Metadata - fromSelection ["ir"] = Just IR - fromSelection ["irOptimized"] = Just IROptimized - fromSelection xs = uncons xs >>= \{head, tail} -> case head of - "evm" -> mapFromSelectionNullable EvmOutputSelection tail + toSelection ABI = [ "abi" ] + toSelection DevDoc = [ "devdoc" ] + toSelection UserDoc = [ "userdoc" ] + toSelection Metadata = [ "metadata" ] + toSelection IR = [ "ir" ] + toSelection IROptimized = [ "irOptimized" ] + toSelection (EvmOutputSelection o) = [ "evm" ] <> toSelection o + toSelection (EwasmOutputSelection o) = [ "ewasm" ] <> toSelection o + + fromSelection [ "abi" ] = Just ABI + fromSelection [ "devdoc" ] = Just DevDoc + fromSelection [ "userdoc" ] = Just UserDoc + fromSelection [ "metadata" ] = Just Metadata + fromSelection [ "ir" ] = Just IR + fromSelection [ "irOptimized" ] = Just IROptimized + fromSelection xs = uncons xs >>= \{ head, tail } -> case head of + "evm" -> mapFromSelectionNullable EvmOutputSelection tail "ewasm" -> mapFromSelectionNullable EwasmOutputSelection tail - _ -> Nothing + _ -> Nothing newtype OutputSelection = OutputSelection - { file :: Array FileLevelSelection + { file :: Array FileLevelSelection , contract :: ContractMapped (Array ContractLevelSelection) } -derive instance eqOutputSelection :: Eq OutputSelection + +derive instance eqOutputSelection :: Eq OutputSelection derive instance ordOutputSelection :: Ord OutputSelection instance decodeJsonOutputSelection :: DecodeJson OutputSelection where @@ -376,13 +392,16 @@ instance decodeJsonOutputSelection :: DecodeJson OutputSelection where instance encodeJsonOutputSelection :: EncodeJson OutputSelection where encodeJson (OutputSelection { file, contract }) = - let fileLevelJson = nub $ encodeJsonSelection <$> file - contractLevelJson = (nub <<< map encodeJsonSelection) <$> contract - allSels = FO.insert "" fileLevelJson contractLevelJson - nonEmptySelections = FO.filter (not <<< null) allSels - in encodeJson nonEmptySelections + let + fileLevelJson = nub $ encodeJsonSelection <$> file + contractLevelJson = (nub <<< map encodeJsonSelection) <$> contract + allSels = FO.insert "" fileLevelJson contractLevelJson + nonEmptySelections = FO.filter (not <<< null) allSels + in + encodeJson nonEmptySelections newtype OutputSelections = OutputSelections (FileMapped OutputSelection) + derive newtype instance encodeJsonOutputSelections :: EncodeJson OutputSelections derive newtype instance eqOutputSelections :: Eq OutputSelections derive newtype instance ordOutputSelections :: Ord OutputSelections @@ -391,11 +410,11 @@ derive newtype instance ordOutputSelections :: Ord OutputSelections --- "settings" newtype CompilerSettings = CompilerSettings - { remappings :: Array Remapping - , optimizer :: Maybe OptimizerSettings - , evmVersion :: Maybe EvmVersion - , metadata :: Maybe MetadataSettings - , libraries :: Maybe Libraries + { remappings :: Array Remapping + , optimizer :: Maybe OptimizerSettings + , evmVersion :: Maybe EvmVersion + , metadata :: Maybe MetadataSettings + , libraries :: Maybe Libraries , outputSelection :: Maybe OutputSelections } @@ -404,10 +423,10 @@ derive instance ordCompilerSettings :: Ord CompilerSettings instance encodeJsonCompilerSettings :: EncodeJson CompilerSettings where encodeJson (CompilerSettings s) = - "remappings" :=? (flattenOptionalArray s.remappings) - ~>? "optimizer" :=? s.optimizer - ~>? "evmVersion" :=? s.evmVersion - ~>? "metadata" :=? s.metadata - ~>? "libraries" :=? s.libraries - ~>? "outputSelection" :=? s.outputSelection - ~>? jsonEmptyObject + "remappings" :=? (flattenOptionalArray s.remappings) + ~>? "optimizer" :=? s.optimizer + ~>? "evmVersion" :=? s.evmVersion + ~>? "metadata" :=? s.metadata + ~>? "libraries" :=? s.libraries + ~>? "outputSelection" :=? s.outputSelection + ~>? jsonEmptyObject diff --git a/test.dhall b/test.dhall new file mode 100644 index 0000000..208fbe6 --- /dev/null +++ b/test.dhall @@ -0,0 +1,6 @@ +let conf = ./spago.dhall + +in conf + // { sources = conf.sources # [ "test/**/*.purs" ] + , dependencies = conf.dependencies # [ "spec", "datetime" ] + } diff --git a/test/Main.purs b/test/Main.purs new file mode 100644 index 0000000..bb1e42b --- /dev/null +++ b/test/Main.purs @@ -0,0 +1,80 @@ +module Test.Main where + +import Prelude + +import Data.Time.Duration (Milliseconds(..)) +import Data.Either (Either(..), isRight) +import Data.Maybe (Maybe(..), fromMaybe, isJust) +import Data.String (Pattern(..), stripPrefix) +import Data.Traversable (for_) +import Effect (Effect) +import Effect.Aff (error, launchAff_, throwError) +import Test.Spec (describe, it, parallel) +import Test.Spec.Assertions (shouldSatisfy) +import Test.Spec.Reporter.Console (consoleReporter) +import Test.Spec.Runner (defaultConfig, runSpec') + +import Language.Solidity.Compiler as Compiler +import Language.Solidity.Compiler.Releases as Releases + +knownCompilers :: Array { version :: String, remote :: String } +knownCompilers = + [ { version: "0.4.26" + , remote: "v0.4.26+commit.4563c3fc" + } + , { version: "0.5.17" + , remote: "v0.5.17+commit.d19bba13" + } + , { version: "0.6.12" + , remote: "v0.6.12+commit.27d51765" + } + , { version: "0.7.6" + , remote: "v0.7.6+commit.7338295f" + } + , { version: "0.8.21" + , remote: "v0.8.21+commit.d9974bed" + } + ] + +compilerVersionMatches :: String -> String -> Boolean +compilerVersionMatches remote compiler = pred + where + cleanup s = fromMaybe s $ stripPrefix (Pattern "v") s + cleanedCompiler = cleanup compiler + cleanedRemote = cleanup remote + pred = isJust $ stripPrefix (Pattern cleanedRemote) cleanedCompiler + +main :: Effect Unit +main = do + let cfg = defaultConfig { timeout = Just (Milliseconds $ 120.0 * 1000.0) } + launchAff_ $ runSpec' cfg [ consoleReporter ] do + parallel $ describe "Releases" do + it "can fetch the release list from the default repo" do + rl <- Releases.getReleaseList Releases.defaultReleaseRepo + rl `shouldSatisfy` isRight + + it "can fetch the latest release from the default repo" do + source <- Releases.getReleaseSource Releases.defaultReleaseRepo "latest" + source `shouldSatisfy` isRight + + parallel $ describe "Compiler" do + it ("can read the version of the default compiler: " <> Compiler.version Compiler.defaultCompiler) do + -- nb: this just shouldn't throw... + pure unit + + parallel $ describe "Known versioned releases" do + for_ knownCompilers $ \{ version, remote } -> do + describe version do + it "can fetch the compiler" do + source <- Releases.getReleaseSource Releases.defaultReleaseRepo version + source `shouldSatisfy` isRight + + it "can use loadRemoteVersion" do + void $ Compiler.loadRemoteVersion remote + + it "can use the fetched compiler" do + Releases.getReleaseSource Releases.defaultReleaseRepo version >>= case _ of + Left err -> throwError $ error err + Right source -> do + compiler <- Compiler.useCompiler source + Compiler.version compiler `shouldSatisfy` (compilerVersionMatches remote)