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