1
- use crate :: { setup, transfer, InitArgs , ARCHIVE_TRIGGER_THRESHOLD , MINTER , NUM_BLOCKS_TO_ARCHIVE } ;
1
+ use crate :: {
2
+ default_approve_args, init_args, send_approval, setup, transfer, InitArgs ,
3
+ ARCHIVE_TRIGGER_THRESHOLD , DECIMAL_PLACES , MINTER , NUM_BLOCKS_TO_ARCHIVE ,
4
+ } ;
2
5
use candid:: { CandidType , Decode , Encode , Principal } ;
3
6
use ic_base_types:: { CanisterId , PrincipalId } ;
4
7
use ic_canisters_http_types:: { HttpRequest , HttpResponse } ;
5
8
use ic_state_machine_tests:: StateMachine ;
6
9
use ic_types:: ingress:: WasmResult ;
10
+ use icrc_ledger_types:: icrc2:: approve:: ApproveArgs ;
11
+ use std:: str:: FromStr ;
7
12
8
13
pub enum LedgerSuiteType {
9
14
ICP ,
@@ -157,6 +162,127 @@ pub fn assert_ledger_upgrade_instructions_consumed_metric_set<T, U>(
157
162
) ;
158
163
}
159
164
165
+ pub fn should_compute_and_export_total_volume_metric < T > (
166
+ ledger_wasm : Vec < u8 > ,
167
+ encode_init_args : fn ( InitArgs ) -> T ,
168
+ ) where
169
+ T : CandidType ,
170
+ {
171
+ const TOTAL_VOLUME_METRIC : & str = "total_volume" ;
172
+ let mut expected_total = 0f64 ;
173
+
174
+ let initial_balances = vec ! [ (
175
+ PrincipalId :: new_user_test_id( 1 ) . 0 . into( ) ,
176
+ u64 :: MAX - 10_000_000 ,
177
+ ) ] ;
178
+ let env = StateMachine :: new ( ) ;
179
+
180
+ let transfer_fee = 10f64 . powf ( DECIMAL_PLACES as f64 - 1f64 ) as u64 ;
181
+ println ! ( "transfer_fee: {}" , transfer_fee) ;
182
+ let args = InitArgs {
183
+ transfer_fee : transfer_fee. into ( ) ,
184
+ ..init_args ( initial_balances)
185
+ } ;
186
+ let args = Encode ! ( & encode_init_args( args) ) . unwrap ( ) ;
187
+ let canister_id = env. install_canister ( ledger_wasm, args, None ) . unwrap ( ) ;
188
+
189
+ let denominator = 10f64 . powf ( DECIMAL_PLACES as f64 ) ;
190
+
191
+ let mut increase_expected_total_volume_and_assert = |amount : u64 | {
192
+ expected_total += amount as f64 / denominator;
193
+ assert_eq ! (
194
+ format!( "{:.0}" , expected_total) ,
195
+ format!(
196
+ "{:.0}" ,
197
+ parse_metric( & env, canister_id, TOTAL_VOLUME_METRIC )
198
+ )
199
+ ) ;
200
+ } ;
201
+
202
+ // Verify the metric returns 0 when no transactions have occurred
203
+ assert_eq ! ( 0 , parse_metric( & env, canister_id, TOTAL_VOLUME_METRIC ) ) ;
204
+
205
+ // Perform a bunch of small transfers to verify that the computation of decimals is correct,
206
+ // and so that the total fee exceeds 1.0.
207
+ let num_operations = denominator as u64 / transfer_fee;
208
+ println ! ( "performing {} transfers" , num_operations) ;
209
+ for _ in 0 ..num_operations {
210
+ transfer (
211
+ & env,
212
+ canister_id,
213
+ PrincipalId :: new_user_test_id ( 1 ) . 0 ,
214
+ PrincipalId :: new_user_test_id ( 2 ) . 0 ,
215
+ transfer_fee,
216
+ )
217
+ . expect ( "transfer failed" ) ;
218
+ }
219
+ increase_expected_total_volume_and_assert ( 2 * num_operations * transfer_fee) ;
220
+
221
+ // Verify total volume accounting handles minting correctly (no fee).
222
+ for _ in 0 ..num_operations {
223
+ transfer (
224
+ & env,
225
+ canister_id,
226
+ MINTER ,
227
+ PrincipalId :: new_user_test_id ( 1 ) . 0 ,
228
+ transfer_fee,
229
+ )
230
+ . expect ( "mint failed" ) ;
231
+ }
232
+ increase_expected_total_volume_and_assert ( num_operations * transfer_fee) ;
233
+
234
+ // Verify total volume accounting handles burning correctly (no fee).
235
+ for _ in 0 ..num_operations {
236
+ transfer (
237
+ & env,
238
+ canister_id,
239
+ PrincipalId :: new_user_test_id ( 1 ) . 0 ,
240
+ MINTER ,
241
+ transfer_fee,
242
+ )
243
+ . expect ( "burn failed" ) ;
244
+ }
245
+ increase_expected_total_volume_and_assert ( num_operations * transfer_fee) ;
246
+
247
+ // Verify total volume accounting handles approvals correctly (no amount).
248
+ let approve_args = ApproveArgs {
249
+ fee : Some ( transfer_fee. into ( ) ) ,
250
+ ..default_approve_args ( PrincipalId :: new_user_test_id ( 1 ) . 0 , 1_000_000_000 )
251
+ } ;
252
+ for _ in 0 ..num_operations {
253
+ send_approval (
254
+ & env,
255
+ canister_id,
256
+ PrincipalId :: new_user_test_id ( 2 ) . 0 ,
257
+ & approve_args,
258
+ )
259
+ . expect ( "approval failed" ) ;
260
+ }
261
+ increase_expected_total_volume_and_assert ( num_operations * transfer_fee) ;
262
+
263
+ // Perform some larger transfers to verify a total volume larger than u64::MAX is handled correctly.
264
+ transfer (
265
+ & env,
266
+ canister_id,
267
+ PrincipalId :: new_user_test_id ( 1 ) . 0 ,
268
+ PrincipalId :: new_user_test_id ( 2 ) . 0 ,
269
+ u64:: MAX - 1_000_000_000 ,
270
+ )
271
+ . expect ( "transfer failed" ) ;
272
+ increase_expected_total_volume_and_assert ( u64:: MAX - 1_000_000_000 + transfer_fee) ;
273
+
274
+ transfer (
275
+ & env,
276
+ canister_id,
277
+ PrincipalId :: new_user_test_id ( 2 ) . 0 ,
278
+ PrincipalId :: new_user_test_id ( 1 ) . 0 ,
279
+ u64:: MAX - 10_000_000_000 ,
280
+ )
281
+ . expect ( "transfer failed" ) ;
282
+
283
+ increase_expected_total_volume_and_assert ( u64:: MAX - 10_000_000_000 + transfer_fee) ;
284
+ }
285
+
160
286
fn assert_existence_of_metric ( env : & StateMachine , canister_id : CanisterId , metric : & str ) {
161
287
let metrics = retrieve_metrics ( env, canister_id) ;
162
288
assert ! (
@@ -180,9 +306,10 @@ pub fn parse_metric(env: &StateMachine, canister_id: CanisterId, metric: &str) -
180
306
let value_str = * tokens
181
307
. get ( 1 )
182
308
. unwrap_or_else ( || panic ! ( "metric line '{}' should have at least two tokens" , line) ) ;
183
- return value_str
184
- . parse ( )
185
- . unwrap_or_else ( |err| panic ! ( "metric value is not an integer: {} ({})" , line, err) ) ;
309
+ let u64_value = f64:: from_str ( value_str)
310
+ . unwrap_or_else ( |err| panic ! ( "metric value is not an number: {} ({})" , line, err) )
311
+ . round ( ) as u64 ;
312
+ return u64_value;
186
313
}
187
314
panic ! ( "metric '{}' not found in metrics: {:?}" , metric, metrics) ;
188
315
}
0 commit comments