Generics in Move allow functions and structs to work with different data types, like a universal key. But if you don’t verify the type, attackers could sneak in the wrong key, leading to unauthorized actions or transaction failures.
Real-World Example
Picture a dApp offering flash loans on Aptos. Users borrow coins (e.g., USD tokens) and must repay with a fee. Without checking the coin type, someone could borrow USD but repay with worthless “FakeCoin,” cheating the system! 😈Let’s see how this happens and how to fix it.
Insecure Code
This flash loan code doesn’t ensure the repaid Coin matches the borrowed type, allowing users to repay with any coin type.
module 0x42::example {
struct Coin<T> { amount: u64 }
struct Receipt { amount: u64 }
public fun flash_loan<T>(user: &signer, amount: u64): (Coin<T>, Receipt) {
let (coin, fee) = withdraw(user, amount);
(coin, Receipt { amount: amount + fee })
}
public fun repay_flash_loan<T>(rec: Receipt, coins: Coin<T>) {
let Receipt { amount } = rec;
assert!(coin::value<T>(&coins) >= amount, 0);
deposit(coins);
}
}
Problem: The Receipt struct isn’t tied to the coin type T, so users can repay a USD loan with any Coin, like a fake token. It’s like accepting a random gift card to settle a bank loan!
Secure Code ✅
Use Move’s type system (e.g., phantom types) to ensure the Receipt and Coin types match, preventing type mismatches.
module 0x42::example {
struct Coin<T> { amount: u64 }
struct Receipt<phantom T> { amount: u64 }
public fun flash_loan<T>(_user: &signer, amount: u64): (Coin<T>, Receipt<T>) {
let (coin, fee) = withdraw(_user, amount);
(coin, Receipt { amount: amount + fee })
}
public fun repay_flash_loan<T>(rec: Receipt<T>, coins: Coin<T>) {
let Receipt { amount } = rec;
assert!(coin::value<T>(&coins) >= amount, 0);
deposit(coins);
}
}
Fix: Adding phantom T to Receipt ensures the repaid Coin matches the borrowed type. It’s like requiring the exact currency for loan repayment—USD in, USD out!
Top comments (0)