Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions run_contests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ bash ./contest.sh test/examples/dispatch/weth9.json
bash ./contest.sh test/examples/dispatch/derive_ord.json
bash ./contest.sh test/examples/dispatch/derive_contract_local.json
bash ./contest.sh test/examples/dispatch/deposit.json
bash ./contest.sh test/examples/dispatch/interfaceid.json
1 change: 1 addition & 0 deletions sol-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ library
Solcore.Desugarer.StrLiteralDesugar
Solcore.Desugarer.ReplaceWildcard
Solcore.Desugarer.ContractDispatch
Solcore.Desugarer.PublicMethods
Solcore.Desugarer.ReplaceFunTypeArgs
Solcore.Desugarer.UniqueTypeGen
Solcore.Diagnostics
Expand Down
64 changes: 46 additions & 18 deletions src/Solcore/Desugarer/ContractDispatch.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ module Solcore.Desugarer.ContractDispatch
contractDispatchTopDecls,
writeContractAbis,
contractAbiJson,
nameTypeName,
publicMethodTypes,
)
where

Expand Down Expand Up @@ -119,18 +121,6 @@ genMainFn addMain c@(Contract cname tys cdecls)
]
mkMethod s = error $ "Internal Error: contract methods must be fully typed: " <> show s

-- skip the optional fallback function and non-public methods in the methods tuple
unwrapSigs (CFunDecl (FunDef True s _))
| sigName s == fallbackName = Nothing
| otherwise = Just s
unwrapSigs _ = Nothing

isTyped (Typed {}) = True
isTyped (Untyped {}) = False

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
Expand Down Expand Up @@ -249,12 +239,6 @@ transformConstructor contractName cons
]
startFun = CFunDecl (FunDef False startSig startBody)

isTyped (Typed {}) = True
isTyped (Untyped {}) = False

getTy (Typed _ _ t) = Just t
getTy (Untyped {}) = Nothing

initFunName :: Name
initFunName = "init_"

Expand All @@ -277,8 +261,52 @@ mkNameInst (DataTy dname [] [] _) fname =
}
mkNameInst dt _ = error ("Internal Error: unexpected name type structure: " <> show dt)

-- | The 'Method' type (as used by the dispatcher) for each public,
-- fully-typed method of a contract, in dispatch order. Used by the
-- @type(C).publicMethods@ primitive to compute interface ids: each 'Method'
-- type has a 'Selector' instance (which reuses 'sigStr'), so the selectors can
-- be derived from these types without reimplementing any hashing in the
-- compiler. The payability and return types are carried faithfully; the
-- function ('fn') field is irrelevant to the selector and is filled with a
-- 'word' placeholder. The fallback and any non-fully-typed methods are
-- skipped.
publicMethodTypes :: Contract Name -> [Ty]
publicMethodTypes (Contract cname _ cdecls) =
mapMaybe methodTy (mapMaybe unwrapSigs cdecls)
where
methodTy (Signature _ _ fname fargs _ (Just ret) payable)
| all isTyped fargs =
Just $
TyCon
"Method"
[ TyCon (nameTypeName cname fname) [],
TyCon (if payable then "Payable" else "NonPayable") [],
tupleTyFromList (mapMaybe getTy fargs),
ret,
word
]
methodTy _ = Nothing

--- Util ---

-- | Pull the signature out of a public contract method, skipping the optional

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this AI comment is not needed.

@mbenke did you mean this refactor?

-- fallback function and any non-public ('FunDef False') declarations. Used
-- both by 'genMainFn' (to build the dispatch table) and 'publicMethodTypes'
-- (to expose the same list as types for the @publicMethods@ primitive).
unwrapSigs :: ContractDecl Name -> Maybe (Signature Name)
unwrapSigs (CFunDecl (FunDef True s _))
| sigName s == fallbackName = Nothing
| otherwise = Just s
unwrapSigs _ = Nothing

isTyped :: Param a -> Bool
isTyped (Typed {}) = True
isTyped (Untyped {}) = False

getTy :: Param a -> Maybe Ty
getTy (Typed _ _ t) = Just t
getTy (Untyped {}) = Nothing

proxyTy :: Ty -> Ty
proxyTy t = TyCon "Proxy" [t]

Expand Down
94 changes: 94 additions & 0 deletions src/Solcore/Desugarer/PublicMethods.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{-# LANGUAGE OverloadedStrings #-}

-- |
-- Module : Solcore.Desugarer.PublicMethods
-- Description : Implements the `type(C).publicMethods` primitive
--
-- The parser/name-resolver turns `type(C).publicMethods` into a call to a
-- per-contract helper function (see 'publicMethodsTagName'). This pass
-- generates the body of that helper for every contract whose primitive is
-- actually used.
--
-- The helper hands back a type-level token — @Proxy(methods)@ — describing the
-- contract's public methods as a right-nested tuple terminated by @()@:
--
-- @Proxy((Method(...), (Method(...), ... ())))@
--
-- Each element carries the very same @Method(name,payability,args,rets,fn)@
-- typing consumed by @Selector.compute@ (see @std/dispatch.solc@), so no
-- selector hashing leaks into the compiler. Walking that tuple — counting the
-- methods (@length@) and XOR-folding their selectors into an interface id — is
-- the @PublicMethods@ type class in @std/dispatch.solc@; the compiler only
-- exposes the method list, never the iteration or hashing.
--
-- This must run BEFORE contract dispatch generation, which produces the
-- per-method @DispatchNameTy_*@ name types (and their @SigString@ instances)
-- that the method tuple refers to.
module Solcore.Desugarer.PublicMethods
( publicMethodsDesugarer,
publicMethodsTopDecls,
)
where

import Data.Generics (listify)
import Data.List (isPrefixOf)
import Solcore.Desugarer.ContractDispatch (publicMethodTypes)
import Solcore.Frontend.Syntax
import Solcore.Frontend.Syntax.NameResolution (publicMethodsTagName)
import Solcore.Primitives.Primitives (tupleTyFromList, unit)

publicMethodsDesugarer :: CompUnit Name -> CompUnit Name
publicMethodsDesugarer (CompUnit ims topdecls) =
CompUnit ims (publicMethodsTopDecls topdecls)

publicMethodsTopDecls :: [TopDecl Name] -> [TopDecl Name]
publicMethodsTopDecls topdecls = topdecls ++ helpers
where
-- every contract paired with the helper name its `publicMethods` primitive
-- would call
contractsByTag =
[(publicMethodsTagName cname, c) | TContr c@(Contract cname _ _) <- topdecls]

-- helper names actually referenced by a `type(C).publicMethods` call
referenced =
[fn | Call Nothing fn [] <- listify isTagCall topdecls]

helpers =
[ genPublicMethodsFn c
| (tag, c) <- contractsByTag,
tag `elem` referenced
]

isTagCall :: Exp Name -> Bool
isTagCall (Call Nothing fn []) = isTagName fn
isTagCall _ = False

isTagName :: Name -> Bool
isTagName (Name s) = "$publicMethods$" `isPrefixOf` s
isTagName _ = False

-- | Generate the helper that yields a contract's public-method tuple as a
-- @Proxy@ type token. The tuple is right-nested and terminated by @()@ so the
-- @PublicMethods@ instances in @std/dispatch.solc@ only need a @()@ base case
-- and an @(n, m)@ recursive case (no special single-method case).
genPublicMethodsFn :: Contract Name -> TopDecl Name
genPublicMethodsFn c@(Contract cname _ _) =
TFunDef (FunDef False sig body)
where
-- the public methods, plus a `()` terminator for the tuple
methodsTuple = tupleTyFromList (publicMethodTypes c ++ [unit])
proxyTy = TyCon "Proxy" [methodsTuple]

sig =
Signature
{ sigVars = [],
sigContext = [],
sigName = publicMethodsTagName cname,
sigParams = [],
sigRetComptime = False,
sigReturn = Just proxyTy,
sigPayable = False
}

-- return Proxy : Proxy((Method(...), (Method(...), ... ())));
body = [Return (TyExp (Con "Proxy" []) proxyTy)]
2 changes: 2 additions & 0 deletions src/Solcore/Frontend/Module/Loader.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1551,6 +1551,8 @@ renameExpTypeRefs renameMap (ExpCond e1 e2 e3) =
(renameExpTypeRefs renameMap e1)
(renameExpTypeRefs renameMap e2)
(renameExpTypeRefs renameMap e3)
renameExpTypeRefs renameMap (ExpTypeInfo cn field) =
ExpTypeInfo (renameTypeName renameMap cn) field

renameMemberQualifierTypeRefs :: Map Name Name -> Exp -> Exp
renameMemberQualifierTypeRefs renameMap e =
Expand Down
14 changes: 13 additions & 1 deletion src/Solcore/Frontend/Parser/Expr.hs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,19 @@ idxOp bp = do
return (\e -> locatedExpFrom [sourceSpanOf e, sourceSpanOf idx] (ExpIndexed e idx))

atomP :: BodyP -> Parser Exp
atomP bp = litP <|> try (lamP bp) <|> proxyP <|> try (dotNameP bp) <|> parenP bp <|> nameP bp
atomP bp = litP <|> try typeInfoP <|> try (lamP bp) <|> proxyP <|> try (dotNameP bp) <|> parenP bp <|> nameP bp

-- | Parse the `type(C).field` primitive, e.g. `type(Token).publicMethods`.
-- The contract name and field are kept as raw names and interpreted during
-- name resolution / desugaring.
typeInfoP :: Parser Exp
typeInfoP = do
keyword "type"
cn <- parens identifier
_ <- char '.'
sc
field <- identifier
return (ExpTypeInfo (Name cn) (Name field))

litP :: Parser Exp
litP =
Expand Down
2 changes: 2 additions & 0 deletions src/Solcore/Frontend/Pretty/TreePretty.hs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,8 @@ instance Pretty Exp where
]
ppr (ExpAt t) =
text "@" <> ppr t
ppr (ExpTypeInfo cn field) =
text "type" <> parens (ppr cn) <> char '.' <> ppr field

pprE :: Maybe Exp -> Doc
pprE Nothing = ""
Expand Down
23 changes: 23 additions & 0 deletions src/Solcore/Frontend/Syntax/NameResolution.hs
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,19 @@ resolveExp (S.ExpAt t) = do
(Con (Name "Proxy") [])
(TyCon (Name "Proxy") [t'])
)
-- `type(C).publicMethods` is a compiler primitive: it desugars to a call to
-- a per-contract helper function generated by the PublicMethods desugarer
-- (see Solcore.Desugarer.PublicMethods). The helper builds the array of
-- public-method selectors used to compute the contract's interface id.
resolveExp (S.ExpTypeInfo cn field)
| field == Name "publicMethods" =
pure (Call Nothing (publicMethodsTagName cn) [])
| otherwise =
throwError $
unlines
[ "Unknown type(...) field: " ++ pretty field,
" only `publicMethods` is currently supported"
]

instance Resolve S.Literal where
type Result S.Literal = Literal
Expand Down Expand Up @@ -1401,6 +1414,16 @@ undefinedName n =
[]
[]

-- | Name of the helper function generated for a contract's `publicMethods`
-- primitive. Both name resolution (which emits the call) and the
-- PublicMethods desugarer (which emits the definition) must agree on this
-- name, so it lives here and is imported by the desugarer.
publicMethodsTagName :: Name -> Name
publicMethodsTagName cn = Name ("$publicMethods$" ++ leafName cn)
where
leafName (Name s) = s
leafName (QualName _ s) = s

unqualifiedConstructorError :: Name -> ResolveM a
unqualifiedConstructorError n =
diagnosticErrorAtName
Expand Down
11 changes: 10 additions & 1 deletion src/Solcore/Frontend/Syntax/SyntaxTree.hs
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,7 @@ data Exp
| ExpBNotWithLocation NodeLocation Exp -- ~ e
| ExpCondWithLocation NodeLocation Exp Exp Exp -- if e1 then e2 else e3
| ExpAtWithLocation NodeLocation Ty -- proxy sugar
| ExpTypeInfoWithLocation NodeLocation Name Name -- type(C).field primitive (e.g. type(C).publicMethods)
deriving (Eq, Ord, Show, Data, Typeable)

pattern Lit :: Literal -> Exp
Expand Down Expand Up @@ -784,7 +785,12 @@ pattern ExpAt ty <- ExpAtWithLocation _ ty
where
ExpAt ty = ExpAtWithLocation unlocatedNode ty

{-# COMPLETE Lit, ExpName, ExpVar, ExpDotName, Lam, TyExp, ExpIndexed, ExpPlus, ExpMinus, ExpTimes, ExpDivide, ExpModulo, ExpBXor, ExpBAnd, ExpBOr, ExpLT, ExpGT, ExpLE, ExpGE, ExpEE, ExpNE, ExpLAnd, ExpLOr, ExpLNot, ExpBNot, ExpCond, ExpAt #-}
pattern ExpTypeInfo :: Name -> Name -> Exp
pattern ExpTypeInfo cn field <- ExpTypeInfoWithLocation _ cn field
where
ExpTypeInfo cn field = ExpTypeInfoWithLocation unlocatedNode cn field

{-# COMPLETE Lit, ExpName, ExpVar, ExpDotName, Lam, TyExp, ExpIndexed, ExpPlus, ExpMinus, ExpTimes, ExpDivide, ExpModulo, ExpBXor, ExpBAnd, ExpBOr, ExpLT, ExpGT, ExpLE, ExpGE, ExpEE, ExpNE, ExpLAnd, ExpLOr, ExpLNot, ExpBNot, ExpCond, ExpAt, ExpTypeInfo #-}

locatedExp :: SourceSpan -> Exp -> Exp
locatedExp sourceSpan (Lit lit) = LitWithLocation location lit
Expand Down Expand Up @@ -816,6 +822,7 @@ locatedExp sourceSpan (ExpLNot exp) = ExpLNotWithLocation (locatedNode sourceSpa
locatedExp sourceSpan (ExpBNot exp) = ExpBNotWithLocation (locatedNode sourceSpan) exp
locatedExp sourceSpan (ExpCond cond thenExp elseExp) = ExpCondWithLocation (locatedNode sourceSpan) cond thenExp elseExp
locatedExp sourceSpan (ExpAt ty) = ExpAtWithLocation (locatedNode sourceSpan) ty
locatedExp sourceSpan (ExpTypeInfo cn field) = ExpTypeInfoWithLocation (locatedNode sourceSpan) cn field

instance HasSourceSpan Exp where
sourceSpanOf (LitWithLocation location _) = sourceSpanOf location
Expand Down Expand Up @@ -871,6 +878,8 @@ instance HasSourceSpan Exp where
firstSourceSpan [sourceSpanOf location, sourceSpanOf cond, sourceSpanOf thenExp, sourceSpanOf elseExp]
sourceSpanOf (ExpAtWithLocation location ty) =
firstSourceSpan [sourceSpanOf location, sourceSpanOf ty]
sourceSpanOf (ExpTypeInfoWithLocation location cn field) =
firstSourceSpan [sourceSpanOf location, sourceSpanOf cn, sourceSpanOf field]

-- pattern matching equations

Expand Down
16 changes: 14 additions & 2 deletions src/Solcore/Pipeline/SolcorePipeline.hs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import Solcore.Desugarer.FieldAccess (fieldDesugarTopDecls)
import Solcore.Desugarer.IfDesugarer (ifDesugarer)
import Solcore.Desugarer.IndirectCall (indirectCallTopDecls)
import Solcore.Desugarer.IntLiteralDesugar (desugarIntLiterals)
import Solcore.Desugarer.PublicMethods (publicMethodsTopDecls)
import Solcore.Desugarer.ReplaceFunTypeArgs
import Solcore.Desugarer.ReplaceWildcard (replaceWildcardTopDecls)
import Solcore.Desugarer.StrLiteralDesugar (desugarStrLiterals)
Expand Down Expand Up @@ -849,12 +850,23 @@ prepareInferenceDeclsForTypeInference opts emitOutput imps inferenceDecls = do
]
writeContractAbis (optOutputDir opts) localTopDecls

-- `type(C).publicMethods` primitive: generate the per-contract helper that
-- yields the public-method tuple as a `Proxy` type token. Runs BEFORE
-- dispatch generation so it sees only the user-declared methods (dispatch
-- later injects `main`/`init_`/deploy helpers, which must NOT count as public
-- methods). The `Method(...)` types it emits refer to the `DispatchNameTy_*`
-- name types that the dispatch pass then creates.
let withPublicMethods = mapModuleInferenceTopDecls publicMethodsTopDecls accessed
liftIO $ when verbose $ do
putStrLn "> publicMethods desugaring:"
putStrLn $ prettyInferenceDecls withPublicMethods

-- contract dispatch generation
dispatched <-
liftIO $
if noGenDispatch
then pure accessed
else timeItNamed "Contract dispatch generation" $ pure (mapModuleInferenceTopDecls contractDispatchTopDecls accessed)
then pure withPublicMethods
else timeItNamed "Contract dispatch generation" $ pure (mapModuleInferenceTopDecls contractDispatchTopDecls withPublicMethods)

liftIO $ when (emitOutput && optDumpDispatch opts) $ do
putStrLn "> Dispatch:"
Expand Down
Loading
Loading