Can we use constant and returns keyword and a;change the state of the variables in solidity? - solidity

the warnings say that the function is declared as view and those lines are changing the state. i want a return statement but also want to do the addition and subtraction in the function body(ie., change the state). is it possible in solidity?
(this code is a part of the ERC20 token
function _transferToken(address _from, address _to, uint _value) constant public returns (string)
{
// Prevent transfer to 0x0 address. Use burn() instead
if(_to == 0x0)
{
return "Invalid address";
}
// Check if the sender has enough
else if(balanceOf[_from] < _value)
{
return "insufficient tokens";
}
// Check for overflows
else if(balanceOf[_to] + _value < balanceOf[_to])
{
return "Transaction failed";
}
else
{
// Subtract from the sender
balanceOf[_from] = balanceOf[_from] - _value; ***warning***
// Add the same to the recipient
balanceOf[_to]=balanceOf[_to] + _value; *****warning*****
return("Successful");
}
}

If you declare a function as view, you must not modify the state. This is not enforced yet, the compiler only raises a warning. But what you are planning on doing is not possible with a view function.
Calling a view function from off chain doesn't cost you any ether because you don't change the state or run any computations on the actual blockchain.
Besides, you shouldn't use the constant modifier for functions anymore. It is an alias for view and is deprecated.
It will be dropped in version 0.5.0 as stated in the official documentation here.

Related

How to create a time-based upkeep directly from my contract rather than use the GUI?

I want to create a time-based upkeep directly from my contract. I was able to register and fund the upkeep but for some reason the function is not getting executed automatically.
Here's the code
`
// Goerli network
address public cronFactoryAddress = 0x1af3cE8de065774B0EC08942FC5779930d1A9622;
address public keeperRegistrar = 0x57A4a13b35d25EE78e084168aBaC5ad360252467;
constructor(){
cronFactory = ICronFactory(cronFactoryAddress);
}
function createUpkeep(string memory _cronString) public{
address _target = address(this);
bytes memory functionToCall = bytes(abi.encodeWithSignature("sendSalary(string)", _cronString));
bytes memory job = cronFactory.encodeCronJob(_target, functionToCall, _cronString);
uint256 maxJobs = cronFactory.s_maxJobs();
address delegateAddress = cronFactory.cronDelegateAddress();
address newCronUpkeep = address(new CronUpkeep(msg.sender, delegateAddress, maxJobs, job));
allUpkeeps.push(newCronUpkeep);
}
function fundUpkeep(uint256 _linkAmount, address _upkeepAddress) public{
bytes4 reg = bytes4(keccak256("register(string,bytes,address,uint32,address,bytes,bytes,uint96,address)"));
bytes memory _data = abi.encode(
"TestV2",
"",
_upkeepAddress,
uint32(500000),
address(this),
"",
"",
_linkAmount,
address(this)
);
bytes memory combinedData = abi.encodePacked(reg, _data);
LinkContract.transferAndCall(keeperRegistrar, _linkAmount, combinedData);
}
sendSalary is the function in my contract that I want to be executed at regular intervals.
cronFactory is the cron factory contract.
cronUpkeep is the cronUpkeep.sol contract from the chainlink github repo.
To create these functions, I created a time-based upkeep manually and used the transaction logs to find what all function are being called and implemented the same here.
But, Once I execute both these functions nothing happens, however, I am able to find the upkeep registered on chainlink's website . And also it shows the trigger as custom trigger on upkeep page on chainlink:
chanlink upkeep
Please let me know how I can solve this? Any help would be appreciated. Thanks in advance
Contracts cannot execute themselves. Function needs to be called. While contract (function) is not called, contract is sleeping, because every time it makes operations, they should be payed (aka gas), so there is no way to throw an allways-active-timer inside of the contract (infinite gas). It means that you have to make calls manually or use automation services like ChainLink, Openzepplin Defender etc.
You can make a requirement by time-passed with
uint256 private lastTimeStamp;
uint256 private interval;
constructor() {
lastTimeStamp = block.timestamp;
interval = 7 days;
}
function isTimePassed() public view returns (bool timePassed) {
timePassed = ((block.timestamp - lastTimeStamp) > /*7 days */ interval);
return timePassed;
}
function smth() public {
(bool timePassed) = isTimePassed();
...
}
Something like this.

How can I delete an element from a nested mapping

I have a nested mapping that goes from address to planId to struct(subscription)
mapping(address => mapping(uint => subscription)) public subscriptions
I have a function to cancel a specific plan that has been created but when I trigger the function I got an error that says
The transaction ran out of gas. Please increase the Gas Limit.
When I debugged the error the debugger points the error inside the cancel function at the code line below
function cancel(uint planId) external {
Subscription storage subscription = subscriptions[msg.sender][planId];
require(
subscription.subscriber != address(0),
'this subscription does not exist'
);
delete subscriptions[msg.sender][planId]; // this one
emit SubscriptionCancelled(msg.sender, planId, block.timestamp);
}
How can I fix that error?
Thanks
I am facing a similar problem. Here's what I did, I realized that it's not possible to assign a default value to a nested mapping once you change it.
In your example, you can try assigning a struct with different values(which you can consider as a replacement for default) and then it wont throw error.
Following is my example:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract test{
mapping(address=> mapping(uint => uint)) public address_id_liked;
function register(uint id) external{
address_id_liked[msg.sender][id] = 1;
}
function test_(uint index) external view returns(uint) {
uint out = address_id_liked[msg.sender][index];
return(out);
}
function ops(uint id, uint num) external {
address_id_liked[msg.sender][id] = num;
}
}
In the ops function, I wanted my mapping to have a default value but it's not happening. So I tried inputting the value which I want to assign to nested mapping. And it's taking all uint values except 0(the default)
P.S- I was using mapping(address=> mapping(uint => bool)) public address_id_liked previously. But I am unable to delete/assign false, hence I tried with uint.
Hope this helps!

When is function reverted?

so because of reentrancy attacks I'm structuring my function to not get hacked. So first updating mappings and so on and then sending the payment.
My question is what if the payment fails to go through. Will entire function be reverted of just the payment?
Because if only the payment than that would mean my mappings would be updated as if the payment went through.
Is this the case?
Thanks for answers!
function withdraw(uint256 _amount) external {
balances[msg.sender] -= _amount;
(bool success, ) = payable(msg.sender).call{value: _amount}("");
}
If the low-level .call() is unsuccessful, the value of success is set to false but that doesn't revert the whole transaction. So the balances value would be reflected even though the payment didn't go through.
You can add a require() condition to check the result of the payment. This will make the parent transaction fail if the internal transaction fails.
(bool success, ) = payable(msg.sender).call{value: _amount}("");
require(success);
Or you can use the .transfer() function (member of address payable, do not confuse with token transfers) that also fails the parent transaction if the internal transaction fals:
function withdraw(uint256 _amount) external {
balances[msg.sender] -= _amount;
payable(msg.sender).transfer(_amount);
}
If you're preventing the reentrancy attacks, you might probably use the modifiers. Therefore, when the Reentrancy is detected, the function would be reverted and even not allowed to enter the function. That is, there would be no other parameters updated.
Besides, I can show you some demo code to answer your question.
contract test {
uint public a = 0;
// a will still be a
function addRevert() public{
a += 1;
goRevert();
}
// a = a + 1
function addNoRevert() public{
a += 1;
}
function goRevert() pure public{
revert();
}
}

Solidity: Indexed expression has to be a type, mapping or array (is function (address,uint256) returns (bool))

* #param value The number of tokens to be spent.
*/
function approve(address spender, uint256 value) returns (bool) {
require(spender != address(0));
approve[spender][msg.sender] = value; // approve used to be _allowed?
emit approve(msg.sender, spender, value);
return true;
}
receive error >Indexed expression has to be a type, mapping or array (is function (address,uint256) returns (bool))
You have defined a function named approve, but then you're trying to access a mapping named approve as well.
Solution: Rename the mapping to use a different name (both the definition and its reference).
The error means you are trying to use indexed epression as it were a mapping but it is defined as a function instead. Also in your code instead of "return true" put>> "return false"

Is there a way to store addresses in the solidity contract to authorize only those addresses to do transactions?

I'm starting with contracts in solidity and a question arose, is there any way I can store addresses in the wallet so that only these stored can make the withdrawal of coins that I make available? Let me explain better, I want to airdrop for 1 month for example and after this period the people who got my coins can withdraw from my contract, but so that only the people who complied with the airdrop rules. I want to do this so they pay transaction fees. But I want only addresses authorized by the owner of the contract to be able to withdraw the currency
Another way of doing this, in order to avoid gas, is to either use merkle trees or signatures. I have implemented signatures in two NFT projects.
Basically, for every address you wish to whitelist, you sign a message offchain, by using a wallet in your possession (it can be empty, you only need to sign messages).
Example call for signing via JavaScript (by using the web3js lib):
web3.eth.accounts.sign(message, privateKey);
By using the privateKey, this allows you to sign multiple messages at once (without having to confirm in MetaMask. It is clear that: please keep that private key as safe as possible! Only execute this code locally on your machine! Do not store the privateKey in any text file, especially not in the project folder, that might get synched to github!
The other way of doing it via MetaMask:
web3.eth.personal.sign(message, from);
(where "from" is the address of the signer).
On the contract side, there are some functions that can be used to retrieve the signer of a message, by reconstructing the message and passing the signature you have generated.
Some reference code:
// used to keep track of issuer addresses, that can issue whitelists
mapping(address => bool) internal issuers;
// builds a prefixed hash to mimic the behavior of eth_sign.
function prefixed(bytes32 hash) internal pure returns(bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
// get the signer address by message and signature
function recoverSigner(bytes32 _message, bytes memory sig) internal pure returns(address){
uint8 v;
bytes32 r;
bytes32 s;
(v, r, s) = splitSignature(sig);
return ecrecover(_message, v, r, s);
}
function signerIsIssuer(bytes memory message, bytes memory _sig, uint256 nonce) public view returns(bool){
// check the issuer
if (!issuers[recoverSigner(message, sig)]) {
return false;
} else {
return true;
}
}
// splits the signature into its components
function splitSignature(bytes memory sig) internal pure returns(uint8, bytes32, bytes32){
require(sig.length == 65);
bytes32 r;
bytes32 s;
uint8 v;
assembly {
// first 32 bytes, after the length prefix
r:= mload(add(sig, 32))
// second 32 bytes
s:= mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v:= byte(0, mload(add(sig, 96)))
}
return (v, r, s);
}
// this has to hash the same parameters as on the offchain side
// do not reuse nonces
function hashMessage(address authorizedWallet, uint256 nonce) public returns(bytes memory){
// create the message hash
bytes32 message = prefixed(keccak256(abi.encodePacked(authorizedWallet, nonce)));
if (signerIsIssuer(message, _sig, _nonce)){
// authorizedWallet really is authorized by signature
}
}
If you want to be able to remove authorization, then you either need to invalidate the nonce, or work with expiration timestamps or keep an address blacklist. Please note, that once a signature is emitted, if the contract has no ways to be told, that that signature is not valid anymore, the wallet address will be authorized forever.
Hope this helps!
code:
contract test{
address owner;
mapping(address=>bool) authorizedAddresses;
mapping(address=>bool) isRegistered;
mapping(uint256=>address) users;
uint256 userCounter;
constructor(){
owner = msg.sender;
}
modifier isOwner(){
require(msg.sender == owner, "Access denied!");
_;
}
function changeOwner(address _to) public isOwner{
owner = _to;
}
function authorizeAddress(address _address) public isOwner{
if(isRegistered[_address]){
authorizedAddresses[_address] = true;
}else{
users[userCounter] = _address;
isRegistered[_address] = true;
authorizedAddresses[_address] = true;
userCounter ++;
}
}
function unauthorizeAddress(address _address) public isOwner{
require(authorizedAddresses[_address], "User is not authorized already");
authorizedAddresses[_address] = false;
}
modifier isAuthorizedAddress(){
require(authorizedAddresses[_address], "You are not authorized to call this function");
_;
}
I should note I did not test this code and may throw error, so if it throws any error let me know and modify it or you can do it let me know how to fix it and edit the code for others.
If you did not understand any part, simply ask to describe :)
Best regards
You can use ERC20 token standart. Just define allowance for those addresses who complied with the airdrop rules.
As others wrote, you can store addresses in a mapping like mapping(address=>bool) authorizedAddresses;
However, it is expensive and inefficient to store hundreds of addresses on the blockchain. Think about that, probably you upload a bunch of addresses you don't even need to, because people will forget that they can get the airdrop and they won't even claim it.
A better approach is to give them signed coupons. When they claim the airdrop on your website, the transaction submits a coupon and your contract will verify if the coupon is:
signed by you
belongs to the sender address
allowed to claim the amount
It all happens in the background, so users won't notice anything.
Generate coupons in JS:
const {
keccak256,
toBuffer,
ecsign,
bufferToHex
} = require("ethereumjs-util")
const { ethers } = require('ethers')
const PRIV_KEY = "YOUR_SIGNER_PRIVATE_KEY"
//utils
function generateHashBuffer(typesArray, valueArray) {
return keccak256(
toBuffer(ethers.utils.defaultAbiCoder.encode(typesArray,
valueArray))
)
}
function createCoupon(hash, signerPvtKey) {
return ecsign(hash, signerPvtKey)
}
function fromHexString(hexString) {
return Uint8Array.from(Buffer.from(hexString, 'hex'))
}
function serializeCoupon(coupon) {
return {
r: bufferToHex(coupon.r),
s: bufferToHex(coupon.s),
v: coupon.v,
}
}
//generate coupon
function generate(claimAmount, address) {
const userAddress = ethers.utils.getAddress(address) // get checksum-correct address
const hashBuffer = generateHashBuffer(
["uint256", "address"],
[claimAmount, userAddress]
)
const coupon = createCoupon(hashBuffer, fromHexString(PRIV_KEY))
console.log({
address,
claimAmount,
coupon: serializeCoupon(coupon)
})
}
Now you have the coupon, you can build an API, so when the user click Claim on your website, he can submit the amount and the signature to the blockchain in the claim transaction.
Verify signature in your contract:
contract Airdrop is
Ownable,
ReentrancyGuard
{
address private COUPON_SIGNER = 0xYOUR_SIGNER_ADDRESS;
mapping(address => bool) public CLAIMED_ADDRESSES;
struct Coupon {
bytes32 r;
bytes32 s;
uint8 v;
}
// check that the coupon sent was signed by the admin signer address
function _isVerifiedCoupon(bytes32 digest, Coupon memory coupon)
internal
view
returns (bool)
{
address signer = ecrecover(digest, coupon.v, coupon.r, coupon.s);
require(signer != address(0), "ECDSA: Invalid whitelist signature.");
return signer == COUPON_SIGNER;
}
// Claim Airdrop
function claim(uint256 amountToClaim, Coupon memory coupon) external nonReentrant {
require(
CLAIMED_ADDRESSES[msg.sender] == false,
"This address already claimed a refund."
);
require(
address(this).balance >= amountToClaim,
"Contract balance is too low."
);
bytes32 digest = keccak256(abi.encode(amountToClaim, msg.sender));
require(
_isVerifiedCoupon(digest, coupon),
"Unexcepted error: unable to verify coupon."
);
CLAIMED_ADDRESSES[msg.sender] = true;
payable(msg.sender).transfer(amountToClaim); // Send ETH
}
}
PLEASE PLEASE don't just copy this code: TEST, TEST, and TEST. Always write unit tests.