1#![allow(missing_docs)]
2
3use casper_eip_712::DomainSeparator;
36use odra::{
37 casper_types::{bytesrepr::Bytes, PublicKey, U256},
38 named_keys::{base64_encoded_key_value_storage, single_value_storage},
39 prelude::*
40};
41
42use crate::{cep18_token::Cep18, eip712};
43
44const PERMIT_TYPEHASH: [u8; 32] = [
46 0x6e, 0x71, 0xed, 0xae, 0x12, 0xb1, 0xb9, 0x7f, 0x4d, 0x1f, 0x60, 0x37, 0x0f, 0xef, 0x10, 0x10,
47 0x5f, 0xa2, 0xfa, 0xae, 0x01, 0x26, 0x11, 0x4a, 0x16, 0x9c, 0x64, 0x84, 0x5d, 0x61, 0x26, 0xc9
48];
49
50#[odra::odra_error]
52pub enum Error {
53 InvalidSignature = 36_000,
57 PermitExpired = 36_001,
60 InvalidPublicKey = 36_002
64}
65
66const CHAIN_NAME_KEY: &str = "chain_name";
68const PERMIT_NONCES_KEY: &str = "permit_nonces";
69
70const DOMAIN_VERSION: &str = "1";
72
73single_value_storage!(
74 CEP2612ChainNameStorage,
75 String,
76 CHAIN_NAME_KEY,
77 ExecutionError::KeyNotFound
78);
79
80base64_encoded_key_value_storage!(CEP2612PermitNoncesStorage, PERMIT_NONCES_KEY, Address, U256);
81
82#[odra::module]
87pub struct CEP2612 {
88 permit_nonces: SubModule<CEP2612PermitNoncesStorage>,
91 chain_name: SubModule<CEP2612ChainNameStorage>,
93 token: SubModule<Cep18>
95}
96
97#[odra::module]
98impl CEP2612 {
99 pub fn init(&mut self, chain_name: String) {
102 self.chain_name.set(chain_name);
103 self.permit_nonces.init();
104 }
105
106 pub fn permit(
124 &mut self,
125 owner: Address,
126 spender: Address,
127 value: U256,
128 deadline: u64,
129 public_key: PublicKey,
130 signature: Bytes
131 ) {
132 if deadline != u64::MAX && self.env().get_block_time() > deadline {
133 self.revert(Error::PermitExpired);
134 }
135
136 if Address::from(public_key.clone()) != owner {
137 self.revert(Error::InvalidPublicKey);
138 }
139
140 let nonce = self.permit_nonces.get(&owner).unwrap_or_default();
141 let message_hash = self.message_hash(owner, spender, value, nonce, deadline);
142 let message = Bytes::from(message_hash.to_vec());
143
144 let is_valid = self
145 .env()
146 .verify_signature(&message, &signature, &public_key);
147 if !is_valid {
148 self.revert(Error::InvalidSignature);
149 }
150
151 self.permit_nonces.set(&owner, nonce + U256::from(1));
152
153 self.token.raw_approve(&owner, &spender, &value);
154 }
155
156 fn domain_separator(&self) -> DomainSeparator {
160 let self_address = self.env().self_address();
161 let name = self.token.name();
162 let chain_name = self.chain_name.get();
163 crate::eip712::domain_separator(&name, DOMAIN_VERSION, chain_name, self_address)
164 }
165
166 fn message_hash(
170 &self,
171 owner: Address,
172 spender: Address,
173 value: U256,
174 nonce: U256,
175 deadline: u64
176 ) -> [u8; 32] {
177 let mut encoded_data = Vec::with_capacity(32 * 5);
178 let mut value_bytes = [0u8; 32];
179 value.to_big_endian(&mut value_bytes);
180 let mut nonce_bytes = [0u8; 32];
181 nonce.to_big_endian(&mut nonce_bytes);
182 encoded_data.extend(eip712::encode_address(owner));
183 encoded_data.extend(eip712::encode_address(spender));
184 encoded_data.extend(casper_eip_712::encode_uint256(value_bytes));
185 encoded_data.extend(casper_eip_712::encode_uint256(nonce_bytes));
186 encoded_data.extend(casper_eip_712::encode_uint64(deadline));
187
188 let domain = self.domain_separator();
189
190 crate::eip712::hash_typed_data(domain, PERMIT_TYPEHASH, encoded_data)
191 }
192}
193
194#[odra::module]
196pub struct CEP2612Wrapper {
197 cep2612: SubModule<CEP2612>,
198 token: SubModule<Cep18>
199}
200
201#[odra::module]
204impl CEP2612Wrapper {
205 pub fn init(
207 &mut self,
208 chain_name: String,
209 symbol: String,
210 name: String,
211 decimals: u8,
212 initial_supply: U256
213 ) {
214 self.cep2612.init(chain_name);
215 self.token.init(symbol, name, decimals, initial_supply);
216 }
217
218 delegate! {
219 to self.cep2612 {
220 fn permit(
221 &mut self,
222 owner: Address,
223 spender: Address,
224 value: U256,
225 deadline: u64,
226 public_key: PublicKey,
227 signature: Bytes
228 );
229 }
230
231 to self.token {
232 fn allowance(&self, owner: &Address, spender: &Address) -> U256;
233 }
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use crate::cep18::events::SetAllowance;
241 use odra::host::{Deployer, HostEnv};
242
243 const TOKEN_NAME: &str = "Test Token";
244 const TOKEN_SYMBOL: &str = "TEST";
245 const TOKEN_DECIMALS: u8 = 8;
246 const INITIAL_SUPPLY: u64 = 1_000_000;
247 const CHAIN_NAME: &str = "casper-test";
248
249 struct Setup {
250 env: HostEnv,
251 wrapper: CEP2612WrapperHostRef,
252 alice: Address,
253 bob: Address,
254 charlie: Address,
255 alice_pubkey: PublicKey
256 }
257
258 fn setup() -> Setup {
259 let env = odra_test::env();
260 let alice = env.get_account(0);
261 let bob = env.get_account(1);
262 let charlie = env.get_account(2);
263 let alice_pubkey = env.public_key(&alice);
264
265 let wrapper = CEP2612Wrapper::deploy(
266 &env,
267 CEP2612WrapperInitArgs {
268 chain_name: CHAIN_NAME.to_string(),
269 symbol: TOKEN_SYMBOL.to_string(),
270 name: TOKEN_NAME.to_string(),
271 decimals: TOKEN_DECIMALS,
272 initial_supply: INITIAL_SUPPLY.into()
273 }
274 );
275
276 Setup {
277 env,
278 wrapper,
279 alice,
280 bob,
281 charlie,
282 alice_pubkey
283 }
284 }
285
286 #[test]
287 fn permit_happy_path() {
288 let Setup {
289 env,
290 mut wrapper,
291 alice,
292 bob,
293 charlie,
294 alice_pubkey
295 } = setup();
296
297 assert_eq!(wrapper.allowance(&alice, &bob), U256::zero());
298
299 let value: U256 = 500u64.into();
300 let deadline: u64 = u64::MAX;
301 let nonce: U256 = U256::zero();
302
303 let signature = sign_permit(
304 &env,
305 &alice,
306 wrapper.address(),
307 alice,
308 bob,
309 value,
310 nonce,
311 deadline
312 );
313
314 env.set_caller(charlie);
316 wrapper.permit(alice, bob, value, deadline, alice_pubkey, signature);
317
318 assert_eq!(wrapper.allowance(&alice, &bob), value);
319 assert!(env.emitted_event(
320 &wrapper,
321 SetAllowance {
322 owner: alice,
323 spender: bob,
324 allowance: value
325 }
326 ));
327 }
328
329 #[test]
330 fn permit_replay_reverts() {
331 let Setup {
332 env,
333 mut wrapper,
334 alice,
335 bob,
336 charlie,
337 alice_pubkey
338 } = setup();
339
340 let value: U256 = 500u64.into();
341 let deadline: u64 = u64::MAX;
342 let nonce: U256 = U256::zero();
343
344 let signature = sign_permit(
345 &env,
346 &alice,
347 wrapper.address(),
348 alice,
349 bob,
350 value,
351 nonce,
352 deadline
353 );
354
355 env.set_caller(charlie);
356 wrapper.permit(
357 alice,
358 bob,
359 value,
360 deadline,
361 alice_pubkey.clone(),
362 signature.clone()
363 );
364
365 assert_eq!(
368 wrapper.try_permit(alice, bob, value, deadline, alice_pubkey, signature),
369 Err(Error::InvalidSignature.into())
370 );
371 }
372
373 #[test]
374 fn permit_subsequent_with_next_nonce_succeeds() {
375 let Setup {
376 env,
377 mut wrapper,
378 alice,
379 bob,
380 charlie,
381 alice_pubkey
382 } = setup();
383
384 let deadline: u64 = u64::MAX;
385 let first_value: U256 = 500u64.into();
386 let second_value: U256 = 1_000u64.into();
387
388 let first_signature = sign_permit(
389 &env,
390 &alice,
391 wrapper.address(),
392 alice,
393 bob,
394 first_value,
395 U256::zero(),
396 deadline
397 );
398 let second_signature = sign_permit(
399 &env,
400 &alice,
401 wrapper.address(),
402 alice,
403 bob,
404 second_value,
405 U256::one(),
406 deadline
407 );
408
409 env.set_caller(charlie);
410 wrapper.permit(
411 alice,
412 bob,
413 first_value,
414 deadline,
415 alice_pubkey.clone(),
416 first_signature
417 );
418 assert_eq!(wrapper.allowance(&alice, &bob), first_value);
419
420 wrapper.permit(
421 alice,
422 bob,
423 second_value,
424 deadline,
425 alice_pubkey,
426 second_signature
427 );
428 assert_eq!(wrapper.allowance(&alice, &bob), second_value);
430 }
431
432 #[test]
433 fn permit_expired() {
434 let Setup {
435 env,
436 mut wrapper,
437 alice,
438 bob,
439 charlie,
440 alice_pubkey
441 } = setup();
442
443 env.advance_block_time(60_000);
445
446 let value: U256 = 500u64.into();
447 let deadline: u64 = 1_000;
448 let nonce: U256 = U256::zero();
449
450 let signature = sign_permit(
451 &env,
452 &alice,
453 wrapper.address(),
454 alice,
455 bob,
456 value,
457 nonce,
458 deadline
459 );
460
461 env.set_caller(charlie);
462 assert_eq!(
463 wrapper.try_permit(alice, bob, value, deadline, alice_pubkey, signature),
464 Err(Error::PermitExpired.into())
465 );
466 }
467
468 #[test]
469 fn permit_max_deadline_skips_expiry_check() {
470 let Setup {
471 env,
472 mut wrapper,
473 alice,
474 bob,
475 charlie,
476 alice_pubkey
477 } = setup();
478
479 env.advance_block_time(1_000_000);
481
482 let value: U256 = 500u64.into();
483 let deadline: u64 = u64::MAX;
484 let nonce: U256 = U256::zero();
485
486 let signature = sign_permit(
487 &env,
488 &alice,
489 wrapper.address(),
490 alice,
491 bob,
492 value,
493 nonce,
494 deadline
495 );
496
497 env.set_caller(charlie);
498 wrapper.permit(alice, bob, value, deadline, alice_pubkey, signature);
499
500 assert_eq!(wrapper.allowance(&alice, &bob), value);
501 }
502
503 #[test]
504 fn permit_invalid_signature() {
505 let Setup {
506 env,
507 mut wrapper,
508 alice,
509 bob,
510 charlie,
511 alice_pubkey
512 } = setup();
513
514 let deadline: u64 = u64::MAX;
515 let nonce: U256 = U256::zero();
516
517 let signature = sign_permit(
519 &env,
520 &alice,
521 wrapper.address(),
522 alice,
523 bob,
524 U256::from(100u64),
525 nonce,
526 deadline
527 );
528
529 env.set_caller(charlie);
532 assert_eq!(
533 wrapper.try_permit(
534 alice,
535 bob,
536 U256::from(200u64),
537 deadline,
538 alice_pubkey,
539 signature
540 ),
541 Err(Error::InvalidSignature.into())
542 );
543 }
544
545 #[test]
546 fn permit_with_mismatched_public_key_reverts() {
547 let Setup {
548 env,
549 mut wrapper,
550 alice,
551 bob,
552 charlie,
553 ..
554 } = setup();
555
556 let attacker = env.get_account(3);
557 let attacker_pubkey = env.public_key(&attacker);
558
559 let value: U256 = 500u64.into();
560 let deadline: u64 = u64::MAX;
561 let nonce: U256 = U256::zero();
562
563 let signature = sign_permit(
566 &env,
567 &attacker,
568 wrapper.address(),
569 alice,
570 bob,
571 value,
572 nonce,
573 deadline
574 );
575
576 env.set_caller(charlie);
579 assert_eq!(
580 wrapper.try_permit(alice, bob, value, deadline, attacker_pubkey, signature),
581 Err(Error::InvalidPublicKey.into())
582 );
583
584 assert_eq!(wrapper.allowance(&alice, &bob), U256::zero());
586 }
587
588 #[allow(clippy::too_many_arguments)]
589 fn sign_permit(
590 env: &HostEnv,
591 signer: &Address,
592 contract_address: Address,
593 owner: Address,
594 spender: Address,
595 value: U256,
596 nonce: U256,
597 deadline: u64
598 ) -> Bytes {
599 let mut value_bytes = [0u8; 32];
600 value.to_big_endian(&mut value_bytes);
601 let mut nonce_bytes = [0u8; 32];
602 nonce.to_big_endian(&mut nonce_bytes);
603
604 let mut encoded_data = Vec::with_capacity(32 * 5);
605 encoded_data.extend(crate::eip712::encode_address(owner));
606 encoded_data.extend(crate::eip712::encode_address(spender));
607 encoded_data.extend(casper_eip_712::encode_uint256(value_bytes));
608 encoded_data.extend(casper_eip_712::encode_uint256(nonce_bytes));
609 encoded_data.extend(casper_eip_712::encode_uint64(deadline));
610
611 let domain = crate::eip712::domain_separator(
612 TOKEN_NAME,
613 DOMAIN_VERSION,
614 CHAIN_NAME.to_string(),
615 contract_address
616 );
617 let message_hash = crate::eip712::hash_typed_data(domain, PERMIT_TYPEHASH, encoded_data);
618 let message = Bytes::from(message_hash.to_vec());
619
620 env.sign_message(&message, signer)
621 }
622}