Q1 The use of pragma in the given contract snippet
pragma solidity ^0.6.0;
contract test {
// Assume other required functionality is correctly implemented
// Assume this contract can work correctly without modifications across 0.6.x/0.7.x/0.8.x compiler versions
}
Q2 The given contract snippet has
pragma solidity 0.8.4;
contract test {
// Assume other required functionality is correctly implemented
function kill() public {
selfdestruct(payable(0x0));
}
}
Q3 The given contract snippet has
pragma solidity 0.8.4;
contract test {
// Assume other required functionality is correctly implemented
modifier onlyAdmin() {
// Assume this is correctly implemented
_;
}
function transferFunds(address payable recipient, uint amount) public {
recipient.transfer(amount);
}
}
Q4 In the given contract snippet
pragma solidity 0.8.4;
contract test {
// Assume other required functionality is correctly implemented
mapping (uint256 => address) addresses;
bool check;
modifier onlyIf() {
if (check) {
_;
}
}
function setAddress(uint id, address addr) public {
addresses[id] = addr;
}
function getAddress(uint id) public onlyIf returns (address) {
return addresses[id];
}
}
Q5 The security concern(s) in the given contract snippet is/are
pragma solidity 0.8.4;
contract test {
// Assume other required functionality is correctly implemented
modifier onlyAdmin {
// Assume this is correctly implemented
_;
}
function delegate (address addr) external {
addr.delegatecall(abi.encodeWithSignature("setDelay(uint256)"));
}
}
Comment:
Controlled delegatecall: delegatecall() or callcode() to an address controlled by the user allows execution of malicious contracts in the context of the callerβs state. Ensure trusted destination addresses for such calls.
Return values of low-level calls: Ensure that return values of low-level calls (call/callcode/delegatecall/send/etc.) are checked to avoid unexpected failures.
Incorrect access control: Contract functions executing critical logic should have appropriate access control enforced via address checks (e.g. owner, controller etc.) typically in modifiers. Missing checks allow attackers to control critical logic.
Account existence check for low-level calls: Low-level calls call/delegatecall/staticcall return true even if the account called is non-existent (per EVM design). Account existence must be checked prior to calling if needed.
Q6 The vulnerability/vulnerabilities present in the given contract snippet is/are
pragma solidity 0.7.0;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
// which works with 0.7.0
contract test {
// Assume other required functionality is correctly implemented
// For e.g. users have deposited balances in the contract
// Assume nonReentrant modifier is always applied
mapping (address => uint256) balances;
function withdraw(uint256 amount) external nonReentrant {
msg.sender.call{value: amount}("");
balances[msg.sender] -= amount;
}
}
Q7 The security concern(s) in the given contract snippet is/are
pragma solidity 0.8.4;
contract test {
// Assume other required functionality is correctly implemented
uint256 private constant secret = 123;
function diceRoll() external view returns (uint256) {
return (((block.timestamp * secret) % 6) + 1);
}
}
Q8 The security concern(s) in the given contract snippet is/are
pragma solidity 0.8.4;
contract test {
// Assume other required functionality is correctly implemented
// Contract admin set to deployer in constructor (not shown)
address admin;
modifier onlyAdmin {
require(tx.origin == admin);
_;
}
function emergencyWithdraw() external payable onlyAdmin {
msg.sender.transfer(address(this).balance);
}
}
Q9 The given contract snippet is vulnerable because of
pragma solidity 0.8.4;
contract test {
// Assume other required functionality is correctly implemented
uint256 private constant MAX_FUND_RAISE = 100 ether;
mapping (address => uint256) contributions;
function contribute() external payable {
require(address(this).balance != MAX_FUND_RAISE);
contributions[msg.sender] += msg.value;
}
}
Q10 In the given contract snippet, the require check will
pragma solidity 0.8.4;
contract test {
// Assume other required functionality is correctly implemented
function callMe (address target) external {
(bool success, ) = target.call("");
require(success);
}
}
Q11 The security concern(s) in the given contract snippet is/are
pragma solidity 0.8.4;
contract test {
// Assume other required functionality is correctly implemented
// Assume admin is set correctly to contract deployer in constructor
address admin;
function setAdmin (address _newAdmin) external {
admin = _newAdmin;
}
}
Q12 The security concern(s) in the given contract snippet is/are
pragma solidity 0.8.4;
contract test {
// Assume other required functionality is correctly implemented
address admin;
address payable pool;
constructor(address _admin) {
admin = _admin;
}
modifier onlyAdmin {
require(msg.sender == admin);
_;
}
function setPoolAddress(address payable _pool) external onlyAdmin {
pool = _pool;
}
function addLiquidity() payable external {
pool.transfer(msg.value);
}
}
Q13 The security concern(s) in the given proxy-based implementation contract snippet is/are
pragma solidity 0.8.4;
import "https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol";
contract test is Initializable {
// Assume other required functionality is correctly implemented
address admin;
uint256 rewards = 10;
modifier onlyAdmin {
require(msg.sender == admin);
_;
}
function initialize (address _admin) external {
require(_admin != address(0));
admin = _admin;
}
function setRewards(uint256 _rewards) external onlyAdmin {
rewards = _rewards;
}
}
Comment:
There are no imported contracts that need to be made upgradable (by implementing Initializable).
Import upgradeable contracts in proxy-based upgradeable contracts: Contracts imported from proxy-based upgradeable contracts should also be upgradeable where such contracts have been modified to use initializers instead of constructors.
Unprotected initializers in proxy-based upgradeable contracts: Proxy-based upgradeable contracts need to use public initializer functions instead of constructors that need to be explicitly called only once. Preventing multiple invocations of such initializer functions (e.g. via initializer modifier from OpenZeppelinβs Initializable library) is a must.
Initializing state-variables in proxy-based upgradeable contracts: This should be done in initializer functions and not as part of the state variable declarations in which case they wonβt be set.
There's no guarantee that the passed arrays are of same length, so if one would be longer than the other one it can cause an Out Of Bounds error, which is why D is correct.
Calls inside a loop: Calls to external contracts inside a loop are dangerous (especially if the loop index can be user-controlled) because it could lead to DoS if one of the calls reverts or execution runs out of gas. Avoid calls within loops, check that loop index cannot be user-controlled or is bounded.
ERC20 transfer() does not return boolean: Contracts compiled with solc >= 0.4.22 interacting with such functions will revert. Use OpenZeppelinβs SafeERC20 wrappers.
Q15 The vulnerability/vulnerabilities present in the given contract snippet is/are
pragma solidity 0.8.4;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol";
contract test {
// Assume other required functionality is correctly implemented
// For e.g. users have deposited balances in the contract
mapping (address => uint256) balances;
function withdrawBalance() external {
msg.sender.call{value: balances[msg.sender]}("");
balances[msg.sender] = 0;
}
}
Q16 The security concern(s) in the given contract snippet is/are
pragma solidity 0.8.4;
contract test {
// Assume other required functionality is correctly implemented
// Assume that hash is the hash of a message computed elsewhere in contract
// Assume that the contract does not make use of chainID or nonces in its logic
function verify(address signer, bytes32 memory hash, bytes32 sigR, bytes32 sigS, uint8 sigV) internal view returns (bool) {
return signer == ecrecover(hash, sigV, sigR, sigS);
}
Comment:
Signature malleability: The ecrecover function is susceptible to signature malleability which could lead to replay attacks. Consider using OpenZeppelinβs ECDSA library.
Insufficient Signature Information: The vulnerability occurs when a digital signature is valid for multiple transactions, which can happen when one sender (say Alice) sends money to multiple recipients through a proxy contract (instead of initiating multiple transactions). In the proxy contract mechanism, Alice can send a digitally signed message off-chain (e.g., via email) to the recipients, similar to writing personal checks in the real world, to let the recipients withdraw money from the proxy contract via transactions. To assure that Alice does approve a certain payment, the proxy contract verifies the validity of the digital signature in question. However, if the signature does not give the due information (e.g., nonce, proxy contract address), then a malicious recipient can replay the message multiple times to withdraw extra payments. This vulnerability was first exploited in a replay attack against smart contracts [36]. This vulnerability can be prevented by incorporating the due information in each message, such as a nonce value and timestamps
Indistinguishable Chains: This vulnerability was first observed from the cross-chain replay attack when Ethereum was divided into two chains, namely, ETH and ETC [10]. Recall that Ethereum uses ECDSA to sign transactions. Prior to the hard fork for EIP-155 [7], each transaction consisted of six fields (i.e., nonce, recipient, value, input, gasPrice, and gasLimit). However, the digital signatures were not chain-specific, because no chain-specific information was even known back then. As a consequence, a transaction created for one chain can be reused for another chain. This vulnerability has been eliminated by incorporating chainID into the fields.