diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0337ce4..6a25e14 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,7 +6,7 @@ name: CI on: # Triggers the workflow on push or pull request events but only for the master branch push: - branches: [ master, act-amm ] + branches: [ master, hevm-transactions ] pull_request: branches: [ master ] @@ -15,6 +15,40 @@ on: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: + runact: + name: run act tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: cachix/install-nix-action@v20 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: haskell/actions/setup@v1 + with: + ghc-version: '9.2.6' # Exact version of ghc to use + # cabal-version: 'latest'. Omitted, but defaults to 'latest' + enable-stack: true + stack-version: 'latest' + - run: | + cd act + stack test + runevm: + name: run hevm tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: cachix/install-nix-action@v20 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: haskell/actions/setup@v1 + with: + ghc-version: '9.4.5' # Exact version of ghc to use + # cabal-version: 'latest'. Omitted, but defaults to 'latest' + enable-stack: true + stack-version: 'latest' + - run: | + cd evm + stack test runhaskell: name: run tests runs-on: ubuntu-latest # or macOS-latest, or windows-latest @@ -30,5 +64,4 @@ jobs: # cabal-version: 'latest'. Omitted, but defaults to 'latest' enable-stack: true stack-version: 'latest' - - run: stack test - - run: stack run act-exec + - run: stack build diff --git a/ACT.md b/act/ACT.md similarity index 100% rename from ACT.md rename to act/ACT.md diff --git a/Architecture.md b/act/Architecture.md similarity index 100% rename from Architecture.md rename to act/Architecture.md diff --git a/act/EVM/TH.hs b/act/EVM/TH.hs deleted file mode 100644 index 095824c..0000000 --- a/act/EVM/TH.hs +++ /dev/null @@ -1,129 +0,0 @@ -{-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE OverloadedLabels #-} -{-# LANGUAGE OverloadedRecordDot #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TupleSections #-} - -module EVM.TH where - -import Act.Prelude (EthTransaction (..)) -import Control.Monad.Trans.State.Strict (State, put) -import Data.ByteString (ByteString) -import Data.Map as Map -import Data.Text (Text, unpack) -import Data.Text.IO (readFile) -import qualified Data.Tree.Zipper as Zipper -import Data.Vector as Vector (fromList) -import EVM (blankState, initialContract) -import EVM.ABI -import EVM.FeeSchedule -import EVM.Solidity (solcRuntime) -import EVM.Types -import GHC.IO.Unsafe -import Language.Haskell.TH.Syntax as TH -import Prelude hiding (FilePath, readFile) - --- put this in sttate.callData --- run it to execute the transaction --- put more for subsequent calls --- run more for more results -makeCallData :: EthTransaction -> ByteString -makeCallData (EthTransaction _ method args _ _) = - abiMethod method (AbiTuple (Vector.fromList args)) - -emptyVM :: [(Addr, ByteString)] -> VM -emptyVM contracts = - VM - { result = Nothing, - state = blankState, - frames = [], - env = envForContracts contracts, - block = emptyBlock, - tx = emptyTransaction, - logs = [], - traces = Zipper.fromForest mempty, - cache = Cache mempty mempty mempty, - burned = 0, - iterations = mempty, - constraints = [], - keccakEqs = [], - allowFFI = True, - overrideCaller = Nothing - } - where - -- question: Is that a reasonable empty first block? - emptyBlock :: Block - emptyBlock = - Block - { coinbase = 0, - timestamp = Lit 0, - number = 0, - prevRandao = 0, - maxCodeSize = 0, - gaslimit = 0, - baseFee = 0, - schedule = berlin -- specifically this, what is it suppsoed to be? - } - emptyTransaction :: TxState - emptyTransaction = - TxState - { gasprice = 0, - gaslimit = 0, - priorityFee = 0, - origin = 0, - toAddr = 0, - value = Lit 0, - substate = emptySubState, - isCreate = True, - txReversion = mempty - } - emptySubState :: SubState - emptySubState = - SubState - { selfdestructs = [], - touchedAccounts = [], - accessedAddresses = mempty, - accessedStorageKeys = mempty, - refunds = [] - } - - envForContracts :: [(Addr, ByteString)] -> Env - envForContracts contracts = - Env - { contracts = Map.fromList (fmap (fmap bytecodeToContract) contracts), - chainId = 0, - storage = EmptyStore, - origStorage = mempty, - sha3Crack = mempty - } - - bytecodeToContract :: ByteString -> Contract - bytecodeToContract = initialContract . RuntimeCode . ConcreteRuntimeCode - --- setup a new VM state from the list of contracts we are using -loadIntoVM :: [(Addr, ByteString)] -> State VM () -loadIntoVM contracts = put (emptyVM contracts) - --- import a list of contracts as an open game --- - first we read off all the files and translate them into solidity bytecode --- - Then we associate each contract to a contract name which -loadEVM :: [(Text, Text)] -> IO (State VM ()) -loadEVM contracts = do - files :: [(Text, Text)] <- traverse (\(name, filename) -> (name,) <$> readFile (unpack filename)) (contracts) - contracts :: [ByteString] <- - traverse - ( \(nm, body) -> do - Just bytecode <- solcRuntime nm body - pure bytecode - ) - files - let bytecodeMap :: [(Addr, ByteString)] = zip [0 ..] contracts - let newVM = loadIntoVM bytecodeMap - pure newVM - -loadContracts :: [(Text, Text)] -> State VM () -loadContracts arg = unsafePerformIO $ loadEVM arg - -compileTimeLoad :: [(Text, Text)] -> Q [Dec] -compileTimeLoad = undefined diff --git a/act/Examples/AmmGenerated.hs b/act/Examples/AmmGenerated.hs deleted file mode 100644 index b4ebdd9..0000000 --- a/act/Examples/AmmGenerated.hs +++ /dev/null @@ -1,13 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} - -module Examples.AmmGenerated where - -import Act.TH - --- This generates the `ammContract` -$(act2OG "amm.act") - --- $(hevm2OG "contract") --- do something??? diff --git a/act/Examples/EVM.hs b/act/Examples/EVM.hs deleted file mode 100644 index 9e37659..0000000 --- a/act/Examples/EVM.hs +++ /dev/null @@ -1,17 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - -module Examples.EVM where - -import EVM.TH - --- todo: --- - Lending platform, look for aave. aave.sol ?? --- - Implement state into the open game --- - Obtain amm.sol and erc20.sol --- - test arbitrage strategy -blockchainState = - loadContracts - [ ("token1", "ERC20.sol"), - ("token2", "ERC20.sol"), - ("amm", "AMM.sol") - ] diff --git a/act/Main.hs b/act/Main.hs deleted file mode 100644 index 6505c39..0000000 --- a/act/Main.hs +++ /dev/null @@ -1,63 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} - -module Main where - -import Examples.AmmGenerated -import Examples.Player -import OpenGames.Engine.Engine - --- questions: --- - What do we improve in this model next? --- - sandwich? (different example) --- - another "from act" example --- - betting contract from act? --- - betting on the exchange rate of an AMM --- - clockwork finance example --- - move on to token swap --- - What do we automate from Act ? --- - extract name state fields --- - what about rollback? --- - strategy stealing? --- - failing transactions added to the global state? --- - generate players ? --- - gas fees + mem pool + Bribable coordinator --- --- To do in general: --- - Work on the common infrastructure around modelling situations --- - coordinator --- - calling subcontract --- - bribes --- --- Next week: --- - Work on another act example, maybe draw from clockwork finance --- - Create an act program for a full AMM with setup --- - Create an act program for betting --- --- ## 10.03 --- - We have a game with multiple AMM and a way to dispatch transactions --- todo: --- - send multiple transactions and check they are executed correctly --- - game to find which transaction order would optimise the payoff --- - run this for 1 amm --- - Work toward having common state between AMMs --- - same operations but now find how to - -ctx = - StochasticStatefulContext @() - (pure ((), (AmmState 8 10, AmmState 10 8))) - (\_ _ -> return ()) - -ev = evaluate (playerAutomatic 10) ((pureAction 1) :- Nil) ctx - -ctx1 = - StochasticStatefulContext @() - (pure ((), (AmmState 10 10))) - (\_ _ -> return ()) - -ev1 = evaluate (swapSequence) ((pureAction (reverse (allTransactionSwap))) :- Nil) ctx1 - -main :: IO () -main = putStrLn "hello Act" diff --git a/act/README.md b/act/README.md new file mode 100644 index 0000000..babe600 --- /dev/null +++ b/act/README.md @@ -0,0 +1,4 @@ +# Act integration of open games + +This project implements the integration of open games with the ACT project. The goal is to be able to +perform game-theoretic analysis of contracts from an act specification. See the test for some examples. diff --git a/amm.act b/act/act-programs/amm.act similarity index 100% rename from amm.act rename to act/act-programs/amm.act diff --git a/simple.act b/act/act-programs/simple.act similarity index 100% rename from simple.act rename to act/act-programs/simple.act diff --git a/act/golden/ev.golden b/act/golden/ev.golden new file mode 100644 index 0000000..5bd4b20 --- /dev/null +++ b/act/golden/ev.golden @@ -0,0 +1,12 @@ +----Analytics begin---- +Player: Marx +Optimal Move: 10 +Current Strategy: fromFreqs [(1,1.0)] +Optimal Payoff: 1.157920892373162e77 +Current Payoff: 1.157920892373162e77 +Observable State: () +Unobservable State: "((),(AmmState {reserve0 = 8, reserve1 = 10},AmmState {reserve0 = 10, reserve1 = 8}))" + --other game-- + --No more information-- + NEWGAME: +----Analytics end---- diff --git a/act/golden/ev1.golden b/act/golden/ev1.golden new file mode 100644 index 0000000..254ff1b --- /dev/null +++ b/act/golden/ev1.golden @@ -0,0 +1,12 @@ +----Analytics begin---- +Player: Marx +Optimal Move: [Transaction {contract = "amm1", method = "swap1", arguments = [5]},Transaction {contract = "amm2", method = "swap0", arguments = [10]}] +Current Strategy: fromFreqs [([Transaction {contract = "amm2", method = "swap0", arguments = [10]},Transaction {contract = "amm1", method = "swap1", arguments = [5]}],1.0)] +Optimal Payoff: 4.0 +Current Payoff: 2.0 +Observable State: () +Unobservable State: "((),AmmState {reserve0 = 10, reserve1 = 10})" + --other game-- + --No more information-- + NEWGAME: +----Analytics end---- diff --git a/act/golden/foo.golden b/act/golden/foo.golden new file mode 100644 index 0000000..079afc2 --- /dev/null +++ b/act/golden/foo.golden @@ -0,0 +1,12 @@ +----Analytics begin---- +Player: Marx +Optimal Move: [Transaction {contract = "amm1", method = "swap0", arguments = [10]},Transaction {contract = "amm2", method = "swap1", arguments = [10]}] +Current Strategy: fromFreqs [([Transaction {contract = "amm1", method = "swap0", arguments = [0]},Transaction {contract = "amm2", method = "swap1", arguments = [0]}],1.0)] +Optimal Payoff: 102.0 +Current Payoff: 101.0 +Observable State: () +Unobservable State: "(((),()),())" + --other game-- + --No more information-- + NEWGAME: +----Analytics end---- diff --git a/act/open-games-act.cabal b/act/open-games-act.cabal new file mode 100644 index 0000000..6a69c9d --- /dev/null +++ b/act/open-games-act.cabal @@ -0,0 +1,81 @@ +cabal-version: 1.12 + +-- This file has been generated from package.yaml by hpack version 0.36.0. +-- +-- see: https://github.com/sol/hpack + +name: open-games-act +version: 0.1.0.0 +synopsis: Act intgration of open games +category: Math +author: Jules Hedges, André Videla, Philipp Zahn & other contributors +maintainer: philipp.zahn@protonmail +copyright: Jules Hedges, André Videla, Philipp Zahn & other contributors +license: AGPL +build-type: Simple +extra-source-files: + README.md + +library + exposed-modules: + Act.Prelude + Act.TH + Act.Utils + Act.Execution + Act.TH.Extractor + Act.TH.State + Examples.Player + other-modules: + Act + Paths_open_games_act + hs-source-dirs: + src + build-depends: + act + , base >=4.7 && <5 + , bytestring + , containers + , data-dword + , hevm + , open-games-hs + , optics + , optics-core + , optics-extra + , rosezipper + , tasty + , tasty-golden + , template-haskell + , text + , transformers + , validation + , vector + default-language: Haskell2010 + +test-suite hevm-tests + type: exitcode-stdio-1.0 + main-is: Spec.hs + other-modules: + Paths_open_games_act + hs-source-dirs: + tests + build-depends: + act + , base >=4.7 && <5 + , bytestring + , containers + , data-dword + , hevm + , open-games-act + , open-games-hs + , optics + , optics-core + , optics-extra + , rosezipper + , tasty + , tasty-golden + , template-haskell + , text + , transformers + , validation + , vector + default-language: Haskell2010 diff --git a/act/package.yaml b/act/package.yaml new file mode 100644 index 0000000..009e71e --- /dev/null +++ b/act/package.yaml @@ -0,0 +1,54 @@ +name: open-games-act +version: '0.1.0.0' +synopsis: Act intgration of open games +category: Math +author: Jules Hedges, André Videla, Philipp Zahn & other contributors +maintainer: philipp.zahn@protonmail +copyright: Jules Hedges, André Videla, Philipp Zahn & other contributors +license: AGPL +extra-source-files: +- README.md + + +library: + source-dirs: src + exposed-modules: + - Act.Prelude + - Act.TH + - Act.Utils + - Act.Execution + - Act.TH.Extractor + - Act.TH.State + - Examples.Player + +dependencies: + - base >=4.7 && <5 + - bytestring + - containers + - open-games-hs + - data-dword + - text + - optics + - optics-extra + - optics-core + - template-haskell + - transformers + - vector + - rosezipper + - act + - hevm + - tasty + - tasty-golden + - validation + +tests: + hevm-tests: + main: Spec.hs + source-dirs: tests + dependencies: + - open-games-act + - tasty + - tasty-golden + - bytestring + + diff --git a/act/Act.hs b/act/src/Act.hs similarity index 100% rename from act/Act.hs rename to act/src/Act.hs diff --git a/act/Act/Execution.hs b/act/src/Act/Execution.hs similarity index 94% rename from act/Act/Execution.hs rename to act/src/Act/Execution.hs index ea6e786..ab02c40 100644 --- a/act/Act/Execution.hs +++ b/act/src/Act/Execution.hs @@ -31,3 +31,7 @@ combine contracts (t : ts) globalState = Just trans -> let newState = trans globalState t in combine contracts ts newState Nothing -> error ("got illegal transaction " ++ show t) combine _ [] st = error "we were not given any transactions" + +class ExecEnv st where + send :: Transaction -> st -> st + run :: st -> st diff --git a/act/Act/Prelude.hs b/act/src/Act/Prelude.hs similarity index 57% rename from act/Act/Prelude.hs rename to act/src/Act/Prelude.hs index 54dd444..dd17816 100644 --- a/act/Act/Prelude.hs +++ b/act/src/Act/Prelude.hs @@ -2,12 +2,13 @@ {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE NoFieldSelectors #-} -module Act.Prelude (Word256, EthTransaction (..), Transaction (..), AbiType (..), AbiValue (..)) where +module Act.Prelude (Word256, Transaction (..), AbiType (..), AbiValue (..)) where import Data.DoubleWord (Word256) import Data.Text import EVM.ABI (AbiType (..), AbiValue (..)) import EVM.Types (Addr, W256) +import GHC.Word data Transaction = Transaction { contract :: String, @@ -15,12 +16,3 @@ data Transaction = Transaction arguments :: [AbiValue] } deriving (Eq, Show, Ord) - -data EthTransaction = EthTransaction - { contract :: Addr, - method :: Text, - arguments :: [AbiValue], - ethAmt :: W256, - gas :: W256 - } - deriving (Eq, Show, Ord) diff --git a/act/Act/TH.hs b/act/src/Act/TH.hs similarity index 99% rename from act/Act/TH.hs rename to act/src/Act/TH.hs index 1e4a680..c799a1e 100644 --- a/act/Act/TH.hs +++ b/act/src/Act/TH.hs @@ -1,4 +1,5 @@ {-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} @@ -12,6 +13,7 @@ import Act.TH.State import Act.Utils import CLI import Data.List +import Data.Text (Text) import Data.Validation import Error import GHC.IO.Unsafe diff --git a/act/Act/TH/Extractor.hs b/act/src/Act/TH/Extractor.hs similarity index 99% rename from act/Act/TH/Extractor.hs rename to act/src/Act/TH/Extractor.hs index 5f41e2f..2f474bb 100644 --- a/act/Act/TH/Extractor.hs +++ b/act/src/Act/TH/Extractor.hs @@ -11,8 +11,6 @@ import Data.List import Language.Haskell.TH.Syntax as TH import Syntax.Annotated -deriving instance Data AbiType - -- Given each method in the contract we need to know how to extract the arguments from the -- arguments' array. for this we create a partial top-level function which matches -- on the argument array and return the correct number of argument in its expected type diff --git a/act/Act/TH/State.hs b/act/src/Act/TH/State.hs similarity index 96% rename from act/Act/TH/State.hs rename to act/src/Act/TH/State.hs index 4b5bfc2..32e7f41 100644 --- a/act/Act/TH/State.hs +++ b/act/src/Act/TH/State.hs @@ -6,6 +6,7 @@ import Act.Utils import qualified Data.Map as M import Language.Haskell.TH.Syntax import Syntax.Annotated (Id, SlotType, Store) +import Syntax.TimeAgnostic -- Generate a type for the global state of the contract stateDec4Interface :: Store -> [Dec] diff --git a/act/Act/Utils.hs b/act/src/Act/Utils.hs similarity index 100% rename from act/Act/Utils.hs rename to act/src/Act/Utils.hs diff --git a/act/Examples/Player.hs b/act/src/Examples/Player.hs similarity index 62% rename from act/Examples/Player.hs rename to act/src/Examples/Player.hs index c2d0fc3..fb117dd 100644 --- a/act/Examples/Player.hs +++ b/act/src/Examples/Player.hs @@ -7,10 +7,11 @@ module Examples.Player where import Act import Data.List -import Examples.AmmGenerated import OpenGames.Engine.Engine import OpenGames.Preprocessor +$(act2OG "act-programs/amm.act") + -- This combines two contracts with non-shared state twoAmms = combine (unionContracts ("amm1", ammContract) ("amm2", ammContract)) @@ -21,10 +22,13 @@ bigPayoff finalUSD initialUSD swappedUSD = finalUSD + initialUSD - swappedUSD swap0 :: Word256 -> Transaction -swap0 d = Transaction "" "swap0" [AbiUInt 64 d] +swap0 d = Transaction "amm1" "swap0" [AbiUInt 64 d] swap1 :: Word256 -> Transaction -swap1 d = Transaction "" "swap1" [AbiUInt 64 d] +swap1 d = Transaction "amm2" "swap1" [AbiUInt 64 d] + +bundles :: Word256 -> [[Transaction]] +bundles swapLimit = [[swap0 n, swap1 n] | n <- [0, 1 .. swapLimit]] diffEur :: AmmState -> AmmState -> Word256 diffEur (AmmState old _) (AmmState new _) = new - old @@ -99,6 +103,53 @@ swapSequence = returns : ; |] --- test out 2 erc contracts --- test out multi-contract calls --- +initSendAndRun x = twoAmms x (AmmState 50 50, AmmState 50 50) + +balance :: (AmmState, AmmState) -> String -> Double +balance (st1, st2) _ = fromIntegral (reserve0 st1 + reserve0 st2) + +actDecision name strategies = + [opengame| inputs : observedInput ; + :---: + + inputs : observedInput ; + operation : dependentDecision name (const strategies) ; + outputs : tx ; + returns : balance finalState name ; + + :---: + outputs : tx ; + returns : finalState; +|] + +append = (++) + +runBlockchain = + [opengame| + inputs : ; + :---: + + operation : actDecision "Marx" (bundles 10) ; + outputs : allTx ; + returns : finalState ; + + inputs : allTx ; + operation : fromFunctions (initSendAndRun) id ; + outputs : finalState ; +|] + +foo = evaluate runBlockchain (Kleisli (const (pure [swap0 0, swap1 0])) :- Nil) void + +ctx = + StochasticStatefulContext @() + (pure ((), (AmmState 8 10, AmmState 10 8))) + (\_ _ -> return ()) + +ev = evaluate (playerAutomatic 10) ((pureAction 1) :- Nil) ctx + +ctx1 = + StochasticStatefulContext @() + (pure ((), (AmmState 10 10))) + (\_ _ -> return ()) + +ev1 = evaluate swapSequence ((pureAction (reverse (allTransactionSwap))) :- Nil) ctx1 diff --git a/act/stack.yaml b/act/stack.yaml new file mode 100644 index 0000000..d87e45a --- /dev/null +++ b/act/stack.yaml @@ -0,0 +1,96 @@ +# This file was automatically generated by 'stack init' +# +# Some commonly used options have been documented as comments in this file. +# For advanced use and comprehensive documentation of the format, please see: +# https://docs.haskellstack.org/en/stable/yaml_configuration/ + +# Resolver to choose a 'specific' stackage snapshot or a compiler version. +# A snapshot resolver dictates the compiler version and the set of packages +# to be used for project dependencies. For example: +# +# resolver: lts-3.5 +# resolver: nightly-2015-09-21 +# resolver: ghc-7.10.2 +# +# The location of a snapshot can be provided as a file or url. Stack assumes +# a snapshot provided as a file might change, whereas a url resource does not. +# +# resolver: ./custom-snapshot.yaml +# resolver: https://example.com/snapshots/2018-01-01.yaml +resolver: lts-20.12 + +# User packages to be built. +# Various formats can be used as shown in the example below. +# +# packages: +# - some-directory +# - https://example.com/foo/bar/baz-0.0.2.tar.gz +# subdirs: +# - auto-update +# - wai +packages: +- . +- .. +# Dependency packages to be pulled from upstream that are not in the resolver. +# These entries can reference officially published versions as well as +# forks / in-progress versions pinned to a git hash. For example: +# +extra-deps: + - lens-5.2.3@sha256:637287c76adff383063b3206a4213640de1a74839ec16008cc71b5b407f7d05e,15237 + - linear-1.22@sha256:6402f0140156d2792ca0cbef3f5af2ec3675660e381574ac968cd5884724b8ba,4132 + - vty-5.38@sha256:c3b46c8072f92b61dad09dac45ede37f1a093836e14ac84ece6515a4d3222864,5628 + - HSH-2.1.3@sha256:71ded11b224f5066373ce985ec63b10c87129850b33916736dd64fa2bea9ea0a,1705 + - monad-bayes-1.2.0@sha256:bf83cf8e6d163c461b9964dbff48d4476b2a82421962af317b3a3d0d738ea2a2,6456 + - poly-0.5.1.0@sha256:8c574f017913982fd70567545bec423823ff5bdf236c19d1443f3af47e0218ba,3418 + - restless-git-0.7@sha256:346a5775a586f07ecb291036a8d3016c3484ccdc188b574bcdec0a82c12db293,968 + - smt2-parser-0.1.0.1@sha256:1e1a4565915ed851c13d1e6b8bb5185cf5d454da3b43170825d53e221f753d77,1421 + - spawn-0.3@sha256:b91e01d8f2b076841410ae284b32046f91471943dc799c1af77d666c72101f02,1162 + - spool-0.1@sha256:77780cbfc2c0be23ff2ea9e474062f3df97fcd9db946ee0b3508280a923b83e2,1461 + - vector-sized-1.5.0@sha256:1c85b70dbfe6fbdcc58d2706c626e6e38d1900276bbb6f1ba6cfee83941f45e6,1836 + - hspec-2.11.7@sha256:2869580a2a29e7beb6268ea3dc561583f4ae229ed1f47fb1c92e8c09ce35acec,1763 + - validation-1.1.3@sha256:e2496d01639d5b8ab1dcf4b1dcf93e3d1380b6f793c4eef18ffc082eead17cf0,3573 + - hspec-core-2.11.7@sha256:90d8873356d7e15f843bc523360e206e8e356ff6b82a1fa4b3889dc31d073ea1,6814 + - hspec-discover-2.11.7@sha256:6307eb16d308258a99a242025df50217d835ba0a3f205b1202a100a175877b38,2169 + - hspec-expectations-0.8.4@sha256:4237f094a7931202ff57ac6475542b0b314b50a7024550e2b6eb87cfb0d4ff93,1702 + + - git: https://github.com/ethereum/hevm + commit: c5e40507c35e6f2119aeb5f0740dc3564100ebdc + - git: https://github.com/ethereum/act.git + subdirs: + - src + commit: 52e99daf3121a4e6a6cb28255e862cf8e83cf4cd + + + + # +# +# Override default flag values for local packages and extra-deps +# flags: {} + +# Extra package databases containing global packages +# extra-package-dbs: [] + +# Control whether we use the GHC we find on the path +system-ghc: true +# +# Require a specific version of stack, using version ranges +# require-stack-version: -any # Default +# require-stack-version: ">=2.1" +# +# Override the architecture used by stack, especially useful on Windows +# Change to x86_64 for Intel and aarch64 for ARM/apple silicon +arch: x86_64 +# +# Extra directories used by stack for building +# extra-include-dirs: [/path/to/dir] +# extra-lib-dirs: [/path/to/dir] +# +# Allow a newer minor version of GHC than the snapshot specifies +# compiler-check: newer-minor +# +ghc-options: + "$locals": -fwarn-incomplete-patterns + +nix: + enable: true + packages: [libff, secp256k1, zlib, solc] diff --git a/act/stack.yaml.lock b/act/stack.yaml.lock new file mode 100644 index 0000000..97de6ae --- /dev/null +++ b/act/stack.yaml.lock @@ -0,0 +1,148 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: +- completed: + hackage: lens-5.2.3@sha256:637287c76adff383063b3206a4213640de1a74839ec16008cc71b5b407f7d05e,15237 + pantry-tree: + sha256: 2a69ee338ef79da9aaa0d406f8307b11e3b8f969aa8fc28e46ca250c1f6ef40d + size: 8351 + original: + hackage: lens-5.2.3@sha256:637287c76adff383063b3206a4213640de1a74839ec16008cc71b5b407f7d05e,15237 +- completed: + hackage: linear-1.22@sha256:6402f0140156d2792ca0cbef3f5af2ec3675660e381574ac968cd5884724b8ba,4132 + pantry-tree: + sha256: 8ccd6646c1a5f2f7998a19a04f00200e44ee08186e980153ec371d6d114eb0e6 + size: 2042 + original: + hackage: linear-1.22@sha256:6402f0140156d2792ca0cbef3f5af2ec3675660e381574ac968cd5884724b8ba,4132 +- completed: + hackage: vty-5.38@sha256:c3b46c8072f92b61dad09dac45ede37f1a093836e14ac84ece6515a4d3222864,5628 + pantry-tree: + sha256: b413c72281bb0f48eb0e4b45e272e3a18036ea91b4850315e8c2487ef719f9f3 + size: 3499 + original: + hackage: vty-5.38@sha256:c3b46c8072f92b61dad09dac45ede37f1a093836e14ac84ece6515a4d3222864,5628 +- completed: + hackage: HSH-2.1.3@sha256:71ded11b224f5066373ce985ec63b10c87129850b33916736dd64fa2bea9ea0a,1705 + pantry-tree: + sha256: 95f49f9dad6e4976d1b53c59fd4405a978ca8baecc721d508a030615241d69be + size: 473 + original: + hackage: HSH-2.1.3@sha256:71ded11b224f5066373ce985ec63b10c87129850b33916736dd64fa2bea9ea0a,1705 +- completed: + hackage: monad-bayes-1.2.0@sha256:bf83cf8e6d163c461b9964dbff48d4476b2a82421962af317b3a3d0d738ea2a2,6456 + pantry-tree: + sha256: ce4a9db9888b589ae56493862ae62f2efb5af8d17d3d55bac0b3f59177881b2b + size: 3823 + original: + hackage: monad-bayes-1.2.0@sha256:bf83cf8e6d163c461b9964dbff48d4476b2a82421962af317b3a3d0d738ea2a2,6456 +- completed: + hackage: poly-0.5.1.0@sha256:8c574f017913982fd70567545bec423823ff5bdf236c19d1443f3af47e0218ba,3418 + pantry-tree: + sha256: e59a1e3809fee49968cf9505edf849109d733e4e795b49e2d5fdef1a4993c31d + size: 2531 + original: + hackage: poly-0.5.1.0@sha256:8c574f017913982fd70567545bec423823ff5bdf236c19d1443f3af47e0218ba,3418 +- completed: + hackage: restless-git-0.7@sha256:346a5775a586f07ecb291036a8d3016c3484ccdc188b574bcdec0a82c12db293,968 + pantry-tree: + sha256: 26b37a66c08215e18a914600aae8a61a6ba4611243a0b31ea27437d6c83701cb + size: 269 + original: + hackage: restless-git-0.7@sha256:346a5775a586f07ecb291036a8d3016c3484ccdc188b574bcdec0a82c12db293,968 +- completed: + hackage: smt2-parser-0.1.0.1@sha256:1e1a4565915ed851c13d1e6b8bb5185cf5d454da3b43170825d53e221f753d77,1421 + pantry-tree: + sha256: c048b3037a35ab6ca5b33d865d7a0b0f56a0ccc942dd57cbbab6af380770ee13 + size: 447 + original: + hackage: smt2-parser-0.1.0.1@sha256:1e1a4565915ed851c13d1e6b8bb5185cf5d454da3b43170825d53e221f753d77,1421 +- completed: + hackage: spawn-0.3@sha256:b91e01d8f2b076841410ae284b32046f91471943dc799c1af77d666c72101f02,1162 + pantry-tree: + sha256: 3fa87961ef3166c0093ebae68dea83cf2d7b9e131a2db28687b696f077c6f81a + size: 262 + original: + hackage: spawn-0.3@sha256:b91e01d8f2b076841410ae284b32046f91471943dc799c1af77d666c72101f02,1162 +- completed: + hackage: spool-0.1@sha256:77780cbfc2c0be23ff2ea9e474062f3df97fcd9db946ee0b3508280a923b83e2,1461 + pantry-tree: + sha256: 48eada528a8eda2fcf0d3517a239c59a699acff96111f427833ba2b04bd6111f + size: 322 + original: + hackage: spool-0.1@sha256:77780cbfc2c0be23ff2ea9e474062f3df97fcd9db946ee0b3508280a923b83e2,1461 +- completed: + hackage: vector-sized-1.5.0@sha256:1c85b70dbfe6fbdcc58d2706c626e6e38d1900276bbb6f1ba6cfee83941f45e6,1836 + pantry-tree: + sha256: 026c260a2c1c6b2deab166dcbeb8888eb284d7e9e73ab837d88d530933929588 + size: 1302 + original: + hackage: vector-sized-1.5.0@sha256:1c85b70dbfe6fbdcc58d2706c626e6e38d1900276bbb6f1ba6cfee83941f45e6,1836 +- completed: + hackage: hspec-2.11.7@sha256:2869580a2a29e7beb6268ea3dc561583f4ae229ed1f47fb1c92e8c09ce35acec,1763 + pantry-tree: + sha256: f241a7710ebee50583f9aebff61aaf4d36619ba7d6538b39b80ee4695c70552e + size: 584 + original: + hackage: hspec-2.11.7@sha256:2869580a2a29e7beb6268ea3dc561583f4ae229ed1f47fb1c92e8c09ce35acec,1763 +- completed: + hackage: validation-1.1.3@sha256:e2496d01639d5b8ab1dcf4b1dcf93e3d1380b6f793c4eef18ffc082eead17cf0,3573 + pantry-tree: + sha256: 5568b8f135d2c0a3d28813d48f254fca228b4cdec2996ff409717b6a40022164 + size: 343 + original: + hackage: validation-1.1.3@sha256:e2496d01639d5b8ab1dcf4b1dcf93e3d1380b6f793c4eef18ffc082eead17cf0,3573 +- completed: + hackage: hspec-core-2.11.7@sha256:90d8873356d7e15f843bc523360e206e8e356ff6b82a1fa4b3889dc31d073ea1,6814 + pantry-tree: + sha256: 8db12c1f6965d9f0898f04d7b5f1d77682c8ab2d5c394c4a431229df7c4acb14 + size: 6231 + original: + hackage: hspec-core-2.11.7@sha256:90d8873356d7e15f843bc523360e206e8e356ff6b82a1fa4b3889dc31d073ea1,6814 +- completed: + hackage: hspec-discover-2.11.7@sha256:6307eb16d308258a99a242025df50217d835ba0a3f205b1202a100a175877b38,2169 + pantry-tree: + sha256: 141b4987d519ad1ca1114737f510f20adc2456bf44c040f41a63792f47d009eb + size: 829 + original: + hackage: hspec-discover-2.11.7@sha256:6307eb16d308258a99a242025df50217d835ba0a3f205b1202a100a175877b38,2169 +- completed: + hackage: hspec-expectations-0.8.4@sha256:4237f094a7931202ff57ac6475542b0b314b50a7024550e2b6eb87cfb0d4ff93,1702 + pantry-tree: + sha256: 87681840d430b84686f83f1ab8b5873b09c349775698665233443914acf9ba2b + size: 741 + original: + hackage: hspec-expectations-0.8.4@sha256:4237f094a7931202ff57ac6475542b0b314b50a7024550e2b6eb87cfb0d4ff93,1702 +- completed: + commit: c5e40507c35e6f2119aeb5f0740dc3564100ebdc + git: https://github.com/ethereum/hevm + name: hevm + pantry-tree: + sha256: 92c4d9ca0864a6d334f4940b558b61daa7dc67ddfc145a6599a035abc31e8153 + size: 5802 + version: 0.51.3 + original: + commit: c5e40507c35e6f2119aeb5f0740dc3564100ebdc + git: https://github.com/ethereum/hevm +- completed: + commit: 52e99daf3121a4e6a6cb28255e862cf8e83cf4cd + git: https://github.com/ethereum/act.git + name: act + pantry-tree: + sha256: 78cc4643860657a2be70ba2f95d675f130c312800393a1cb52c88ec6701ce7bd + size: 1217 + subdir: src + version: 0.1.0.0 + original: + commit: 52e99daf3121a4e6a6cb28255e862cf8e83cf4cd + git: https://github.com/ethereum/act.git + subdir: src +snapshots: +- completed: + sha256: af5d667f6096e535b9c725a72cffe0f6c060e0568d9f9eeda04caee70d0d9d2d + size: 649133 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/12.yaml + original: lts-20.12 diff --git a/act/tests/Spec.hs b/act/tests/Spec.hs new file mode 100644 index 0000000..950b260 --- /dev/null +++ b/act/tests/Spec.hs @@ -0,0 +1,27 @@ +import Data.ByteString.Lazy.Char8 (pack) +import Examples.Player +import OpenGames.Engine.Diagnostics (generateOutputStr) +import Test.Tasty +import Test.Tasty.Golden + +main :: IO () +main = + defaultMain $ + testGroup + "ACT tests" + [ goldenVsStringDiff + "Should find the best transaction" + (\ref new -> ["git", "diff", "--no-index", ref, new]) + "golden/foo.golden" + (pure $ pack $ generateOutputStr foo), + goldenVsStringDiff + "Should find the best swaps between two AMMs" + (\ref new -> ["git", "diff", "--no-index", ref, new]) + "golden/ev.golden" + (pure $ pack $ generateOutputStr ev), + goldenVsStringDiff + "Should find the optimal transaction order" + (\ref new -> ["git", "diff", "--no-index", ref, new]) + "golden/ev1.golden" + (pure $ pack $ generateOutputStr ev1) + ] diff --git a/evm/README.md b/evm/README.md new file mode 100644 index 0000000..bd422c4 --- /dev/null +++ b/evm/README.md @@ -0,0 +1,13 @@ +## Open-games HEVM integration + +This project integrates open games with HEVM, an execution environement for EVM bytecode. + +This allows the game-theoretic analysis of smart contracts + +## How to build lido contracts + +- run `nix develop .` in the parent directory, this will take care of GHC, Stack and solidity. +- copy the content of the contracts/ directory from the lido project (https://github.com/lidofinance/dual-governance/tree/main/contracts) into here (the evm/ directory of the open games project). +- download the openzepplin dependency with `npm install openzeppelin`. +- copy the contracts from openzeppelin into a `@openzeppelin` folder in here. The path should be `evm/@openzeppelin`. The following command should work `cp -r node_modules/openzeppelin/ @openzeppelin`. +- build the project with `stack build`, you can inspect the imported functions with `stack repl` and using `:browse` after importing the lido module from the examples. diff --git a/evm/flake.lock b/evm/flake.lock new file mode 100644 index 0000000..8d980f7 --- /dev/null +++ b/evm/flake.lock @@ -0,0 +1,93 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1732238832, + "narHash": "sha256-sQxuJm8rHY20xq6Ah+GwIUkF95tWjGRd1X8xF+Pkk38=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8edf06bea5bcbee082df1b7369ff973b91618b8d", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "solc": "solc" + } + }, + "solc": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": [ + "nixpkgs" + ], + "solc-macos-amd64-list-json": "solc-macos-amd64-list-json" + }, + "locked": { + "lastModified": 1731758759, + "narHash": "sha256-NX4+V6Q8bwopah0oza/Dpf6UsYNGbokW2kE9qT3wdHY=", + "owner": "hellwolf", + "repo": "solc.nix", + "rev": "0714c24cd521b9eb3ee435818c5d743ac6179176", + "type": "github" + }, + "original": { + "owner": "hellwolf", + "repo": "solc.nix", + "type": "github" + } + }, + "solc-macos-amd64-list-json": { + "flake": false, + "locked": { + "narHash": "sha256-KBEEpcDeKtVvCeguRP0D499yg9O5Jef9Nxn3yfrmw9g=", + "type": "file", + "url": "https://github.com/ethereum/solc-bin/raw/67f45d8/macosx-amd64/list.json" + }, + "original": { + "type": "file", + "url": "https://github.com/ethereum/solc-bin/raw/67f45d8/macosx-amd64/list.json" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/evm/flake.nix b/evm/flake.nix new file mode 100644 index 0000000..c8aa7f9 --- /dev/null +++ b/evm/flake.nix @@ -0,0 +1,24 @@ +{ + inputs = { + solc = { + url = "github:hellwolf/solc.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, solc }: let + pkgs = import nixpkgs { + system = "aarch64-darwin"; + overlays = [ + solc.overlay + ]; + }; + in { + devShell.aarch64-darwin = with pkgs; mkShell { + buildInputs = [ + (solc.mkDefault pkgs solc_0_8_26) + ]; + }; + }; +} + diff --git a/evm/golden/hevm.golden b/evm/golden/hevm.golden new file mode 100644 index 0000000..47c6c5d --- /dev/null +++ b/evm/golden/hevm.golden @@ -0,0 +1,12 @@ +----Analytics begin---- +Player: AllPlayers +Optimal Move: EthTransaction {contract = LitAddr 0x1000, CAlLEr = LItAddr 0x1234, METhoD = "REtRIEvE(uiNt256)", ARgUments = [2], ethAmt = 0x0, gas = 100000000} +Current Strategy: fromFreqs [(EthTransaction {contract = LitAddr 0x1000, CALLer = LITAdDr 0x1234, METHoD = "retRiEVe(UiNt256)", ARgUments = [1], ethAmt = 0x0, gas = 100000000},1.0)] +Optimal Payoff: 1.000000002e9 +Current Payoff: 1.000000001e9 +Observable State: () +Unobservable State: "()" + --other game-- + --No more information-- + NEWGAME: +----Analytics end---- diff --git a/evm/golden/prisoner.golden b/evm/golden/prisoner.golden new file mode 100644 index 0000000..4da9d15 --- /dev/null +++ b/evm/golden/prisoner.golden @@ -0,0 +1,92 @@ +----Analytics begin---- +Player: player1 +Optimal Move: EthTransaction {contract = LitAddr 0x1000, CALlEr = LItAddR 0x1234, METHOd = "dEfECT()", ArGuMEntS = [], ethAmt = 0x0, gas = 10000000} +Current Strategy: fromFreqs [(EthTransaction {contract = LitAddr 0x1000, callER = LITADDr 0X1234, MetHOD = "cooperATE()", ArGUmEntS = [], ethAmt = 0x0, gas = 10000000},1.0)] +Optimal Payoff: 1.000006e9 +Current Payoff: 1.000004e9 +Observable State: () +Unobservable State: "()" + --other game-- + --No more information-- + NEWGAME: + +Player: player2 +Optimal Move: EthTransaction {contract = LitAddr 0x1000, cAlLeR = LiTADdR 0x1235, mEtHOD = "DEfEct()", ARGumeNTs = [], ethAmt = 0x0, gas = 10000000} +Current Strategy: fromFreqs [(EthTransaction {contract = LitAddr 0x1000, caLLeR = LItAdDr 0x1235, MEthod = "cooPeRatE()", ArgUmENTs = [], ethAmt = 0x0, gas = 10000000},1.0)] +Optimal Payoff: 1.000006e9 +Current Payoff: 1.000004e9 +Observable State: () +Unobservable State: "()" + --other game-- + --No more information-- + NEWGAME: +----Analytics end---- +----Analytics begin---- +Player: player1 +Optimal Move: EthTransaction {contract = LitAddr 0x1000, CALlEr = LItAddR 0x1234, METHOd = "dEfECT()", ArGuMEntS = [], ethAmt = 0x0, gas = 10000000} +Current Strategy: fromFreqs [(EthTransaction {contract = LitAddr 0x1000, caLLEr = LitADDr 0x1234, MethoD = "deFeCt()", aRguMEntS = [], ethAmt = 0x0, gas = 10000000},1.0)] +Optimal Payoff: 1.000006e9 +Current Payoff: 1.000006e9 +Observable State: () +Unobservable State: "()" + --other game-- + --No more information-- + NEWGAME: + +Player: player2 +Optimal Move: EthTransaction {contract = LitAddr 0x1000, cAlLeR = LiTADdR 0x1235, mEtHOD = "DEfEct()", ARGumeNTs = [], ethAmt = 0x0, gas = 10000000} +Current Strategy: fromFreqs [(EthTransaction {contract = LitAddr 0x1000, caLLeR = LItAdDr 0x1235, MEthod = "cooPeRatE()", ArgUmENTs = [], ethAmt = 0x0, gas = 10000000},1.0)] +Optimal Payoff: 1.000002e9 +Current Payoff: 1.0e9 +Observable State: () +Unobservable State: "()" + --other game-- + --No more information-- + NEWGAME: +----Analytics end---- +----Analytics begin---- +Player: player1 +Optimal Move: EthTransaction {contract = LitAddr 0x1000, CALlEr = LItAddR 0x1234, METHOd = "dEfECT()", ArGuMEntS = [], ethAmt = 0x0, gas = 10000000} +Current Strategy: fromFreqs [(EthTransaction {contract = LitAddr 0x1000, callER = LITADDr 0X1234, MetHOD = "cooperATE()", ArGUmEntS = [], ethAmt = 0x0, gas = 10000000},1.0)] +Optimal Payoff: 1.000002e9 +Current Payoff: 1.0e9 +Observable State: () +Unobservable State: "()" + --other game-- + --No more information-- + NEWGAME: + +Player: player2 +Optimal Move: EthTransaction {contract = LitAddr 0x1000, cAlLeR = LiTADdR 0x1235, mEtHOD = "DEfEct()", ARGumeNTs = [], ethAmt = 0x0, gas = 10000000} +Current Strategy: fromFreqs [(EthTransaction {contract = LitAddr 0x1000, CalleR = LITAddR 0X1235, METhoD = "DefeCT()", ArGuments = [], ethAmt = 0x0, gas = 10000000},1.0)] +Optimal Payoff: 1.000006e9 +Current Payoff: 1.000006e9 +Observable State: () +Unobservable State: "()" + --other game-- + --No more information-- + NEWGAME: +----Analytics end---- +----Analytics begin---- +Player: player1 +Optimal Move: EthTransaction {contract = LitAddr 0x1000, CALlEr = LItAddR 0x1234, METHOd = "dEfECT()", ArGuMEntS = [], ethAmt = 0x0, gas = 10000000} +Current Strategy: fromFreqs [(EthTransaction {contract = LitAddr 0x1000, caLLEr = LitADDr 0x1234, MethoD = "deFeCt()", aRguMEntS = [], ethAmt = 0x0, gas = 10000000},1.0)] +Optimal Payoff: 1.000002e9 +Current Payoff: 1.000002e9 +Observable State: () +Unobservable State: "()" + --other game-- + --No more information-- + NEWGAME: + +Player: player2 +Optimal Move: EthTransaction {contract = LitAddr 0x1000, cAlLeR = LiTADdR 0x1235, mEtHOD = "DEfEct()", ARGumeNTs = [], ethAmt = 0x0, gas = 10000000} +Current Strategy: fromFreqs [(EthTransaction {contract = LitAddr 0x1000, CalleR = LITAddR 0X1235, METhoD = "DefeCT()", ArGuments = [], ethAmt = 0x0, gas = 10000000},1.0)] +Optimal Payoff: 1.000002e9 +Current Payoff: 1.000002e9 +Observable State: () +Unobservable State: "()" + --other game-- + --No more information-- + NEWGAME: +----Analytics end---- diff --git a/evm/open-games-hevm.cabal b/evm/open-games-hevm.cabal new file mode 100644 index 0000000..696da51 --- /dev/null +++ b/evm/open-games-hevm.cabal @@ -0,0 +1,81 @@ +cabal-version: 1.12 + +-- This file has been generated from package.yaml by hpack version 0.37.0. +-- +-- see: https://github.com/sol/hpack + +name: open-games-hevm +version: 0.1.0.0 +synopsis: HEVM intgration of open games +category: Math +author: Jules Hedges, André Videla, Philipp Zahn & other contributors +maintainer: philipp.zahn@protonmail +copyright: Jules Hedges, André Videla, Philipp Zahn & other contributors +license: AGPL +build-type: Simple +extra-source-files: + README.md + +library + exposed-modules: + EVM.Prelude + EVM.TH + Examples.HEVM + Examples.Lido + Examples.Prisoner + other-modules: + Examples.Components + OpenGames.Engine.Copy + OpenGames.Engine.HEVMGames + Paths_open_games_hevm + hs-source-dirs: + src + build-depends: + base >=4.7 && <5 + , bytestring + , containers + , data-dword + , hashmap + , hevm + , mtl + , open-games-hs + , optics + , optics-core + , optics-extra + , rosezipper + , tasty + , tasty-golden + , template-haskell + , text + , transformers + , vector + default-language: Haskell2010 + +test-suite hevm-tests + type: exitcode-stdio-1.0 + main-is: Spec.hs + other-modules: + Paths_open_games_hevm + hs-source-dirs: + tests + build-depends: + base >=4.7 && <5 + , bytestring + , containers + , data-dword + , hashmap + , hevm + , mtl + , open-games-hevm + , open-games-hs + , optics + , optics-core + , optics-extra + , rosezipper + , tasty + , tasty-golden + , template-haskell + , text + , transformers + , vector + default-language: Haskell2010 diff --git a/evm/package.yaml b/evm/package.yaml new file mode 100644 index 0000000..8fc7684 --- /dev/null +++ b/evm/package.yaml @@ -0,0 +1,52 @@ +name: open-games-hevm +version: '0.1.0.0' +synopsis: HEVM intgration of open games +category: Math +author: Jules Hedges, André Videla, Philipp Zahn & other contributors +maintainer: philipp.zahn@protonmail +copyright: Jules Hedges, André Videla, Philipp Zahn & other contributors +license: AGPL +extra-source-files: +- README.md + + +library: + source-dirs: src + exposed-modules: + - EVM.Prelude + - EVM.TH + - Examples.HEVM + - Examples.Lido + - Examples.Prisoner + +dependencies: + - base >=4.7 && <5 + - bytestring + - containers + - open-games-hs + - data-dword + - text + - optics + - optics-extra + - optics-core + - template-haskell + - transformers + - vector + - rosezipper + - hevm + - mtl + - hashmap + - tasty + - tasty-golden + +tests: + hevm-tests: + main: Spec.hs + source-dirs: tests + dependencies: + - open-games-hevm + - tasty + - tasty-golden + - bytestring + + diff --git a/solitidy/AMM.sol b/evm/solidity/AMM.sol similarity index 100% rename from solitidy/AMM.sol rename to evm/solidity/AMM.sol diff --git a/evm/solidity/DualGovernance.sol b/evm/solidity/DualGovernance.sol new file mode 100644 index 0000000..ffc5d82 --- /dev/null +++ b/evm/solidity/DualGovernance.sol @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {Duration} from "./types/Duration.sol"; +import {Timestamp} from "./types/Timestamp.sol"; +import {ITimelock} from "./interfaces/ITimelock.sol"; +import {IResealManager} from "./interfaces/IResealManager.sol"; + +import {IStETH} from "./interfaces/IStETH.sol"; +import {IWstETH} from "./interfaces/IWstETH.sol"; +import {IWithdrawalQueue} from "./interfaces/IWithdrawalQueue.sol"; +import {IDualGovernance} from "./interfaces/IDualGovernance.sol"; +import {IResealManager} from "./interfaces/IResealManager.sol"; + +import {Proposers} from "./libraries/Proposers.sol"; +import {Tiebreaker} from "./libraries/Tiebreaker.sol"; +import {ExternalCall} from "./libraries/ExternalCalls.sol"; +import {State, DualGovernanceStateMachine} from "./libraries/DualGovernanceStateMachine.sol"; +import {IDualGovernanceConfigProvider} from "./DualGovernanceConfigProvider.sol"; + +import {Escrow} from "./Escrow.sol"; + +contract DualGovernance is IDualGovernance { + using Proposers for Proposers.Context; + using Tiebreaker for Tiebreaker.Context; + using DualGovernanceStateMachine for DualGovernanceStateMachine.Context; + + // --- + // Errors + // --- + + error NotAdminProposer(); + error UnownedAdminExecutor(); + error CallerIsNotResealCommittee(address caller); + error CallerIsNotAdminExecutor(address caller); + error InvalidConfigProvider(IDualGovernanceConfigProvider configProvider); + error ProposalSubmissionBlocked(); + error ProposalSchedulingBlocked(uint256 proposalId); + error ResealIsNotAllowedInNormalState(); + + // --- + // Events + // --- + + event CancelAllPendingProposalsSkipped(); + event CancelAllPendingProposalsExecuted(); + event EscrowMasterCopyDeployed(address escrowMasterCopy); + event ConfigProviderSet(IDualGovernanceConfigProvider newConfigProvider); + + // --- + // Tiebreaker Sanity Check Param Immutables + // --- + + struct SanityCheckParams { + uint256 minWithdrawalsBatchSize; + Duration minTiebreakerActivationTimeout; + Duration maxTiebreakerActivationTimeout; + uint256 maxSealableWithdrawalBlockersCount; + } + + Duration public immutable MIN_TIEBREAKER_ACTIVATION_TIMEOUT; + Duration public immutable MAX_TIEBREAKER_ACTIVATION_TIMEOUT; + uint256 public immutable MAX_SEALABLE_WITHDRAWAL_BLOCKERS_COUNT; + + // --- + // External Parts Immutables + + struct ExternalDependencies { + IStETH stETH; + IWstETH wstETH; + IWithdrawalQueue withdrawalQueue; + ITimelock timelock; + IResealManager resealManager; + IDualGovernanceConfigProvider configProvider; + } + + ITimelock public immutable TIMELOCK; + IResealManager public immutable RESEAL_MANAGER; + address public immutable ESCROW_MASTER_COPY; + + // --- + // Aspects + // --- + + Proposers.Context internal _proposers; + Tiebreaker.Context internal _tiebreaker; + DualGovernanceStateMachine.Context internal _stateMachine; + + // --- + // Standalone State Variables + // --- + IDualGovernanceConfigProvider internal _configProvider; + address internal _resealCommittee; + + constructor(ExternalDependencies memory dependencies, SanityCheckParams memory sanityCheckParams) { + TIMELOCK = dependencies.timelock; + RESEAL_MANAGER = dependencies.resealManager; + + MIN_TIEBREAKER_ACTIVATION_TIMEOUT = sanityCheckParams.minTiebreakerActivationTimeout; + MAX_TIEBREAKER_ACTIVATION_TIMEOUT = sanityCheckParams.maxTiebreakerActivationTimeout; + MAX_SEALABLE_WITHDRAWAL_BLOCKERS_COUNT = sanityCheckParams.maxSealableWithdrawalBlockersCount; + + _setConfigProvider(dependencies.configProvider); + + ESCROW_MASTER_COPY = address( + new Escrow({ + dualGovernance: this, + stETH: dependencies.stETH, + wstETH: dependencies.wstETH, + withdrawalQueue: dependencies.withdrawalQueue, + minWithdrawalsBatchSize: sanityCheckParams.minWithdrawalsBatchSize + }) + ); + emit EscrowMasterCopyDeployed(ESCROW_MASTER_COPY); + + _stateMachine.initialize(dependencies.configProvider.getDualGovernanceConfig(), ESCROW_MASTER_COPY); + } + + // --- + // Proposals Flow + // --- + + function submitProposal(ExternalCall[] calldata calls) external returns (uint256 proposalId) { + _stateMachine.activateNextState(_configProvider.getDualGovernanceConfig(), ESCROW_MASTER_COPY); + if (!_stateMachine.canSubmitProposal()) { + revert ProposalSubmissionBlocked(); + } + Proposers.Proposer memory proposer = _proposers.getProposer(msg.sender); + proposalId = TIMELOCK.submit(proposer.executor, calls); + } + + function scheduleProposal(uint256 proposalId) external { + _stateMachine.activateNextState(_configProvider.getDualGovernanceConfig(), ESCROW_MASTER_COPY); + ( /* id */ , /* status */, /* executor */, Timestamp submittedAt, /* scheduledAt */ ) = + TIMELOCK.getProposalInfo(proposalId); + if (!_stateMachine.canScheduleProposal(submittedAt)) { + revert ProposalSchedulingBlocked(proposalId); + } + TIMELOCK.schedule(proposalId); + } + + function cancelAllPendingProposals() external { + _stateMachine.activateNextState(_configProvider.getDualGovernanceConfig(), ESCROW_MASTER_COPY); + + Proposers.Proposer memory proposer = _proposers.getProposer(msg.sender); + if (proposer.executor != TIMELOCK.getAdminExecutor()) { + revert NotAdminProposer(); + } + + State currentState = _stateMachine.getCurrentState(); + if (currentState != State.VetoSignalling && currentState != State.VetoSignallingDeactivation) { + /// @dev Some proposer contracts, like Aragon Voting, may not support canceling decisions that have already + /// reached consensus. This could lead to a situation where a proposer’s cancelAllPendingProposals() call + /// becomes unexecutable if the Dual Governance state changes. However, it might become executable again if + /// the system state shifts back to VetoSignalling or VetoSignallingDeactivation. + /// To avoid such a scenario, an early return is used instead of a revert when proposals cannot be canceled + /// due to an unsuitable Dual Governance state. + emit CancelAllPendingProposalsSkipped(); + return; + } + + TIMELOCK.cancelAllNonExecutedProposals(); + emit CancelAllPendingProposalsExecuted(); + } + + function canSubmitProposal() public view returns (bool) { + return _stateMachine.canSubmitProposal(); + } + + function canScheduleProposal(uint256 proposalId) external view returns (bool) { + ( /* id */ , /* status */, /* executor */, Timestamp submittedAt, /* scheduledAt */ ) = + TIMELOCK.getProposalInfo(proposalId); + return _stateMachine.canScheduleProposal(submittedAt) && TIMELOCK.canSchedule(proposalId); + } + + // --- + // Dual Governance State + // --- + + function activateNextState() external { + _stateMachine.activateNextState(_configProvider.getDualGovernanceConfig(), ESCROW_MASTER_COPY); + } + + function setConfigProvider(IDualGovernanceConfigProvider newConfigProvider) external { + _checkCallerIsAdminExecutor(); + _setConfigProvider(newConfigProvider); + + /// @dev the minAssetsLockDuration is kept as a storage variable in the signalling Escrow instance + /// to sync the new value with current signalling escrow, it's value must be manually updated + _stateMachine.signallingEscrow.setMinAssetsLockDuration( + newConfigProvider.getDualGovernanceConfig().minAssetsLockDuration + ); + } + + function getConfigProvider() external view returns (IDualGovernanceConfigProvider) { + return _configProvider; + } + + function getVetoSignallingEscrow() external view returns (address) { + return address(_stateMachine.signallingEscrow); + } + + function getRageQuitEscrow() external view returns (address) { + return address(_stateMachine.rageQuitEscrow); + } + + function getCurrentState() external view returns (State currentState) { + currentState = _stateMachine.getCurrentState(); + } + + function getCurrentStateContext() external view returns (DualGovernanceStateMachine.Context memory) { + return _stateMachine.getCurrentContext(); + } + + function getDynamicDelayDuration() external view returns (Duration) { + return _stateMachine.getDynamicDelayDuration(_configProvider.getDualGovernanceConfig()); + } + + // --- + // Proposers & Executors Management + // --- + + function registerProposer(address proposer, address executor) external { + _checkCallerIsAdminExecutor(); + _proposers.register(proposer, executor); + } + + function unregisterProposer(address proposer) external { + _checkCallerIsAdminExecutor(); + _proposers.unregister(proposer); + + /// @dev after the removal of the proposer, check that admin executor still belongs to some proposer + if (!_proposers.isExecutor(TIMELOCK.getAdminExecutor())) { + revert UnownedAdminExecutor(); + } + } + + function isProposer(address account) external view returns (bool) { + return _proposers.isProposer(account); + } + + function getProposer(address account) external view returns (Proposers.Proposer memory proposer) { + proposer = _proposers.getProposer(account); + } + + function getProposers() external view returns (Proposers.Proposer[] memory proposers) { + proposers = _proposers.getAllProposers(); + } + + function isExecutor(address account) external view returns (bool) { + return _proposers.isExecutor(account); + } + + // --- + // Tiebreaker Protection + // --- + + function addTiebreakerSealableWithdrawalBlocker(address sealableWithdrawalBlocker) external { + _checkCallerIsAdminExecutor(); + _tiebreaker.addSealableWithdrawalBlocker(sealableWithdrawalBlocker, MAX_SEALABLE_WITHDRAWAL_BLOCKERS_COUNT); + } + + function removeTiebreakerSealableWithdrawalBlocker(address sealableWithdrawalBlocker) external { + _checkCallerIsAdminExecutor(); + _tiebreaker.removeSealableWithdrawalBlocker(sealableWithdrawalBlocker); + } + + function setTiebreakerCommittee(address tiebreakerCommittee) external { + _checkCallerIsAdminExecutor(); + _tiebreaker.setTiebreakerCommittee(tiebreakerCommittee); + } + + function setTiebreakerActivationTimeout(Duration tiebreakerActivationTimeout) external { + _checkCallerIsAdminExecutor(); + _tiebreaker.setTiebreakerActivationTimeout( + MIN_TIEBREAKER_ACTIVATION_TIMEOUT, tiebreakerActivationTimeout, MAX_TIEBREAKER_ACTIVATION_TIMEOUT + ); + } + + function tiebreakerResumeSealable(address sealable) external { + _tiebreaker.checkCallerIsTiebreakerCommittee(); + _tiebreaker.checkTie(_stateMachine.getCurrentState(), _stateMachine.getNormalOrVetoCooldownStateExitedAt()); + RESEAL_MANAGER.resume(sealable); + } + + function tiebreakerScheduleProposal(uint256 proposalId) external { + _tiebreaker.checkCallerIsTiebreakerCommittee(); + _stateMachine.activateNextState(_configProvider.getDualGovernanceConfig(), ESCROW_MASTER_COPY); + _tiebreaker.checkTie(_stateMachine.getCurrentState(), _stateMachine.getNormalOrVetoCooldownStateExitedAt()); + TIMELOCK.schedule(proposalId); + } + + struct TiebreakerState { + address tiebreakerCommittee; + Duration tiebreakerActivationTimeout; + address[] sealableWithdrawalBlockers; + } + + function getTiebreakerState() external view returns (TiebreakerState memory tiebreakerState) { + ( + tiebreakerState.tiebreakerCommittee, + tiebreakerState.tiebreakerActivationTimeout, + tiebreakerState.sealableWithdrawalBlockers + ) = _tiebreaker.getTiebreakerInfo(); + } + + // --- + // Reseal executor + // --- + + function resealSealable(address sealable) external { + if (msg.sender != _resealCommittee) { + revert CallerIsNotResealCommittee(msg.sender); + } + if (_stateMachine.getCurrentState() == State.Normal) { + revert ResealIsNotAllowedInNormalState(); + } + RESEAL_MANAGER.reseal(sealable); + } + + function setResealCommittee(address resealCommittee) external { + _checkCallerIsAdminExecutor(); + _resealCommittee = resealCommittee; + } + + // --- + // Private methods + // --- + + function _setConfigProvider(IDualGovernanceConfigProvider newConfigProvider) internal { + if (address(newConfigProvider) == address(0)) { + revert InvalidConfigProvider(newConfigProvider); + } + + if (newConfigProvider == _configProvider) { + return; + } + + _configProvider = IDualGovernanceConfigProvider(newConfigProvider); + emit ConfigProviderSet(newConfigProvider); + } + + function _checkCallerIsAdminExecutor() internal view { + if (TIMELOCK.getAdminExecutor() != msg.sender) { + revert CallerIsNotAdminExecutor(msg.sender); + } + } +} diff --git a/solitidy/ERC20.sol b/evm/solidity/ERC20.sol similarity index 100% rename from solitidy/ERC20.sol rename to evm/solidity/ERC20.sol diff --git a/evm/solidity/LidoReward.sol b/evm/solidity/LidoReward.sol new file mode 100644 index 0000000..f92a673 --- /dev/null +++ b/evm/solidity/LidoReward.sol @@ -0,0 +1,691 @@ +// SPDX-FileCopyrightText: 2021 Lido <info@lido.fi> + +// SPDX-License-Identifier: GPL-3.0 + +/* See contracts/COMPILERS.md */ +pragma solidity 0.8.9; + +import "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4.4/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts-v4.4/token/ERC20/utils/SafeERC20.sol"; + +interface ILido { + /** + * @notice A payable function supposed to be called only by LidoExecLayerRewardsVault contract + * @dev We need a dedicated function because funds received by the default payable function + * are treated as a user deposit + */ + function receiveELRewards() external payable; +} + + +/** + * @title A vault for temporary storage of execution layer rewards (MEV and tx priority fee) + */ +contract LidoExecutionLayerRewardsVault { + using SafeERC20 for IERC20; + + address public immutable LIDO; + address public immutable TREASURY; + + /** + * Emitted when the ERC20 `token` recovered (i.e. transferred) + * to the Lido treasury address by `requestedBy` sender. + */ + event ERC20Recovered( + address indexed requestedBy, + address indexed token, + uint256 amount + ); + + /** + * Emitted when the ERC721-compatible `token` (NFT) recovered (i.e. transferred) + * to the Lido treasury address by `requestedBy` sender. + */ + event ERC721Recovered( + address indexed requestedBy, + address indexed token, + uint256 tokenId + ); + + /** + * Emitted when the vault received ETH + */ + event ETHReceived( + uint256 amount + ); + + /** + * Ctor + * + * @param _lido the Lido token (stETH) address + * @param _treasury the Lido treasury address (see ERC20/ERC721-recovery interfaces) + */ + constructor(address _lido, address _treasury) { + require(_lido != address(0), "LIDO_ZERO_ADDRESS"); + require(_treasury != address(0), "TREASURY_ZERO_ADDRESS"); + + LIDO = _lido; + TREASURY = _treasury; + } + + /** + * @notice Allows the contract to receive ETH + * @dev execution layer rewards may be sent as plain ETH transfers + */ + receive() external payable { + emit ETHReceived(msg.value); + } + + /** + * @notice Withdraw all accumulated rewards to Lido contract + * @dev Can be called only by the Lido contract + * @param _maxAmount Max amount of ETH to withdraw + * @return amount of funds received as execution layer rewards (in wei) + */ + function withdrawRewards(uint256 _maxAmount) external returns (uint256 amount) { + require(msg.sender == LIDO, "ONLY_LIDO_CAN_WITHDRAW"); + + uint256 balance = address(this).balance; + amount = (balance > _maxAmount) ? _maxAmount : balance; + if (amount > 0) { + ILido(LIDO).receiveELRewards{value: amount}(); + } + return amount; + } + + /** + * Transfers a given `_amount` of an ERC20-token (defined by the `_token` contract address) + * currently belonging to the burner contract address to the Lido treasury address. + * + * @param _token an ERC20-compatible token + * @param _amount token amount + */ + function recoverERC20(address _token, uint256 _amount) external { + require(_amount > 0, "ZERO_RECOVERY_AMOUNT"); + + emit ERC20Recovered(msg.sender, _token, _amount); + + IERC20(_token).safeTransfer(TREASURY, _amount); + } + + /** + * Transfers a given token_id of an ERC721-compatible NFT (defined by the token contract address) + * currently belonging to the burner contract address to the Lido treasury address. + * + * @param _token an ERC721-compatible token + * @param _tokenId minted token id + */ + function recoverERC721(address _token, uint256 _tokenId) external { + emit ERC721Recovered(msg.sender, _token, _tokenId); + + IERC721(_token).transferFrom(address(this), TREASURY, _tokenId); + } +} +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721.sol) + +pragma solidity ^0.8.0; + +import "../../utils/introspection/IERC165.sol"; + +/** + * @dev Required interface of an ERC721 compliant contract. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitted when `tokenId` token is transferred from `from` to `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Returns the number of tokens in ``owner``'s account. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Returns the owner of the `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Transfers `tokenId` token from `from` to `to`. + * + * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Gives permission to `to` to transfer `tokenId` token to another account. + * The approval is cleared when the token is transferred. + * + * Only a single account can be approved at a time, so approving the zero address clears previous approvals. + * + * Requirements: + * + * - The caller must own the token or be an approved operator. + * - `tokenId` must exist. + * + * Emits an {Approval} event. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Returns the account approved for `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Approve or remove `operator` as an operator for the caller. + * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. + * + * Requirements: + * + * - The `operator` cannot be the caller. + * + * Emits an {ApprovalForAll} event. + */ + function setApprovalForAll(address operator, bool _approved) external; + + /** + * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. + * + * See {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; +} +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol) + +pragma solidity ^0.8.0; + +import "../IERC20.sol"; +import "../../../utils/Address.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using Address for address; + + function safeTransfer( + IERC20 token, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove( + IERC20 token, + address spender, + uint256 value + ) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + require( + (value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender) + value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + unchecked { + uint256 oldAllowance = token.allowance(address(this), spender); + require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); + uint256 newAllowance = oldAllowance - value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { + // Return data is optional + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (utils/Address.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + diff --git a/evm/solidity/Prisonner.sol b/evm/solidity/Prisonner.sol new file mode 100644 index 0000000..1c5eac4 --- /dev/null +++ b/evm/solidity/Prisonner.sol @@ -0,0 +1,71 @@ + +pragma solidity ^0.8.19; + +contract Prison { + + address prisoner1; + address prisoner2; + bool prisoner1Defect; + bool prisoner2Defect; + bool prisoner1Played; + bool prisoner2Played; + + function check() public { + if (prisoner1Played && prisoner2Played) { + // if they both defect, they get a small prize + if (prisoner1Defect && prisoner2Defect) { + (bool res1, ) = prisoner1.call{value: 2000}(""); + (bool res2, ) = prisoner2.call{value: 2000}(""); + require(res1, "transfer 1 failed!"); + require(res2, "transfer 2 failed!"); + } + // if prisoner1 defect but prisoner2 cooperates + // prisonner1 gets a large prize and prisonner2 nothing + else if (prisoner1Defect && !prisoner2Defect) { + (bool res2, ) = prisoner1.call{value: 6000}(""); + require(res2, "transfer 2 failed!"); + } + // if prisoner2 defects but prisoner1 cooperates + // prisonner2 gets a large prize and prisoner1 nothing + else if (!prisoner1Defect && prisoner2Defect) { + (bool res1, ) = prisoner2.call{value: 6000}(""); + require(res1, "transfer 1 failed!"); + } + // if both prisoners cooperate they both get 4000 + else { + (bool res1, ) = prisoner1.call{value: 4000}(""); + (bool res2, ) = prisoner2.call{value: 4000}(""); + require(res1, "transfer 1 failed!"); + require(res2, "transfer 2 failed!"); + } + } + } + + function cooperate() public { + if (!prisoner1Played) { + prisoner1 = msg.sender; + prisoner1Defect = false; + prisoner1Played = true; + check(); + } else if (!prisoner2Played) { + prisoner2 = msg.sender; + prisoner2Defect = false; + prisoner2Played = true; + check(); + } + } + + function defect() public { + if (!prisoner1Played) { + prisoner1 = msg.sender; + prisoner1Defect = true; + prisoner1Played = true; + check(); + } else if (!prisoner2Played) { + prisoner2 = msg.sender; + prisoner2Defect = true; + prisoner2Played = true; + check(); + } + } +} diff --git a/evm/solidity/Simple.sol b/evm/solidity/Simple.sol new file mode 100644 index 0000000..ad158d7 --- /dev/null +++ b/evm/solidity/Simple.sol @@ -0,0 +1,5 @@ +contract Neg { + function negate(int i) external pure returns (int) { + return i * -1; + } +} diff --git a/solitidy/UniswapExchange.sol b/evm/solidity/UniswapExchange.sol similarity index 100% rename from solitidy/UniswapExchange.sol rename to evm/solidity/UniswapExchange.sol diff --git a/solitidy/UniswapFactory.sol b/evm/solidity/UniswapFactory.sol similarity index 99% rename from solitidy/UniswapFactory.sol rename to evm/solidity/UniswapFactory.sol index 11c1a70..9c59563 100644 --- a/solitidy/UniswapFactory.sol +++ b/evm/solidity/UniswapFactory.sol @@ -1,4 +1,5 @@ pragma solidity ^0.4.20; + import "./UniswapExchange.sol"; diff --git a/evm/solidity/Withdraw.sol b/evm/solidity/Withdraw.sol new file mode 100644 index 0000000..857c850 --- /dev/null +++ b/evm/solidity/Withdraw.sol @@ -0,0 +1,29 @@ +pragma solidity >=0.8.2 <0.9.0; + +/** + * @title Piggybank + * @dev Deposit and withdraw eth + */ +contract Piggybank { + + uint256 _balance; + + /** + * @dev Deposit ether + */ + function deposit() public payable { + _balance = _balance + msg.value; + } + + /** + * @dev Withdraw a fixed amount of ether + * @param _amount The amount to withdraw + */ + function retrieve(uint256 _amount) public { + require(_balance >= _amount, "insufficient funds."); + _balance = _balance - _amount; + (bool res, ) = msg.sender.call{value: _amount}(""); + require(res, "transfer failed!"); + } +} + diff --git a/evm/src/EVM/Prelude.hs b/evm/src/EVM/Prelude.hs new file mode 100644 index 0000000..22e90d1 --- /dev/null +++ b/evm/src/EVM/Prelude.hs @@ -0,0 +1,22 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE NoFieldSelectors #-} + +module EVM.Prelude (Word256, EthTransaction (..), AbiType (..), AbiValue (..), abiMethod) where + +import Data.DoubleWord (Word256) +import Data.Text +import EVM.ABI (AbiType (..), AbiValue (..), abiMethod) +import EVM.Types (Addr, EType (..), Expr (..), W256) +import GHC.Word + +data EthTransaction = EthTransaction + { contract :: Expr EAddr, + caller :: Expr EAddr, + method :: Text, + arguments :: [AbiValue], + ethAmt :: W256, + gas :: Word64 + } + deriving (Eq, Show, Ord) diff --git a/evm/src/EVM/TH.hs b/evm/src/EVM/TH.hs new file mode 100644 index 0000000..e367e27 --- /dev/null +++ b/evm/src/EVM/TH.hs @@ -0,0 +1,355 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE OverloadedLabels #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DeriveLift #-} + +module EVM.TH (sendAndRun, sendAndRunAll, sendAndRun', makeTxCall, balance, loadAll + , ContractFileInfo, mkContractFileInfo, ContractInfo, mkContractInfo, AbiValue (..), Expr (..), stToIO, setupAddresses, getAllContracts) where + +import Control.Monad.ST +import Control.Monad.Trans.State.Strict +import Control.Monad.Trans.State.Strict (State, put) +import Data.ByteString (ByteString) +import Data.Map as Map +import Data.Text (Text, intercalate, pack, toLower, unpack) +import Data.Text.IO (readFile) +import Data.Maybe (fromMaybe) +import qualified Data.Tree.Zipper as Zipper +import Data.Vector as Vector (Vector, fromList, toList) +import EVM (blankState, emptyContract, exec1, initialContract, loadContract, resetState) +import EVM.Exec (exec, run) +import EVM.Expr +import EVM.FeeSchedule +import EVM.Fetch +import EVM.Prelude +import EVM.Solidity (Contracts (..), Method (..), SolcContract (..), readStdJSON, solcRuntime, solidity, solc, Language(..)) +import EVM.Stepper +import EVM.Transaction (initTx) +import EVM.Types +import GHC.IO.Unsafe +import GHC.ST +import Language.Haskell.TH.Syntax as TH +import Optics.Core +import Optics.State +import Optics.State.Operators +import Prelude hiding (FilePath, readFile) + +-- put this in sttate.callData +-- run it to execute the transaction +-- put more for subsequent calls +-- run more for more results +makeCallData :: EthTransaction -> Expr Buf +makeCallData (EthTransaction _ caller method args _ _) = + ConcreteBuf $ abiMethod method (AbiTuple (Vector.fromList args)) + +makeTxCall :: EthTransaction -> EVM Concrete s () +makeTxCall tx@(EthTransaction addr caller meth args amt gas) = do + resetState + assign (#tx % #isCreate) False + modify (execState (loadContract addr)) + assign (#state % #callvalue) (Lit amt) + assign (#state % #calldata) (makeCallData tx) + assign (#state % #caller) (caller) + assign (#state % #gas) gas + modify initTx + +loadIntoVM :: [(Expr EAddr, ByteString)] -> ST s (VM Concrete s) +loadIntoVM contracts = do + blankSt <- blankState + pure $ + VM + { result = Nothing, + state = blankSt, + frames = [], + env = envForContracts contracts, + block = emptyBlock, + tx = emptyTransaction, + logs = [], + traces = Zipper.fromForest mempty, + cache = Cache mempty mempty, + burned = 0, + iterations = mempty, + constraints = [], + config = + RuntimeConfig + True + Nothing + False + EmptyBase + } + where + -- question: Is that a reasonable empty first block? + emptyBlock :: Block + emptyBlock = + Block + { coinbase = LitAddr 0, + timestamp = Lit 0, + number = 0, + prevRandao = 0, + maxCodeSize = 0, + gaslimit = 0, + baseFee = 0, + schedule = feeSchedule + } + emptyTransaction :: TxState + emptyTransaction = + TxState + { gasprice = 0, + gaslimit = 0, + priorityFee = 0, + origin = LitAddr 0, + toAddr = LitAddr 0, + value = Lit 0, + subState = emptySubState, + isCreate = True, + txReversion = mempty + } + emptySubState :: SubState + emptySubState = + SubState + { selfdestructs = [], + touchedAccounts = [], + accessedAddresses = mempty, + accessedStorageKeys = mempty, + refunds = [] + } + + envForContracts :: [(Expr EAddr, ByteString)] -> Env + envForContracts contracts = + Env + { contracts = Map.fromList (fmap (fmap bytecodeToContract) contracts), + chainId = 0 + -- storage = EmptyStore, + -- origStorage = mempty + } + + bytecodeToContract :: ByteString -> Contract + bytecodeToContract = initialContract . RuntimeCode . ConcreteRuntimeCode + +int :: Int -> Exp +int = LitE . IntegerL . toInteger + +instance Lift a => Lift (Vector a) where + lift vec = do ll <- traverse lift (Vector.toList vec) + let gg = ListE ll + [| Vector.fromList $( pure gg ) |] + +instance Lift AbiType where + lift (AbiUIntType n) = [| AbiUIntType n |] + lift (AbiIntType n) = [| AbiIntType n |] + lift AbiAddressType = [| AbiAddressType |] + lift AbiBoolType = [| AbiBoolType |] + lift (AbiBytesType n) = [| AbiBytesType n |] + lift AbiBytesDynamicType = [| AbiBytesDynamicType |] + lift AbiStringType = [| AbiStringType |] + lift (AbiArrayDynamicType ty) = [| AbiArrayDynamicType ty |] + lift (AbiArrayType ty arr) = [| AbiArrayType ty arr |] + lift (AbiTupleType tys) = [| AbiTupleType tys |] + lift AbiFunctionType = [| AbiFunctionType |] + +constructorExprForType :: Quote m => AbiType -> Name -> m Exp +constructorExprForType (AbiUIntType w) = pure . ((ConE (mkName "AbiUInt") `AppE` int w) `AppE`) . VarE +constructorExprForType (AbiIntType w) = pure . ((ConE (mkName "AbiInt") `AppE` int w) `AppE`) . VarE +constructorExprForType (AbiAddressType) = pure . (ConE (mkName "AbiAddress") `AppE`) . VarE +constructorExprForType (AbiBoolType) = pure . (ConE (mkName "AbiBool") `AppE`) . VarE +constructorExprForType (AbiBytesType w) = pure . ((ConE (mkName "AbiBytes") `AppE` int w) `AppE`) . VarE +constructorExprForType (AbiBytesDynamicType) = pure . (ConE (mkName "AbiBytesDynamic") `AppE`) . VarE +constructorExprForType (AbiStringType) = pure . (ConE (mkName "AbiString") `AppE`) . VarE +constructorExprForType (AbiArrayDynamicType ty) = \nm -> [|AbiArrayDynamic ty $(pure (VarE nm))|] +constructorExprForType (AbiArrayType size ty) = error "arrays unsuppported" +constructorExprForType (AbiTupleType types) = error "tuples unsupported" -- ConE (mkName "AbiTuple") [] [VarP (mkName name)] +constructorExprForType (AbiFunctionType) = error "functions unsupported" + +data ContractInfo' a = ContractInfo' { name :: Text, boundName :: Text, payload :: a} + deriving Functor + +type ContractInfo = ContractInfo' () + +mkContractInfo :: Text -> Text -> ContractInfo +mkContractInfo name boundName = ContractInfo' name boundName () + +data ContractFileInfo' a = ContractFileInfo' + { file :: Text, + modules :: [ContractInfo' a] + } + +type ContractFileInfo = ContractFileInfo' () + +mkContractFileInfo :: Text -> [ContractInfo] -> ContractFileInfo +mkContractFileInfo = ContractFileInfo' + +pat = VarP . mkName + +generateTxFactory :: Method -> Integer -> Text -> Q Dec +generateTxFactory (Method _ args name sig _) addr contractName = do + runIO $ print ("arguments for method " <> name <> ":" <> pack (show args)) + let signatureString :: Q Exp = pure $ LitE $ StringL $ unpack sig + let argExp :: Q Exp = ListE <$> traverse (\(nm, ty) -> constructorExprForType ty (mkName $ unpack nm)) args + let patterns :: [Pat] = fmap (VarP . mkName . unpack . fst) args + let contractAddress :: Q Exp = pure $ AppE (ConE (mkName "LitAddr")) (LitE (IntegerL addr)) + body <- + [e| + EthTransaction + $(contractAddress) + src + $(signatureString) + $(argExp) + amt + gas + |] + pure $ + FunD + (mkName (unpack (toLower contractName <> "_" <> name))) + [ Clause + ( [pat "src", pat "amt", pat "gas"] + ++ patterns + ) + (NormalB body) + [] + ] + +instance Lift (Addr) where + lift (Addr word) = let v = toInteger word in [e|fromInteger v|] + +instance Lift (Expr 'EAddr) where + lift (LitAddr a) = [e|LitAddr (fromInteger a)|] + +instance Num (Expr 'EAddr) where + fromInteger i = LitAddr (fromInteger i) + +loadAll :: [ContractFileInfo] -> Q [Dec] +loadAll contracts = do + allContracts <- runIO $ traverse loadSolcInfo contracts + let allContractsHash = zip [ 0x1000.. ] (concat allContracts) + methods <- generateDefsForMethods allContractsHash + let contractMap = generateContractMap allContractsHash + contractNames <- traverse (\(addr, ContractInfo' nm bn con) -> contractName bn addr) allContractsHash + -- traverse (\(ix, ContractFileInfo _ nm) -> contractName nm ix) (zip [0x1000 ..] contracts) + init <- + [d| + initial = loadIntoVM contractMap + |] + pure (init ++ methods ++ contractNames) + where + -- Address, Contract name, Bound name + generateContractMap :: [(Integer, ContractInfo' SolcContract)] -> [(Integer, ByteString)] + generateContractMap = fmap (\(i, contract) -> (i, contract.payload.runtimeCode)) + -- Address, Contract name, Bound name + generateDefsForMethods :: [(Integer, ContractInfo' SolcContract)] -> Q [Dec] + generateDefsForMethods [] = pure [] + generateDefsForMethods ((hash, ContractInfo' name boundName contract) : xs) = do + let methods = Map.elems contract.abiMap + traverse (\x -> generateTxFactory x hash boundName) methods + + contractName :: Text -> Integer -> Q Dec + contractName binder value = do + let nm = mkName (unpack (binder <> "_contract")) + let value' = LitAddr (fromInteger value) + addr <- [e|value'|] + pure (ValD (VarP nm) (NormalB addr) []) + +loadSolcInfo :: ContractFileInfo -> IO [ContractInfo' SolcContract] +loadSolcInfo (ContractFileInfo' contractFilename modules) = do + file <- readFile (unpack contractFilename) + json <- solc Solidity file + let (Contracts sol, _, _) = fromMaybe (error ("canot read json:" ++ unpack json)) (readStdJSON json) + let retrievedMap = fmap (\mod -> fmap (\() -> Map.lookup ("hevm.sol:" <> mod.name) sol) mod) modules + emitMissing retrievedMap + where + emitMissing :: Show a => [ContractInfo' (Maybe a)] -> IO [ContractInfo' a] + emitMissing [] = pure [] + emitMissing (ContractInfo' t s Nothing : xs) = putStrLn ("contract " ++ show t ++ "is missing") >> emitMissing xs + emitMissing (ContractInfo' t s (Just x) : xs) = (ContractInfo' t s x :) <$> emitMissing xs + +run' :: EVM Concrete s (VM Concrete s) +run' = do + vm <- get + case vm.result of + Nothing -> exec1 >> run' + Just (HandleEffect (Query (PleaseAskSMT (Lit c) _ cont))) -> + undefined -- cont (Case (c > 0)) >> run' + Just (VMFailure y) -> pure vm + Just (VMSuccess y) -> pure vm + +-- send and run a transaction on the EVM Concrete state +sendAndRun' :: EthTransaction -> EVM Concrete RealWorld (VM Concrete RealWorld) +sendAndRun' tx = do + EVM.TH.makeTxCall tx + vm <- run' + pure vm + +sendAndRunAll :: [EthTransaction] -> EVM Concrete RealWorld (VM Concrete RealWorld) +sendAndRunAll [transaction] = sendAndRun' transaction +sendAndRunAll (tx : ts) = do + EVM.TH.makeTxCall tx + vm <- run' + sendAndRunAll ts + +-- exectute the EVM Concrete state in IO +sendAndRun :: + EthTransaction -> + VM Concrete RealWorld -> + EVM Concrete RealWorld (VM Concrete RealWorld) +sendAndRun tx st = do + put st + sendAndRun' tx + get + +adjustOrAdd :: (Ord k) => (v -> v) -> v -> k -> Map.Map k v -> Map.Map k v +adjustOrAdd f def = alter (Just . maybe def f) + +setupAddresses :: [(Expr EAddr, Expr EWord)] -> VM Concrete s -> VM Concrete s +setupAddresses amounts = + over (#env % #contracts) (updateContractMap amounts) + where + updateContractMap :: + [(Expr EAddr, Expr EWord)] -> + Map.Map (Expr EAddr) Contract -> + Map.Map (Expr EAddr) Contract + updateContractMap [] x = x + updateContractMap ((addr, amount) : cs) map = + let map' = adjustOrAdd (set #balance amount) (set #balance amount emptyContract) addr map + in updateContractMap cs map' + + createNew (addr, amount) = (addr, set #balance amount emptyContract) + + updateContractState :: (Expr EAddr, Contract) -> VM Concrete s -> VM Concrete s + updateContractState (addr, contract) = set (#env % #contracts % at addr) (Just contract) + +getAllContracts :: VM Concrete s -> [(Expr EAddr, Expr EWord)] +getAllContracts vm = + let contracts = Map.toList $ view (#env % #contracts) vm + contractsAmounts = fmap (fmap (view #balance)) contracts + in contractsAmounts + +balance :: VM Concrete s -> Expr EAddr -> W256 +balance st addr = + let contract = Map.lookup addr st.env.contracts + Just balance = fmap (view #balance) contract + Just int = maybeLitWord balance + in int + +-- TODO: use foundry +-- thatOneMethod = +-- let st = loadContracts [ContractFileInfo "solidity/Simple.sol" "Neg" "test"] +-- ourTransaction = +-- EthTransaction +-- (LitAddr 0xabcd) +-- (LitAddr 0x1234) +-- "negate(int256)" +-- [AbiInt 256 3] +-- 100000000 +-- 100000000 +-- steps = do +-- evm (makeTxCall ourTransaction) +-- runFully +-- in interpret (zero 0 (Just 0)) undefined steps diff --git a/evm/src/Examples/Components.hs b/evm/src/Examples/Components.hs new file mode 100644 index 0000000..9bc7c37 --- /dev/null +++ b/evm/src/Examples/Components.hs @@ -0,0 +1,289 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE TemplateHaskell #-} + +module Examples.Components where + +import OpenGames.Engine.BayesianGames (addPayoffs) +import OpenGames.Engine.Engine +import OpenGames.Preprocessor + +{- +WORK IN PROGRESS +Basic Components needed to model different txs +NOTE We assume that the type of contract functionality used by the player is provided by the modeller +NOTE For instance, in a case of a swap, the modeller invokes the right transaction type, say a deposit or swap. +-} + +----------------------------- +-- 1. Single player decisions + +-- Single player decision +-- The structure should cover different kinds of transactions +-- Only the account difference affects payoffs here +-- NOTE In so far as there are multiple parameters to choose, we will model this step by step. +singlePlayerTransactionChoice name actionSpace replaceMeWithAccountDiff = + [opengame| + + inputs : state, privateValue ; + feedback : ; + + :---------------------------: + + // Decision to choose parameters for a specific transaction; in the case of a deposit, how much to deposit + // This generates an account balance difference to be directly taken into account here + // Account difference operation tbd + inputs : state, privateValue ; + feedback : ; + operation : dependentDecision name actionSpace ; + outputs : dec ; + returns : replaceMeWithAccountDiff stateNew ; + + :---------------------------: + + outputs : dec ; + returns : stateNew ; + |] + +------------------------------------ +-- 2. Accounting for further payoffs +-- NOTE The player might derive further payoffs from at least three different sources +-- i. Actions by other players affect his utility as well +-- ii. He receives additional utility directly from the action; possibly with only privately known values +-- iii. He receives direct additional utility from the state change (possible further consequences outside the model). + +-- Account for additional value directly derived from action +-- NOTE value given exogenously +addPrivateValueDirectExogenous name payoffFunctionDirect privateValueDirect = + [opengame| + + inputs : dec ; + feedback : ; + + :---------------------------: + + // Computes possible side values not explicitly accounted for in the account difference + inputs : dec ; + feedback : ; + operation : forwardFunction (payoffFunctionDirect privateValueDirect) ; + outputs : privatePayoffDirect ; + returns : ; + + // Book-keeping: Assign the private "direct" payoff to the player. + inputs : privatePayoffDirect ; + feedback : ; + operation : addPayoffs name; + outputs : ; + returns : ; + + :---------------------------: + + outputs : ; + returns : ; + |] + +-- Account for additional value directly derived from action +-- NOTE value given endogenously +addPrivateValueDirectEndogenous name payoffFunctionDirect = + [opengame| + + inputs : dec, privateValueDirect ; + feedback : ; + + :---------------------------: + + // Computes possible side values not explicitly accounted for in the account difference + inputs : dec, privateValueDirect ; + feedback : ; + operation : forwardFunction payoffFunctionDirect ; + outputs : privatePayoffDirect ; + returns : ; + + // Book-keeping: Assign the private "direct" payoff to the player. + inputs : privatePayoffDirect ; + feedback : ; + operation : addPayoffs name; + outputs : ; + returns : ; + + :---------------------------: + + outputs : ; + returns : ; + |] + +{-- +-- Account for additional value indirectly derived from state +-- NOTE value given exogenously +addPrivateValueStateExogenously name payoffFunctionState privateValueState = [opengame| + + inputs : ; + feedback : ; + + :---------------------------: + + // Book-keeping: Assign the private "state" payoff to the player. + inputs : ; + feedback : ; + operation : addPayoffsReturns name; + outputs : ; + returns : privatePayoffState ; + + // Private Payoff derived from state change + inputs : ; + feedback : privatePayoffState ; + operation : backwardFunction (payoffFunctionState privateValueState) ; + outputs : ; + returns : stateNew ; + + :---------------------------: + + outputs : ; + returns : stateNew ; + |] + +-- Account for additional value indirectly derived from state +-- NOTE value given exogenously +addPrivateValueStateEndogenously name payoffFunctionState = [opengame| + + inputs : privateValueState ; + feedback : ; + + :---------------------------: + + // Book-keeping: Assign the private "state" payoff to the player. + inputs : ; + feedback : ; + operation : addPayoffsReturns name; + outputs : ; + returns : privatePayoffState ; + + // Private Payoff derived from state change and privateValueState + inputs : privateValueState ; + feedback : privatePayoffState ; + operation : fromLens id payoffFunctionState ; + outputs : dummyOutput ; + returns : stateNew ; + + :---------------------------: + + outputs : ; + returns : stateNew ; + |] + +------------------------------------------------------- +-- FIXME ONLY FOR TESTING PURPOSE; ERASE ONCE CLARIFIED +singleDecisionVerbose name addOne = [opengame| + inputs : ; + feedback : ; + + :----------------------------: + inputs : ; + feedback : ; + operation : dependentDecision "test" (const actionSpace) ; + outputs : dec ; + returns : dec ; + + inputs : ; + feedback : ; + operation : addPayoffsReturns name ; + outputs : ; + returns : payoff ; + + inputs : ; + feedback : payoff ; + operation : backwardFunction addOne ; + outputs : ; + returns : testInput ; + :----------------------------: + + outputs : ; + returns : testInput ; + |] + +test = 5 + +testGame f = evaluate (singleDecisionVerbose "test" f) + +actionSpace = [1] + +strategyTest + :: List + '[Kleisli Stochastic a Double] +strategyTest = (pureAction 1.5) :- Nil + +outputTest con f = generateOutput $ (testGame f) strategyTest con + +-------------------------------------------------------- +-} + +------------------------- +-- 3. Advancing the state + +-- Given the state and contract specific parameters, advance to a new state +advancingState contractFunctionality = + [opengame| + + inputs : state, parameters ; + feedback : ; + + :---------------------------: + + inputs : state, parameters ; + feedback : ; + operation : forwardFunction contractFunctionality ; + outputs : newState ; + returns : ; + + :---------------------------: + + outputs : newState ; + returns : ; + |] + +---------------------- +-- 4. Composed modules + +-- Player observes private information, chooses a transaction, account balances are update and receives (possibly) additional utility from his action +playerWithAdditionalValue name probDistribution actionSpace replaceMeWithAccountDiff contractFunctionality payoffFunctionDirect = + [opengame| + + inputs : state, parameters ; + feedback : ; + + :---------------------------: + + // Private value for action is drawn + inputs : ; + feedback : ; + operation : natureDraw probDistribution ; + outputs : privateValueDirect ; + returns : ; + + // Player observes his private value and decides; account differences are updated + inputs : state, privateValueDirect ; + feedback : ; + operation : singlePlayerTransactionChoice name actionSpace replaceMeWithAccountDiff ; + outputs : dec ; + returns : stateNew ; + + // Update state + inputs : state, parameters ; + feedback : ; + operation : advancingState contractFunctionality ; + outputs : stateNew ; + returns : ; + + // Add utility for the action chosen + inputs : dec, privateValueDirect ; + feedback : ; + operation : addPrivateValueDirectEndogenous name payoffFunctionDirect ; + outputs : ; + returns : ; + + :---------------------------: + + // Export state so that we can connect this game with others + outputs : stateNew ; + returns : ; + |] diff --git a/evm/src/Examples/HEVM.hs b/evm/src/Examples/HEVM.hs new file mode 100644 index 0000000..d71f37d --- /dev/null +++ b/evm/src/Examples/HEVM.hs @@ -0,0 +1,147 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedLabels #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeFamilies #-} + +module Examples.HEVM where + +import Control.Monad.ST +import Control.Monad.Trans.State.Strict +import Data.DoubleWord +import qualified Data.Map as Map +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.IO as T +import Debug.Trace +import EVM (emptyContract, exec1) +import EVM.Exec +import EVM.Fetch (zero) +import EVM.Format +import EVM.Prelude +import EVM.Stepper (evm, interpret, runFully) +import EVM.TH +import EVM.Types +import EVM.Effects +import GHC.Float +import OpenGames hiding (dependentDecision, fromFunctions, fromLens) +import OpenGames.Engine.Diagnostics +import OpenGames.Engine.HEVMGames +import OpenGames.Preprocessor hiding (Lit) +import Optics.Core (at, over, preview, set, view, (%), (%?), (&), (.~)) + +$(loadAll [mkContractFileInfo "solidity/Withdraw.sol" [mkContractInfo "Piggybank" "store"]]) + +deposit :: EthTransaction +deposit = store_deposit userContractAddress 1000 10_000_000 + +dummyTx :: Word256 -> EthTransaction +dummyTx = store_retrieve userContractAddress 0 100000000 + +userContractAddress = LitAddr 0x1234 + +withdrawContractAddress = LitAddr 0x1000 + +transactionList :: Word256 -> [EthTransaction] +transactionList max = [dummyTx n | n <- [1 .. max]] + +playerManual globalState = + [opengame| + inputs : ; + feedback : ; + :-------: + + operation : hevmDecision "AllPlayers" (transactionList 2) ; + outputs : transactions ; + returns : balance finalState userContractAddress; + + inputs : transactions, globalState; + feedback : ; + operation : fromLensM (uncurry sendAndRun) (const pure) ; + outputs : finalState ; + returns : ; + + :-------: + outputs: ; + returns : ; +|] + +playerAutomatic = + [opengame| + inputs : ; + feedback : ; + :-------: + + operation : hevmDecision "AllPlayers" (transactionList 2) ; + outputs : transactions ; + returns : balance finalState userContractAddress; + + inputs : transactions; + feedback : ; + operation : fromLensM sendAndRun' (const pure) ; + outputs : finalState ; + returns : ; + + :-------: + outputs: ; + returns : ; +|] + +outcome = do + i <- stToIO initial + let newI = setupAddresses [(userContractAddress, Lit 1_000_000_000)] i + newI <- runEnv defaultEnv $ interpret (zero 0 (Just 0)) newI (evm (makeTxCall deposit) >> runFully) + let term :- Nil = evaluate (playerManual newI) ((pure (dummyTx 1)) :- Nil) void + let t' = evalStateT term newI + tevaluated <- stToIO t' + generateOutput ([tevaluated] :- Nil) + +outcomeAutomatic = do + i <- stToIO initial + putStrLn "initial contracts:" + print $ getAllContracts i + let newI = setupAddresses [(userContractAddress, Lit 1_000_000_000)] i + putStrLn "setup contracts:" + print $ getAllContracts newI + newI <- runEnv defaultEnv $ interpret (zero 0 (Just 0)) newI (evm (makeTxCall deposit) >> runFully) + let term :- Nil = evaluate (playerAutomatic) ((pure (dummyTx 1)) :- Nil) void + let t' = evalStateT term newI + putStrLn "end contracts:" + print $ getAllContracts newI + tevaluated <- stToIO t' + pure $ generateOutputStr ([tevaluated] :- Nil) + +showVM :: VM Concrete s -> Text +showVM vm = + T.unlines + [ "Contracts:", + indent 2 . T.unlines . Map.elems $ Map.mapWithKey (\a c -> T.pack (show a) <> " :\n " <> showContract c) vm.env.contracts, + -- , "Storage: " <> (formatExpr vm.env.storage) + -- + "CallValue: " <> (formatExpr vm.state.callvalue), + "Result: " <> (T.pack $ show vm.result) + ] + +showContract :: Contract -> Text +showContract c = + T.unlines + [ "balance: " <> (T.pack $ show c.balance) + ] + +interp = do + i <- stToIO initial + let newI = setupAddresses [(userContractAddress, Lit 1_000_000_000)] i + newI <- runEnv defaultEnv $ interpret (zero 0 (Just 0)) newI (evm (makeTxCall deposit) >> runFully) + let storage = preview (#env % #contracts % at withdrawContractAddress %? #storage) newI + let orig = preview (#env % #contracts % at withdrawContractAddress %? #origStorage) newI + traceM ("storage: " <> show storage) + traceM ("origStorage: " <> show orig) + T.putStrLn (showVM newI) diff --git a/evm/src/Examples/Lido.hs b/evm/src/Examples/Lido.hs new file mode 100644 index 0000000..0da2372 --- /dev/null +++ b/evm/src/Examples/Lido.hs @@ -0,0 +1,17 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE TemplateHaskell #-} + +module Examples.Lido where + +import EVM.TH + +$(loadAll [mkContractFileInfo "DualGovernance.sol" [mkContractInfo "DualGovernance" "dualgov"]]) +-- lido = loadContracts [("EmergencyProtectedTimelockModel", "solidity/EmergencyProtectedTimelockModel.sol")] +-- lido = [loadContract| file : solidity/LidoReward.sol, contract : LidoExecutionLayerRewardsVault, name : lido] + +-- lido = loadContracts [("LidoExecutionLayerRewardsVault", "solidity/LidoReward.sol")] +-- lido = [loadContract| file : solidity/LidoReward.sol, contract : LidoExecutionLayerRewardsVault, name : lido] +-- + +-- $(loadAll [ContractInfo "solidity/EmergencyProtectedTimelockModel.sol" "TimelockModel" "model"]) diff --git a/evm/src/Examples/Prisoner.hs b/evm/src/Examples/Prisoner.hs new file mode 100644 index 0000000..f1efed8 --- /dev/null +++ b/evm/src/Examples/Prisoner.hs @@ -0,0 +1,104 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedLabels #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeFamilies #-} + +module Examples.Prisoner where + +import Control.Monad.Trans.State.Strict (evalStateT) +import EVM.Fetch (zero) +import EVM.Prelude +import EVM.Stepper (evm, interpret, runFully) +import EVM.TH +import EVM.Types +import OpenGames hiding (dependentDecision, fromFunctions, fromLens) +import OpenGames.Engine.HEVMGames +import OpenGames.Preprocessor hiding (Lit) + +$(loadAll [mkContractFileInfo "solidity/Prisonner.sol" [mkContractInfo "Prison" "prison"]]) + +player1 = LitAddr 0x1234 + +player2 = LitAddr 0x1235 + +p1defect = prison_defect player1 0 10_000_000 + +p2defect = prison_defect player2 0 10_000_000 + +p1coop = prison_cooperate player1 0 10_000_000 + +p2coop = prison_cooperate player2 0 10_000_000 + +-- each player can either cooperate or defect +optionsPlayer1 = + [ prison_defect player1 0 10_000_000, + prison_cooperate player1 0 10_000_000 + ] + +optionsPlayer2 = + [ prison_defect player2 0 10_000_000, + prison_cooperate player2 0 10_000_000 + ] + +sendAndRunBoth (a, b) = sendAndRunAll [a, b] + +hevmDilemma = + [opengame| + inputs : ; + feedback : ; + :-----------: + + operation : hevmDecision "player1" optionsPlayer1 ; + outputs : decisionP1 ; + returns : balance finalState player1 ; + + operation : hevmDecision "player2" optionsPlayer2 ; + outputs : decisionP2 ; + returns : balance finalState player2 ; + + inputs : decisionP1, decisionP2 ; + feedback : ; + operation : fromLensM sendAndRunBoth (const pure); + outputs : finalState ; + returns : ; + + :-----------: + outputs: ; + returns: ; + |] + +outcomeAutomatic = do + let addresses = + [ (player1, Lit 1_000_000_000), + (player2, Lit 1_000_000_000), + (prison_contract, Lit 10_000) + ] + i <- setupAddresses addresses <$> stToIO initial + let aaa :- bbb :- Nil = evaluate hevmDilemma (const p1coop :- const p2coop :- Nil) void + evaluated1 <- stToIO (evalStateT aaa i) + evaluated2 <- stToIO (evalStateT bbb i) + let out1 = generateOutputStr (evaluated1 :- evaluated2 :- Nil) + + let aaa :- bbb :- Nil = evaluate hevmDilemma (const p1defect :- const p2coop :- Nil) void + evaluated1 <- stToIO (evalStateT aaa i) + evaluated2 <- stToIO (evalStateT bbb i) + let out2 = generateOutputStr (evaluated1 :- evaluated2 :- Nil) + let aaa :- bbb :- Nil = evaluate hevmDilemma (const p1coop :- const p2defect :- Nil) void + evaluated1 <- stToIO (evalStateT aaa i) + evaluated2 <- stToIO (evalStateT bbb i) + let out3 = generateOutputStr (evaluated1 :- evaluated2 :- Nil) + let aaa :- bbb :- Nil = evaluate hevmDilemma (const p1defect :- const p2defect :- Nil) void + evaluated1 <- stToIO (evalStateT aaa i) + evaluated2 <- stToIO (evalStateT bbb i) + + let out4 = generateOutputStr (evaluated1 :- evaluated2 :- Nil) + pure (out1 ++ out2 ++ out3 ++ out4) diff --git a/evm/src/OpenGames/Engine/Copy.hs b/evm/src/OpenGames/Engine/Copy.hs new file mode 100644 index 0000000..58eb3e1 --- /dev/null +++ b/evm/src/OpenGames/Engine/Copy.hs @@ -0,0 +1,52 @@ +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE OverloadedLabels #-} +{-# LANGUAGE NoFieldSelectors #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleInstances #-} + +module OpenGames.Engine.Copy where + +import Control.Monad.ST +import Control.Monad.Trans.State.Strict (StateT, get, modify, put) +import Data.Kind +import Data.Vector.Unboxed.Mutable +import EVM.Types +import Optics.Core +import Optics.State + +class Copy (a :: Type -> Type) where + copy :: StateT (a s) (ST s) (a s) + +class Restore (a :: Type -> Type) where + restore :: a s -> StateT (a s) (ST s) () + +instance Copy (VM Concrete) where + copy = do + state <- get + let st = state ^. #state + let fr = state ^. #frames + st' <- copyFrameState st + fr' <- traverse copyFrame fr + let newState = + state + & #state + .~ st' + & #frames + .~ fr' + & #result + .~ Nothing + pure newState + where + copyFrame :: Frame Concrete s -> StateT (VM Concrete s) (ST s) (Frame Concrete s) + copyFrame (Frame ctx state) = Frame ctx <$> copyFrameState state + copyFrameState :: FrameState Concrete s -> StateT (VM Concrete s) (ST s) (FrameState Concrete s) + copyFrameState oldFrame = do + let mem = oldFrame ^. #memory + mem' <- case mem of + ConcreteMemory mem -> ConcreteMemory <$> clone mem + SymbolicMemory x -> pure (SymbolicMemory x) + + pure (oldFrame & #memory .~ mem') + +instance Restore (VM Concrete) where + restore = put diff --git a/evm/src/OpenGames/Engine/HEVMGames.hs b/evm/src/OpenGames/Engine/HEVMGames.hs new file mode 100644 index 0000000..b469a82 --- /dev/null +++ b/evm/src/OpenGames/Engine/HEVMGames.hs @@ -0,0 +1,95 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeFamilies #-} + +module OpenGames.Engine.HEVMGames where + +import Control.Monad.ST +import Control.Monad.Trans (lift) +import Control.Monad.Trans.State.Strict (StateT, evalStateT, execStateT, modify) +import Data.Foldable (maximumBy) +import Data.HashMap as HM hiding (map, mapMaybe, null) +import Data.Maybe (fromJust) +import Data.Ord (comparing) +import Data.Utils +import EVM.Types (VM, W256, VMType(..)) +import GHC.Float (int2Double) +import GHC.ST +import OpenGames.Engine.Copy +import OpenGames.Engine.Diagnostics +import OpenGames.Engine.OpenGames hiding (lift) +import OpenGames.Engine.OpticClass +import OpenGames.Engine.TLL + +type OpenGameM m a b x s y r = OpenGame (MonadOpticM m W256) (MonadContextM m W256) a b x s y r + +type HEVMState = StateT (VM Concrete RealWorld) (ST RealWorld) + +type HEVMGame a b x s y r = OpenGameM HEVMState a b x s y r + +-- converting words to double for diagnostic reasons +word2Double :: W256 -> Double +word2Double x = fromInteger (fromIntegral x) + +hevmDecision :: + forall x y. + (Show y) => + String -> + [y] -> + OpenGameM HEVMState '[x -> y] '[HEVMState (DiagnosticInfoBayesian x y)] x () y W256 +hevmDecision name ys = OpenGame play eval + where + play :: List '[x -> y] -> MonadOpticM HEVMState W256 x () y W256 + play (strat :- Nil) = + MonadOpticM + (\input -> pure ((), strat input)) + (\() payoff -> modify (adjustOrAdd (+ payoff) payoff name)) + + eval :: + List '[x -> y] -> + MonadContextM HEVMState W256 x () y W256 -> + List '[HEVMState (DiagnosticInfoBayesian x y)] + eval (strat :- Nil) (MonadContextM h k) = output :- Nil + where + output :: HEVMState (DiagnosticInfoBayesian x y) + output = do + (residual, observation) <- h + let u y = do + saveState <- copy + payoff <- evalStateT (k residual y) HM.empty + restore saveState + pure payoff + let actualMove = strat observation + actualPayoff <- u actualMove + allResults <- traverse (\move -> (move,) <$> u move) ys + let (optimalMove, optimalPayoff) = maximumBy (comparing snd) allResults + return $ + DiagnosticInfoBayesian + { equilibrium = actualPayoff == optimalPayoff, + player = name, + optimalMove = optimalMove, + strategy = pure (strat observation), + optimalPayoff = word2Double optimalPayoff, + context = error "impossible to implement", + payoff = word2Double actualPayoff, + state = observation, + unobservedState = "()" + } + +fromLens :: (x -> y) -> (x -> r -> s) -> HEVMGame '[] '[] x s y r +fromLens v u = + OpenGame + { play = \Nil -> MonadOpticM (\x -> return (x, v x)) (\x r -> return (u x r)), + evaluate = \Nil _ -> Nil + } + +fromFunctions :: (x -> y) -> (r -> s) -> HEVMGame '[] '[] x s y r +fromFunctions f g = fromLens f (const g) + +fromLensM :: (x -> HEVMState y) -> (x -> r -> HEVMState s) -> HEVMGame '[] '[] x s y r +fromLensM f g = + OpenGame + { play = \Nil -> MonadOpticM (\x -> (x,) <$> f x) (\x r -> lift $ g x r), + evaluate = \Nil _ -> Nil + } diff --git a/evm/stack.yaml b/evm/stack.yaml new file mode 100644 index 0000000..272e5b1 --- /dev/null +++ b/evm/stack.yaml @@ -0,0 +1,101 @@ +# This file was automatically generated by 'stack init' +# +# Some commonly used options have been documented as comments in this file. +# For advanced use and comprehensive documentation of the format, please see: +# https://docs.haskellstack.org/en/stable/yaml_configuration/ + +# Resolver to choose a 'specific' stackage snapshot or a compiler version. +# A snapshot resolver dictates the compiler version and the set of packages +# to be used for project dependencies. For example: +# +# resolver: lts-3.5 +# resolver: nightly-2015-09-21 +# resolver: ghc-7.10.2 +# +# The location of a snapshot can be provided as a file or url. Stack assumes +# a snapshot provided as a file might change, whereas a url resource does not. +# +# resolver: ./custom-snapshot.yaml +# resolver: https://example.com/snapshots/2018-01-01.yaml +resolver: lts-22.0 + +# User packages to be built. +# Various formats can be used as shown in the example below. +# +# packages: +# - some-directory +# - https://example.com/foo/bar/baz-0.0.2.tar.gz +# subdirs: +# - auto-update +# - wai +packages: +- . +- .. +# Dependency packages to be pulled from upstream that are not in the resolver. +# These entries can reference officially published versions as well as +# forks / in-progress versions pinned to a git hash. For example: +# +extra-deps: + - HSH-2.1.3@sha256:71ded11b224f5066373ce985ec63b10c87129850b33916736dd64fa2bea9ea0a,1705 + - poly-0.5.1.0@sha256:8c574f017913982fd70567545bec423823ff5bdf236c19d1443f3af47e0218ba,3418 + - restless-git-0.7@sha256:346a5775a586f07ecb291036a8d3016c3484ccdc188b574bcdec0a82c12db293,968 + - smt2-parser-0.1.0.1@sha256:1e1a4565915ed851c13d1e6b8bb5185cf5d454da3b43170825d53e221f753d77,1421 + - spawn-0.3@sha256:b91e01d8f2b076841410ae284b32046f91471943dc799c1af77d666c72101f02,1162 + - spool-0.1@sha256:77780cbfc2c0be23ff2ea9e474062f3df97fcd9db946ee0b3508280a923b83e2,1461 + - vector-sized-1.5.0@sha256:1c85b70dbfe6fbdcc58d2706c626e6e38d1900276bbb6f1ba6cfee83941f45e6,1836 + - monad-bayes-1.3.0.3@sha256:c99b035f2c76ef56cb7471126d7e6ef70bd06135ad6882def759289942130850,6635 + - git: https://github.com/ethereum/hevm + commit: 46ca5e37fae75bc9d5f10d7a9242684f8eae2590 + + + # - HSH-2.1.3@sha256:71ded11b224f5066373ce985ec63b10c87129850b33916736dd64fa2bea9ea0a,1705 + # - restless-git-0.7@sha256:346a5775a586f07ecb291036a8d3016c3484ccdc188b574bcdec0a82c12db293,968 + # - s-cargot-0.1.4.0@sha256:61ea1833fbb4c80d93577144870e449d2007d311c34d74252850bb48aa8c31fb,3525 + # - semver-range-0.2.8@sha256:44918080c220cf67b6e7c8ad16f01f3cfe1ac69d4f72e528e84d566348bb23c3,1941 + # - probability-0.2.7@sha256:3e9fe130af3bd75e791e1add902eb2caab170cc95373281c0a9626b01fc3b104,2869 + # - splitmix-0.1 + # - mwc-random-0.15.0.2 + # - monad-bayes-1.1.0 + # - smt2-parser-0.1.0.1 + # - spawn-0.3 + # - spool-0.1 + # - git: https://github.com/ethereum/hevm + # commit: c5e40507c35e6f2119aeb5f0740dc3564100ebdc + # - git: https://github.com/ethereum/act.git + # subdirs: + # - src + # commit: 52e99daf3121a4e6a6cb28255e862cf8e83cf4cd + +allow-newer: true + +# - acme-missiles-0.3 +# - git: https://github.com/commercialhaskell/stack.git +# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a +# +# Override default flag values for local packages and extra-deps +# flags: {} + +# Extra package databases containing global packages +# extra-package-dbs: [] + +# Control whether we use the GHC we find on the path +system-ghc: true +# +# Require a specific version of stack, using version ranges +# require-stack-version: -any # Default +# require-stack-version: ">=2.1" +# +# Override the architecture used by stack, especially useful on Windows +# Change to x86_64 for Intel and aarch64 for ARM/apple silicon +arch: aarch64 +# +# Extra directories used by stack for building +# extra-include-dirs: [/path/to/dir] +# extra-lib-dirs: [/path/to/dir] +# +# Allow a newer minor version of GHC than the snapshot specifies +# compiler-check: newer-minor +# +ghc-options: + "$locals": -fwarn-incomplete-patterns + diff --git a/evm/stack.yaml.lock b/evm/stack.yaml.lock new file mode 100644 index 0000000..cde9eb9 --- /dev/null +++ b/evm/stack.yaml.lock @@ -0,0 +1,79 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: +- completed: + hackage: HSH-2.1.3@sha256:71ded11b224f5066373ce985ec63b10c87129850b33916736dd64fa2bea9ea0a,1705 + pantry-tree: + sha256: 95f49f9dad6e4976d1b53c59fd4405a978ca8baecc721d508a030615241d69be + size: 473 + original: + hackage: HSH-2.1.3@sha256:71ded11b224f5066373ce985ec63b10c87129850b33916736dd64fa2bea9ea0a,1705 +- completed: + hackage: poly-0.5.1.0@sha256:8c574f017913982fd70567545bec423823ff5bdf236c19d1443f3af47e0218ba,3418 + pantry-tree: + sha256: e59a1e3809fee49968cf9505edf849109d733e4e795b49e2d5fdef1a4993c31d + size: 2531 + original: + hackage: poly-0.5.1.0@sha256:8c574f017913982fd70567545bec423823ff5bdf236c19d1443f3af47e0218ba,3418 +- completed: + hackage: restless-git-0.7@sha256:346a5775a586f07ecb291036a8d3016c3484ccdc188b574bcdec0a82c12db293,968 + pantry-tree: + sha256: 26b37a66c08215e18a914600aae8a61a6ba4611243a0b31ea27437d6c83701cb + size: 269 + original: + hackage: restless-git-0.7@sha256:346a5775a586f07ecb291036a8d3016c3484ccdc188b574bcdec0a82c12db293,968 +- completed: + hackage: smt2-parser-0.1.0.1@sha256:1e1a4565915ed851c13d1e6b8bb5185cf5d454da3b43170825d53e221f753d77,1421 + pantry-tree: + sha256: c048b3037a35ab6ca5b33d865d7a0b0f56a0ccc942dd57cbbab6af380770ee13 + size: 447 + original: + hackage: smt2-parser-0.1.0.1@sha256:1e1a4565915ed851c13d1e6b8bb5185cf5d454da3b43170825d53e221f753d77,1421 +- completed: + hackage: spawn-0.3@sha256:b91e01d8f2b076841410ae284b32046f91471943dc799c1af77d666c72101f02,1162 + pantry-tree: + sha256: 3fa87961ef3166c0093ebae68dea83cf2d7b9e131a2db28687b696f077c6f81a + size: 262 + original: + hackage: spawn-0.3@sha256:b91e01d8f2b076841410ae284b32046f91471943dc799c1af77d666c72101f02,1162 +- completed: + hackage: spool-0.1@sha256:77780cbfc2c0be23ff2ea9e474062f3df97fcd9db946ee0b3508280a923b83e2,1461 + pantry-tree: + sha256: 48eada528a8eda2fcf0d3517a239c59a699acff96111f427833ba2b04bd6111f + size: 322 + original: + hackage: spool-0.1@sha256:77780cbfc2c0be23ff2ea9e474062f3df97fcd9db946ee0b3508280a923b83e2,1461 +- completed: + hackage: vector-sized-1.5.0@sha256:1c85b70dbfe6fbdcc58d2706c626e6e38d1900276bbb6f1ba6cfee83941f45e6,1836 + pantry-tree: + sha256: 026c260a2c1c6b2deab166dcbeb8888eb284d7e9e73ab837d88d530933929588 + size: 1302 + original: + hackage: vector-sized-1.5.0@sha256:1c85b70dbfe6fbdcc58d2706c626e6e38d1900276bbb6f1ba6cfee83941f45e6,1836 +- completed: + hackage: monad-bayes-1.3.0.3@sha256:c99b035f2c76ef56cb7471126d7e6ef70bd06135ad6882def759289942130850,6635 + pantry-tree: + sha256: b096ffbc426039793134d917a107079a8179c7576969ee06c50fe33a42fcbcdf + size: 4852 + original: + hackage: monad-bayes-1.3.0.3@sha256:c99b035f2c76ef56cb7471126d7e6ef70bd06135ad6882def759289942130850,6635 +- completed: + commit: 46ca5e37fae75bc9d5f10d7a9242684f8eae2590 + git: https://github.com/ethereum/hevm + name: hevm + pantry-tree: + sha256: c855c40c933fec1b2f9f1eac366a658be3ad0afda03e930dfdc2d6cb4e1d3788 + size: 7720 + version: 0.53.0 + original: + commit: 46ca5e37fae75bc9d5f10d7a9242684f8eae2590 + git: https://github.com/ethereum/hevm +snapshots: +- completed: + sha256: e176944bc843f740e05242fa7a66ca1f440c127e425254f7f1257f9b19add23f + size: 712153 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/0.yaml + original: lts-22.0 diff --git a/evm/tests/Spec.hs b/evm/tests/Spec.hs new file mode 100644 index 0000000..0adb5c0 --- /dev/null +++ b/evm/tests/Spec.hs @@ -0,0 +1,22 @@ +import Data.ByteString.Lazy.Char8 (pack) +import qualified Examples.HEVM as HEVM +import qualified Examples.Prisoner as P +import Test.Tasty +import Test.Tasty.Golden + +main :: IO () +main = + defaultMain $ + testGroup + "HEVM tests" + [ goldenVsStringDiff + "Should detect deviation when better transaction is available" + (\ref new -> ["git", "diff", "--no-index", ref, new]) + "golden/hevm.golden" + (pack <$> HEVM.outcomeAutomatic), + goldenVsStringDiff + "Should detect deviation for the prisoner's dilemma" + (\ref new -> ["git", "diff", "--no-index", ref, new]) + "golden/prisoner.golden" + (pack <$> P.outcomeAutomatic) + ] diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..b8aa410 --- /dev/null +++ b/flake.lock @@ -0,0 +1,129 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1733015953, + "narHash": "sha256-t4BBVpwG9B4hLgc6GUBuj3cjU7lP/PJfpTHuSqE+crk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ac35b104800bff9028425fec3b6e8a41de2bbfff", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "solc": "solc" + } + }, + "solc": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": [ + "nixpkgs" + ], + "solc-macos-amd64-list-json": "solc-macos-amd64-list-json" + }, + "locked": { + "lastModified": 1731758759, + "narHash": "sha256-NX4+V6Q8bwopah0oza/Dpf6UsYNGbokW2kE9qT3wdHY=", + "owner": "hellwolf", + "repo": "solc.nix", + "rev": "0714c24cd521b9eb3ee435818c5d743ac6179176", + "type": "github" + }, + "original": { + "owner": "hellwolf", + "repo": "solc.nix", + "type": "github" + } + }, + "solc-macos-amd64-list-json": { + "flake": false, + "locked": { + "narHash": "sha256-KBEEpcDeKtVvCeguRP0D499yg9O5Jef9Nxn3yfrmw9g=", + "type": "file", + "url": "https://github.com/ethereum/solc-bin/raw/67f45d8/macosx-amd64/list.json" + }, + "original": { + "type": "file", + "url": "https://github.com/ethereum/solc-bin/raw/67f45d8/macosx-amd64/list.json" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..aa50554 --- /dev/null +++ b/flake.nix @@ -0,0 +1,55 @@ +{ + description = "Opt-in Stack Flake"; + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + inputs.flake-utils.url = "github:numtide/flake-utils"; + inputs.solc = { + url = "github:hellwolf/solc.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = { self, nixpkgs, flake-utils, solc }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ + solc.overlay + ]; + }; + + hPkgs = pkgs.haskell.packages."ghc963"; # need to match Stackage LTS version from stack.yaml resolver + + devTools = [ + hPkgs.ghc # GHC compiler in the desired version (will be available on PATH) + pkgs.zlib # External C library needed by some Haskell packages + pkgs.libff + pkgs.git + pkgs.secp256k1 + stack-wrapped + (solc.mkDefault pkgs pkgs.solc_0_8_26) + ]; + + stack-wrapped = pkgs.symlinkJoin { + name = "stack"; # will be available as the usual `stack` in terminal + paths = [ pkgs.stack ]; + buildInputs = [ pkgs.makeWrapper ]; + postBuild = '' + wrapProgram $out/bin/stack \ + --add-flags "\ + --no-nix \ + --system-ghc \ + --no-install-ghc \ + " + ''; + }; + in { + devShells.default = pkgs.mkShell { + buildInputs = devTools; + + # Make external Nix c libraries like zlib known to GHC, like + # pkgs.haskell.lib.buildStackProject does + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath devTools; + }; + }); +} + diff --git a/graphics/Main.hs b/graphics/app/Main.hs similarity index 100% rename from graphics/Main.hs rename to graphics/app/Main.hs diff --git a/graphics/package.yaml b/graphics/package.yaml new file mode 100644 index 0000000..16c45f9 --- /dev/null +++ b/graphics/package.yaml @@ -0,0 +1,11 @@ + +executables: + graphics: + main: Main.hs + source-dirs: graphics + dependencies: + - open-games-hs + - graphviz + - template-haskell + - criterion + diff --git a/graphics/stack.yaml b/graphics/stack.yaml new file mode 100644 index 0000000..14c9c3d --- /dev/null +++ b/graphics/stack.yaml @@ -0,0 +1,19 @@ + +resolver: lts-21.0 + +packages: +- .. + +extra-deps: + - monad-bayes-1.2.0@sha256:bf83cf8e6d163c461b9964dbff48d4476b2a82421962af317b3a3d0d738ea2a2,6456 + - poly-0.5.1.0@sha256:8c574f017913982fd70567545bec423823ff5bdf236c19d1443f3af47e0218ba,3418 + - hspec-2.11.7@sha256:2869580a2a29e7beb6268ea3dc561583f4ae229ed1f47fb1c92e8c09ce35acec,1763 + - vector-sized-1.5.0@sha256:1c85b70dbfe6fbdcc58d2706c626e6e38d1900276bbb6f1ba6cfee83941f45e6,1836 + - hspec-core-2.11.7@sha256:90d8873356d7e15f843bc523360e206e8e356ff6b82a1fa4b3889dc31d073ea1,6814 + - hspec-discover-2.11.7@sha256:6307eb16d308258a99a242025df50217d835ba0a3f205b1202a100a175877b38,2169 + - hspec-expectations-0.8.4@sha256:4237f094a7931202ff57ac6475542b0b314b50a7024550e2b6eb87cfb0d4ff93,1702 + + +nix: + enable: true + packages: [libff] diff --git a/graphics/stack.yaml.lock b/graphics/stack.yaml.lock new file mode 100644 index 0000000..58f707e --- /dev/null +++ b/graphics/stack.yaml.lock @@ -0,0 +1,61 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: +- completed: + hackage: monad-bayes-1.2.0@sha256:bf83cf8e6d163c461b9964dbff48d4476b2a82421962af317b3a3d0d738ea2a2,6456 + pantry-tree: + sha256: ce4a9db9888b589ae56493862ae62f2efb5af8d17d3d55bac0b3f59177881b2b + size: 3823 + original: + hackage: monad-bayes-1.2.0@sha256:bf83cf8e6d163c461b9964dbff48d4476b2a82421962af317b3a3d0d738ea2a2,6456 +- completed: + hackage: poly-0.5.1.0@sha256:8c574f017913982fd70567545bec423823ff5bdf236c19d1443f3af47e0218ba,3418 + pantry-tree: + sha256: e59a1e3809fee49968cf9505edf849109d733e4e795b49e2d5fdef1a4993c31d + size: 2531 + original: + hackage: poly-0.5.1.0@sha256:8c574f017913982fd70567545bec423823ff5bdf236c19d1443f3af47e0218ba,3418 +- completed: + hackage: hspec-2.11.7@sha256:2869580a2a29e7beb6268ea3dc561583f4ae229ed1f47fb1c92e8c09ce35acec,1763 + pantry-tree: + sha256: f241a7710ebee50583f9aebff61aaf4d36619ba7d6538b39b80ee4695c70552e + size: 584 + original: + hackage: hspec-2.11.7@sha256:2869580a2a29e7beb6268ea3dc561583f4ae229ed1f47fb1c92e8c09ce35acec,1763 +- completed: + hackage: vector-sized-1.5.0@sha256:1c85b70dbfe6fbdcc58d2706c626e6e38d1900276bbb6f1ba6cfee83941f45e6,1836 + pantry-tree: + sha256: 026c260a2c1c6b2deab166dcbeb8888eb284d7e9e73ab837d88d530933929588 + size: 1302 + original: + hackage: vector-sized-1.5.0@sha256:1c85b70dbfe6fbdcc58d2706c626e6e38d1900276bbb6f1ba6cfee83941f45e6,1836 +- completed: + hackage: hspec-core-2.11.7@sha256:90d8873356d7e15f843bc523360e206e8e356ff6b82a1fa4b3889dc31d073ea1,6814 + pantry-tree: + sha256: 8db12c1f6965d9f0898f04d7b5f1d77682c8ab2d5c394c4a431229df7c4acb14 + size: 6231 + original: + hackage: hspec-core-2.11.7@sha256:90d8873356d7e15f843bc523360e206e8e356ff6b82a1fa4b3889dc31d073ea1,6814 +- completed: + hackage: hspec-discover-2.11.7@sha256:6307eb16d308258a99a242025df50217d835ba0a3f205b1202a100a175877b38,2169 + pantry-tree: + sha256: 141b4987d519ad1ca1114737f510f20adc2456bf44c040f41a63792f47d009eb + size: 829 + original: + hackage: hspec-discover-2.11.7@sha256:6307eb16d308258a99a242025df50217d835ba0a3f205b1202a100a175877b38,2169 +- completed: + hackage: hspec-expectations-0.8.4@sha256:4237f094a7931202ff57ac6475542b0b314b50a7024550e2b6eb87cfb0d4ff93,1702 + pantry-tree: + sha256: 87681840d430b84686f83f1ab8b5873b09c349775698665233443914acf9ba2b + size: 741 + original: + hackage: hspec-expectations-0.8.4@sha256:4237f094a7931202ff57ac6475542b0b314b50a7024550e2b6eb87cfb0d4ff93,1702 +snapshots: +- completed: + sha256: 1867d84255dff8c87373f5dd03e5a5cb1c10a99587e26c8793e750c54e83ffdc + size: 639139 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/21/0.yaml + original: lts-21.0 diff --git a/open-games-hs.cabal b/open-games-hs.cabal index bf882bc..869319c 100644 --- a/open-games-hs.cabal +++ b/open-games-hs.cabal @@ -1,6 +1,6 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.1. +-- This file has been generated from package.yaml by hpack version 0.36.0. -- -- see: https://github.com/sol/hpack @@ -19,6 +19,7 @@ extra-source-files: library exposed-modules: + Data.Utils OpenGames OpenGames.Engine.BayesianGames OpenGames.Engine.Diagnostics @@ -26,6 +27,7 @@ library OpenGames.Engine.OpenGames OpenGames.Engine.OpticClass OpenGames.Engine.TLL + OpenGames.Engine.IOGames OpenGames.Preprocessor OpenGames.Preprocessor.BlockSyntax OpenGames.Preprocessor.Codegen @@ -49,90 +51,35 @@ library Examples.Markov.TestSimpleMonteCarlo.Continuation Graphics other-modules: - Data.Utils Examples.Markov.NStageMarkov Examples.Staking.AndGateMarkov Examples.Staking.AndGateMarkovMC Examples.Token.Concrete OpenGames.Engine.AtomicGames OpenGames.Engine.BayesianGamesNonState - OpenGames.Engine.IOGames OpenGames.Engine.Nat + OpenGames.Engine.Stateful OpenGames.Engine.Vec Paths_open_games_hs hs-source-dirs: src build-depends: QuickCheck - , act - , ad - , base >=4.7 && <5 - , comonad - , criterion - , extra - , fgl - , ghc - , graphviz - , hashable - , hashmap - , haskeline - , lens - , monad-bayes - , mtl - , mwc-random - , parsec - , poly - , probability - , profunctors - , random - , template-haskell - , transformers - , typed-process - , vector - default-language: Haskell2010 - -executable act-exec - main-is: Main.hs - other-modules: - Act - Act.Execution - Act.Prelude - Act.TH - Act.TH.Extractor - Act.TH.State - Act.Utils - EVM.TH - Examples.AmmGenerated - Examples.EVM - Examples.Player - Paths_open_games_hs - hs-source-dirs: - act - ghc-options: -fwarn-unused-imports -Wno-partial-type-signatures - build-depends: - QuickCheck - , act , ad , base >=4.7 && <5 - , bytestring , comonad - , containers , criterion - , data-dword , extra , fgl - , file-embed , ghc , graphviz , hashable , hashmap , haskeline - , hevm >=0.51.0 , lens , monad-bayes , mtl , mwc-random - , open-games-hs , optics-core , optics-extra , parsec @@ -140,45 +87,6 @@ executable act-exec , probability , profunctors , random - , rosezipper - , template-haskell - , text - , transformers - , typed-process - , validation - , vector - default-language: Haskell2010 - -executable graphics - main-is: Main.hs - other-modules: - Paths_open_games_hs - hs-source-dirs: - graphics - build-depends: - QuickCheck - , act - , ad - , base >=4.7 && <5 - , comonad - , criterion - , extra - , fgl - , ghc - , graphviz - , hashable - , hashmap - , haskeline - , lens - , monad-bayes - , mtl - , mwc-random - , open-games-hs - , parsec - , poly - , probability - , profunctors - , random , template-haskell , transformers , typed-process @@ -194,7 +102,6 @@ executable open-games-exe ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: QuickCheck - , act , ad , base >=4.7 && <5 , comonad @@ -211,6 +118,8 @@ executable open-games-exe , mtl , mwc-random , open-games-hs + , optics-core + , optics-extra , parsec , poly , probability diff --git a/package.yaml b/package.yaml index a2011c9..422d188 100644 --- a/package.yaml +++ b/package.yaml @@ -13,7 +13,7 @@ extra-source-files: library: source-dirs: src exposed-modules: - # - OpenGames.Engine.AtomicGames + - Data.Utils - OpenGames - OpenGames.Engine.BayesianGames - OpenGames.Engine.Diagnostics @@ -21,6 +21,7 @@ library: - OpenGames.Engine.OpenGames - OpenGames.Engine.OpticClass - OpenGames.Engine.TLL + - OpenGames.Engine.IOGames - OpenGames.Preprocessor - OpenGames.Preprocessor.BlockSyntax - OpenGames.Preprocessor.Codegen @@ -46,7 +47,6 @@ library: dependencies: - base >=4.7 && <5 - - act - mtl - ghc - transformers @@ -71,6 +71,8 @@ dependencies: - vector - monad-bayes - criterion + - optics-core + - optics-extra executables: @@ -84,30 +86,3 @@ executables: dependencies: - open-games-hs - act-exec: - main: Main.hs - source-dirs: act - ghc-options: - - -fwarn-unused-imports - - -Wno-partial-type-signatures - dependencies: - - act - - data-dword - - open-games-hs - - optics-core - - optics-extra - - rosezipper - - file-embed - - bytestring - - validation - - hevm >= 0.51.0 - - containers - - text - graphics: - main: Main.hs - source-dirs: graphics - dependencies: - - open-games-hs - - graphviz - - template-haskell - - criterion diff --git a/src/Examples/Auctions/DutchAuction.hs b/src/Examples/Auctions/DutchAuction.hs index e5080aa..3949972 100644 --- a/src/Examples/Auctions/DutchAuction.hs +++ b/src/Examples/Auctions/DutchAuction.hs @@ -14,8 +14,8 @@ module Examples.Auctions.DutchAuction where -import Control.Monad.State hiding (lift, state, void) -import qualified Control.Monad.State as ST +import qualified Control.Monad.Trans as ST (lift) +import Control.Monad.Trans.State.Strict import OpenGames import OpenGames.Preprocessor diff --git a/src/Examples/Markov/NStageMarkov.hs b/src/Examples/Markov/NStageMarkov.hs index 66b5e7e..461e51d 100644 --- a/src/Examples/Markov/NStageMarkov.hs +++ b/src/Examples/Markov/NStageMarkov.hs @@ -6,8 +6,8 @@ module Examples.Markov.NStageMarkov where -import Control.Monad.State hiding (lift, state, void) -import qualified Control.Monad.State as ST +import qualified Control.Monad.Trans as ST (lift) +import Control.Monad.Trans.State.Strict import Data.Tuple.Extra (uncurry3) import Examples.SimultaneousMoves (ActionPD (..)) import Numeric.Probability.Distribution hiding (filter, lift, map) diff --git a/src/Examples/Markov/RepeatedPD.hs b/src/Examples/Markov/RepeatedPD.hs index 9530e1a..1fffe34 100644 --- a/src/Examples/Markov/RepeatedPD.hs +++ b/src/Examples/Markov/RepeatedPD.hs @@ -6,8 +6,8 @@ module Examples.Markov.RepeatedPD where -import Control.Monad.State hiding (lift, state, void) -import qualified Control.Monad.State as ST +import qualified Control.Monad.Trans as ST (lift) +import Control.Monad.Trans.State.Strict import Examples.SimultaneousMoves (ActionPD (..), prisonersDilemmaMatrix) import OpenGames import OpenGames.Preprocessor diff --git a/src/Examples/Markov/TestSimpleMonteCarlo/Continuation.hs b/src/Examples/Markov/TestSimpleMonteCarlo/Continuation.hs index 076a848..95e5441 100644 --- a/src/Examples/Markov/TestSimpleMonteCarlo/Continuation.hs +++ b/src/Examples/Markov/TestSimpleMonteCarlo/Continuation.hs @@ -11,8 +11,9 @@ module Examples.Markov.TestSimpleMonteCarlo.Continuation ) where -import Control.Monad.State hiding (lift, state, void) -import qualified Control.Monad.State as ST +import qualified Control.Monad as ST (replicateM_) +import qualified Control.Monad.Trans as ST (lift) +import Control.Monad.Trans.State.Strict as ST import Data.Utils import qualified Data.Vector as V import Examples.SimultaneousMoves (ActionPD (..), prisonersDilemmaMatrix) @@ -98,13 +99,13 @@ transformStratTuple (x :- y :- Nil) = -- extract continuation extractContinuation :: MonadOptic s () a () -> s -> StateT Vector IO () -extractContinuation (MonadOptic v u) x = do +extractContinuation (MonadOpticM v u) x = do (z, a) <- ST.lift (v x) u z () -- extract next state (action) extractNextState :: MonadOptic s () a () -> s -> IO a -extractNextState (MonadOptic v _) x = do +extractNextState (MonadOpticM v _) x = do (z, a) <- v x pure a @@ -142,7 +143,7 @@ sampleDetermineContinuationPayoffs :: (ActionPD, ActionPD) -> StateT Vector IO () sampleDetermineContinuationPayoffs sampleSize iterator strat initialValue = do - replicateM_ sampleSize (determineContinuationPayoffs iterator strat initialValue) + ST.replicateM_ sampleSize (determineContinuationPayoffs iterator strat initialValue) v <- ST.get ST.put (average sampleSize v) @@ -188,7 +189,7 @@ determineContinuationPayoffsIO iterator strat action = do executeStrat = play prisonersDilemmaCont strat -- fix context used for the evaluation -contextCont iterator strat initialAction = MonadContext (pure ((), initialAction)) (\_ action -> determineContinuationPayoffsIO iterator strat action) +contextCont iterator strat initialAction = MonadContextM (pure ((), initialAction)) (\_ action -> determineContinuationPayoffsIO iterator strat action) repeatedPDEq iterator strat initialAction = evaluate prisonersDilemmaCont strat context where diff --git a/src/Examples/Markov/TwoStageMarkov.hs b/src/Examples/Markov/TwoStageMarkov.hs index 0563693..2913293 100644 --- a/src/Examples/Markov/TwoStageMarkov.hs +++ b/src/Examples/Markov/TwoStageMarkov.hs @@ -6,8 +6,8 @@ module Examples.Markov.TwoStageMarkov where -import Control.Monad.State hiding (lift, state, void) import qualified Control.Monad.State as ST +import Control.Monad.Trans.State.Strict import Data.Tuple.Extra (uncurry3) import Examples.SimultaneousMoves (ActionPD (..), Location (..)) import Numeric.Probability.Distribution hiding (filter, lift, map) diff --git a/src/Examples/Staking/AndGateMarkov.hs b/src/Examples/Staking/AndGateMarkov.hs index 243a54d..67407a4 100644 --- a/src/Examples/Staking/AndGateMarkov.hs +++ b/src/Examples/Staking/AndGateMarkov.hs @@ -6,13 +6,13 @@ module Examples.Staking.AndGateMarkov where -import Control.Monad.State hiding (lift, state, void) -import qualified Control.Monad.State as ST +import qualified Control.Monad.Trans as ST (lift) +import Control.Monad.Trans.State.Strict hiding (lift, state, void) +import qualified Control.Monad.Trans.State.Strict +import Debug.Trace import OpenGames.Engine.Engine import OpenGames.Preprocessor -import Debug.Trace - -- TODO change the structure of the continuation iteration -- DONE What effect happens through the state hack and the discounting? The discounting at least does not seem to make a difference. diff --git a/src/Examples/Staking/AndGateMarkovMC.hs b/src/Examples/Staking/AndGateMarkovMC.hs index 137ccd8..16adfa4 100644 --- a/src/Examples/Staking/AndGateMarkovMC.hs +++ b/src/Examples/Staking/AndGateMarkovMC.hs @@ -8,8 +8,8 @@ module Examples.Staking.AndGateMarkovMC where -import Control.Monad.State hiding (lift, state, void) import qualified Control.Monad.State as ST +import Control.Monad.Trans.State.Strict hiding (lift, state, void) import qualified Data.Vector as V import Numeric.Probability.Distribution hiding (filter, lift, map) import OpenGames hiding (Agent, discount, fromFunctions, fromLens, nature) @@ -322,24 +322,24 @@ andGateGame (AndGateMarkovParams {..}) = -- extract continuation extractContinuation :: MonadOptic s () s () -> s -> StateT Vector IO () -extractContinuation (MonadOptic v u) x = do +extractContinuation (MonadOpticM v u) x = do (z, a) <- ST.lift (v x) u z () -- extract next state (action) extractNextState :: MonadOptic s () s () -> s -> IO s -extractNextState (MonadOptic v _) x = do +extractNextState (MonadOpticM v _) x = do (z, a) <- v x pure a extractContinuation2 :: MonadOptic Double () ([Double], Bool) () -> Double -> StateT Vector IO () -extractContinuation2 (MonadOptic v u) x = do +extractContinuation2 (MonadOpticM v u) x = do (z, a) <- ST.lift (v x) u z () -- extract next state (action) extractNextState2 :: MonadOptic Double () ([Double], Bool) () -> Double -> IO ([Double], Bool) -extractNextState2 (MonadOptic v _) x = do +extractNextState2 (MonadOpticM v _) x = do (z, a) <- v x pure a @@ -373,11 +373,11 @@ determineContinuationPayoffs3 iterator initialAction = do executeStrat = play (andGateGame andGateMarkovTestParams) strategyTuple -- fix context used for the evaluation -contextCont parameters iterator strat initialAction = MonadContext (pure ((), initialAction)) (\_ action -> determineContinuationPayoffs parameters iterator strat action) +contextCont parameters iterator strat initialAction = MonadContextM (pure ((), initialAction)) (\_ action -> determineContinuationPayoffs parameters iterator strat action) -contextCont' parameters iterator strat initialAction = MonadContext (pure ((), initialAction)) (\_ action -> determineContinuationPayoffs' parameters iterator strat action) +contextCont' parameters iterator strat initialAction = MonadContextM (pure ((), initialAction)) (\_ action -> determineContinuationPayoffs' parameters iterator strat action) -contextCont3 iterator initialAction = MonadContext (pure ((), initialAction)) (\_ action -> determineContinuationPayoffs3 iterator action) +contextCont3 iterator initialAction = MonadContextM (pure ((), initialAction)) (\_ action -> determineContinuationPayoffs3 iterator action) ----------- -- Strategy diff --git a/src/OpenGames/Engine/BayesianGames.hs b/src/OpenGames/Engine/BayesianGames.hs index 7e718ec..948f6d4 100644 --- a/src/OpenGames/Engine/BayesianGames.hs +++ b/src/OpenGames/Engine/BayesianGames.hs @@ -36,7 +36,8 @@ module OpenGames.Engine.BayesianGames where import Control.Arrow hiding ((+++), (+:+)) -import Control.Monad.State hiding (state) +import Control.Monad.Trans (lift) +import Control.Monad.Trans.State.Strict hiding (state) import Data.Foldable import Data.HashMap as HM hiding (map, mapMaybe, null) -- temporary lol diff --git a/src/OpenGames/Engine/Diagnostics.hs b/src/OpenGames/Engine/Diagnostics.hs index 55056cf..54005d0 100644 --- a/src/OpenGames/Engine/Diagnostics.hs +++ b/src/OpenGames/Engine/Diagnostics.hs @@ -11,14 +11,25 @@ module OpenGames.Engine.Diagnostics ( DiagnosticInfoBayesian (..), + PrintOutput (..), generateOutput, + generateOutputStr, generateIsEq, + showDiagnosticInfoL, + nextState, + nextContinuation, + equilibriumMap, + toEquilibrium, + generateEquilibrium ) where import OpenGames.Engine.OpticClass import OpenGames.Engine.TLL +import qualified Control.Monad.Trans.State.Strict as ST hiding (state) +import qualified Control.Monad.Trans as ST (lift) + -------------------------------------------------------- -- Diagnosticinformation and processesing of information -- for standard game-theoretic analysis @@ -98,6 +109,10 @@ data PrintOutput = PrintOutput instance (Show y, Ord y, Show x) => Apply PrintOutput [DiagnosticInfoBayesian x y] String where apply _ x = showDiagnosticInfoL x +-- is this ok? +instance (Show y, Ord y, Show x) => Apply PrintOutput (DiagnosticInfoBayesian x y) String where + apply _ x = showDiagnosticInfoL [x] + instance (Show y, Ord y, Show x) => Apply PrintOutput (Maybe [DiagnosticInfoBayesian x y]) String where apply _ x = showDiagnosticInfoL (maybe [] id x) @@ -106,10 +121,38 @@ data Concat = Concat instance Apply Concat String (String -> String) where apply _ x = \y -> x ++ "\n NEWGAME: \n" ++ y +-- for apply output of equilibrium function +data Equilibrium = Equilibrium + +instance Apply Equilibrium [DiagnosticInfoBayesian x y] Bool where + apply _ x = equilibriumMap x + +data And = And + +instance Apply And Bool (Bool -> Bool) where + apply _ x = \y -> y && x + +-- map diagnostics to equilibrium +toEquilibrium :: DiagnosticInfoBayesian x y -> Bool +toEquilibrium = equilibrium + +equilibriumMap :: [DiagnosticInfoBayesian x y] -> Bool +equilibriumMap = and . fmap toEquilibrium + --------------------- -- main functionality -- all information for all players +generateOutputStr :: + forall xs. + ( MapL PrintOutput xs (ConstMap String xs), + FoldrL Concat String (ConstMap String xs) + ) => + List xs -> + String +generateOutputStr hlist = + "----Analytics begin----" ++ (foldrL Concat "" $ mapL @_ @_ @(ConstMap String xs) PrintOutput hlist) ++ "----Analytics end----\n" + generateOutput :: forall xs. ( MapL PrintOutput xs (ConstMap String xs), @@ -118,8 +161,7 @@ generateOutput :: List xs -> IO () generateOutput hlist = - putStrLn $ - "----Analytics begin----" ++ (foldrL Concat "" $ mapL @_ @_ @(ConstMap String xs) PrintOutput hlist) ++ "----Analytics end----\n" + putStrLn $ generateOutputStr hlist -- output equilibrium relevant information generateIsEq :: @@ -132,3 +174,31 @@ generateIsEq :: generateIsEq hlist = putStrLn $ "----Analytics begin----" ++ (foldrL Concat "" $ mapL @_ @_ @(ConstMap String xs) PrintIsEq hlist) ++ "----Analytics end----\n" + +-- give equilibrium value for further use +generateEquilibrium :: forall xs. + ( MapL Equilibrium xs (ConstMap Bool xs) + , FoldrL And Bool (ConstMap Bool xs) + ) => List xs -> Bool +generateEquilibrium hlist = foldrL And True $ mapL @_ @_ @(ConstMap Bool xs) Equilibrium hlist + + +--------------------------------------- +-- Helper functionality for play output + +-- Transform the optic into the next state given some input +nextState :: + StochasticStatefulOptic s t a b -> + s -> + Stochastic a +nextState (StochasticStatefulOptic v _) x = do + (z, a) <- v x + pure a + +nextContinuation + :: StochasticStatefulOptic s t a () + -> s + -> ST.StateT Vector Stochastic t +nextContinuation (StochasticStatefulOptic v u) x = do + (z,a) <- ST.lift (v x) + u z () diff --git a/src/OpenGames/Engine/IOGames.hs b/src/OpenGames/Engine/IOGames.hs index cbd06d2..fd4ae10 100644 --- a/src/OpenGames/Engine/IOGames.hs +++ b/src/OpenGames/Engine/IOGames.hs @@ -6,6 +6,7 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} @@ -15,7 +16,12 @@ module OpenGames.Engine.IOGames Agent (..), DiagnosticsMC (..), dependentDecisionIO, + MonadOptic, + MonadOpticM (..), + MonadContext, + MonadContextM (..), fromLens, + fromLensM, fromFunctions, nature, discount, @@ -23,11 +29,15 @@ module OpenGames.Engine.IOGames where import Control.Arrow hiding ((+:+)) -import Control.Monad.State hiding (state) +import Control.Monad (replicateM) +import Control.Monad.ST +import Control.Monad.Trans (lift) +import Control.Monad.Trans.State.Strict hiding (state) import Data.Foldable import Data.HashMap as HM hiding (map, mapMaybe, null) import Data.Ord (comparing) import Data.Utils +import GHC.ST import OpenGames.Engine.OpenGames hiding (lift) import OpenGames.Engine.OpticClass import OpenGames.Engine.TLL @@ -51,7 +61,12 @@ data DiagnosticsMC y = DiagnosticsMC deriving (Show) -- NOTE This ignores the state -dependentDecisionIO :: (Eq x, Show x, Ord y, Show y) => String -> Int -> [y] -> IOOpenGame '[Kleisli CondensedTableV x y] '[IO (DiagnosticsMC y)] x () y Double +dependentDecisionIO :: + (Eq x, Show x, Ord y, Show y) => + String -> + Int -> + [y] -> + IOOpenGame '[Kleisli CondensedTableV x y] '[IO (DiagnosticsMC y)] x () y Double -- s t a b -- ^ (average utility of current strategy, [average utility of all possible alternative actions]) @@ -65,9 +80,9 @@ dependentDecisionIO name sampleSize ys = OpenGame {play, evaluate} action <- genFromTable (runKleisli strat x) gS return ((), action) u () r = modify (adjustOrAdd (+ r) r name) - in MonadOptic v u + in MonadOpticM v u - evaluate (strat :- Nil) (MonadContext h k) = output :- Nil + evaluate (strat :- Nil) (MonadContextM h k) = output :- Nil where output = do zippedLs <- samplePayoffs @@ -116,18 +131,25 @@ dependentDecisionIO name sampleSize ys = OpenGame {play, evaluate} fromLens :: (x -> y) -> (x -> r -> s) -> IOOpenGame '[] '[] x s y r fromLens v u = OpenGame - { play = \Nil -> MonadOptic (\x -> return (x, v x)) (\x r -> return (u x r)), + { play = \Nil -> MonadOpticM (\x -> return (x, v x)) (\x r -> return (u x r)), evaluate = \Nil _ -> Nil } fromFunctions :: (x -> y) -> (r -> s) -> IOOpenGame '[] '[] x s y r fromFunctions f g = fromLens f (const g) +fromLensM :: (x -> IO y) -> (x -> r -> IO s) -> IOOpenGame '[] '[] x s y r +fromLensM f g = + OpenGame + { play = \Nil -> MonadOpticM (\x -> (x,) <$> f x) (\x r -> lift $ g x r), + evaluate = \Nil _ -> Nil + } + nature :: CondensedTableV x -> IOOpenGame '[] '[] () () x () nature table = OpenGame {play, evaluate} where play _ = - MonadOptic v u + MonadOpticM v u where v () = do g <- newStdGen @@ -145,6 +167,6 @@ discount name f = { play = \_ -> let v () = return ((), ()) u () () = modify (adjustOrAdd f (f 0) name) - in MonadOptic v u, + in MonadOpticM v u, evaluate = \_ _ -> Nil } diff --git a/src/OpenGames/Engine/OpticClass.hs b/src/OpenGames/Engine/OpticClass.hs index 4ec833b..2698492 100644 --- a/src/OpenGames/Engine/OpticClass.hs +++ b/src/OpenGames/Engine/OpticClass.hs @@ -3,6 +3,7 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE TupleSections #-} module OpenGames.Engine.OpticClass ( Stochastic (..), @@ -12,6 +13,8 @@ module OpenGames.Engine.OpticClass StochasticOptic (..), StochasticContext (..), MonadOptic (..), + MonadOpticM (..), + MonadContextM (..), MonadContext (..), Optic (..), Precontext (..), @@ -21,7 +24,8 @@ module OpenGames.Engine.OpticClass ) where -import Control.Monad.State hiding (state) +import Control.Monad.Trans (lift) +import Control.Monad.Trans.State.Strict hiding (state) import Data.HashMap as HM hiding (map, mapMaybe, null) import Numeric.Probability.Distribution hiding (lift) @@ -59,6 +63,8 @@ class ContextAdd c where --- replicate the old implementation of a stochastic context type Stochastic = T Double +type VectorI = HM.Map String + type Vector = HM.Map String Double data StochasticStatefulOptic s t a b where @@ -172,48 +178,51 @@ instance ContextAdd StochasticContext where then Nothing else Just (StochasticContext (fromFreqs fs) (\z a2 -> k z (Right a2))) --- Experimental non Stochastic --- Same as used in learning implementation --- Can be used for IO as well -data MonadOptic s t a b where - MonadOptic :: - (s -> IO (z, a)) -> - (z -> b -> StateT Vector IO t) -> - MonadOptic s t a b - -instance Optic MonadOptic where - lens v u = MonadOptic (\s -> return (s, v s)) (\s b -> return (u s b)) - (>>>>) (MonadOptic v1 u1) (MonadOptic v2 u2) = MonadOptic v u +data MonadOpticM m i s t a b where + MonadOpticM :: + (s -> m (z, a)) -> + (z -> b -> StateT (VectorI i) m t) -> + MonadOpticM m i s t a b + +instance (Monad m) => Optic (MonadOpticM m i) where + lens v u = MonadOpticM (\s -> return (s, v s)) (\s b -> return (u s b)) + (>>>>) (MonadOpticM v1 u1) (MonadOpticM v2 u2) = MonadOpticM v u where v s = do (z1, a) <- v1 s; (z2, p) <- v2 a; return ((z1, z2), p) u (z1, z2) q = do b <- u2 z2 q; u1 z1 b - (&&&&) (MonadOptic v1 u1) (MonadOptic v2 u2) = MonadOptic v u + (&&&&) (MonadOpticM v1 u1) (MonadOpticM v2 u2) = MonadOpticM v u where v (s1, s2) = do (z1, a1) <- v1 s1; (z2, a2) <- v2 s2; return ((z1, z2), (a1, a2)) u (z1, z2) (b1, b2) = do t1 <- u1 z1 b1; t2 <- u2 z2 b2; return (t1, t2) - (++++) (MonadOptic v1 u1) (MonadOptic v2 u2) = MonadOptic v u + (++++) (MonadOpticM v1 u1) (MonadOpticM v2 u2) = MonadOpticM v u where v (Left s1) = do (z1, a1) <- v1 s1; return (Left z1, Left a1) v (Right s2) = do (z2, a2) <- v2 s2; return (Right z2, Right a2) u (Left z1) b = u1 z1 b u (Right z2) b = u2 z2 b -data MonadContext s t a b where - MonadContext :: (Show z) => IO (z, s) -> (z -> a -> StateT Vector IO b) -> MonadContext s t a b +data MonadContextM m i s t a b where + MonadContextM :: (Show z) => m (z, s) -> (z -> a -> StateT (VectorI i) m b) -> MonadContextM m i s t a b -instance Precontext MonadContext where - void = MonadContext (return ((), ())) (\() () -> return ()) +instance (Monad m) => Precontext (MonadContextM m i) where + void = MonadContextM (return ((), ())) (\() () -> return ()) -instance Context MonadContext MonadOptic where - cmap (MonadOptic v1 u1) (MonadOptic v2 u2) (MonadContext h k) = +instance (Monad m) => Context (MonadContextM m i) (MonadOpticM m i) where + cmap (MonadOpticM v1 u1) (MonadOpticM v2 u2) (MonadContextM h k) = let h' = do (z, s) <- h; (_, s') <- v1 s; return (z, s') k' z a = do (z', a') <- lift (v2 a); b' <- k z a'; u2 z' b' - in MonadContext h' k' - (//) (MonadOptic v u) (MonadContext h k) = + in MonadContextM h' k' + (//) (MonadOpticM v u) (MonadContextM h k) = let h' = do (z, (s1, s2)) <- h; return ((z, s1), s2) k' (z, s1) a2 = do (_, a1) <- lift (v s1); (_, b2) <- k z (a1, a2); return b2 - in MonadContext h' k' - (\\) (MonadOptic v u) (MonadContext h k) = + in MonadContextM h' k' + (\\) (MonadOpticM v u) (MonadContextM h k) = let h' = do (z, (s1, s2)) <- h; return ((z, s2), s1) k' (z, s2) a1 = do (_, a2) <- lift (v s2); (b1, _) <- k z (a1, a2); return b1 - in MonadContext h' k' + in MonadContextM h' k' + +type MonadOptic = MonadOpticM IO Double + +type MonadContext = MonadContextM IO Double + +-- maybe `fromFunctions` should live here diff --git a/src/OpenGames/Engine/Stateful.hs b/src/OpenGames/Engine/Stateful.hs new file mode 100644 index 0000000..9031e27 --- /dev/null +++ b/src/OpenGames/Engine/Stateful.hs @@ -0,0 +1,89 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE TemplateHaskell #-} + +module OpenGames.Engine.Stateful where + +import OpenGames +import OpenGames.Engine.Engine +import OpenGames.Preprocessor + +-- import Control.Monad.State + +data Transaction = Tx Int + deriving (Eq, Ord, Show) + +data State = State {contracts :: [Int]} + deriving (Show) + +sendAndRun :: [Transaction] -> State -> State +sendAndRun = undefined + +load :: [Int] -> State -> State +load = undefined + +strategy :: [Transaction] +strategy = undefined + +balance :: State -> String -> Double +balance st name = undefined -- balance (lookup "marx" st) + +-- actDecision1 :: String -> [Tx] -> OG ..... +actDecision name strategies = + [opengame| inputs : observedInput ; + :---: + + inputs : observedInput ; + operation : dependentDecision name (const strategies) ; + outputs : tx ; + returns : balance finalState name ; + + :---: + outputs : tx ; + returns : finalState; +|] + +append = (++) + +runBlockchain = + [opengame| + inputs : initialState ; + :---: + + operation : actDecision "Alice" [Tx 1, Tx 2] ; + outputs : aliceTx ; + returns : finalState ; + + operation : actDecision "Bob" [Tx 3, Tx 4] ; + outputs : bobTx ; + returns : finalState ; + + inputs : (append (pure aliceTx) (pure bobTx)), initialState ; + operation : fromFunctions (uncurry sendAndRun) id ; + outputs : finalState ; +|] + +-- +-- playerAutomatic usd = +-- [opengame| +-- inputs : globalState, ; +-- feedback : ; +-- :-------: +-- +-- operation : dependentDecision "AllPlayers" (const strategy) ; +-- outputs : transactions ; +-- returns : (payoff finalState) +-- +-- inputs : globalState, transactions; +-- feedback : ; +-- operation : forwardFunction (uncurry sendAndRun) ; +-- outputs : finalState ; +-- returns : ; +-- +-- :-------: +-- outputs: ; +-- returns : ; + +-- | ] diff --git a/stack.yaml b/stack.yaml index 1e33dd9..2fb14f8 100644 --- a/stack.yaml +++ b/stack.yaml @@ -17,7 +17,7 @@ # # resolver: ./custom-snapshot.yaml # resolver: https://example.com/snapshots/2018-01-01.yaml -resolver: lts-20.22 +resolver: lts-22.0 # User packages to be built. # Various formats can be used as shown in the example below. @@ -35,24 +35,9 @@ packages: # forks / in-progress versions pinned to a git hash. For example: # extra-deps: - - HSH-2.1.3@sha256:71ded11b224f5066373ce985ec63b10c87129850b33916736dd64fa2bea9ea0a,1705 - - restless-git-0.7@sha256:346a5775a586f07ecb291036a8d3016c3484ccdc188b574bcdec0a82c12db293,968 - - s-cargot-0.1.4.0@sha256:61ea1833fbb4c80d93577144870e449d2007d311c34d74252850bb48aa8c31fb,3525 - - semver-range-0.2.8@sha256:44918080c220cf67b6e7c8ad16f01f3cfe1ac69d4f72e528e84d566348bb23c3,1941 - # - text-format-0.3.2@sha256:2a3fc17cf87120fcfdbca62a0da59759755549285841cdc1483081c35fb3d115,1814 - - probability-0.2.7@sha256:3e9fe130af3bd75e791e1add902eb2caab170cc95373281c0a9626b01fc3b104,2869 - # - random-1.2.0 - - splitmix-0.1 - - mwc-random-0.15.0.2 - - monad-bayes-1.1.0 - - smt2-parser-0.1.0.1 - - spawn-0.3 - - spool-0.1 - - hevm-0.51.0 - - git: https://github.com/ethereum/act.git - subdirs: - - src - commit: 52e99daf3121a4e6a6cb28255e862cf8e83cf4cd + - monad-bayes-1.3.0.4@sha256:101a60697c2bf0fae60157284bb8127e6a84e37c82c562cd7a262b50a34f3b5d,6650 + - vector-sized-1.5.0@sha256:1c85b70dbfe6fbdcc58d2706c626e6e38d1900276bbb6f1ba6cfee83941f45e6,1836 + - poly-0.5.1.0@sha256:8c574f017913982fd70567545bec423823ff5bdf236c19d1443f3af47e0218ba,3418 allow-newer: true @@ -75,7 +60,7 @@ allow-newer: true # # Override the architecture used by stack, especially useful on Windows # Change to x86_64 for Intel and aarch64 for ARM/apple silicon -arch: x86_64 +arch: aarch64 # # Extra directories used by stack for building # extra-include-dirs: [/path/to/dir] @@ -86,7 +71,3 @@ arch: x86_64 # ghc-options: "$locals": -fwarn-incomplete-patterns - -nix: - enable: true - packages: [libff, secp256k1, zlib] diff --git a/stack.yaml.lock b/stack.yaml.lock index a9a227f..b122f21 100644 --- a/stack.yaml.lock +++ b/stack.yaml.lock @@ -5,105 +5,29 @@ packages: - completed: - hackage: HSH-2.1.3@sha256:71ded11b224f5066373ce985ec63b10c87129850b33916736dd64fa2bea9ea0a,1705 + hackage: monad-bayes-1.3.0.4@sha256:101a60697c2bf0fae60157284bb8127e6a84e37c82c562cd7a262b50a34f3b5d,6650 pantry-tree: - sha256: 95f49f9dad6e4976d1b53c59fd4405a978ca8baecc721d508a030615241d69be - size: 473 + sha256: 2d9102c645cd43c5144f3a02873e841d0be2f70748dca7091a578ae909ed22af + size: 4852 original: - hackage: HSH-2.1.3@sha256:71ded11b224f5066373ce985ec63b10c87129850b33916736dd64fa2bea9ea0a,1705 + hackage: monad-bayes-1.3.0.4@sha256:101a60697c2bf0fae60157284bb8127e6a84e37c82c562cd7a262b50a34f3b5d,6650 - completed: - hackage: restless-git-0.7@sha256:346a5775a586f07ecb291036a8d3016c3484ccdc188b574bcdec0a82c12db293,968 + hackage: vector-sized-1.5.0@sha256:1c85b70dbfe6fbdcc58d2706c626e6e38d1900276bbb6f1ba6cfee83941f45e6,1836 pantry-tree: - sha256: 26b37a66c08215e18a914600aae8a61a6ba4611243a0b31ea27437d6c83701cb - size: 269 + sha256: 026c260a2c1c6b2deab166dcbeb8888eb284d7e9e73ab837d88d530933929588 + size: 1302 original: - hackage: restless-git-0.7@sha256:346a5775a586f07ecb291036a8d3016c3484ccdc188b574bcdec0a82c12db293,968 + hackage: vector-sized-1.5.0@sha256:1c85b70dbfe6fbdcc58d2706c626e6e38d1900276bbb6f1ba6cfee83941f45e6,1836 - completed: - hackage: s-cargot-0.1.4.0@sha256:61ea1833fbb4c80d93577144870e449d2007d311c34d74252850bb48aa8c31fb,3525 + hackage: poly-0.5.1.0@sha256:8c574f017913982fd70567545bec423823ff5bdf236c19d1443f3af47e0218ba,3418 pantry-tree: - sha256: 280c1899ea1a905f01785e175ff029748e9913388d25e02a8e4cdceb9a92b722 - size: 1467 + sha256: e59a1e3809fee49968cf9505edf849109d733e4e795b49e2d5fdef1a4993c31d + size: 2531 original: - hackage: s-cargot-0.1.4.0@sha256:61ea1833fbb4c80d93577144870e449d2007d311c34d74252850bb48aa8c31fb,3525 -- completed: - hackage: semver-range-0.2.8@sha256:44918080c220cf67b6e7c8ad16f01f3cfe1ac69d4f72e528e84d566348bb23c3,1941 - pantry-tree: - sha256: fd72964da8246cc09d477b4c6e6f20971de058917d08d9f8183f5c0e2116f9c6 - size: 401 - original: - hackage: semver-range-0.2.8@sha256:44918080c220cf67b6e7c8ad16f01f3cfe1ac69d4f72e528e84d566348bb23c3,1941 -- completed: - hackage: probability-0.2.7@sha256:3e9fe130af3bd75e791e1add902eb2caab170cc95373281c0a9626b01fc3b104,2869 - pantry-tree: - sha256: ee8953628fe301a29be9ef64ebd96f8c704969fec2b4e0b39243e6499911b767 - size: 2711 - original: - hackage: probability-0.2.7@sha256:3e9fe130af3bd75e791e1add902eb2caab170cc95373281c0a9626b01fc3b104,2869 -- completed: - hackage: splitmix-0.1@sha256:d50c4d0801a35be7875a040470c09863342514930c82a7d25780a6c2efc4fda9,5249 - pantry-tree: - sha256: 2c884d06818f79030551aab192eee23734fd499361f4f4dcb4c51d1b1b2d786e - size: 1148 - original: - hackage: splitmix-0.1 -- completed: - hackage: mwc-random-0.15.0.2@sha256:109e0fb72ce64bda468fc44d9cb5abbf455d6337140b57eb851a8183baba0597,3372 - pantry-tree: - sha256: 67732b8c3612c58e5286541213550899dc9495a09ca5283fab4437c489e624b9 - size: 721 - original: - hackage: mwc-random-0.15.0.2 -- completed: - hackage: monad-bayes-1.1.0@sha256:8929887b2883e553b928dcc9b1326171c87b6aa26f11800dc8c55b119a9e9649,6123 - pantry-tree: - sha256: bf7f9b1351226a957c7ebd0c42316505be713690cd9d44425bd9cfd494a94161 - size: 3568 - original: - hackage: monad-bayes-1.1.0 -- completed: - hackage: smt2-parser-0.1.0.1@sha256:1e1a4565915ed851c13d1e6b8bb5185cf5d454da3b43170825d53e221f753d77,1421 - pantry-tree: - sha256: c048b3037a35ab6ca5b33d865d7a0b0f56a0ccc942dd57cbbab6af380770ee13 - size: 447 - original: - hackage: smt2-parser-0.1.0.1 -- completed: - hackage: spawn-0.3@sha256:b91e01d8f2b076841410ae284b32046f91471943dc799c1af77d666c72101f02,1162 - pantry-tree: - sha256: 3fa87961ef3166c0093ebae68dea83cf2d7b9e131a2db28687b696f077c6f81a - size: 262 - original: - hackage: spawn-0.3 -- completed: - hackage: spool-0.1@sha256:77780cbfc2c0be23ff2ea9e474062f3df97fcd9db946ee0b3508280a923b83e2,1461 - pantry-tree: - sha256: 48eada528a8eda2fcf0d3517a239c59a699acff96111f427833ba2b04bd6111f - size: 322 - original: - hackage: spool-0.1 -- completed: - hackage: hevm-0.51.0@sha256:6116fac8aa1434685e41e839dce6b107a64f94e1bc09609d467e81b55f434172,9294 - pantry-tree: - sha256: a4ed66030cbef539ce248f4fd056582ba80e3994fd989a113cdd5c7ca29e25f8 - size: 4296 - original: - hackage: hevm-0.51.0 -- completed: - commit: 52e99daf3121a4e6a6cb28255e862cf8e83cf4cd - git: https://github.com/ethereum/act.git - name: act - pantry-tree: - sha256: 78cc4643860657a2be70ba2f95d675f130c312800393a1cb52c88ec6701ce7bd - size: 1217 - subdir: src - version: 0.1.0.0 - original: - commit: 52e99daf3121a4e6a6cb28255e862cf8e83cf4cd - git: https://github.com/ethereum/act.git - subdir: src + hackage: poly-0.5.1.0@sha256:8c574f017913982fd70567545bec423823ff5bdf236c19d1443f3af47e0218ba,3418 snapshots: - completed: - sha256: dcf4fc28f12d805480ddbe8eb8c370e11db12f0461d0110a4240af27ac88d725 - size: 650255 - url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/22.yaml - original: lts-20.22 + sha256: e176944bc843f740e05242fa7a66ca1f440c127e425254f7f1257f9b19add23f + size: 712153 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/0.yaml + original: lts-22.0 diff --git a/tests/ArrowTest.hs b/tests/ArrowTest.hs deleted file mode 100644 index 5f417db..0000000 --- a/tests/ArrowTest.hs +++ /dev/null @@ -1,92 +0,0 @@ -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} - -module ArrowTest where - -import Data.Bifunctor -import Data.Bool -import Data.List -import Engine.Diagnostics -import Examples.Bayesian as B -import GHC.Real -import Language.Haskell.TH.Syntax as TH -import Numeric.Probability.Distribution -import Preprocessor.AbstractSyntax -import Preprocessor.Lambda -import Preprocessor.Parser -import Preprocessor.THSyntax -import Test.Hspec as Spec -import Test.QuickCheck - -btest = - "t | <- nature (uniform [Rat, Omerta]) -< | ;" - ++ "x | pdMatrix1 x y <- reindex const (decision \"prisoner1\" [Confess, DontConfess]) -< | ;" - ++ "y | pdMatrix2 t x y <- decision \"prisoner2\" [Confess, DontConfess] -< | t ;" - -doTest = - "t1, t2 | <- nature (do {t1 <- uniform [BOSType1, BOSType2]; t2 <- uniform [BOSType1, BOSType2]; return (t1, t2)}) -< | ;" - ++ " x | bos_bayesian_matrix1 t1 x y <- decision \"man\" [BayesianB, BayesianS] -< | t1;" - ++ " y | bos_bayesian_matrix2 t2 x y <- decision \"woman\" [BayesianB, BayesianS] -< | t2;" - -value :: GameAST String Lambda -value = - MkParsedBlock - [] - [] - [ MkParsedLine ["t"] [] (App (Var "nature") (App (Var "uniform") (LList [Var "Rat", Var "Omerta"]))) [] [], - MkParsedLine - ["x"] - [App (App (Var "pdMatrix1") (Var "x")) (Var "y")] - (App (App (Var "reindex") (Var "const")) (App (App (Var "decision") (Lit $ LString "prisoner1")) (LList [Var "Confess", Var "DontConfess"]))) - [] - [], - MkParsedLine - ["y"] - [App (App (App (Var "pdMatrix2") (Var "t")) (Var "x")) (Var "y")] - (App (App (Var "decision") (Lit $ LString "prisoner2")) (LList [Var "Confess", Var "DontConfess"])) - [] - [Var "t"] - ] - [] - [] - -simpleLine :: ParsedLine String String -simpleLine = MkParsedLine ["output"] ["input"] "middle" ["input2"] ["output2"] - -simpleLam :: ParsedLine Pattern Lambda -simpleLam = undefined -- bimap undefined Var simpleLine - -rangeTest = - "t1 | <- nature (uniform [0 .. 6]) -< | ;" - ++ "t2 | <- nature (uniform [0 .. 6]) -< | ;" - ++ "x | playerOneUtility t1 x y <- decision \"player1\" [0 .. 12] -< | t1;" - ++ "y | playerTwoUtility t2 x y <- decision \"player2\" [0 .. 12] -< | t2;" - -simple = - [ Line [] [] [|nature (uniform [0 .. 6])|] ["t1"] [], - Line [] [] [|nature (uniform [0 .. 6])|] ["t2"] [], - Line [[|t1|]] [] [|decision "player1" [0 .. 12]|] ["x"] [[|playerOneUtility t1 x y|]], - Line [[|t2|]] [] [|decision "player2" [0 .. 12]|] ["y"] [[|playerTwoUtility t2 x y|]] - ] - -convertLines :: [Line p (Q Exp)] -> Q (Block p Exp) -convertLines lines = do - lines <- sequence $ fmap sequence lines - pure (Block [] [] lines [] []) - --- main :: IO () --- main = do --- hspec $ parallel $ do --- describe "testing quasiquoted AST" $ parallel $ do --- ref <- Spec.runIO $ TH.runQ $ (Just . THS.compileBlock <$> convertLines simple) --- it "should parse the same freeOpenGame" $ do --- parseLambdaAsOpenGame rangeTest --- `shouldBe` ref --- --- it "should be the same bayesian AST" $ --- parseLambda btest --- `shouldBe` --- Right value