How can I transfer eth from an account wallet to a smart contract - solidity

I'm creating a smart contract that allows people to pay for a monthly subscription
I got stacked in this :
how to transfer plan.amount from the user wallet to the smart contract?
function subscribe(uint planId) external {
Plan storage plan = plans[planId];
require(plan.merchant != address(0), 'address not valid');
bool sent = payable(address(this)).send(plan.amount);
require(sent, "tx failed");
emit PaymentSent(
msg.sender,
plan.merchant,
plan.amount, // the monthly amount for Subscription
planId,
block.timestamp
);
subscriptions[msg.sender][planId] = Subscription(
msg.sender,
block.timestamp,
block.timestamp + 4 weeks // next payement
);
emit SubscriptionCreated(msg.sender, planId, block.timestamp);
}

The subscribe() function needs to use the payable modifier in order to accept ETH. Then you can validate how much the user has sent while invoking your function with the msg.value global variable.
It's not possible to request a specific amount from the contract, as the contract code is executed after the user has sent the transaction. You always need to validate how much has the user sent along with the transaction invoking the function.
function subscribe(uint planId) external payable {
// revert if the sent value is not expected
require(msg.value == 1 ether, "You need to send 1 ETH");
}
However you can control the predefined value on the UI, while creating the transaction request to their MetaMask or other wallet.
await window.ethereum.request(
method: 'eth_sendTransaction',
[
from: userAddress,
to: yourContract,
data: <invoking the subscribe() function>,
value: <1 ETH in wei, in hex>
]
);
Docs: https://docs.metamask.io/guide/ethereum-provider.html#ethereum-request-args

Related

Allowance is returned zero even after approval

I was unit testing my contract and within the tests i approved that the smart contract should be able to spend the tokens of the msg.sender (the owner) and then tried to transferFrom function to transfer the tokens from the msg.sender to the contract. However, the first obstacle i faced was that the allowance is returned as zero when i called the transferFrom function. Now i was able to relearn that the transferFrom function should be another transaction for it to be able to work and i console loged all the variables making sure that the allowance is being changed when calling the approve function. However when i recall the transferFrom the error persists and shows me Insufficient balance!
`
This is my deposit function where the transferFrom is called only if the approval was a success
/**
* #notice addEth is a user function where the user chooses the pool and sends eth to it, funding it
* #param depositAmount is the eth being sent from the user to the pool
* #param poolId_ is the address of the pool being chosen by the user
*/
function deposit(uint256 depositAmount, uint256 poolId_) public{
// Check if the pool is either closed or paused or exists
Pool storage p = checkIfPoolExistsOrClosed(poolId_);
// Make sure the eth being sent is not equal to zero
if (depositAmount <= 0) revert WrongAmount();
// Check if the pool is empty, if it is the price of token is 1
if(p.valueInPool == 0) {
tokensForUser = depositAmount;
}else {
// Get the amount of tokens to be minted to the user
tokensForUser = (depositAmount /
(p.valueInPool/
IProperSubsetERC20(p.poolTokenAddress).totalSupply()));
}
// check if the approval was a success
if(!contractIsApproved[msg.sender]) revert ApprovalFailed();
// Send the USDC tokens to the fund contract
bool transfer = IProperSubsetUSDC(usdcAddress).transferFrom(msg.sender, address(this), depositAmount);
// Send the USDC tokens to the fund contract
// (bool success,)=usdcAddress.delegatecall(abi.encodeWithSignature('transfer(address,uint256)', address(this), depositAmount));
// Call the ERC20 contract to mint tokens to user
IProperSubsetERC20(p.poolTokenAddress).mint(msg.sender, tokensForUser);
// Update the amount of liquidity in the pool
p.valueInPool = p.valueInPool + depositAmount;
// Emit event after adding eth to pool
emit Deposit(msg.sender, poolId_, depositAmount);
}
This is my approval function where i call the approve function to add an allowance
/**
* #notice function to approve contract to spend the user's USDC tokens
* #param amount of usdt willing to give the contract approval for spending
*/
function approveUser(uint256 amount) public returns(bool){
// Approve spending the msg.sender tokens
(bool success,) = usdcAddress.delegatecall(abi.encodeWithSignature('approve(address,uint256)', address(this), amount));
// If the approve function is succesfull we update the map to show that this address is approved
if(success){
contractIsApproved[msg.sender] = true;
}
// Return if the function is successfull
return success;
}
`
Now these are the tests where the approve and the transferFrom function are called
it("Try approving for user 1 the transfer of their tokens from the contract", async function() {
await deployFunds.connect(signers[1]).approveUser(1000);
})
it("Try depositing and the price of the pool tokens should be equal to one since it is new", async function() {
const tx = await deployFunds.connect(signers[1]).deposit(1000, 2);
const receipt = await tx.wait();
filter = receipt.events?.filter((x) => {return x.event == "Deposit"});
poolId = filter.length > 0 ? filter[0].args[1] : '0x000';
tokensForUser = filter.length > 0 ? filter[0].args[2]: "0";
mintedTokens = await deployERC20.balanceOf(user1);
expect(filter.length).above(0);
expect(poolId).to.equal(2);
expect(tokensForUser).to.equal(1000);
})
I tried console logging all the variables being changed in the approve function and everything was checking out but when i console log the allowance when calling the transferFrom is is printed as zero. I then tried to put the approve function and the transferFrom in each transaction alone and still the error persists as Insufficient allowance
(bool success,) = usdcAddress.delegatecall(abi.encodeWithSignature('approve(address,uint256)', address(this), amount));
Delegatecall stores the state changes (updates a storage slot) in your contract - not in the usdcAddress contract.
In order to get approval from the user, they need to send a separate transaction executing the approve() function directly on the usdcAddress contract - not through any other contract.

Ethereum lottery smart contract reverts due to insufficient funds. Where does my code consumes that much gas?

I am experimenting with solidity and I faced an issue for what I could not find a solution.
The program should let addresses buy ticket at a preset price, and the owner can start the "roll the dice" function which randomly selects the winner and transfer the funds to that address.
I beleive that this program would be easier with mapping instead of array, but getting experience with array was the main purpose of this program.
The error happens when the user calls buyTicket function. Based on the response I beleive the contract comsumes too much gas. Can someone tell me why it doesnt work? I appreciate any other helping comment regarding the rest of the code:)
Thanks in advance!
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract Lottery {
constructor () {
owner = msg.sender;
}
address[] public Players;
address private owner;
uint public ticketPrice;
uint public price;
uint public nonce;
uint public variations;
address payable winner;
bool hasTicketAnswer;
event Winner(address _winner);
event PriceSet(uint _setPrice);
event TicketBought();
function setTicketPrice(uint _ticketPrice) public {
require(msg.sender == owner, "Only Owner...");
ticketPrice = _ticketPrice;
emit PriceSet(_ticketPrice);
}
function hasTicket(address _sender) private returns(bool) {
hasTicketAnswer = false;
for (uint i = 0; i < Players.length; i++) {
if (Players[i] == _sender) hasTicketAnswer = true;
}
return hasTicketAnswer;
}
function buyTicket() external payable {
require(ticketPrice > 0, "Price did not set, be patient...");
require(hasTicket(msg.sender) == false, "You cannot have two tickets...");
require(msg.sender.balance <= ticketPrice, "Insufficient funds...");
payable(address(this)).transfer(ticketPrice);
Players.push(address(msg.sender));
price += msg.value;
emit TicketBought();
}
function checkBalance() public view returns(uint) {
return address(this).balance;
}
function rollTheDice() public payable {
variations = Players.length;
winner = payable(Players[uint(keccak256(abi.encodePacked(msg.sender, nonce, block.timestamp))) % variations]);
winner.transfer(price);
emit Winner(winner);
}
receive () external payable {
}
}
Besides probably finding the problem, I've read some things that I'd like to comment on.
Your problem
The reason why you're getting the "Insufficient funds" error is because the condition is returning false. You're asking the msg.sender balance to be less than or equal (<=) to ticketPrice, when it should be more than or equal (>=).
Let's say Alice has a balance of 0.05 ETH and interacts with the contract whose ticket price is 0.001 ETH...
require(0.05 ETH <= 0.001 ETH) // Reverting...
Observations
I'm curious if you're intentionally coding the buyTicket function in that way. What it actually does is checking if the msg.sender has enough ETH to buy a ticket in its wallet, which doesn't mean that the user is effectively sending ETH in the transaction (the amount of wei sent in the transaction can be checked with msg.value, you can read more about it here).
My last observation is the payable(address(this)).transfer(ticketPrice) line of code, because it's not necessary to do so, once a payable function receives ETH, it is saved into the contract... In that line you're just making the Bob's contract to send ETH to the Bob's contract, which just wastes gas without reason
I hope I've helped with you and if I wasn't completely clear in any thing I've said don't doubt in asking me

"revert ERC20: transfer amount exceeds allowance" error when transferring from a smart contract

I have a smart contract named "Manager" with a "claimRewards" method, that will be called by users and should transfer to them some ERC20 tokens. The ERC20 is another smart contract named "rdk".
function claimRewards() public {
uint256 howMany = calculateRewards(msg.sender);
if (howMany > 0) {
// does the contract wallet have tokens enough?
uint256 walletBalance = rdk.balanceOf(address(this));
emit Log(walletBalance, howMany);
require (walletBalance >= howMany, "empty wallet");
// transfer tokens from contract wallet to player
rdk.transferFrom(address(this), msg.sender, howMany);
}
}
The ERC20 tokens are in the smart contract wallet. The event "Log" shows these values:
Manager.Log(
balance: 101000 (type: uint256),
howMany: 9 (type: uint256)
)
So there are 101000 tokens on the smart contract wallet, and it has to transfer 9 to msg.sender
Before calling the method "claimRewards" I approve the smart contract to spend 100000 of those tokens:
it("Should transfer tokens from operator for rewards ", async function () {
await token.approve(manager.address, 100000, { from: accounts[1] });
await token.transfer(manager.address, 100000, { from: accounts[1] });
});
But when running my tests on Truffle, I got the error:
Error: Returned error: VM Exception while processing transaction: revert ERC20: transfer amount exceeds allowance -- Reason given: ERC20: transfer amount exceeds allowance.
I cannot understand if the smart contract "Manager" is the owner of 100000 RDK tokens, and even has been approved for spending 100000 RDK tokens, why is showing that error when transferring 9 of those tokens to the caller of the method.
Anyone could help me with this issue?
await token.approve(manager.address, 100000, { from: accounts[1] });
This JS snippet approves the Manager to spend accounts[1]'s tokens.
rdk.transferFrom(address(this), msg.sender, howMany);
But this Solidity snippet checks for approval from the Manager address (effectively the msg.sender in the rdk contract) to the same Manager address (1st argument of address(this)).
If you want to transfer tokens from the Manager contract, you can simply invoke the token's transfer() function in the Manager contract.
// transfer `howMany` of `rdk` tokens
// from the `Manager` address
// to the user invoking the `claimRewards()` function
rdk.transfer(msg.sender, howMany);
This will not check the allowance but only the sender (Manager) balance.

How can I set the tx ETH amount?

I know a user can send ETH to the contract/function manually, but is there way to request a specific amount directly in the code so that it is added to the gas fee, for example, the way dxsale.app does it when creating a presale (see screenshot below) - it adds the 0.1 ETH cost of presale to the 0.0072 gas fee for a total of 0.1072.
Can this be done in an ERC20 contract? I know I can receive ETH in a payable function, e.g.
function deposit() payable public {
require(msg.value > 0, "You need to send some Ether");
}
but can I specify the amount I want to receive (for a flat fee), so that it is added to the tx cost?
Thank you
TLDR: The contract function is executed after the transaction has been sent. So it's not possible to to set the transaction params from the contract.
is there way to request a specific amount directly in the code
Not in the Solidity code. You can only throw an exception if the sent amount is not an expected value.
function deposit() payable public {
require(msg.value == 0.1 ether, "You need to send 0.1 Ether");
}
You can specify the amount when you're creating the transaction request. For example MetaMask uses the Ethereum Provider API, so you can predefine the amount that MetaMask will show.
const params = [
{
from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155',
to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567',
value: '0x9184e72a', // 2441406250
},
];
ethereum
.request({
method: 'eth_sendTransaction',
params,
});
But the user can override this option in their wallet, or they can always submit a transaction using a different way (not requested with the custom value).
The value (both in the params JS object and the Solidity msg.value global variable) always excludes the gas fees.
So if you're sending 0.1 ETH, the gas fees are added on top of that and the total cost might be 0.10012345. The params.value will contain 0.1 ETH (hex number in wei), as well as the msg.value will contain 0.1 ETH (decimal number in wei).

Sending ether in solidity smart contract

I'm writing a smart contract in solidity, and I need to send ether from my coinbase (eth.coinbase) to my friend coinbase (address = 0x0123).
If I try to use an address.send(value) the function doesn't decrease my account and doesn't increase my friend coin base.
I only can send ether in geth with "eth.sendTransaction(VALUE, {from:eth.coinbase, to:address})"
so I want to know if its possible to call an eth method in contract or a different way to send ether in smart contract
function withdraw() returns (bool) {
address x = 0x0123;
uint amount = 100 ether;
if (x.send(amount))
return true;
else
return false;
}
address.send does not propagate exception that's why you don't see any issue. Make sure you have enough Eth in your contract.
Have a look on this documentation that will explain how to set up your smart contract: https://developer.ibm.com/clouddataservices/2016/05/19/block-chain-technology-smart-contracts-and-ethereum/
You can use following function. just change variable name with yours.
function Transfer(uint amount,address reciever){
// check sender balance is less than of amount which he wants to send.
if(balance[msg.sender] < amount){
return;
}
// decrease sender's balance.
balance[msg.sender] = balance[msg.sender] - amount;
// increase reciever's balance.
balance[reciever] = balance[reciever] + amount;
// event
// transaction(msg.sender,reciever,amount);
}