From 5647e64fb2e7e0784a4704ec6ef797aeb751a9f5 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Wed, 3 Sep 2025 12:37:26 +0200 Subject: [PATCH 01/19] SolcoreParser: remove quotes from string literals --- src/Solcore/Frontend/Parser/SolcoreParser.y | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Solcore/Frontend/Parser/SolcoreParser.y b/src/Solcore/Frontend/Parser/SolcoreParser.y index 67080e0d..b47241ab 100644 --- a/src/Solcore/Frontend/Parser/SolcoreParser.y +++ b/src/Solcore/Frontend/Parser/SolcoreParser.y @@ -390,7 +390,7 @@ PatList : Pattern %shift {[$1]} Literal :: { Literal } Literal : number {IntLit $ toInteger $1} - | stringlit {StrLit $1} + | stringlit {StrLit $ rmquotes $1} -- basic type definitions @@ -491,7 +491,7 @@ YulExpCommaList : YulExp {[$1]} YulLiteral :: { YLiteral } YulLiteral : number {YulNumber $ toInteger $1} - | stringlit {YulString $1} + | stringlit {YulString (rmquotes $1)} OptSemi :: { () } OptSemi : ';' { () } @@ -564,6 +564,9 @@ tupleExp [t1] = t1 tupleExp [t1, t2] = pairExp t1 t2 tupleExp (t1 : ts) = pairExp t1 (tupleExp ts) +rmquotes :: String -> String +rmquotes = read + parseError (Token (line, col) lexeme) = alexError $ "Parse error while processing lexeme: " ++ show lexeme ++ "\n at line " ++ show line ++ ", column " ++ show col From 228db27c08cf7164eb27048c52a643161e0d9ac9 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Thu, 9 Oct 2025 08:55:49 +0200 Subject: [PATCH 02/19] Add datasize & dataoffset Yul primitives --- src/Solcore/Frontend/TypeInference/TcStmt.hs | 1 - src/Solcore/Primitives/Primitives.hs | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Solcore/Frontend/TypeInference/TcStmt.hs b/src/Solcore/Frontend/TypeInference/TcStmt.hs index 8a9258a9..1325cce7 100644 --- a/src/Solcore/Frontend/TypeInference/TcStmt.hs +++ b/src/Solcore/Frontend/TypeInference/TcStmt.hs @@ -1080,7 +1080,6 @@ tcYulExp (YCall n es) (_ :=> t) <- freshInst sch ts <- mapM tcYulExp es t' <- freshTyVar - mapM_ (unify word) ts unify t (foldr (:->) t' ts) withCurrentSubst t' diff --git a/src/Solcore/Primitives/Primitives.hs b/src/Solcore/Primitives/Primitives.hs index 7b41553f..d870d09c 100644 --- a/src/Solcore/Primitives/Primitives.hs +++ b/src/Solcore/Primitives/Primitives.hs @@ -202,6 +202,8 @@ yulPrimOps = [ (Name "stop", monotype unit) , (Name "calldatacopy", monotype (word :-> word :-> word :-> word)) , (Name "codesize", monotype word) , (Name "codecopy", monotype (word :-> word :-> word :-> unit)) + , (Name "datasize", monotype (string :-> word)) + , (Name "dataoffset", monotype (string :-> word)) , (Name "extcodesize", monotype (word :-> word)) , (Name "extcodecopy", monotype (word :-> word :-> word :-> unit)) , (Name "returndatasize", monotype word) From f13829eecb6f339ab87eb53a922302dbd6ecafb2 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Thu, 9 Oct 2025 09:55:11 +0200 Subject: [PATCH 03/19] Use start as deployment entrypoint --- src/Solcore/Desugarer/EmitCore.hs | 5 ++--- src/Solcore/Desugarer/Specialise.hs | 15 ++++++--------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/Solcore/Desugarer/EmitCore.hs b/src/Solcore/Desugarer/EmitCore.hs index bef541f1..71672d89 100644 --- a/src/Solcore/Desugarer/EmitCore.hs +++ b/src/Solcore/Desugarer/EmitCore.hs @@ -146,14 +146,13 @@ emitCDecl cd@(CDataDecl dt) = do addData dt >> pure [] emitCDecl cd = debug ["!! emitCDecl ", show cd] >> pure [] --- By now, constructor should be converted to a function "constructor" - +-- look up the deployer start routine findConstructor :: [ContractDecl Id] -> Maybe (FunDef Id) findConstructor = go where go [] = Nothing go (CFunDecl d:ds)| isConstructor d = Just d go (_:ds) = go ds - isConstructor (FunDef sig _) = sigName sig == Name "constructor" + isConstructor (FunDef sig _) = sigName sig == "start" ----------------------------------------------------------------------- -- Translating function definitions diff --git a/src/Solcore/Desugarer/Specialise.hs b/src/Solcore/Desugarer/Specialise.hs index 3dc6224f..1fefd96b 100644 --- a/src/Solcore/Desugarer/Specialise.hs +++ b/src/Solcore/Desugarer/Specialise.hs @@ -220,12 +220,9 @@ specialiseTopDecl (TContr (Contract name args decls)) = withLocalState do -- Deployer code modify (\st -> st { specTable = emptyTable }) let deployerName = Name (pretty name <> "$Deployer") - let mconstructor = findConstructor decls - deployDecls <- case mconstructor of - Just c -> withLocalState do - cname' <- specConstructor c - st <- gets specTable - let cdecl = st Map.! cname' + mStart <- specEntry "start" + deployDecls <- case mStart of + Just s -> do depDecls <- getSpecialisedDecls -- use mutual to group constructor with its dependencies pure [CMutualDecl depDecls] @@ -255,8 +252,7 @@ getConstructor :: ContractDecl Id -> Maybe (Constructor Id) getConstructor (CConstrDecl c) = Just c getConstructor _ = Nothing - -specEntry :: Name -> SM () +specEntry :: Name -> SM (Maybe Name) specEntry name = withLocalState do let any = TVar (Name "any") let anytype = TyVar any @@ -264,9 +260,10 @@ specEntry name = withLocalState do case mres of Just (fd, ty, subst) -> do debug ["< resolution: ", show name, " : ", pretty ty, "@", pretty subst] - void(specFunDef fd) + Just <$> specFunDef fd Nothing -> do warns ["!! Warning: no resolution found for ", show name] + pure Nothing specConstructor (Constructor [] body) = do let sig = Signature [] [] (Name "constructor") [] (Just unit) From 9a8ecae751e9d420df4a5c87e3f8b8c2ebbfef9d Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Tue, 14 Oct 2025 17:14:29 +0200 Subject: [PATCH 04/19] Primitives: add mcopy --- src/Solcore/Primitives/Primitives.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Solcore/Primitives/Primitives.hs b/src/Solcore/Primitives/Primitives.hs index d870d09c..394562d5 100644 --- a/src/Solcore/Primitives/Primitives.hs +++ b/src/Solcore/Primitives/Primitives.hs @@ -208,6 +208,7 @@ yulPrimOps = [ (Name "stop", monotype unit) , (Name "extcodecopy", monotype (word :-> word :-> word :-> unit)) , (Name "returndatasize", monotype word) , (Name "returndatacopy", monotype (word :-> word :-> word :-> unit)) + , (Name "mcopy", monotype (word :-> word :-> word :-> unit)) , (Name "extcodehash", monotype (word :-> word)) , (Name "create", monotype (word :-> word :-> word :-> unit)) , (Name "create2", monotype (word :-> word :-> word :-> unit)) From d36a4a1b5fe2242514cd7dd6b851b2536e965740 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Wed, 15 Oct 2025 08:02:59 +0200 Subject: [PATCH 05/19] testsol.sh: improved deployment --- testsol.sh | 84 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/testsol.sh b/testsol.sh index 9c71a59c..5b075a2e 100644 --- a/testsol.sh +++ b/testsol.sh @@ -30,8 +30,8 @@ function hevmcore() { local hexfile=$base.hex rm -f -v $yulfile $hexfile cabal exec yule -- $1 --nodeploy -o $yulfile - solc --strict-assembly --bin --optimize --optimize-yul $yulfile | tail -1 > $hexfile - hevm exec --code-file $hexfile | awk -f parse_hevm_output.awk + hex=$(solc --strict-assembly --bin --optimize --optimize-yul $yulfile | tail -1) + hevm exec --code $hex | awk -f parse_hevm_output.awk } function hevmsol() { @@ -48,7 +48,7 @@ function hevmsol() { cabal exec sol-core -- -f $file $* && \ cabal exec yule -- $core --nodeploy -O -o $yulfile && \ solc --strict-assembly --bin --optimize $yulfile | tail -1 > $hexfile && \ - hevm exec --code-file $hexfile | awk -f parse_hevm_output.awk + hevm exec --code $(cat $hexfile) | awk -f parse_hevm_output.awk } @@ -65,22 +65,76 @@ function deploysol() { cabal exec sol-core -- -f $file $* && \ cabal exec yule -- $core -o $yulfile hex=$(solc --strict-assembly --bin --optimize --optimize-yul $yulfile | tail -1) - rawtx=$(cast mktx --private-key=$PRIVATE_KEY --create $hex) - addr=$(cast publish $rawtx | jq .contractAddress) + rawtx=$(cast mktx --private-key=$DEPLOYER_KEY --create $hex) + addr=$(cast publish $rawtx | jq .contractAddress | tr -d '"') echo $addr } function deploycore() { - local base=$(basename $1 .core) - local yulfile=$base.yul - echo $yulfile - local hexfile=$base.hex - rm -f -v $yulfile $hexfile - cabal exec yule -- $1 -o $yulfile - hex=$(solc --strict-assembly --bin --optimize --optimize-yul $yulfile | tail -1) - rawtx=$(cast mktx --private-key=$PRIVATE_KEY --create $hex) - addr=$(cast publish $rawtx | jq .contractAddress) - echo $addr + local base=$(basename $1 .core) + local yulfile=$base.yul + echo $yulfile + local hexfile=$base.hex + rm -f -v $yulfile $hexfile + cabal exec yule -- $1 -o $yulfile + hex=$(solc --strict-assembly --bin --optimize --optimize-yul $yulfile | tail -1) + rawtx=$(cast mktx --private-key=$DEPLOYER_KEY --create $hex) + addr=$(cast publish $rawtx | jq .contractAddress | tr -d '"') + echo $addr +} + +# function deployyul() { +# local yulfile=$1 +# local base=$(basename $1 .yul) +# hex=$(solc --strict-assembly --bin --optimize --optimize-yul $yulfile | tail -1) +# rawtx=$(cast mktx --private-key=$DEPLOYER_KEY --create $hex) +# addr=$(cast publish $rawtx | jq .contractAddress | tr -d '"') +# echo $addr +# } + +function deploysail() { + local sail=$1 + local base=$(basename $1 .core) + shift + local yulfile=$base.yul + echo $yulfile + local hexfile=$base.hex + rm -f -v $yulfile #$hexfile + echo cabal exec yule -- $sail -o $yulfile + cabal exec yule -- $sail -o $yulfile + #deployyul $1 $* +} + +function deployyul() { + local yulfile=$1 + shift + local data=$(cast ae $* | cut -c 3-) + local base=$(basename $yulfile .yul) + echo "Args: $*" + echo "ABI-enc: $data" + prog=$(solc --strict-assembly --bin --optimize --optimize-yul $yulfile | tail -1) + hex="$prog$data" + echo Hex: $hex + rawtx=$(cast mktx --private-key=$DEPLOYER_KEY --create $hex) + txoutput=$(cast publish $rawtx) + echo $txoutput | jq . + export contractAddress=$(echo $txoutput | jq .contractAddress | tr -d '"') + echo $contractAddress +} + +# deploy contract with 1 uint arg +function deployyul1() { + local yulfile=$1 + local data=$(cast ae "constructor(uint256)" $2 | cut -c 3-) + local base=$(basename $1 .yul) + prog=$(solc --strict-assembly --bin --optimize --optimize-yul $yulfile | tail -1) + hex="$prog$data" + echo Hex: $hex + rawtx=$(cast mktx --private-key=$DEPLOYER_KEY --create $hex) + txoutput=$(cast publish $rawtx) + echo $txoutput | jq . + export contractAddress=$(echo $txoutput | jq .contractAddress | tr -d '"') + echo $contractAddress } function sail() { From 62b9885521360d37856c76d971770bb946ed7913 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Wed, 15 Oct 2025 08:03:15 +0200 Subject: [PATCH 06/19] yule: simplified deployCode --- yule/Main.hs | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/yule/Main.hs b/yule/Main.hs index 170fd52e..ee44ce23 100644 --- a/yule/Main.hs +++ b/yule/Main.hs @@ -53,23 +53,9 @@ addRetCode c = c <> retCode where |] deployCode :: String -> Bool -> YulCode -deployCode name withConstructor = YulCode $ [yulBlock| - { - mstore(64, memoryguard(128)) - let memPtr := mload(64) - } - |] - <> callConstructor withConstructor - <> [yulBlock| - { datacopy(0, `dataoffset`, `datasize`) - return(0, `datasize`) - } |] - where - cname = yulString name - callConstructor True = pure [yulStmt| usr$constructor() |] - callConstructor False = [] - datasize = [yulExp| datasize(${cname}) |] -- YCall "datasize"[cname] - dataoffset = [yulExp| dataoffset(`cname`) |] +deployCode _name withStart = YulCode $ go withStart where + go True = [ [yulStmt| usr$start() |] ] + go False = [] createDeployment :: YulObject -> YulObject createDeployment (YulObject yulName yulCode [InnerObject(YulObject innerName innerCode [])]) From ee022db90fa5234f9c94bf314e8c5477fba11c56 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Tue, 21 Oct 2025 08:15:11 +0200 Subject: [PATCH 07/19] Fix parsing and pretty-printing of Yul for statements --- src/Language/Yul.hs | 7 +++---- src/Language/Yul/Parser.hs | 1 + test/Cases.hs | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Language/Yul.hs b/src/Language/Yul.hs index 42e186cb..dca82d61 100644 --- a/src/Language/Yul.hs +++ b/src/Language/Yul.hs @@ -89,7 +89,7 @@ hlist, vlist, nvlist, pprBlock :: Pretty a => [a] -> Doc hlist = hsep . map ppr vlist = vcat . map ppr nvlist = nest 2 . vlist -pprBlock stmts = lbrace $$ nvlist stmts $$ rbrace +pprBlock stmts = braces(nvlist stmts) instance Pretty YulObject where @@ -133,9 +133,8 @@ instance Pretty YulStmt where $$ maybe empty (\stmts -> text "default" <+> pprBlock stmts) def where pprCase (lit, stmts) = text "case" <+> ppr lit <+> pprBlock stmts ppr (YFor pre cond post stmts) = - text "for" <+> braces (hlist pre) - <+> ppr cond - <+> hlist post <+> pprBlock stmts + text "for" <+> braces (hlist pre) <+> ppr cond <+> braces (hlist post) + $$ pprBlock stmts ppr YBreak = text "break" ppr YContinue = text "continue" ppr YLeave = text "leave" diff --git a/src/Language/Yul/Parser.hs b/src/Language/Yul/Parser.hs index 66b1c4d9..dfa268a6 100644 --- a/src/Language/Yul/Parser.hs +++ b/src/Language/Yul/Parser.hs @@ -71,6 +71,7 @@ yulStmt = choice , yulFun , YLet <$> (pKeyword "let" *> commaSep pName) <*> optional (symbol ":=" *> yulExpression) , YIf <$> (pKeyword "if" *> yulExpression) <*> yulBlock + , YFor <$> (pKeyword "for" *> yulBlock) <*> yulExpression <*> yulBlock <*> yulBlock , YSwitch <$> (pKeyword "switch" *> yulExpression) <*> many yulCase <*> diff --git a/test/Cases.hs b/test/Cases.hs index 775f0a2a..9d1067d2 100644 --- a/test/Cases.hs +++ b/test/Cases.hs @@ -187,6 +187,7 @@ cases = , runTestExpectingFailure "overlapping-heads.solc" caseFolder , runTestExpectingFailure "instance-wrong-sig.solc" caseFolder , runTestForFile "match-yul.solc" caseFolder + , runTestForFile "yul-for.solc" caseFolder ] where caseFolder = "./test/examples/cases" From 7c3be1f1e1c01e2a152c858f0453b47d904ecbe2 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Fri, 24 Oct 2025 08:07:40 +0200 Subject: [PATCH 08/19] add missing yul-for testcase --- test/examples/cases/yul-for.solc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 test/examples/cases/yul-for.solc diff --git a/test/examples/cases/yul-for.solc b/test/examples/cases/yul-for.solc new file mode 100644 index 00000000..f978b196 --- /dev/null +++ b/test/examples/cases/yul-for.solc @@ -0,0 +1,14 @@ +contract YulFor { + function main() { + let loopStart = 128; + let loopEnd = 256; + let res : word; + assembly { + let i := loopStart + for {} lt(i, loopEnd) { i := add(i, 32) } + { mstore(i, 42) } + res := mload(192) + } + return res; + } +} From 3f464390b5d7c27dec7948b3ffb0e68ce0697c4b Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Mon, 27 Oct 2025 08:37:45 +0100 Subject: [PATCH 09/19] Desugar constructors with multiple arguments --- src/Solcore/Desugarer/ContractDispatch.hs | 101 +++++++++++++++++++- src/Solcore/Pipeline/Options.hs | 4 + src/Solcore/Pipeline/SolcorePipeline.hs | 6 +- test/examples/spec/135cons3.solc | 108 ++++++++++++++++++++++ testsol.sh | 14 +-- 5 files changed, 221 insertions(+), 12 deletions(-) create mode 100644 test/examples/spec/135cons3.solc diff --git a/src/Solcore/Desugarer/ContractDispatch.hs b/src/Solcore/Desugarer/ContractDispatch.hs index 867006f4..ffac47d6 100644 --- a/src/Solcore/Desugarer/ContractDispatch.hs +++ b/src/Solcore/Desugarer/ContractDispatch.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE QuasiQuotes #-} {-| Module : Solcore.Desugarer.ContractDispatch Description : Implements method dispatch via function selectors in calldata @@ -22,15 +23,22 @@ import Solcore.Frontend.Syntax import Solcore.Primitives.Primitives (word, unit, tupleExpFromList, tupleTyFromList) import Data.Text.Encoding (encodeUtf8) import Language.Yul +import Language.Yul.QuasiQuote contractDispatchDesugarer :: CompUnit Name -> CompUnit Name contractDispatchDesugarer (CompUnit ims topdecls) = CompUnit ims (Set.toList extras <> topdecls') where (extras, topdecls') = mapAccumL go Set.empty topdecls - - go acc (TContr c) = (Set.union acc (genNameDecls c), TContr (genMainFn c)) + go acc (TContr c) + | "main" `notElem` functionNames c = (Set.union acc (genNameDecls c), TContr (genMainFn True c)) + | otherwise = (acc, TContr (genMainFn False c)) go acc v = (acc, v) +functionNames :: Contract a -> [Name] +functionNames = foldr go [] . decls where + go (CFunDecl fd) = (sigName (funSignature fd) :) + go _ = id + genNameDecls :: Contract Name -> Set (TopDecl Name) genNameDecls (Contract cname _ cdecls) = foldl go Set.empty cdecls where @@ -40,9 +48,12 @@ genNameDecls (Contract cname _ cdecls) = foldl go Set.empty cdecls in Set.union (Set.fromList [TDataDef dataTy, TInstDef instDef]) acc go acc _ = acc -genMainFn :: Contract Name -> Contract Name -genMainFn (Contract cname tys cdecls) = Contract cname tys (CFunDecl mainfn : cdecls) +genMainFn :: Bool -> Contract Name -> Contract Name +genMainFn addMain (Contract cname tys cdecls) + | addMain = Contract cname tys (CFunDecl mainfn : Set.toList cdecls') + | otherwise = Contract cname tys (Set.toList cdecls') where + cdecls' = Set.unions (map (transformCDecl cname) cdecls) mainfn = FunDef (Signature [] [] "main" [] Nothing) body body = [ StmtExp (Call Nothing (QualName "RunContract" "exec") [cdata])] cdata = Con "Contract" [methods, fallback] @@ -72,6 +83,88 @@ genMainFn (Contract cname tys cdecls) = Contract cname tys (CFunDecl mainfn : cd getTy (Typed _ t) = Just t getTy (Untyped {}) = Nothing +transformCDecl :: Name -> ContractDecl Name -> Set (ContractDecl Name) +transformCDecl contractName (CConstrDecl c) = transformConstructor contractName c +transformCDecl _ d = Set.singleton d + +transformConstructor :: Name -> Constructor Name -> Set (ContractDecl Name) +transformConstructor contractName cons + | all isTyped params = Set.fromList[initFun, copyArgsFun, startFun] + | otherwise = error $ "Internal Error: contract constructor must be fully typed" + where + params = constrParams cons + argsTuple = (tupleTyFromList (mapMaybe getTy params)) + initFun = CFunDecl (FunDef initSig (constrBody cons)) + initSig = Signature + { sigVars = mempty + , sigContext = mempty + , sigName = initFunName + , sigParams = params + , sigReturn = Just unit + } + + copySig = Signature + { sigVars = mempty + , sigContext = mempty + , sigName = "copy_arguments_for_constructor" + , sigParams = mempty + , sigReturn = Just argsTuple + } + contractString = show contractName + yulContractName = YLit $ YulString contractString + deployer = YLit $ YulString $ contractString <> "Deploy" + copyBody = + [ Let "res" (Just argsTuple) Nothing + , Let "memoryDataOffset" (Just word) Nothing + , Asm [yulBlock|{ + let programSize := datasize(`deployer`) + let argSize := sub(codesize(), programSize) + memoryDataOffset := mload(64) + mstore(64, add(memoryDataOffset, argSize)) + codecopy(memoryDataOffset, programSize, argSize) + }|] + , Let "source" (Just (memoryT bytesT)) (Just (memoryE(Var "memoryDataOffset"))) + , Var "res" := Call Nothing "abi_decode" + [ Var "source" + , proxyExp argsTuple + , proxyExp (TyCon "MemoryWordReader" []) + ] + , Return (Var "res") + ] + memoryT t = TyCon "memory" [t] + memoryE e = Con "memory" [e] + bytesT = TyCon "bytes" [] + copyArgsFun = CFunDecl (FunDef copySig copyBody) + + startSig = Signature + { sigVars = mempty + , sigContext = mempty + , sigName = "start" + , sigParams = mempty + , sigReturn = Just unit + } + startBody = + [ Asm [yulBlock|{ mstore(64, memoryguard(128)) }|] + , Let "conargs" (Just argsTuple) (Just (Call Nothing "copy_arguments_for_constructor"[])) + -- , Match [Var "conargs"] ... + , Let "fun" Nothing (Just (Var initFunName)) + , StmtExp $ Call Nothing "fun" [Var "conargs"] + , Asm [yulBlock|{ + let size := datasize(`yulContractName`) + codecopy(0, dataoffset(`yulContractName`), datasize(`yulContractName`)) + return(0, size) + }|] + ] + startFun = CFunDecl (FunDef startSig startBody) + + isTyped (Typed {}) = True + isTyped (Untyped {}) = False + + getTy (Typed _ t) = Just t + getTy (Untyped {}) = Nothing + +initFunName :: Name +initFunName = "init_" mkNameTy :: Name -> Name -> DataTy mkNameTy cname fname = DataTy (nameTypeName cname fname) [] [] diff --git a/src/Solcore/Pipeline/Options.hs b/src/Solcore/Pipeline/Options.hs index e0d84a57..1c60c0c0 100644 --- a/src/Solcore/Pipeline/Options.hs +++ b/src/Solcore/Pipeline/Options.hs @@ -14,6 +14,7 @@ data Option , optVerbose :: !Bool , optDumpAST :: !Bool , optDumpEnv :: !Bool + , optDumpDispatch :: !Bool , optDumpDS :: !Bool , optDumpDF :: !Bool , optDumpSpec :: !Bool @@ -37,6 +38,7 @@ emptyOption path = Option , optVerbose = False , optDumpAST = False , optDumpEnv = False + , optDumpDispatch = False , optDumpDS = False , optDumpDF = False , optDumpSpec = False @@ -85,6 +87,8 @@ options <> help "Dump AST after name resolution") <*> switch ( long "dump-env" <> help "Dump env after name resolution") + <*> switch ( long "dump-dispatch" + <> help "Dump dispatched contract") <*> switch ( long "dump-ds" <> help "Dump desugared contract") <*> switch ( long "dump-df" diff --git a/src/Solcore/Pipeline/SolcorePipeline.hs b/src/Solcore/Pipeline/SolcorePipeline.hs index e8041e56..9a680607 100644 --- a/src/Solcore/Pipeline/SolcorePipeline.hs +++ b/src/Solcore/Pipeline/SolcorePipeline.hs @@ -73,12 +73,16 @@ compile opts = runExceptT $ do liftIO $ when (optDumpEnv opts) $ pPrint env - -- contrct dispatch generation + -- contract dispatch generation dispatched <- liftIO $ if noGenDispatch then pure resolved else timeItNamed "Contract dispatch generation" $ pure (contractDispatchDesugarer resolved) + liftIO $ when (optDumpDispatch opts) $ do + putStrLn "> Dispatch:" + putStrLn $ pretty dispatched + -- SCC analysis connected <- ExceptT $ timeItNamed "SCC " $ sccAnalysis dispatched diff --git a/test/examples/spec/135cons3.solc b/test/examples/spec/135cons3.solc new file mode 100644 index 00000000..586e8754 --- /dev/null +++ b/test/examples/spec/135cons3.solc @@ -0,0 +1,108 @@ +// test constructor with multiple args +import std; +// import prelude; + +function addU(x : uint256, y : uint256) -> uint256 { + let res: word; + match x { | uint256(xw) => + match y { | uint256(yw) => + assembly { + res := add(xw, yw) + } + } + } + return uint256(res); +} + +forall t.t:Typedef(word) => +function log1(v:t, topic:word) -> () { + let w : word = Typedef.rep(v); + assembly { + mstore(0,w) + log1(0,32,topic) + } +} + +contract Counter { + + // setCounter & getCounter are intentionally low-level to avoid clutter + function setCounter(v: uint256) -> () { + match v { | uint256(w) => + assembly { + sstore(0x00, w) + } + } + } + + function getCounter() -> uint256 { + let res; + assembly { + res := sload(0x00) + } + return uint256(res); + } + + constructor(x:uint256, y:uint256, z:uint256) + // function myconstructor(x:uint256, y:uint256, z:uint256) -> () + { + log1(x, 0xc1); + log1(y, 0xc2); + log1(z, 0xc3); + setCounter(addU(addU(x,y),z)); + } + +/* This should desugar to: (check with --dump-dispatch */ + +/* + init_(x:uint256, y:uint256, z:uint256) + // function myconstructor(x:uint256, y:uint256, z:uint256) -> () + { + setCounter(x+y+z); + } + function copy_arguments_for_constructor() -> (uint256, uint256, uint256) { // result type CHANGES + let res : (uint256, uint256, uint256); // type(res) CHANGES + let memoryDataOffset : word; + + assembly { + let programSize := datasize("CounterDeploy") // ${deployerName} where deployerName = contractName <> "Deploy" + let argSize := sub(codesize(), programSize) + memoryDataOffset := mload(64) + mstore(64, add(memoryDataOffset, argSize)) + codecopy(memoryDataOffset, programSize, argSize) + } + + let source : memory(bytes) = memory(memoryDataOffset); + res = abi_decode(source, Proxy:Proxy( (uint256, uint256, uint256) ), Proxy:Proxy(MemoryWordReader)); + return res; + } + + function start() -> () { + assembly { mstore(64, memoryguard(128)) } + + let conargs = copy_arguments_for_constructor(); + // Possible hack: let fn = init; fn(conargs); + // match conargs { | (a1, a2, a3) => myconstructor(a1,a2,a3) ; } + match conargs { | (a1, a2, a3) => init_(a1,a2,a3) ; } + + assembly { + let size := datasize("Counter") + codecopy(0, dataoffset("Counter"), datasize("Counter")) + return(0, size) + } + /* Haskell with Yul QQ (#231) + let cname = "Counter" in Asm [yulBlock| + let size := datasize(`cname`) + codecopy(0, dataoffset(`cname`), datasize(`cname`)) + return(0, size) + |] + */ + return (); + } + */ + + // TODO: remove main, use dispatch instead + function main() -> uint256 { + return getCounter(); + } + +} diff --git a/testsol.sh b/testsol.sh index 5b075a2e..8021f058 100644 --- a/testsol.sh +++ b/testsol.sh @@ -92,17 +92,17 @@ function deploycore() { # echo $addr # } -function deploysail() { - local sail=$1 +function deployhull() { + local hull=$1 local base=$(basename $1 .core) shift local yulfile=$base.yul echo $yulfile local hexfile=$base.hex rm -f -v $yulfile #$hexfile - echo cabal exec yule -- $sail -o $yulfile - cabal exec yule -- $sail -o $yulfile - #deployyul $1 $* + echo cabal exec yule -- $hull -o $yulfile + cabal exec yule -- $hull -o $yulfile + deployyul $yulfile $* } function deployyul() { @@ -115,7 +115,7 @@ function deployyul() { prog=$(solc --strict-assembly --bin --optimize --optimize-yul $yulfile | tail -1) hex="$prog$data" echo Hex: $hex - rawtx=$(cast mktx --private-key=$DEPLOYER_KEY --create $hex) + rawtx=$(cast mktx --private-key=$DEPLOYER_KEY --create $hex $*) txoutput=$(cast publish $rawtx) echo $txoutput | jq . export contractAddress=$(echo $txoutput | jq .contractAddress | tr -d '"') @@ -137,7 +137,7 @@ function deployyul1() { echo $contractAddress } -function sail() { +function hull() { local base=$(basename $1 .core) local yulfile=$base.yul rm -f -v $yulfile From e46a300cb7765e68dbca981e711c4368336eece7 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Wed, 29 Oct 2025 06:37:09 +0100 Subject: [PATCH 10/19] fix test 135cons3 --- test/examples/spec/135cons3.solc | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/test/examples/spec/135cons3.solc b/test/examples/spec/135cons3.solc index 586e8754..07d91117 100644 --- a/test/examples/spec/135cons3.solc +++ b/test/examples/spec/135cons3.solc @@ -2,17 +2,6 @@ import std; // import prelude; -function addU(x : uint256, y : uint256) -> uint256 { - let res: word; - match x { | uint256(xw) => - match y { | uint256(yw) => - assembly { - res := add(xw, yw) - } - } - } - return uint256(res); -} forall t.t:Typedef(word) => function log1(v:t, topic:word) -> () { @@ -48,7 +37,7 @@ contract Counter { log1(x, 0xc1); log1(y, 0xc2); log1(z, 0xc3); - setCounter(addU(addU(x,y),z)); + setCounter(Add.add(Add.add(x,y),z)); } /* This should desugar to: (check with --dump-dispatch */ From a3abbdee887343ef9c0d505ef926c3679fbe631d Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Fri, 31 Oct 2025 13:30:21 +0100 Subject: [PATCH 11/19] Primitives: fix type of calldatacopy --- src/Solcore/Primitives/Primitives.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Solcore/Primitives/Primitives.hs b/src/Solcore/Primitives/Primitives.hs index a00f5229..cef706da 100644 --- a/src/Solcore/Primitives/Primitives.hs +++ b/src/Solcore/Primitives/Primitives.hs @@ -213,7 +213,7 @@ yulPrimOps = [ (Name "stop", monotype unit) , (Name "callvalue", monotype word) , (Name "calldataload", monotype (word :-> word)) , (Name "calldatasize", monotype word) - , (Name "calldatacopy", monotype (word :-> word :-> word :-> word)) + , (Name "calldatacopy", monotype (word :-> word :-> word :-> unit)) , (Name "codesize", monotype word) , (Name "codecopy", monotype (word :-> word :-> word :-> unit)) , (Name "datasize", monotype (string :-> word)) From a91f963c1a022f28a491298cf4a597047e7395c7 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Fri, 31 Oct 2025 13:53:34 +0100 Subject: [PATCH 12/19] std: ABI en/decoding for strings --- std/dispatch.solc | 21 ++++++-- std/std.solc | 127 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 133 insertions(+), 15 deletions(-) diff --git a/std/dispatch.solc b/std/dispatch.solc index b1454fec..78eac881 100644 --- a/std/dispatch.solc +++ b/std/dispatch.solc @@ -38,6 +38,17 @@ instance ():ABIString { } } +instance memory(string):ABIString { + function append(head : word, tail : word, prx : Proxy(memory(string))) -> word { + let size : word = 6; + assembly { + mstore(head, add(mload(head), size)) + mstore(tail, 0x737472696e670000000000000000000000000000000000000000000000000000) + } + return Add.add(tail, size); + } +} + function append_left_bracket(head : word, tail : word) -> word { let size = 1; assembly { @@ -183,12 +194,14 @@ forall payability args rets fn // abi encode rets to memory let ptr = abi_encode(rets); - // TODO: this is broken for dynamically sized types... - let retSz : word = ABIAttribs.headSize(prets); + // let retSz : word = ABIAttribs.headSize(prets); + // the approach above does not work for dynamically sized types... + // ...instead we take the size of memory allocated by the encoding let start : word = Typedef.rep(ptr); - + let end : word = get_free_memory(); + let retSz : word = Sub.sub(end, start); assembly { - return(start,retSz) + return(start, retSz) } } diff --git a/std/std.solc b/std/std.solc index 1640fad4..f9bc1e53 100644 --- a/std/std.solc +++ b/std/std.solc @@ -36,6 +36,16 @@ pragma no-coverage-condition ABIDecode, MemoryType; - memory vectors */ +forall t.t:Typedef(word) => +function log1(v:t, topic:word) -> () { + let w : word = Typedef.rep(v); + assembly { + mstore(0,w) + log1(0,32,topic) + } +} + + // --- booleans --- // TODO: this should short circuit. probably needs some compiler magic to do so. @@ -101,6 +111,12 @@ forall abs rep . class abs:Typedef(rep) { function rep(x:abs) -> rep; } +forall t. +default instance t:Typedef(t) { + function abs(x:t) -> t { return x; } + function rep(x:t) -> t { return x; } +} + // --- Equality --- forall a. class a:Eq { @@ -149,6 +165,7 @@ function lt(x:a, y:a) -> bool { return gt(y,x); } + // --- Arithmetic --- forall t . class t:Add { @@ -423,6 +440,23 @@ forall t . instance returndata(t) : Typedef(word) { data mapping(member, index) = mapping(word) ; +// --- Low-level memory ops + +function mload(a:word) -> word { + let res: word; + assembly { res := mload(a) } + return res; +} + +function mstore(a:word, v:word) -> () { + assembly { mstore(a,v) } +} + +function strlen(s:memory(string)) -> word { + match s { | memory(a) => return mload(a); } +} + + // --- Free Memory Pointer --- // Memory in solidity is bump allocated in a single arena @@ -534,6 +568,8 @@ forall ty . class ty:WordReader { function read(reader:ty) -> word; // returns a new WordReader that points to a location `offset` bytes further into the array function advance(reader:ty, offset:word) -> ty; + // copies a block from the underlying source to memory + function copyToMem(reader:ty, dst: word, cnt: word) -> (); } // WordReader for memory @@ -551,6 +587,11 @@ instance MemoryWordReader:WordReader { | MemoryWordReader(ptr) => return MemoryWordReader(Add.add(ptr, offset)); } } + function copyToMem(reader:MemoryWordReader, dst:word, cnt: word) -> () { + match reader { + | MemoryWordReader(ptr) => assembly { mcopy(dst, ptr, cnt) } + } + } } // WordReader for calldata @@ -581,6 +622,11 @@ instance CalldataWordReader:WordReader { | CalldataWordReader(ptr) => return CalldataWordReader(Add.add(ptr, offset)); } } + function copyToMem(reader:CalldataWordReader, dst:word, cnt: word) -> () { + match reader { + | CalldataWordReader(ptr) => assembly { calldatacopy(dst, ptr, cnt) } + } + } } // --- HasWordReader --- @@ -623,6 +669,7 @@ instance uint256:MemoryType(uint256) { } // We load a DynArray into a sized pointer to the first element +/* forall ty ret . ty:MemoryType(ret) => instance DynArray(ty):MemoryType(slice(memory(ret))) { function loadFromMemory(p : Proxy (DynArray(ty)), loc:word) -> slice(memory(ret)) { let length; @@ -633,19 +680,20 @@ forall ty ret . ty:MemoryType(ret) => instance DynArray(ty):MemoryType(slice(mem return slice(Typedef.abs(loc) : memory(ret), length); } } - +*/ // FAIL: patterson // FAIL: bound variable // if we ty is a MemoryType that returns deref and deref is ABIEncode, then we can encode a memory(ty) // by loading it and then running the ABI encoding for the loaded value +/* forall ty deref . ty:MemoryType(deref), deref:ABIEncode => instance memory(ty):ABIEncode { function encodeInto(x:memory(ty), basePtr:word, offset:word, tail:word) -> word { let prx : Proxy(ty); // FIXED: before was Proxy(deref) return ABIEncode.encodeInto(MemoryType.loadFromMemory(prx, Typedef.rep(x)) : deref, basePtr, offset, tail); } } - +*/ // --- ABI Tuples --- // Tuples in Solidity are always desugared to nested pairs (to allow for @@ -691,6 +739,10 @@ forall t . instance DynArray(t):ABIAttribs { function headSize(ty : Proxy(DynArray(t))) -> word { return 32; } function isStatic(ty : Proxy(DynArray(t))) -> bool { return false; } } +instance string:ABIAttribs { + function headSize(ty: Proxy(string)) -> word { return 32; } + function isStatic(ty : Proxy(string)) -> bool { return false; } +} // computes the attribs for a pair of two types that implement attribs forall a b . a:ABIAttribs, b:ABIAttribs => instance (a,b):ABIAttribs { @@ -711,14 +763,14 @@ forall a b . a:ABIAttribs, b:ABIAttribs => instance (a,b):ABIAttribs { // if an abi tuple contains dynamic elems we store it in the tail, otherwise we // treat it the same as a series of nested pairs forall tuple . tuple:ABIAttribs => instance ABITuple(tuple):ABIAttribs { - function headSize(ty : Proxy(tuple)) -> word { + function headSize(ty : Proxy(ABITuple(tuple))) -> word { let px : Proxy(tuple); match ABIAttribs.isStatic(px) { | true => return ABIAttribs.headSize(px); | false => return 32; } } - function isStatic(ty : Proxy(tuple)) -> bool { + function isStatic(ty : Proxy(ABITuple(tuple))) -> bool { let px : Proxy(tuple); return ABIAttribs.isStatic(px); } @@ -726,21 +778,21 @@ forall tuple . tuple:ABIAttribs => instance ABITuple(tuple):ABIAttribs { // for pointer types we fetch the attribs of the pointed to type, not the pointer itself forall ty . ty:ABIAttribs => instance memory(ty):ABIAttribs { - function headSize(ty : Proxy(ty)) -> word { + function headSize(p : Proxy(memory(ty))) -> word { let px : Proxy(ty); return ABIAttribs.headSize(px); } - function isStatic(ty : Proxy(ty)) -> bool { + function isStatic(p : Proxy(memory(ty))) -> bool { let px : Proxy(ty); return ABIAttribs.isStatic(px); } } forall ty . ty:ABIAttribs => instance calldata(ty):ABIAttribs { - function headSize(ty : Proxy(ty)) -> word { + function headSize(p : Proxy(calldata(ty))) -> word { let px : Proxy(ty); return ABIAttribs.headSize(px); } - function isStatic(ty : Proxy(ty)) -> bool { + function isStatic(ty : Proxy(calldata(ty))) -> bool { let px : Proxy(ty); return ABIAttribs.isStatic(px); } @@ -775,6 +827,31 @@ instance uint256:ABIEncode { } } +function round_up_to_mul_of_32(value:word) -> word { + let result : word; + assembly { result := and(add(value, 31), not(31)) } + return result; +} + +instance memory(string):ABIEncode { + function encodeInto(x:memory(string), basePtr:word, offset:word, tail:word) -> word { + let tailOffset = Sub.sub(tail,basePtr); + let srcPtr = Typedef.rep(x); + + assembly { + let length := mload(srcPtr) + let total := add(length, 32) + + mstore(add(basePtr, offset), sub(tail, basePtr)) + mcopy(tail, srcPtr, total) + let rounded := and(add(total, 31), not(31)) + tail := add(tail, rounded) + } + + return tail; + } +} + instance ():ABIEncode { // a unit256 is written directly into the head function encodeInto(x:(), basePtr:word, offset:word, tail:word) -> word { @@ -850,6 +927,11 @@ forall ty reader . reader:WordReader => instance ABIDecoder(ty, reader):WordRead | ABIDecoder(ptr) => return ABIDecoder(WordReader.advance(ptr, offset)); } } + function copyToMem(decoder:ABIDecoder(ty, reader), dst:word, cnt: word) -> () { + match decoder { + | ABIDecoder(ptr) => WordReader.copyToMem(ptr, dst, cnt); + } + } } // ABI Decoding for uint @@ -865,7 +947,28 @@ forall reader . reader:WordReader => instance ABIDecoder((), reader):ABIDecode(( } } -// ABI decoding for a pait of decodable values +// ABI decoding for strings (only in memory) +forall reader. reader : WordReader => +instance ABIDecoder(memory(string), reader):ABIDecode(memory(string)) +{ + function decode(ptr:ABIDecoder(memory(string), reader), currentHeadOffset:word) -> memory(string) { + let tmp:word; + let headRdr = WordReader.advance(ptr, currentHeadOffset); + let tailPtr : word = WordReader.read(headRdr); + + let src = WordReader.advance(ptr, tailPtr); + let srcRdr = getReader(src); + let length = WordReader.read(src); + let total = Add.add(length, 32); + let rounded = round_up_to_mul_of_32(total); + let resultPtr : word = allocate_memory(rounded) ; + WordReader.copyToMem(srcRdr, resultPtr, total); + return memory(resultPtr); + } // decode ends +} + + +// ABI decoding for a pair of decodable values // FAIL: Coverage forall a b a_decoded b_decoded reader . reader:WordReader, ABIDecoder(b,reader):ABIDecode(b_decoded), ABIDecoder(a,reader):ABIDecode(a_decoded), a:ABIAttribs => instance ABIDecoder((a,b), reader):ABIDecode((a_decoded,b_decoded)) { @@ -896,6 +999,7 @@ forall reader tuple tuple_decoded . reader:WordReader, tuple:ABIDecode(tuple_dec } } +/* forall reader tuple tuple_decoded . reader:WordReader, tuple:ABIDecode(tuple_decoded), tuple:ABIAttribs => instance ABIDecoder(memory(ABITuple(tuple)), reader):ABIDecode(memory(tuple_decoded)) { @@ -909,7 +1013,8 @@ forall reader tuple tuple_decoded . reader:WordReader, tuple:ABIDecode(tuple_dec } } } - +*/ +/* forall reader baseType baseType_decoded .baseType : ABIAttribs, reader:WordReader, ABIDecoder(baseType, reader):ABIDecode(baseType_decoded) => instance ABIDecoder(memory(DynArray(baseType)), reader):ABIDecode(memory(DynArray(baseType_decoded))) { @@ -935,7 +1040,7 @@ forall reader baseType baseType_decoded .baseType : ABIAttribs, reader:WordReade return result; } } - +*/ forall ty reader. function getReader(d:ABIDecoder(ty, reader)) -> reader { match d { From b5d4f90794d9876b17ff9f3028e43a4e0052aa42 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Fri, 31 Oct 2025 14:18:09 +0100 Subject: [PATCH 13/19] dispatch: workaround for Issue #252 --- std/dispatch.solc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/std/dispatch.solc b/std/dispatch.solc index 78eac881..2d9e480e 100644 --- a/std/dispatch.solc +++ b/std/dispatch.solc @@ -198,10 +198,11 @@ forall payability args rets fn // the approach above does not work for dynamically sized types... // ...instead we take the size of memory allocated by the encoding let start : word = Typedef.rep(ptr); + let encStart : word = Typedef.rep(ptr); // cannot be called start - see #252 let end : word = get_free_memory(); - let retSz : word = Sub.sub(end, start); + let retSz : word = Sub.sub(end, encStart); assembly { - return(start, retSz) + return(encStart, retSz) } } From 3452f51b54e9308577e236fa6d95f9ed9f37691b Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Fri, 31 Oct 2025 15:25:50 +0100 Subject: [PATCH 14/19] std: uncomment some instances that do not overlap after all --- std/std.solc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/std/std.solc b/std/std.solc index 23b1bde3..b28da4fb 100644 --- a/std/std.solc +++ b/std/std.solc @@ -1009,7 +1009,7 @@ forall reader tuple tuple_decoded . reader:WordReader, tuple:ABIDecode(tuple_dec } } -/* + forall reader tuple tuple_decoded . reader:WordReader, tuple:ABIDecode(tuple_decoded), tuple:ABIAttribs => instance ABIDecoder(memory(ABITuple(tuple)), reader):ABIDecode(memory(tuple_decoded)) { @@ -1023,8 +1023,7 @@ forall reader tuple tuple_decoded . reader:WordReader, tuple:ABIDecode(tuple_dec } } } -*/ -/* + forall reader baseType baseType_decoded .baseType : ABIAttribs, reader:WordReader, ABIDecoder(baseType, reader):ABIDecode(baseType_decoded) => instance ABIDecoder(memory(DynArray(baseType)), reader):ABIDecode(memory(DynArray(baseType_decoded))) { @@ -1050,7 +1049,7 @@ forall reader baseType baseType_decoded .baseType : ABIAttribs, reader:WordReade return result; } } -*/ + forall ty reader. function getReader(d:ABIDecoder(ty, reader)) -> reader { match d { From 34585a06d139e98efbb26f88de0aeda01c665686 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Fri, 31 Oct 2025 15:26:40 +0100 Subject: [PATCH 15/19] std: remove commented-out code --- std/std.solc | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/std/std.solc b/std/std.solc index b28da4fb..409805db 100644 --- a/std/std.solc +++ b/std/std.solc @@ -1069,46 +1069,6 @@ forall baseType baseType_decoded reader . ABIDecoder(baseType, CalldataWordReade } } -// forall nm args rets f g . nm:Selector, args:ABIDecode, rets:ABIEncode, f:invokable(args, rets) => instance Dispatch(nm,args,rets,f):GenerateDispatch { -// function dispatch_if_selector_match(d:Dispatch(nm,args,rets,f)) -> g { -// return lam() { -// match d { -// | Dispatch(name, args, rets, fn) => match selector_matches(name) { -// | false => return (); -// | true => return (); -// }} -// }; -// } -// } - -//// /// Translation of the above contract -//// struct StorageContext { -//// x:uint; -//// y:bool; -//// } -//// -//// function C_f(ctxt:StorageContext) public { -//// ctxt.x = 42; -//// } -//// -//// -//// function entry_C() { -//// GenerateDispatch.dispatch_if_selector_match(DispatchFunction("f()", C_f)); // could also be (nested) pairs of dispatch functions, if the contract had more functions -//// revert("unknown selector"); -//// } -//// -//// // init code for contract creation -//// function init_C() { -//// // constructor code -//// let code_start := allocate_unbounded() // fetch some free memory -//// let code_length := __builtin_fetch_code(entry_C, code_start) // sounds weirder than it is - this will just add the code for entry_C to a Yul subobject and use some Yul builtins for fetching the code to be deployed -//// assembly { -//// return(code_start, code_length) -//// } -//// } -//// -//// - // --- Assignment --- From 1138f87ee3b21bb34e66fcb0e5aa174c8c268935 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Fri, 31 Oct 2025 15:28:07 +0100 Subject: [PATCH 16/19] tests: add dispatch/stringid --- test/Cases.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Cases.hs b/test/Cases.hs index 775f0a2a..14346aa1 100644 --- a/test/Cases.hs +++ b/test/Cases.hs @@ -61,6 +61,7 @@ dispatches = testGroup "Files for dispatch cases" [ runDispatchTest "basic.solc" + , runDispatchTest "stringid.solc" ] where runDispatchTest file = runTestForFileWith (emptyOption mempty) file "./test/examples/dispatch" From 5af6bcd92dbd095c13fa52d9a1296846e93df7ea Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Sat, 1 Nov 2025 17:17:33 +0100 Subject: [PATCH 17/19] std: fix bug in loadBytesFromStorage --- std/std.solc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/std.solc b/std/std.solc index 409805db..704f8cfb 100644 --- a/std/std.solc +++ b/std/std.solc @@ -1356,7 +1356,7 @@ instance memory(string):Storable(string) { function sload(src:storage(string)) -> memory(string) { let srcPtr : word = Typedef.rep(src); let dstPtr : word = get_free_memory(); - let endPtr = loadBytesFromStorage(srcPtr, srcPtr); + let endPtr = loadBytesFromStorage(srcPtr, dstPtr); set_free_memory(endPtr); return memory(dstPtr); } From 1a5d03115556e8b297f7013cc69328bf6fc3c8c0 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Sun, 2 Nov 2025 17:07:53 +0100 Subject: [PATCH 18/19] std: mstring --- std/dispatch.solc | 15 +++- std/std.solc | 110 ++++++++++++++++++++++---- test/examples/spec/129bminiERC20.solc | 109 +++++++++++++++++++++++++ 3 files changed, 217 insertions(+), 17 deletions(-) create mode 100644 test/examples/spec/129bminiERC20.solc diff --git a/std/dispatch.solc b/std/dispatch.solc index 2d9e480e..b36d5085 100644 --- a/std/dispatch.solc +++ b/std/dispatch.solc @@ -32,14 +32,25 @@ instance uint256:ABIString { } } +instance address:ABIString { + function append(head : word, tail : word, prx : Proxy(address)) -> word { + let size : word = 7; + assembly { + mstore(head, add(mload(head), size)) + mstore(tail, 0x6164647265737300000000000000000000000000000000000000000000000000) + } + return Add.add(tail, size); + } +} + instance ():ABIString { function append(head : word, tail : word, prx : Proxy(())) -> word { return(tail); } } -instance memory(string):ABIString { - function append(head : word, tail : word, prx : Proxy(memory(string))) -> word { +instance mstring:ABIString { + function append(head : word, tail : word, prx : Proxy(mstring)) -> word { let size : word = 6; assembly { mstore(head, add(mload(head), size)) diff --git a/std/std.solc b/std/std.solc index 704f8cfb..0b6b7d2a 100644 --- a/std/std.solc +++ b/std/std.solc @@ -1,4 +1,4 @@ -pragma no-bounded-variable-condition ABIEncode, GenerateDispatch, Storable; +pragma no-bounded-variable-condition ABIEncode, GenerateDispatch; pragma no-patterson-condition ABIEncode, Num; pragma no-coverage-condition ABIDecode, MemoryType; @@ -462,8 +462,8 @@ function mstore(a:word, v:word) -> () { assembly { mstore(a,v) } } -function strlen(s:memory(string)) -> word { - match s { | memory(a) => return mload(a); } +function strlen(s:mstring) -> word { + match s { | strptr(a) => return mload(a); } } @@ -561,7 +561,21 @@ data bytes; // --- strings --- // TODO: should this be a typedef over `bytes`? -data string; +// data string; +data mstring = strptr(word); // or: strptr(memory(string)) + +forall t . instance mstring : Typedef(word) { + function abs(x: word) -> mstring { + return strptr(x); + } + + function rep(x: mstring) -> word { + match x { + | strptr(w) => return w; + } + } +} + // --- slices (sized pointers) --- @@ -745,13 +759,17 @@ instance uint256:ABIAttribs { function headSize(ty : Proxy(uint256)) -> word { return 32; } function isStatic(ty : Proxy(uint256)) -> bool { return true; } } +instance address:ABIAttribs { + function headSize(ty : Proxy(address)) -> word { return 32; } + function isStatic(ty : Proxy(address)) -> bool { return true; } +} forall t . instance DynArray(t):ABIAttribs { function headSize(ty : Proxy(DynArray(t))) -> word { return 32; } function isStatic(ty : Proxy(DynArray(t))) -> bool { return false; } } -instance string:ABIAttribs { - function headSize(ty: Proxy(string)) -> word { return 32; } - function isStatic(ty : Proxy(string)) -> bool { return false; } +instance mstring:ABIAttribs { + function headSize(ty: Proxy(mstring)) -> word { return 32; } + function isStatic(ty : Proxy(mstring)) -> bool { return false; } } // computes the attribs for a pair of two types that implement attribs @@ -837,14 +855,23 @@ instance uint256:ABIEncode { } } +instance address:ABIEncode { + // a unit256 is written directly into the head + function encodeInto(x:address, basePtr:word, offset:word, tail:word) -> word { + let repx : word = Typedef.rep(x); + assembly { mstore(add(basePtr, offset), repx) } + return tail; + } +} + function round_up_to_mul_of_32(value:word) -> word { let result : word; assembly { result := and(add(value, 31), not(31)) } return result; } -instance memory(string):ABIEncode { - function encodeInto(x:memory(string), basePtr:word, offset:word, tail:word) -> word { +instance mstring:ABIEncode { + function encodeInto(x:mstring, basePtr:word, offset:word, tail:word) -> word { let tailOffset = Sub.sub(tail,basePtr); let srcPtr = Typedef.rep(x); @@ -951,6 +978,13 @@ forall reader . reader:WordReader => instance ABIDecoder(uint256, reader):ABIDec } } +// ABI decoding address - TODO: check range? +forall reader . reader:WordReader => instance ABIDecoder(address, reader):ABIDecode(address) { + function decode(ptr:ABIDecoder(address, reader), currentHeadOffset:word) -> address { + return Typedef.abs(WordReader.read(WordReader.advance(ptr, currentHeadOffset))) : address; + } +} + forall reader . reader:WordReader => instance ABIDecoder((), reader):ABIDecode(()) { function decode(ptr:ABIDecoder((), reader), currentHeadOffset:word) -> () { return (); @@ -958,6 +992,7 @@ forall reader . reader:WordReader => instance ABIDecoder((), reader):ABIDecode(( } // ABI decoding for strings (only in memory) +/* forall reader. reader : WordReader => instance ABIDecoder(memory(string), reader):ABIDecode(memory(string)) { @@ -976,6 +1011,26 @@ instance ABIDecoder(memory(string), reader):ABIDecode(memory(string)) return memory(resultPtr); } // decode ends } +*/ + +forall reader. reader : WordReader => +instance ABIDecoder(mstring, reader):ABIDecode(mstring) +{ + function decode(ptr:ABIDecoder(mstring, reader), currentHeadOffset:word) -> mstring { + let tmp:word; + let headRdr = WordReader.advance(ptr, currentHeadOffset); + let tailPtr : word = WordReader.read(headRdr); + + let src = WordReader.advance(ptr, tailPtr); + let srcRdr = getReader(src); + let length = WordReader.read(src); + let total = Add.add(length, 32); + let rounded = round_up_to_mul_of_32(total); + let resultPtr : word = allocate_memory(rounded) ; + WordReader.copyToMem(srcRdr, resultPtr, total); + return strptr(resultPtr); + } // decode ends +} // ABI decoding for a pair of decodable values @@ -1109,8 +1164,9 @@ instance ():StorageSize { } } -instance word:StorageSize { - function size(x:Proxy(word)) -> word { +forall self. +default instance self:StorageSize { + function size(x:Proxy(self)) -> word { return 1; } } @@ -1321,6 +1377,7 @@ class lhs:Assign(rhs) { } // a can be stored in storage(b); e.g. memory(string) : Storable(string) +/* pragma no-coverage-condition Storable, Assign; forall a b. class a:Storable(b) { @@ -1328,9 +1385,8 @@ class a:Storable(b) { function sload(r:storage(b)) -> a; } - -forall a b. a:Storable(b) => -instance storage(b):Assign(a) { +forall a . a:Storable(a) => +instance storage(a):Assign(a) { function assign(l:storage(b), r:a) -> () { Storable.store(l, r); } @@ -1345,7 +1401,8 @@ default instance a:Storable(a) { return StorageType.sload(Typedef.rep(l)); } } - +*/ +/* instance memory(string):Storable(string) { function store(dst:storage(string), src:memory(string)) -> () { let srcPtr : word = Typedef.rep(src); @@ -1361,6 +1418,29 @@ instance memory(string):Storable(string) { return memory(dstPtr); } } +*/ + +forall a . a:StorageType => +instance storage(a):Assign(a) { + function assign(l:storage(a), r:a) -> () { + let slot = Typedef.rep(l); + StorageType.store(slot, r); + } +} + +instance mstring:StorageType { + function store(slot:word, src:mstring) -> () { + let srcPtr : word = Typedef.rep(src); + storeBytesFromMemory(slot, srcPtr); + } + + function sload(slot:word) -> mstring { + let dstPtr : word = get_free_memory(); + let endPtr = loadBytesFromStorage(slot, dstPtr); + set_free_memory(endPtr); + return strptr(dstPtr); + } +} // Shamelessly stolen from function copy_byte_array_to_storage_from_t_bytes_memory_ptr_to_t_bytes_storage // TODO: consider wrapping behaviour at end of storage diff --git a/test/examples/spec/129bminiERC20.solc b/test/examples/spec/129bminiERC20.solc new file mode 100644 index 00000000..10d7a647 --- /dev/null +++ b/test/examples/spec/129bminiERC20.solc @@ -0,0 +1,109 @@ +import dispatch; + +function caller() -> address { + let res: word; + assembly { + res := caller() + } + return address(res); +} + +function myrevert(msg: word) -> () { + assembly { mstore(0, msg) revert(0, 32) } +} + +function require(cond: bool, msg: word ) { + if( !cond ) { myrevert(msg); } +} + +// function make_short_mstring(len:word, payload:word) + +contract MiniERC20 { + reserved : word; // forge idiosyncrasies + name : mstring; + symbol : mstring; + owner : address; + decimals : uint256; + totalSupply : uint256; + balances : mapping(address,uint256); + allowance : mapping(address, mapping(address, uint256)); + + + constructor(name_ : mstring, symbol_ : mstring, totalSupply_ : uint256) { + name = name_; + symbol = symbol_; + owner = caller(); + decimals = uint256(18); + owner = caller(); + mint(totalSupply_); + } + + function getName() -> mstring { + return name; + } + + function getSymbol() -> mstring { + return symbol; + } + + function getOwner() -> address { + return owner; + } + + function getTotalSupply() -> uint256 { + return totalSupply; + } + function mint(amount:uint256) -> () { + balances[owner] = Num.add(balances[owner], amount); + totalSupply = Num.add(totalSupply, amount); + } + + function transferFrom(src:address, dst:address, amt:uint256) -> uint256 { + let msg_sender = caller(); + require( balances[src] >= amt /* "token/insufficient-balance" */ + , 0x746f6b656e2f696e73756666696369656e742d62616c616e6365 + ); + + if (src != msg_sender && allowance[src][msg_sender] != (Num.maxVal():uint256)) { + require( allowance[src][msg_sender] >= amt /* "token/insufficient-allowance" */ + , 0x746f6b656e2f696e73756666696369656e742d616c6c6f77616e6365 + ); + allowance[src][msg_sender] -= amt; + } + balances[src] = balances[src] - amt; + balances[dst] = balances[dst] + amt; + // return true; + return uint256(1); + } + +/* + function approve(address usr, uint256 amt) public returns (bool) { + allowance[msg.sender][usr] = amt; + emit Approval(msg.sender, usr, amt); + return true; + } +*/ + + function approve(usr: address, amt: uint256) -> uint256 { + let msg_sender = caller(); + allowance[msg_sender][usr] = amt; + // emit Approval(msg.sender, usr, amt); + // return true; + return uint256(1); + } +/* + function init() -> () { + owner = address(0x123456789abcdef); + decimals = Num.fromWord(18) : uint256; + } + function main() -> uint256 { + let msg_sender = caller(); + init(); + mint(uint256(1000)); + allowance[owner][msg_sender] = uint256(1000); + transferFrom(owner, msg_sender, uint256(42)); + + return allowance[owner][msg_sender]; + } +*/ +} From c750838f912ff15fba44e14c62acc1be1d69c36d Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Sun, 2 Nov 2025 19:12:09 +0100 Subject: [PATCH 19/19] add missing tests --- test/examples/dispatch/StoreString.solc | 94 +++++++++++++++++++++++++ test/examples/dispatch/stringid.solc | 41 +++++++++++ 2 files changed, 135 insertions(+) create mode 100644 test/examples/dispatch/StoreString.solc create mode 100644 test/examples/dispatch/stringid.solc diff --git a/test/examples/dispatch/StoreString.solc b/test/examples/dispatch/StoreString.solc new file mode 100644 index 00000000..d25ee17b --- /dev/null +++ b/test/examples/dispatch/StoreString.solc @@ -0,0 +1,94 @@ +import dispatch; + +// PoC of start/init combo + +// -------------------------------------------------------------------------------- +contract StoreString { + + function setName(v: mstring) -> () { + StorageType.store(0, v); + let x : mstring = StorageType.sload(0); + let ptr : word = Typedef.rep(x); + let len : word; + let n1 : word; + assembly { + len := sload(0) + n1 := sload(1) + } + log1(len, 0x5e01); + log1(n1, 0x5e02); + + assembly { + len := mload(ptr) + n1 := mload(add(ptr,32)) + } + log1(len, 0x5e11); + log1(n1, 0x5e12); + } + + function getName() -> mstring { + return StorageType.sload(0); + } + + function getAnswer() -> uint256 { + return uint256(42); + } + + constructor(name: mstring) { + setName(name); + } +// Desugars to +/* + + function copy_arguments_for_constructor() -> (mstring) { + let res : mstring; + let programSize : word; + let argSize : word; + let memoryDataOffset : word; + + assembly { + programSize := datasize("StoreStringDeploy") + argSize := sub(codesize(), programSize) + memoryDataOffset := mload(64) + mstore(64, add(memoryDataOffset, argSize)) + codecopy(memoryDataOffset, programSize, argSize) + } + + let source : memory(bytes) = memory(memoryDataOffset); + res = abi_decode(source, Proxy:Proxy( mstring ), Proxy:Proxy(MemoryWordReader)); + return res; + + } + + function init(name: mstring) -> () { + let ptr : word = Typedef.rep(name); + let len : word; + let n1 : word; + assembly { + len := mload(ptr) + n1 := mload(add(ptr,32)) + } + log1(len, 0xc001); + log1(n1, 0xc002); + Storable.store(storage(0), name); + } + + function start() -> () { + assembly { mstore(64, memoryguard(128)) } + + let t = copy_arguments_for_constructor(); + let fn = init; + fn(t); + + assembly { + let size := datasize("StoreString") + codecopy(0, dataoffset("StoreString"), datasize("StoreString")) + return(0, size) + } + return (); + } + function main() -> mstring { + return getName(); + } +*/ +} diff --git a/test/examples/dispatch/stringid.solc b/test/examples/dispatch/stringid.solc new file mode 100644 index 00000000..ba2db515 --- /dev/null +++ b/test/examples/dispatch/stringid.solc @@ -0,0 +1,41 @@ +import dispatch; + +contract C { + constructor() {} + function id(x:mstring) -> (mstring) { + let ptr : word = Typedef.rep(x); + let len : word; + let n1 : word; + assembly { + len := mload(ptr) + n1 := mload(add(ptr,32)) + } + log1(len, 0xc001); + log1(n1, 0xc002); + + return x; + } + + function const_a() -> (mstring) { + let resPtr = allocate_memory(64); + let payload = 0x7777777777777777777777777777777777777777777777777777777777777777; + mstore(resPtr, 3); + mstore(resPtr+32, payload); + return strptr(resPtr); + } + function mylen(x:mstring) -> uint256 { + let ptr : word = Typedef.rep(x); + let l : word; + let n1 : word; + assembly { + l := mload(ptr) + n1 := mload(add(ptr,32)) + } + // log1(l, 0xc001); + // log1(n1, 0xc002); + + return uint256(l); + } + + // function answer() -> uint256 { return uint256(17); } +}