Lesson 6 - Address Validation
Introduction
In this lesson, you will learn the importance of validating the recipient address whenever a token is transferred to any address. The lack of address validation may result in lost or locked tokens if default addresses are used mistakenly on production chains.
Prerequisites
To be able to understand and complete this lesson, we believe that the minimum requirement should be the completion of Lesson 1 - Getting Started. If you desire to be even more prepared please have a look at the official website of ink!.
Objectives and Outcomes
In this lesson you will learn:
The consequences of not checking the recipient address against a list of known bad addresses;
How to attack a smart contract by using the lack of address validation;
How to mitigate the attack.
Exercise
Vulnerable Smart contract
A token contract that allows transfer to any addresses, without validation, is vulnerable. The following transfer function does not perform any address validations on the user-provided token recipient address and may result in lost or locked tokens if default addresses are used mistakenly on production chains.
#![cfg_attr(not(feature = "std"), no_std)]
#[ink::contract]
mod s0token {
use ink::{storage::Mapping};
#[ink(storage)]
pub struct S0token {
total_supply: u32,
balances: Mapping<AccountId, u32>
}
impl S0token {
/// Creates a token contract with the given initial supply belonging to the contract creator
#[ink(constructor)]
pub fn new_token(supply: u32) -> Self {
let mut balances = Mapping::default();
let caller = Self::env().caller();
balances.insert(&caller,&supply);
Self {
total_supply: supply,
balances
}
}
/// Total Supply of s0token
#[ink(message)]
pub fn total_supply(&self) -> u32 {
self.total_supply
}
/// Current balance of the chosen account.
#[ink(message)]
pub fn balance_of(&self, account: AccountId) -> u32 {
match self.balances.get(&account) {
Some(value) => value,
None => 0,
}
}
/// Transfers an amount of tokens to the chosen recipient.
#[ink(message)]
pub fn transfer(&mut self, recipient: AccountId, amount: u32) {
let sender = self.env().caller();
let sender_balance = self.balance_of(sender);
if sender_balance < amount {
return;
}
self.balances.insert(sender, &(sender_balance - amount));
let recipient_balance = self.balance_of(recipient);
self.balances.insert(recipient, &(recipient_balance + amount));
}
}
}
Unfortunately, Bob did not follow the security guidelines and this has for consequences that his contract is vulnerable. Do you see where the security vulnerability occurs?
==- Hint First, identify lines where the check of the function caller is done.
==- Answer The validation of the address is not done correctly.
Simulated Attack
==- Reveal Attack
Setup
When developing contracts in Ink, there are a number of addresses that are used for development purposes locally and on the testnet. However, use of these addresses on the production network can result in lost gas fees and unretrievable tokens and AZERO if accidentally used within that environment. A list of these accounts can be found in the
ink_envtest documentation.
https://paritytech.github.io/ink/ink_env/test/struct.DefaultAccounts.html
Tests Output
Build and Deploy to Testnet
Using Cargo Contract Command line
Output
Output
Transfer 1000 tokens to the Zero Address
Output showing 1000 tokens assigned to the zero address
These 1000 tokens are now tied to the zero address account and are not retrievable.
The same thing can occur with the test accounts used for testing purposes, such as Alice or Bob.
Transfer 200 tokens to Alice
Output
There is also a case where a contract is instantiated with an empty seed phrase.
Make sure that there is no environment variable for $SEED
Should return an empty space.
Now instantiate a new contract. The environment variable $SEED can be used or just an empty string "", the reason to use the environment variable is to demonstrate a developer that has opened a new terminal without exporting their SEED and how easy it would be to deploy a contract unknowingly with that address. An attacker could send enough Azero to that address that would pay for the gas to instantiate a new contract and wait for more Azero to be sent to the address or transfer the newly created token.
Open a new terminal without an export environment variables
Error from no $URL variable
Try again to instantiate the contract
Output
Empty SEED string call
==-
Secure Solution
This token contract fixes the lack of address validation by checking that no default or zero address accounts are used as the recipient for receiving tokens.
Mitigation of the lack of address validation is accomplished by checking the recipient address against a list of known bad addresses. One concern we have in validating these addresses is that they are commonly used during development for debug purposes. The inability to use these addresses in the test environment may hamper development efforts. In order to account for this, address validation in the example only occurs in the release build. This is accomplished by checking the current contract configuration for debug_assertions, which are disabled in a release build.
Since the test environment is not available once deployed to the production chain, we must hard code the address values into an array that can be checked during the transfer process. The zero address and empty seed address are also included as they have been an issue on other chains. ==- Solution
These following transfer function now performs any address validations by checking the recipient against known default addresses that would result in lost or locked tokens if they are used mistakenly on production chains.
Verifying Secure Solution
When developing contracts in ink!, there are a number of addresses that are used for development purposes locally and on the testnet. However, use of these addresses on the production network can result in lost gas fees and unretrievable tokens and AZERO if accidentally used within that environment. A list of these accounts can be found in the
ink_envtest documentation.
https://paritytech.github.io/ink/ink_env/test/struct.DefaultAccounts.html
Setup
Tests still function properly, since the zero address and test accounts are allowed in development.
Tests Output
Build and Deploy to Testnet
Using Cargo Contract Command line
Output
Output
Attempt to transfer 1000 tokens to the Zero Address
Output showing that no tokens were assigned to the zero address
The contract now prevents any tokens from being transfered to the zero address account.
The same prevention will also can occur with the test accounts, such as Alice or Bob.
Attempt to transfer 250 tokens to Alice
Output
Balance of Empty SEED Address
Try to transfer to the Empty SEED Address
Output
==-
Question
If you are up to the challenge, see if you know the answer to this question:
True or false: In the previous lessons of this security course, is there any lessen where the address validation checks should also have been performed?
==- Answer True. For example, in Lesson 4 the address validation should also have been performed.
Last updated
Was this helpful?
