diff --git a/run_contests.sh b/run_contests.sh index 60a7705cb..c2e914ed7 100755 --- a/run_contests.sh +++ b/run_contests.sh @@ -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 diff --git a/sol-core.cabal b/sol-core.cabal index 488357638..47319b745 100644 --- a/sol-core.cabal +++ b/sol-core.cabal @@ -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 diff --git a/src/Solcore/Desugarer/ContractDispatch.hs b/src/Solcore/Desugarer/ContractDispatch.hs index 2e4f7d1f9..0862f2ef8 100644 --- a/src/Solcore/Desugarer/ContractDispatch.hs +++ b/src/Solcore/Desugarer/ContractDispatch.hs @@ -13,6 +13,8 @@ module Solcore.Desugarer.ContractDispatch contractDispatchTopDecls, writeContractAbis, contractAbiJson, + nameTypeName, + publicMethodTypes, ) where @@ -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 @@ -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_" @@ -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 +-- 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] diff --git a/src/Solcore/Desugarer/PublicMethods.hs b/src/Solcore/Desugarer/PublicMethods.hs new file mode 100644 index 000000000..dcd11c963 --- /dev/null +++ b/src/Solcore/Desugarer/PublicMethods.hs @@ -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)] diff --git a/src/Solcore/Frontend/Module/Loader.hs b/src/Solcore/Frontend/Module/Loader.hs index b3df388b1..3cf177012 100644 --- a/src/Solcore/Frontend/Module/Loader.hs +++ b/src/Solcore/Frontend/Module/Loader.hs @@ -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 = diff --git a/src/Solcore/Frontend/Parser/Expr.hs b/src/Solcore/Frontend/Parser/Expr.hs index 31a56694e..bce5ac61d 100644 --- a/src/Solcore/Frontend/Parser/Expr.hs +++ b/src/Solcore/Frontend/Parser/Expr.hs @@ -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 = diff --git a/src/Solcore/Frontend/Pretty/TreePretty.hs b/src/Solcore/Frontend/Pretty/TreePretty.hs index d5ff5edda..a6b5d1fa8 100644 --- a/src/Solcore/Frontend/Pretty/TreePretty.hs +++ b/src/Solcore/Frontend/Pretty/TreePretty.hs @@ -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 = "" diff --git a/src/Solcore/Frontend/Syntax/NameResolution.hs b/src/Solcore/Frontend/Syntax/NameResolution.hs index ab9dedfd1..762a5a133 100644 --- a/src/Solcore/Frontend/Syntax/NameResolution.hs +++ b/src/Solcore/Frontend/Syntax/NameResolution.hs @@ -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 @@ -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 diff --git a/src/Solcore/Frontend/Syntax/SyntaxTree.hs b/src/Solcore/Frontend/Syntax/SyntaxTree.hs index 096e719e1..890c65331 100644 --- a/src/Solcore/Frontend/Syntax/SyntaxTree.hs +++ b/src/Solcore/Frontend/Syntax/SyntaxTree.hs @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/src/Solcore/Pipeline/SolcorePipeline.hs b/src/Solcore/Pipeline/SolcorePipeline.hs index dbe05feaa..42efc8efb 100644 --- a/src/Solcore/Pipeline/SolcorePipeline.hs +++ b/src/Solcore/Pipeline/SolcorePipeline.hs @@ -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) @@ -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:" diff --git a/std/dispatch.solc b/std/dispatch.solc index 3ec114b4c..5a0052911 100644 --- a/std/dispatch.solc +++ b/std/dispatch.solc @@ -11,10 +11,13 @@ export { MethodLevelCallvalueCheck, NonPayable, Payable, + PublicMethods, RunContract, RunDispatch, Selector, SigString, + calculateInterfaceId, + calculateSelector, do_exec, fallback_default_implementation, selector_matches, @@ -126,6 +129,67 @@ forall name payability args rets fn } } +// --- Interface Id --- + +// The ABI selector for a single method type. This is the same encoding used by +// `Selector.compute`; it is exposed as a standalone function so callers can +// read a method's selector directly. +forall ty . ty:Selector => function calculateSelector(prx : Proxy(ty)) -> bytes4 { + return Selector.compute(prx); +} + +// `type(C).publicMethods` hands back a `Proxy` over the contract's public +// methods, encoded as a right-nested tuple terminated by `()`: +// +// Proxy((Method(...), (Method(...), ... ()))) +// +// Each element is a `Method(name,payability,args,rets,fn)` — the same typing +// `Selector.compute` consumes — so its selector is recovered from the type with +// no hashing in the compiler. +// +// The tuple is *heterogeneous*: every method is a distinct `Method(...)` type, +// so it cannot be indexed by a runtime `word`. `PublicMethods` therefore walks +// it structurally — a `()` base case and a `(Method(...), m)` recursive case, +// mirroring `RunDispatch` — XOR-folding the per-method selectors into an +// interface id. The recursive instance carries the very same `name:SigString, +// args:SigString` constraints as the `Method(...):Selector` instance, so +// `Selector.compute` resolves directly on the head method. +// +// `calculateInterfaceId(type(C).publicMethods)` then reads like Solidity's +// `type(I).interfaceId`, with all selector/iteration logic in the standard +// library rather than the compiler. +forall ty . class ty : PublicMethods { + function interfaceId(p : Proxy(ty)) -> bytes4; +} + +// Base case: the `()` tuple terminator — no methods left. +instance () : PublicMethods { + function interfaceId(p : Proxy(())) -> bytes4 { + return 0; + } +} + +// Recursive case: a head `Method(...)` (same typing and constraints +// `Selector.compute` uses) followed by the remaining methods `m`. +forall name payability args rets fn m + . name : SigString + , args : SigString + , m : PublicMethods +=> instance (Method(name,payability,args,rets,fn), m) : PublicMethods { + function interfaceId(p : Proxy((Method(name,payability,args,rets,fn), m))) -> bytes4 { + let head : bytes4 = Selector.compute(Proxy : Proxy(Method(name,payability,args,rets,fn))); + let rest : bytes4 = PublicMethods.interfaceId(Proxy : Proxy(m)); + return bytes4(Typedef.rep(head) ^ Typedef.rep(rest)); + } +} + +// Compute an ERC-165 style interface id by XOR-folding the public-method +// selectors. Mirrors Solidity's `type(I).interfaceId`. +forall ty . ty : PublicMethods => +function calculateInterfaceId(methods : Proxy(ty)) -> bytes4 { + return PublicMethods.interfaceId(methods); +} + // --- Method Execution --- // Describes how to execute a given method / fallback diff --git a/std/std.solc b/std/std.solc index 6477d0614..27824cfcb 100644 --- a/std/std.solc +++ b/std/std.solc @@ -852,6 +852,12 @@ instance bytes4:Typedef(word) { } } +instance bytes4:Int { + function fromInteger(x:integer) -> bytes4 { + return bytes4(wordFromInteger(x)); + } +} + // --- Bytes32 --- data bytes32 = bytes32(word); diff --git a/test/examples/dispatch/deposit.json b/test/examples/dispatch/deposit.json index 137c5e2aa..f60e2b5a3 100644 --- a/test/examples/dispatch/deposit.json +++ b/test/examples/dispatch/deposit.json @@ -12,6 +12,32 @@ "kind": "constructor" }, + { + "input": { + "comment": "supportsInterface(0xc751f4a2) - contract's own interface id (XOR of get_zero_hash/get_deposit_root/get_deposit_count/deposit/supportsInterface selectors); bytes4 arg is right-aligned", + "calldata": "01ffc9a700000000000000000000000000000000000000000000000000000000c751f4a2", + "value": "0" + }, + "kind": "call", + "output": { + "returndata": "0000000000000000000000000000000000000000000000000000000000000001", + "status": "success" + } + }, + + { + "input": { + "comment": "supportsInterface(0x00000000) - non-matching interface id returns false", + "calldata": "01ffc9a70000000000000000000000000000000000000000000000000000000000000000", + "value": "0" + }, + "kind": "call", + "output": { + "returndata": "0000000000000000000000000000000000000000000000000000000000000000", + "status": "success" + } + }, + { "input": { "comment": "get_zero_hash(0)", diff --git a/test/examples/dispatch/deposit.solc b/test/examples/dispatch/deposit.solc index 9656d84d4..cfa3da235 100644 --- a/test/examples/dispatch/deposit.solc +++ b/test/examples/dispatch/deposit.solc @@ -160,7 +160,11 @@ contract DepositContract { } public function supportsInterface(interfaceId: bytes4) -> bool { - unimplemented(); - return false; + // ERC-165: match against this contract's own interface id, computed as the + // XOR of every public method selector via the `type(C).publicMethods` + // primitive (see std/dispatch.solc `calculateInterfaceId`). bytes4 has no + // Eq instance, so compare the underlying word reps like `selector_matches`. + let expected: bytes4 = calculateInterfaceId(type(DepositContract).publicMethods); + return Typedef.rep(interfaceId) == Typedef.rep(expected); } } diff --git a/test/examples/dispatch/interfaceid.json b/test/examples/dispatch/interfaceid.json new file mode 100644 index 000000000..132e98ca0 --- /dev/null +++ b/test/examples/dispatch/interfaceid.json @@ -0,0 +1,40 @@ +{ + "interfaceid": { + "bytecode": "", + "contract": "InterfaceId", + "tests": [ + { + "input": { + "comment": "constructor()", + "calldata": "", + "value": "0" + }, + "kind": "constructor" + }, + { + "input": { + "comment": "interfaceId()(bytes4) - XOR of foo/bar/interfaceId selectors = 0xed9d1481", + "calldata": "a64d0cd4", + "value": "0" + }, + "kind": "call", + "output": { + "returndata": "00000000000000000000000000000000000000000000000000000000ed9d1481", + "status": "success" + } + }, + { + "input": { + "comment": "foo(uint256) - sanity check the contract still dispatches normally", + "calldata": "2fbebd380000000000000000000000000000000000000000000000000000000000000007", + "value": "0" + }, + "kind": "call", + "output": { + "returndata": "0000000000000000000000000000000000000000000000000000000000000007", + "status": "success" + } + } + ] + } +} diff --git a/test/examples/dispatch/interfaceid.solc b/test/examples/dispatch/interfaceid.solc new file mode 100644 index 000000000..b1ed7b9ad --- /dev/null +++ b/test/examples/dispatch/interfaceid.solc @@ -0,0 +1,25 @@ +import std.{*}; +import std.dispatch.{*}; + +// Demonstrates the `type(C).publicMethods` primitive together with +// `calculateInterfaceId` from std/dispatch.solc, replicating Solidity's +// `type(I).interfaceId`. +// +// The interface id is the XOR of the selectors of every public method: +// foo(uint256) -> 0x2fbebd38 +// bar(address) -> 0x646ea56d +// interfaceId() -> 0xa64d0cd4 +// XOR -> 0xed9d1481 +contract InterfaceId { + public function foo(x : uint256) -> uint256 { + return x; + } + + public function bar(a : address) -> uint256 { + return 0; + } + + public function interfaceId() -> bytes4 { + return calculateInterfaceId(type(InterfaceId).publicMethods); + } +}