-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathErc20.v
186 lines (178 loc) · 6.18 KB
/
Erc20.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
Require Import FullSpecificationSmartContracts.Common.
(** Here we give an application of our DSL above with a representation of a simplified version of
an [ERC-20] smart contract. *)
(** The init parameters are a name and a symbol for the token we are creating. They do not play
any role in the computations, but are there in the ERC-20 of OpenZeppelin that we study. *)
Definition InitInput : Set :=
string * string.
(** The init outputs a new token kind *)
Definition InitOutput : Set :=
TokenKind.
(** The state of the contract is just the name and the symbol of its token. The token balance for
each user is stored in the global world and we do not have to handle it here. *)
Module State.
Record t : Set := {
name : string;
symbol : string;
}.
End State.
(** The commands representing the entrypoints of our smart contract, with the type of their
output. *)
Module Command.
Inductive t {token_kind : InitOutput} : Set -> Set :=
| BalanceOf : User -> t (TokenQuantity token_kind)
| GetAllowance : User -> User -> t (TokenQuantity token_kind)
| Transfer : User -> TokenQuantity token_kind -> t unit
| Approve (spender : User) (quantity : TokenQuantity token_kind) : t unit
| Mint (account : User) (value : TokenQuantity token_kind) : t unit.
Arguments t : clear implicits.
End Command.
(** The definition of our ERC-20 smart contract *)
Definition smart_contract :
SmartContract.t
InitInput
InitOutput
Command.t
State.t :=
{|
(* The constructor function *)
SmartContract.init _sender '(name, symbol) :=
let! token_kind := M.MakeAction Action.CreateTokenKind in
let state := {|
State.name := name;
State.symbol := symbol;
|} in
M.Pure (Some (token_kind, state));
SmartContract.call A token_kind sender command state :=
match command in Command.t _ A return M.t (option (A * _)) with
(* The "balanceOf" entrypoint *)
| Command.BalanceOf user =>
(* We run the action to get the balance *)
let! balance := M.MakeAction (Action.GetBalance token_kind user) in
M.Pure (Some (balance, state))
(* The "allowance" entrypoint *)
| Command.GetAllowance user spender =>
let! allowance := M.MakeAction (Action.GetAllowance token_kind user spender) in
M.Pure (Some (allowance, state))
(* The "transfer" entrypoint *)
| Command.Transfer to value =>
(* We run the action to make the transfer and to know if it succeeded *)
let! is_success := M.MakeAction (Action.Transfer token_kind sender to value) in
if is_success then
M.Pure (Some (tt, state))
else
M.Pure None
(* The "approve" entrypoint *)
| Command.Approve spender value =>
let! _ := M.MakeAction (Action.Approve token_kind sender spender value) in
M.Pure (Some (tt, state))
| Command.Mint account value =>
let! is_success := M.MakeAction (Action.Mint token_kind account value) in
if is_success then
M.Pure (Some (tt, state))
else
M.Pure None
end;
|}.
(** Now we will verify that our ERC-20 smart contract is safe *)
Module IsSafe.
(** Here is the specification saying that our smart contract is safe. It applies the predicate
saying that a smart contract is safe to our definition of ERC-20. *)
Lemma is_safe : NoStealing.InSmartContract.t Erc20.smart_contract.
(** Here is the proof that the specification is correct. It should be executed in an IDE to see
the proof state as we progress line by line.
The general idea is to go over all the possible execution scenarios of the smart contract,
with the init, the "balanceOf", and the "transfer" entrypoints, and to prove that they are all
safe.
*)
Proof.
constructor; intros; cbn.
{ (* init *)
destruct init_input as [name symbol].
unfold NoStealing.InRun.t; cbn.
destruct Primitives.create_token_kind.
apply ActionTree.Forall.Let. {
(* The init is safe because the action we make to create a new token kind is safe *)
apply ActionTree.Forall.MakeAction.
cbn.
trivial.
}
apply ActionTree.Forall.Pure.
}
{ (* call *)
destruct command.
{ (* BalanceOf *)
unfold NoStealing.InRun.t; cbn.
apply ActionTree.Forall.Let. {
(* The "balanceOf" entrypoint is safe because the action we make to get the balance of a
user is safe *)
apply ActionTree.Forall.MakeAction.
cbn.
trivial.
}
apply ActionTree.Forall.Pure.
}
{ (* GetAllowance *)
cbn.
unfold NoStealing.InActionTree.t.
apply ActionTree.Forall.Let. {
apply ActionTree.Forall.MakeAction.
cbn.
trivial.
}
apply ActionTree.Forall.Pure.
}
{ (* Transfer *)
unfold NoStealing.InRun.t; cbn.
(* We have two cases, depending on whether the transfer succeeded or not. In both cases we
make a single transfer (or transfer attempt) with the right user for the source account,
so this is safe.
*)
destruct Primitives.transfer; cbn.
{ apply ActionTree.Forall.Let. {
apply ActionTree.Forall.MakeAction.
cbn.
reflexivity.
}
apply ActionTree.Forall.Pure.
}
{ apply ActionTree.Forall.Let. {
apply ActionTree.Forall.MakeAction.
cbn.
reflexivity.
}
apply ActionTree.Forall.Pure.
}
}
{ (* Approve *)
cbn.
apply ActionTree.Forall.Let. {
apply ActionTree.Forall.MakeAction.
cbn.
trivial.
}
apply ActionTree.Forall.Pure.
}
{ (* Mint *)
unfold NoStealing.InRun.t; cbn.
destruct Primitives.mint.
{ cbn.
apply ActionTree.Forall.Let. {
apply ActionTree.Forall.MakeAction.
cbn.
trivial.
}
apply ActionTree.Forall.Pure.
}
{ cbn.
apply ActionTree.Forall.Let. {
apply ActionTree.Forall.MakeAction.
cbn.
trivial.
}
apply ActionTree.Forall.Pure.
}
}
}
Qed.
End IsSafe.