Skip to content

Commit e4c692d

Browse files
committed
Check store isomorphism after behavoours and examples
1 parent 9c6b845 commit e4c692d

File tree

7 files changed

+345
-32
lines changed

7 files changed

+345
-32
lines changed

src/Act/HEVM.hs

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import Data.ByteString (ByteString)
2727
import Data.Text.Encoding (encodeUtf8)
2828
import Control.Concurrent.Async
2929
import Control.Monad
30-
import Data.Foldable (sequenceA_)
30+
import Data.Foldable (sequenceA_, traverse_)
3131
import Data.DoubleWord
3232
import Data.Maybe
3333
import Data.Type.Equality (TestEquality(..))
@@ -149,7 +149,8 @@ translateConstructor bytecode (Constructor cid iface _ preconds _ _ upds) cmap =
149149
preconds' <- mapM (toProp initmap) preconds
150150
cmap' <- applyUpdates initmap initmap upds
151151
fresh <- getFresh
152-
pure $ ([EVM.Success (snd calldata <> preconds' <> symAddrCnstr 1 fresh) mempty (EVM.ConcreteBuf bytecode) (M.map fst cmap')], calldata, ifaceToSig iface, cmap')
152+
let acmap = abstractCmap initAddr cmap'
153+
pure $ ([EVM.Success (snd calldata <> preconds' <> symAddrCnstr 1 fresh) mempty (EVM.ConcreteBuf bytecode) (M.map fst cmap')], calldata, ifaceToSig iface, acmap)
153154
where
154155
calldata = makeCtrCalldata iface
155156
initcontract = EVM.C { EVM.code = EVM.RuntimeCode (EVM.ConcreteRuntimeCode bytecode)
@@ -162,7 +163,7 @@ translateConstructor bytecode (Constructor cid iface _ preconds _ _ upds) cmap =
162163
symAddrCnstr :: Int -> Int -> [EVM.Prop]
163164
symAddrCnstr start end = fmap (\i -> EVM.PNeg (EVM.PEq (EVM.WAddr (EVM.SymAddr $ "freshSymAddr" <> (T.pack $ show i))) (EVM.Lit 0))) [start..end]
164165

165-
translateBehvs :: Monad m => ContractMap -> [Behaviour] -> ActT m [(Id, [EVM.Expr EVM.End], Calldata, Sig)]
166+
translateBehvs :: Monad m => ContractMap -> [Behaviour] -> ActT m [(Id, [(EVM.Expr EVM.End, ContractMap)], Calldata, Sig)]
166167
translateBehvs cmap behvs = do
167168
let groups = (groupBy sameIface behvs) :: [[Behaviour]]
168169
mapM (\behvs' -> do
@@ -187,15 +188,16 @@ ifaceToSig (Interface name args) = Sig (T.pack name) (fmap fromdecl args)
187188
where
188189
fromdecl (Decl t _) = t
189190

190-
translateBehv :: Monad m => ContractMap -> Behaviour -> ActT m (EVM.Expr EVM.End)
191+
translateBehv :: Monad m => ContractMap -> Behaviour -> ActT m (EVM.Expr EVM.End, ContractMap)
191192
translateBehv cmap (Behaviour _ _ _ _ preconds caseconds _ upds ret) = do
192193
fresh <- getFresh
193194
preconds' <- mapM (toProp cmap) preconds
194195
caseconds' <- mapM (toProp cmap) caseconds
195196
ret' <- returnsToExpr cmap ret
196197
cmap' <- applyUpdates cmap cmap upds
197198
fresh' <- getFresh
198-
pure $ EVM.Success (preconds' <> caseconds' <> symAddrCnstr (fresh+1) fresh') mempty ret' (M.map fst cmap')
199+
let acmap = abstractCmap initAddr cmap'
200+
pure $ (EVM.Success (preconds' <> caseconds' <> symAddrCnstr (fresh+1) fresh') mempty ret' (M.map fst cmap'), acmap)
199201

200202

201203
applyUpdates :: Monad m => ContractMap -> ContractMap -> [StorageUpdate] -> ActT m ContractMap
@@ -727,7 +729,7 @@ checkConstructors solvers initcode runtimecode (Contract ctor@(Constructor _ ifa
727729
res1 <- lift $ checkResult calldata (Just sig) =<< checkEquiv solvers solbehvs actbehvs
728730
lift $ showMsg "\x1b[1mChecking if constructor input spaces are the same.\x1b[m"
729731
res2 <- lift $ checkResult calldata (Just sig) =<< checkInputSpaces solvers solbehvs actbehvs
730-
pure $ checks *> res1 *> res2 *> Success (abstractCmap initAddr $ cmap)
732+
pure $ checks *> res1 *> res2 *> Success cmap
731733
where
732734
removeFails branches = filter isSuccess $ branches
733735

@@ -737,17 +739,18 @@ checkBehaviours solvers (Contract _ behvs) actstorage = do
737739
let hevmstorage = translateCmap actstorage
738740
fresh <- getFresh
739741
actbehvs <- translateBehvs actstorage behvs
740-
(liftM $ concatError def) $ flip mapM actbehvs $ \(name,behvs',calldata, sig) -> do
742+
(liftM $ concatError def) $ flip mapM actbehvs $ \(name,actbehv,calldata, sig) -> do
743+
let (behvs', fcmaps) = unzip actbehv
744+
741745
solbehvs <- lift $ removeFails <$> getRuntimeBranches solvers hevmstorage calldata fresh
742-
743746
lift $ showMsg $ "\x1b[1mChecking behavior \x1b[4m" <> name <> "\x1b[m of Act\x1b[m"
744747
-- equivalence check
745748
lift $ showMsg $ "\x1b[1mChecking if behaviour is matched by EVM\x1b[m"
746749
res1 <- lift $ checkResult calldata (Just sig) =<< checkEquiv solvers solbehvs behvs'
747750
-- input space exhaustiveness check
748751
lift $ showMsg $ "\x1b[1mChecking if the input spaces are the same\x1b[m"
749752
res2 <- lift $ checkResult calldata (Just sig) =<< checkInputSpaces solvers solbehvs behvs'
750-
pure $ res1 *> res2
753+
pure $ traverse_ (checkStoreIsomorphism actstorage) fcmaps *> res1 *> res2
751754

752755
where
753756
removeFails branches = filter isSuccess $ branches
@@ -794,7 +797,7 @@ abstractCmap this cmap =
794797
traverseStorage _ _ = error $ "Internal error: unexpected storage shape"
795798

796799
makeContract :: EVM.Expr EVM.EAddr -> (EVM.Expr EVM.EContract, Id) -> (EVM.Expr EVM.EContract, Id)
797-
makeContract addr (EVM.C code storage _ _, cid) = (EVM.C code (traverseStorage addr (simplify storage)) (EVM.Balance addr) (Just 0), cid)
800+
makeContract addr (EVM.C code storage _ _, cid) = (EVM.C code (simplify (traverseStorage addr (simplify storage))) (EVM.Balance addr) (Just 0), cid)
798801
makeContract _ (EVM.GVar _, _) = error "Internal error: contract cannot be gvar"
799802

800803
-- | Remove unreachable addresses from a contract map
@@ -833,42 +836,55 @@ pruneContractState entryaddr cmap =
833836

834837

835838
-- | Check if two contract maps are isomorphic
836-
-- Note that is problem is not as difficult as graph isomorphism since edges are labeld.
839+
-- Perform a breadth first traversal and try to find a bijection between the addresses of the two stores
840+
-- Note that is problem is not as difficult as graph isomorphism since edges are labeld.
841+
-- Assumes that the stores are abstracted, pruned, and simplified.
842+
-- All writes are to a unique concrete slot and the value is a simbolic address.
837843
checkStoreIsomorphism :: ContractMap -> ContractMap -> Error String ()
838-
checkStoreIsomorphism cmap1 cmap2 = visit [(initAddr, initAddr)] [] cmap1 cmap2 M.empty M.empty
844+
checkStoreIsomorphism cmap1 cmap2 = bfs [(idOfAddr initAddr, idOfAddr initAddr)] [] M.empty M.empty
839845
where
840846
-- tries to find a bijective renaming between the addresses of the two maps
841-
visit :: [(EVM.Expr EVM.EAddr, EVM.Expr EVM.EAddr)] -- Queue of the addresses we are exploring (dequeue)
842-
-> [(EVM.Expr EVM.EAddr, EVM.Expr EVM.EAddr)] -- Queue of the addresses we are exploring (enueue)
843-
-> ContractMap -> ContractMap -- The two contract maps
844-
-> M.Map Id Id -> M.Map Id Id -- Two renamings keeping track of the address correspondence on the two maps
845-
-> Error String ()
846-
visit [] [] _ _ _ _ = pure ()
847-
visit [] q2 _ _ _ _ = visit (rev q2) [] _ _ _ _
848-
visit ((addr1, addr2):q1) q2 cmap1 cmap2 map1 map2 = do
849-
let addrs1 = sortOn (\x y -> fst x <= fst y) $ getAddrs addr1 cmap1
850-
let addrs2 = sortOn (\x y -> fst x <= fst y) $ getAddrs addr2 cmap2
851-
(renaming1, renaming2, q2) <- addNeighbors addrs1 addrs2 map1 map2 q2
852-
visit q1 q2' cmap1 cmap2 renaming1 renaming2
847+
bfs :: [(T.Text, T.Text)] -- Queue of the addresses we are exploring (dequeue)
848+
-> [(T.Text, T.Text)] -- Queue of the addresses we are exploring (enueue)
849+
-> M.Map T.Text T.Text -> M.Map T.Text T.Text -- Two renamings keeping track of the address bijection
850+
-> Error String ()
851+
bfs [] [] _ _ = pure ()
852+
bfs [] q2 m1 m2 = bfs (reverse q2) [] m1 m2
853+
bfs ((addr1, addr2):q1) q2 map1 map2 =
854+
case (M.lookup (EVM.SymAddr addr1) cmap1, M.lookup (EVM.SymAddr addr2) cmap2) of
855+
(Just (EVM.C _ storage1 _ _, _), Just (EVM.C _ storage2 _ _, _)) ->
856+
let addrs1 = sortOn fst $ getAddrs storage1 in
857+
let addrs2 = sortOn fst $ getAddrs storage2 in
858+
visit addrs1 addrs2 map1 map2 q2 `bindValidation` (\(renaming1, renaming2, q2') ->
859+
bfs q1 q2' renaming1 renaming2)
860+
(_, _) -> error "Internal error: contract not found in map"
853861

854862
-- assumes that slots are unique because of simplifcation
855-
addNeighbors [] [] map1 map2 discovered = pure (map1, map2, discovered)
856-
addNeighbors ((s1, a1):addrs1) ((s2, a2):addrs2) map1 map2 discovered | s1 == s2 =
857-
case (M.lookup s1 map1, M.lookup s2 map2) of
858-
(Just s1', Just s2') ->
859-
if s2 == s2' && s1 == s2' then addNeighbors addrs1 addrs2 map1 map2 discovered
863+
visit :: [(Int, EVM.Expr EVM.EAddr)] -> [(Int, EVM.Expr EVM.EAddr)]
864+
-> M.Map T.Text T.Text -> M.Map T.Text T.Text
865+
-> [(T.Text, T.Text)]
866+
-> Error String (M.Map T.Text T.Text, M.Map T.Text T.Text, [(T.Text, T.Text)])
867+
visit [] [] map1 map2 discovered = pure (map1, map2, discovered)
868+
visit ((s1, EVM.SymAddr a1):addrs1) ((s2, EVM.SymAddr a2):addrs2) map1 map2 discovered | s1 == s2 =
869+
case (M.lookup a1 map1, M.lookup a2 map2) of
870+
(Just a2', Just a1') ->
871+
if a2 == a2' && a1 == a1' then visit addrs1 addrs2 map1 map2 discovered
860872
else throw (nowhere, "The shape of the resulting map is not preserved.")
861-
Nothing -> pure $ visit addrs1 addrs2 (M.insert s1 s2 map1) (M.insert s2 s1 map2) ((s1, s2): discovered)
862-
addNeighbors _ _ = throw (nowhere, "The shape of the resulting map is not preserved.")
873+
(Nothing, Nothing) -> visit addrs1 addrs2 (M.insert a1 a2 map1) (M.insert a2 a1 map2) ((a1, a2): discovered)
874+
(_, _) -> throw (nowhere, "The shape of the resulting map is not preserved.")
875+
visit _ _ _ _ _ = throw (nowhere, "The shape of the resulting map is not preserved.")
863876

864877
-- Find addresses mentioned in storage
865878
getAddrs :: EVM.Expr EVM.Storage -> [(Int, EVM.Expr EVM.EAddr)]
866-
getAddrs (EVM.SStore (EVM.Lit n) (EVM.WAddr symaddr) storage) = (n, symaddr) : getAddrs storage
879+
getAddrs (EVM.SStore (EVM.Lit n) (EVM.WAddr symaddr) storage) = (fromIntegral n, symaddr) : getAddrs storage
867880
getAddrs (EVM.SStore _ _ _) = error $ "Internal error: unexpected storage shape"
868881
getAddrs (EVM.ConcreteStore _) = error $ "Internal error: unexpected storage shape"
869882
getAddrs (EVM.AbstractStore {}) = []
870883
getAddrs _ = error $ "Internal error: unexpected storage shape"
871884

885+
idOfAddr :: EVM.Expr EVM.EAddr -> T.Text
886+
idOfAddr (EVM.SymAddr addr) = addr
887+
idOfAddr _ = error "Internal error: upecting symbolic address"
872888

873889
-- | Find the input space of an expr list
874890
inputSpace :: [EVM.Expr EVM.End] -> [EVM.Prop]
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// contract A
2+
constructor of A
3+
interface constructor(uint z)
4+
5+
iff
6+
CALLVALUE == 0
7+
8+
creates
9+
uint x := z
10+
11+
12+
behaviour x of A
13+
interface x()
14+
15+
iff
16+
CALLVALUE == 0
17+
18+
returns
19+
pre(x)
20+
21+
// contract C
22+
constructor of C
23+
interface constructor()
24+
iff
25+
CALLVALUE == 0
26+
27+
creates
28+
A a1 := create A(42)
29+
A a2 := create A(17)
30+
31+
behaviour change of C
32+
interface change()
33+
34+
iff
35+
36+
CALLVALUE == 0
37+
38+
storage
39+
a1 => a2
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
pragma solidity >=0.8.0;
2+
3+
contract A {
4+
uint public x;
5+
6+
constructor (uint z) {
7+
x = z;
8+
}
9+
}
10+
11+
12+
contract C {
13+
A a1;
14+
A a2;
15+
16+
constructor () {
17+
a1 = new A(42);
18+
a2 = new A(17);
19+
}
20+
21+
function change() public {
22+
a1 = a2;
23+
}
24+
}

tests/hevm/fail/shape/shape.act

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// contract A
2+
constructor of A
3+
interface constructor(uint z)
4+
5+
iff
6+
CALLVALUE == 0
7+
8+
creates
9+
uint x := z
10+
11+
12+
behaviour x of A
13+
interface x()
14+
15+
iff
16+
CALLVALUE == 0
17+
18+
returns
19+
pre(x)
20+
21+
behaviour set_x of A
22+
interface set_x(uint z)
23+
24+
iff
25+
CALLVALUE == 0
26+
27+
storage
28+
x => z
29+
30+
// contract B
31+
constructor of B
32+
interface constructor(uint z)
33+
iff
34+
CALLVALUE == 0
35+
36+
creates
37+
uint y := z
38+
A a := create A(0)
39+
40+
behaviour y of B
41+
interface y()
42+
43+
iff
44+
CALLVALUE == 0
45+
46+
returns
47+
pre(y)
48+
49+
behaviour a of B
50+
interface a()
51+
52+
iff
53+
CALLVALUE == 0
54+
55+
returns
56+
pre(a)
57+
58+
59+
// contract C
60+
constructor of C
61+
interface constructor(address y)
62+
pointers
63+
y |-> B
64+
65+
iff
66+
CALLVALUE == 0
67+
68+
creates
69+
A a := y.a
70+
B b := y
71+
72+
behaviour change of C
73+
interface change()
74+
75+
iff
76+
77+
CALLVALUE == 0
78+
79+
storage
80+
a => create A(42)

tests/hevm/fail/shape/shape.sol

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
pragma solidity >=0.8.0;
2+
3+
contract A {
4+
uint public x;
5+
6+
constructor (uint z) {
7+
x = z;
8+
}
9+
10+
function set_x(uint z) public{
11+
x = z;
12+
}
13+
}
14+
15+
contract B {
16+
uint public y;
17+
A public a;
18+
19+
constructor (uint z) {
20+
y = z;
21+
a = new A(0);
22+
}
23+
}
24+
25+
contract C {
26+
A a;
27+
B b;
28+
29+
constructor (address y) {
30+
a = B(y).a();
31+
b = B(y);
32+
}
33+
34+
function change() public {
35+
a = new A(42);
36+
}
37+
}

0 commit comments

Comments
 (0)