I've got a question regarding modifiers, requires and function flow.
Here's a simple example for my point.
contract Numbers {
uint256[] private _numbers;
modifier bigNumber(uint256 someNumber) {
require(someNumber > 10, "Numbers: Number must be greater than 10");
_;
}
function _addNumber(uint256 someNumber) private bigNumber(someNumber) {
_numbers.push(someNumber);
}
function addNumbers(uint256[] memory _newNumbers) external {
for (uint256 i = 0; i < _newNumbers.length; i++) {
_addNumber(_newNumbers[i]);
}
}
}
Let's say I call this function with addNumbers([11, 12, 13, 8, 20, 21, 5, 22, 23]);
I'd get an error when it reaches 8.
Is there a way to continue running the function with the following values?
I thought of adding an if to "catch" the error before, but maybe there's a better way of doing so.
Thanks in advance!
The failed require() condition throws an exception.
In your case, the easiest way is to duplicate the condition in your addNumbers() function.
function addNumbers(uint256[] memory _newNumbers) external {
for (uint256 i = 0; i < _newNumbers.length; i++) {
// duplicated condition from the modifier
if (_newNumbers[i] <= 10) {
_addNumber(_newNumbers[i]);
}
}
}
If the _addNumber() function was external (i.e. executable from outside of the contract), you could use the try/catch expression.
// mind the changed visibility from `private` to `external`
function _addNumber(uint256 someNumber) external bigNumber(someNumber) {
_numbers.push(someNumber);
}
function addNumbers(uint256[] memory _newNumbers) external {
for (uint256 i = 0; i < _newNumbers.length; i++) {
try this._addNumber(_newNumbers[i]) {
// do nothing if didn't throw an exception
} catch (bytes memory) {
// do nothing if threw an exception
}
}
}
Try/catch is currently (v0.8) supported only for external calls and contract creation. That's why you'd need to make the _addNumber() function external to make it work.
Docs: https://docs.soliditylang.org/en/v0.8.6/control-structures.html#try-catch
Related
I am trying to create a whitelist. I've used a for and if loop to check if the msg.sender already exists in the array. When the whitelist() function is run, no errors are returned, but when I run check(), it tells me the address doesn't exist in the array, same thing with directing fetching the array.
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SelfWhitelist {
address[] public addressWhitelist;
function whitelist() public returns(string memory) {
for(uint i = 0; i < addressWhitelist.length; i++) {
if(addressWhitelist[i] != msg.sender) {
addressWhitelist.push(msg.sender);
return "Whitelisted!";
}
}
return "Already whitelisted!";
}
function check() public view returns (bool){
for(uint i = 0; i < addressWhitelist.length; i++){
if(addressWhitelist[i] == msg.sender)
return true;
}
return false;
}
}
I added this block of code to check for duplicate entries in the array.
for(uint i = 0; i < addressWhitelist.length; i++) {
if(addressWhitelist[i] != msg.sender) {
addressWhitelist.push(msg.sender);
return "Whitelisted!";
}
Expected for no errors and my address being pushed to the array.
The code ran without errors, but nothing was added to the array.
I would use mapping type to hold the whitelisted addresses, then you don't have to loop over the array
contract SelfWhitelist {
mapping(address => bool) public addressWhitelist;
function whitelist() public returns(string memory) {
if (check()) {
return "Already whitelisted!";
}
addressWhitelist[msg.sender] = true;
return "Whitelisted!";
}
function check() public view returns (bool) {
return addressWhitelist[msg.sender];
}
}
And in your code, seems you have logical issues:
for(uint i = 0; i < addressWhitelist.length; i++) {
if(addressWhitelist[i] != msg.sender) {
addressWhitelist.push(msg.sender);
return "Whitelisted!";
}
}
if the array is empty, the loop never executes, so nothing will be added
if the first item is not msg.sender, msg.sender will be whitelisted, even though it might be already whitelisted.
I am a newbie studying Uniswap V3's github code. I came up with noDelegateCall.sol and I found out the way to prevent a contract to be delegatecalled from another cotract. As I tried to implement this, I caught up with the problem.
pragma solidity >0.8.0;
contract Receiver {
string greeting = "Hello";
address private immutable original;
event Greeting(string greeting, address original, address addressThis);
constructor() {
original = address(this);
}
function checkNotDelegateCall() private view {
require(address(this) == original);
}
modifier noDelegateCall() {
checkNotDelegateCall();
_;
}
function greet() external noDelegateCall {
emit Greeting(greeting, original, address(this));
}
}
contract Sender {
string greeting = "Hi";
function delegatedGreeting(address _contract) external {
(bool success,) = _contract.delegatecall(
abi.encodeWithSignature("greet()")
);
}
}
If I call function delegatedGreeting, I expect the function to be reverted because variables original and address(this) differs. However, although it emits an empty event, it still doesn't reverts. Why does this happen?
When the low-level delegatecall reverts, it doesn't automatically revert the main transaction. Instead, it returns false as the first return value of the delegatecall() function.
So you need to check the return value (in your case bool success) and validate that.
function delegatedGreeting(address _contract) external {
(bool success,) = _contract.delegatecall(
abi.encodeWithSignature("greet()")
);
require(success == true, "delegatecall failed");
}
I am currently writing a Smart Contract in Solidity. The smart contract, amongst other information, stores an array of properties object at the general level. The property object Looks like this:
struct PropertyObj {
string id;
uint weiPrice;
address owner;
}
Now there is a specific function that iterates over the array, finds the property and returns it (code below)
function getPropertyByid(string memory _propertyId)private view returns(PropertyObj memory){
for(uint i = 0; i<PropertyArray.length; i++){
if (keccak256(bytes((PropertyArray[i].id))) == keccak256(bytes((_propertyId)))) {
return PropertyArray[i];
}
return null;
}
}
The "Problem" is that, unlike other programming languages, Solidity does not allow to return null (as far as I am concerned).
In other words, if throughout the iteration we do not find the property, then what we shall return if we specified that we need to return PropertyObj memory in the function signature?
Solidity does not have null value, as you're correctly stating.
Your function can throw an exception using the revert() function.
It also seems that your implementation has a logical error. Your example would "return null" if the hash was not found during the first iteration. Instead, you may want to throw the exception after the loop has ended.
for(uint i = 0; i<PropertyArray.length; i++){
if (keccak256(bytes((PropertyArray[i].id))) == keccak256(bytes((_propertyId)))) {
return PropertyArray[i];
}
}
revert('Not found');
Other option would be to return the empty object (with default values, i.e. zeros), if it fits your use case.
for(uint i = 0; i<PropertyArray.length; i++) {
// ...
}
// not found, return empty `PropertyObj`
PropertyObj memory emptyPropertyObj;
return emptyPropertyObj;
I am using the following code to return array.
function getActiveDepositIndexes() public view returns (uint256 [] storage) {
User storage user = users[msg.sender];
Deposit[] storage deposits = user.deposits;
uint[] memory indices = new uint[](deposits.length);
for(uint i = 0; i < deposits.length; i++) {
if(deposits[i].active && !deposits[i].closed){
indices.push(i);
}
}
return indices;
}
But I am getting following error,
TypeError: Data location must be "memory" for return parameter in function, but "storage" was given.
function getActiveDepositIndexes() public view returns (uint256 [] storage) {
^----------------^
Environment:
Truffle v5.1.20 (core: 5.1.20)
Solidity - 0.6.0 (solc-js)
Node v8.16.2
Web3.js v1.2.1
Seems like you explicitly specify storage for function return value. Try replacing your function signature with the following:
function getActiveDepositIndexes() public view returns (uint[]) {...}
memory is used for return values by default
Use the following code instead:
function getActiveDepositIndexes() public view returns (uint256[] memory) {
User storage user = users[msg.sender];
Deposit[] storage deposits = user.deposits;
uint[] memory indices = new uint[](deposits.length);
for(uint i = 0; i < deposits.length; i++) {
if(deposits[i].active && !deposits[i].closed){
indices[i] = i;
}
}
return indices;
}
Issues with such logic on EVM is that there is no way of returning dynamic sized memory arrays and hence push() is also not usuable with memory elements. This workaround in the above example results in zero (placeholder) alues at inactive indices according to your code...
I am working on a smart contract, and I am testing it by deploying it on truffle. While it compiles fine, when I call the train() function, I get the following error:
Error: VM Exception while processing transaction: invalid opcode
After reading a bit on this, I understood it is usually caused after a revert has occurred, so I tried commenting out the 2 require functions I had just to see if it would behave differently, and it did not.
Checking out this question did not help me, or I did not see how it could.
Here is the train() function, as well as the mapping and struct type I am using in it. I should note that upon creation of a Developer, their wallet is set to 300 so I do not see how the first call of the train function by the owner could revert.
struct Developer {
address owner;
string name;
bytes32 namehash;
bytes32[] skills;
uint256[] skill_levels;
uint wallet;
}
mapping (bytes32=>Developer) public developers_all;
function train(string _name, bytes32 _skill) public {
bytes32 h = keccak256(abi.encodePacked(_name));
require(developers_all[h].owner == msg.sender, "Only the owner of the developer can train them");
require(developers_all[h].wallet >= 150, "Insufficient funds");
uint256 i = 0;
do {
if (developers_all[h].skills[i] == _skill) {
developers_all[h].skill_levels[i]++;
} else if ((i == (developers_all[h].skills.length - 1)) || (developers_all[h].skills.length == 0)) {
developers_all[h].skills.push(_skill);
developers_all[h].skill_levels.push(1);
}
i++;
} while (i < developers_all[h].skills.length);
developers_all[h].wallet = developers_all[h].wallet - 150;
}
Thank you for any help.
This is most likely because you are trying to access the first entry of an empty array. You're using a do while loop, and you're trying to access developers_all[h].skills[i] before you're checking developers_all[h].skills.length == 0, so it is possible that the array is empty at the first if statement in the do while.
You could rewrite the code to something like the following, to make sure you're never accessing an unassigned array slot.
bool foundSkill = false;
for (uint i = 0; i < developers_all[h].skills.length; i++) {
if (developers_all[h].skills[i] == _skill) {
developers_all[h].skill_levels[i]++;
foundSkill = true;
break;
}
}
if (!foundSkill) {
developers_all[h].skills.push(_skill);
developers_all[h].skill_levels.push(1);
}
Do note that looping through the whole array and doing the comparisons is quite costly, and can become impossible if the array size gets too big. You might want consider changing the structure to something like:
struct Developer {
address owner;
string name;
bytes32 namehash;
mapping(bytes32 => uint) skill_levels;
uint wallet;
}
That way you could just replace the whole thing with
developers_all[h].skill_levels[skill]++;
But you wouldn't be able to loop over the skills.