
Reentrancy Attacks in Solidity Smart Contracts
It's been several years since hackers stole a lot of money from the DAO in 2016 by using a trick called a reentrancy attack. This kind of attack can still happen today and cause serious financial problems. Luckily, there are ways to stop these attacks and keep your project safe.How does it work?A reentrancy attack on a smart contract happens when one contract gives control to another contract. Then, the second contract can call the first contract again before the first call is finished. ...
Integer Overflows and Underflows
Integer overflow and underflow are common issues in programming, including Solidity. Arithmetic operations can result in these issues when the result exceeds the representable range of numbers. This often leads to unpredictable contract behavior and poses potential security risks.IN DETAILSThe Ethereum Virtual Machine (EVM) imposes size limitations on integer data types. Each type has a fixed range of values. For instance, a variable of type uint8 can only hold integer values from 0 to 255, i...
Smart Contracts Security Audits Infrastructure vulnerability analysis

Reentrancy Attacks in Solidity Smart Contracts
It's been several years since hackers stole a lot of money from the DAO in 2016 by using a trick called a reentrancy attack. This kind of attack can still happen today and cause serious financial problems. Luckily, there are ways to stop these attacks and keep your project safe.How does it work?A reentrancy attack on a smart contract happens when one contract gives control to another contract. Then, the second contract can call the first contract again before the first call is finished. ...
Integer Overflows and Underflows
Integer overflow and underflow are common issues in programming, including Solidity. Arithmetic operations can result in these issues when the result exceeds the representable range of numbers. This often leads to unpredictable contract behavior and poses potential security risks.IN DETAILSThe Ethereum Virtual Machine (EVM) imposes size limitations on integer data types. Each type has a fixed range of values. For instance, a variable of type uint8 can only hold integer values from 0 to 255, i...
Smart Contracts Security Audits Infrastructure vulnerability analysis

Subscribe to Oxorio

Subscribe to Oxorio
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers


StarkNet is a scalable Layer 2 solution for Ethereum, built on zk-STARK technology. It enables fast, secure, and low-cost transactions through the use of validity rollups. StarkNet operates on the Cairo VM, allowing for the development of smart contracts in the Cairo language.
While Cairo is a relatively new language, it is rapidly evolving and gaining popularity. However, as with any new technology, security concerns require careful examination.
Let's start our dive into the world of Cairo by exploring its fundamentals. This will allow us to more effectively absorb subsequent information.
The primary data type in Cairo is a felt. It represents an integer whose value lies in the range from 0 to P-1, where P is a fixed large prime number equal to 2^251 + 17 * 2^192 + 1 bits.
Cairo employs a read-only, non-deterministic memory model. Each memory cell can be written to only once and its value cannot be modified during program execution. The syntax [x] represents the value at memory address x. This model implies that if a value is set at the beginning of a program, it remains constant throughout its execution.
Cairo employs the keyword fn to define functions. While this language supports parameter modifiers (such as mut for mutable and ref for reference parameters), it does not provide a mechanism for adding modifiers to the function itself. Unlike Solidity, which uses modifiers like external or view to influence function visibility and behavior, Cairo lacks such modifiers.
Want to dive even deeper into Cairo? The official documentation is your go-to guide.
Having explored the fundamental elements of Cairo, we will now turn our attention to analyzing the most common attack vectors and vulnerabilities inherent to this language. We will also discuss methods to mitigate these issues.
Let's illustrate this problem using the example of addresses. As previously mentioned, Starknet addresses are of type felt, which has a range of 0 < x < P (2^251 + 17 * 2^192 + 1 bits). In Ethereum L1, addresses are of type uint160, with a range of 0 <= x < 2^160.
When transferring addresses between layers from L1 to L2, the address is typically represented as uint256. However, this can lead to issues where a valid L1 address is mapped to a null address or an unexpected address on L2.
For instance, consider an L1-to-L2 bridge contract that allows depositing tokens from L1 to L2. If the to address parameter is not properly validated, it could result in tokens being sent to an unintended address on L2.
To mitigate this, it is crucial to validate parameters, especially those provided by users, when sending messages from L1 to L2. It is important to remember that the range of the felt type in Cairo is smaller than the range of the uint256 type used in Solidity.
Remember that this issue is relevant to all uint256 values when creating and verifying code.
An incorrect implementation of access control can lead to serious consequences. The compromise of a single account as a result of an attack can lead to the compromise of the entire smart contract and, consequently, the loss of funds stored within.
A standard security mechanism is to verify the address of the calling function. In Solidity, the requireoperator is typically used for this, causing the transaction to revert if the conditions are not met. In Cairo, the assert operator plays a similar role.
Let's consider a withdrawAssets function that includes a check of the caller's address to execute the function.
fn withdrawAssets() {
let caller = get_caller_address();
assert(caller == whitelistedAddress, 'caller not whitelisted');
}
// Code wrote by oxorio.
To effectively implement access control in StarkNet contracts, it is recommended to use the OpenZeppelin library. It provides pre-built contracts, such as Ownable and AccessControl, that allow for flexible definition of roles and management of access to contract functions.
However, when designing an access control system, the principle of least privilege should be adhered to. This means that each role should be granted only those permissions that are strictly necessary for its functions. Excessive permissions can create vulnerabilities and increase the risk of contract compromise.
While reentrancy attacks have become less frequent due to the introduction of various protection methods and increased developer awareness, they are still possible in StarkNet.
OpenZeppelin developers have implemented a reentrancy guard mechanism here as well, which protects critical contract functions from unauthorized re-entries.
Unlike Solidity, Cairo does not support function modifiers. For this reason, the OpenZeppelin library offers two methods: _start and _end, which must be called at the beginning and end of the protected function, respectively.
func _start{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
let (has_entered) = ReentrancyGuard_entered.read();
with_attr error_message("ReentrancyGuard: reentrant call") {
assert has_entered = FALSE;
}
ReentrancyGuard_entered.write(TRUE);
return ();
}
func _end{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
ReentrancyGuard_entered.write(FALSE);
return ();
}
// Code from OpenZeppelin’s library reentrancy guard.
The protected function should call ReentrancyGuard._start before the first statement of the function and ReentrancyGuard._end before the return statement. If the function is reentered, ReentrancyGuard._start will be called again, and upon subsequent access to storage, it will see that has_entered is now TRUE, causing the assertion to fail.
Furthermore, developers should always remember to adhere to the checks-effects-interaction pattern, which also helps prevent this type of attack.
The key takeaway from this attack is the critical importance of adhering to the principle of nonce uniqueness. For every transaction, especially those involving signatures, the nonce must be strictly incremented. This implies that the signature infrastructure must request the current nonce value before generating each new signature.
Let's imagine you've staked your funds in a protocol and have accumulated rewards. To withdraw these rewards, you need to confirm the transaction with a valid signature. The signature verification function takes the amount, r, and s values of the signature as input. If the signature is valid, it will retrieve the address of the ecosystem token and transfer the amount to the calling subscriber.
However, an attacker could replay the transaction and call this function as many times as desired, as long as the amount parameter matches the original signed message. To prevent this, we add a nonce value to the function.
@external
func swap_game_currency_safe{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr,
ecdsa_ptr : SignatureBuiltin*
}(
r: felt,
s: felt,
amount: felt
):
alloc_locals
let (local caller_address) = get_caller_address()
let (local nonce) = nonces.read(caller_address)
let (_signer) = signer.read()
# update nonce
nonces.write(caller_address, value=nonce+1)
let (message) = hash2{hash_ptr=pedersen_ptr}(amount, caller_address)
let (message_part_2) = hash2{hash_ptr=pedersen_ptr}(message, nonce)
verify_ecdsa_signature(
message=message_part_2, public_key=_signer, signature_r=r, signature_s=s
)
let (token_address_) = token_address.read()
let (contract_address) = get_contract_address()
IERC20.transferFrom(contract_address=token_address_, sender=contract_address, recipient=caller_address, amount=Uint256(amount, 0))
return ()
end
// Code was taken from https://ctrlc03.github.io/.
Now, for each function call, the user's current nonce is retrieved from the storage and incremented by 1, so that the next call uses the updated value.
Unlike Ethereum, StarkNet distinguishes between contract classes and contract instances. A contract class encapsulates immutable bytecode representing the contract's logic, while a contract instance is a specific deployment of a contract class with its own unique state.
A common pitfall in StarkNet development is the inadvertent direct deployment of an implementation contract, circumventing the proxy contract. To correctly implement the proxy pattern in StarkNet, the following steps should be followed:
Define a contract class containing the core logic of the smart contract.
Deploy a proxy contract that will forward all calls to the implementation contract.
Initialize the implementation contract through the proxy contract, passing the necessary parameters.
Deviation from this pattern undermines the core purpose of the proxy pattern: providing abstraction and centralized control over contract state access. Bypassing the proxy allows for arbitrary modifications to the implementation contract's state, potentially leading to unforeseen consequences and compromising system security.
To simplify working with contracts that have initialization functions, it is also recommended to use the OpenZeppelin's Initializable library. It provides convenient tools for the safe and reliable management of contract lifecycles.
Ensuring security is a paramount concern for both developers and auditors. Cairo, as a relatively new programming language, also necessitates a thorough analysis in terms of reliability and the identification of potential vulnerabilities.
Staying updated with the latest in blockchain security is crucial. We invite you to follow OXORIO on LinkedIn and Twitter for insights and updates. Additionally, visit our website at oxor.io for detailed information about our services and how we can assist in securing your blockchain projects.
StarkNet is a scalable Layer 2 solution for Ethereum, built on zk-STARK technology. It enables fast, secure, and low-cost transactions through the use of validity rollups. StarkNet operates on the Cairo VM, allowing for the development of smart contracts in the Cairo language.
While Cairo is a relatively new language, it is rapidly evolving and gaining popularity. However, as with any new technology, security concerns require careful examination.
Let's start our dive into the world of Cairo by exploring its fundamentals. This will allow us to more effectively absorb subsequent information.
The primary data type in Cairo is a felt. It represents an integer whose value lies in the range from 0 to P-1, where P is a fixed large prime number equal to 2^251 + 17 * 2^192 + 1 bits.
Cairo employs a read-only, non-deterministic memory model. Each memory cell can be written to only once and its value cannot be modified during program execution. The syntax [x] represents the value at memory address x. This model implies that if a value is set at the beginning of a program, it remains constant throughout its execution.
Cairo employs the keyword fn to define functions. While this language supports parameter modifiers (such as mut for mutable and ref for reference parameters), it does not provide a mechanism for adding modifiers to the function itself. Unlike Solidity, which uses modifiers like external or view to influence function visibility and behavior, Cairo lacks such modifiers.
Want to dive even deeper into Cairo? The official documentation is your go-to guide.
Having explored the fundamental elements of Cairo, we will now turn our attention to analyzing the most common attack vectors and vulnerabilities inherent to this language. We will also discuss methods to mitigate these issues.
Let's illustrate this problem using the example of addresses. As previously mentioned, Starknet addresses are of type felt, which has a range of 0 < x < P (2^251 + 17 * 2^192 + 1 bits). In Ethereum L1, addresses are of type uint160, with a range of 0 <= x < 2^160.
When transferring addresses between layers from L1 to L2, the address is typically represented as uint256. However, this can lead to issues where a valid L1 address is mapped to a null address or an unexpected address on L2.
For instance, consider an L1-to-L2 bridge contract that allows depositing tokens from L1 to L2. If the to address parameter is not properly validated, it could result in tokens being sent to an unintended address on L2.
To mitigate this, it is crucial to validate parameters, especially those provided by users, when sending messages from L1 to L2. It is important to remember that the range of the felt type in Cairo is smaller than the range of the uint256 type used in Solidity.
Remember that this issue is relevant to all uint256 values when creating and verifying code.
An incorrect implementation of access control can lead to serious consequences. The compromise of a single account as a result of an attack can lead to the compromise of the entire smart contract and, consequently, the loss of funds stored within.
A standard security mechanism is to verify the address of the calling function. In Solidity, the requireoperator is typically used for this, causing the transaction to revert if the conditions are not met. In Cairo, the assert operator plays a similar role.
Let's consider a withdrawAssets function that includes a check of the caller's address to execute the function.
fn withdrawAssets() {
let caller = get_caller_address();
assert(caller == whitelistedAddress, 'caller not whitelisted');
}
// Code wrote by oxorio.
To effectively implement access control in StarkNet contracts, it is recommended to use the OpenZeppelin library. It provides pre-built contracts, such as Ownable and AccessControl, that allow for flexible definition of roles and management of access to contract functions.
However, when designing an access control system, the principle of least privilege should be adhered to. This means that each role should be granted only those permissions that are strictly necessary for its functions. Excessive permissions can create vulnerabilities and increase the risk of contract compromise.
While reentrancy attacks have become less frequent due to the introduction of various protection methods and increased developer awareness, they are still possible in StarkNet.
OpenZeppelin developers have implemented a reentrancy guard mechanism here as well, which protects critical contract functions from unauthorized re-entries.
Unlike Solidity, Cairo does not support function modifiers. For this reason, the OpenZeppelin library offers two methods: _start and _end, which must be called at the beginning and end of the protected function, respectively.
func _start{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
let (has_entered) = ReentrancyGuard_entered.read();
with_attr error_message("ReentrancyGuard: reentrant call") {
assert has_entered = FALSE;
}
ReentrancyGuard_entered.write(TRUE);
return ();
}
func _end{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}() {
ReentrancyGuard_entered.write(FALSE);
return ();
}
// Code from OpenZeppelin’s library reentrancy guard.
The protected function should call ReentrancyGuard._start before the first statement of the function and ReentrancyGuard._end before the return statement. If the function is reentered, ReentrancyGuard._start will be called again, and upon subsequent access to storage, it will see that has_entered is now TRUE, causing the assertion to fail.
Furthermore, developers should always remember to adhere to the checks-effects-interaction pattern, which also helps prevent this type of attack.
The key takeaway from this attack is the critical importance of adhering to the principle of nonce uniqueness. For every transaction, especially those involving signatures, the nonce must be strictly incremented. This implies that the signature infrastructure must request the current nonce value before generating each new signature.
Let's imagine you've staked your funds in a protocol and have accumulated rewards. To withdraw these rewards, you need to confirm the transaction with a valid signature. The signature verification function takes the amount, r, and s values of the signature as input. If the signature is valid, it will retrieve the address of the ecosystem token and transfer the amount to the calling subscriber.
However, an attacker could replay the transaction and call this function as many times as desired, as long as the amount parameter matches the original signed message. To prevent this, we add a nonce value to the function.
@external
func swap_game_currency_safe{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr,
ecdsa_ptr : SignatureBuiltin*
}(
r: felt,
s: felt,
amount: felt
):
alloc_locals
let (local caller_address) = get_caller_address()
let (local nonce) = nonces.read(caller_address)
let (_signer) = signer.read()
# update nonce
nonces.write(caller_address, value=nonce+1)
let (message) = hash2{hash_ptr=pedersen_ptr}(amount, caller_address)
let (message_part_2) = hash2{hash_ptr=pedersen_ptr}(message, nonce)
verify_ecdsa_signature(
message=message_part_2, public_key=_signer, signature_r=r, signature_s=s
)
let (token_address_) = token_address.read()
let (contract_address) = get_contract_address()
IERC20.transferFrom(contract_address=token_address_, sender=contract_address, recipient=caller_address, amount=Uint256(amount, 0))
return ()
end
// Code was taken from https://ctrlc03.github.io/.
Now, for each function call, the user's current nonce is retrieved from the storage and incremented by 1, so that the next call uses the updated value.
Unlike Ethereum, StarkNet distinguishes between contract classes and contract instances. A contract class encapsulates immutable bytecode representing the contract's logic, while a contract instance is a specific deployment of a contract class with its own unique state.
A common pitfall in StarkNet development is the inadvertent direct deployment of an implementation contract, circumventing the proxy contract. To correctly implement the proxy pattern in StarkNet, the following steps should be followed:
Define a contract class containing the core logic of the smart contract.
Deploy a proxy contract that will forward all calls to the implementation contract.
Initialize the implementation contract through the proxy contract, passing the necessary parameters.
Deviation from this pattern undermines the core purpose of the proxy pattern: providing abstraction and centralized control over contract state access. Bypassing the proxy allows for arbitrary modifications to the implementation contract's state, potentially leading to unforeseen consequences and compromising system security.
To simplify working with contracts that have initialization functions, it is also recommended to use the OpenZeppelin's Initializable library. It provides convenient tools for the safe and reliable management of contract lifecycles.
Ensuring security is a paramount concern for both developers and auditors. Cairo, as a relatively new programming language, also necessitates a thorough analysis in terms of reliability and the identification of potential vulnerabilities.
Staying updated with the latest in blockchain security is crucial. We invite you to follow OXORIO on LinkedIn and Twitter for insights and updates. Additionally, visit our website at oxor.io for detailed information about our services and how we can assist in securing your blockchain projects.
No activity yet