I have 2 contracts, first one is openzeppelin ERC20 token and second one is a lottery contract where players can bet on a number.
lottery.sol
pragma solidity ^0.8.4;
import "./Token.sol"; //import ERC20 token
import "#openzeppelin/contracts/token/ERC20/ERC20.sol";
import "#openzeppelin/contracts/access/Ownable.sol";
contract Lottery is Ownable {
Token token;
constructor(Token _token) public {
token = _token;
}
// store information about player's bet
struct PlayersStruct {
uint betAmount;
uint betOnNumber;
}
mapping(address => PlayersStruct) public balances;
function enterLottery(string memory _betOnNumber) public payable {
address player = msg.sender;
uint amount = msg.value;
// transfer token from player's wallet to lottery contract
token.transferFrom(player, address(this), betAmount);
balances[player].betAmount += amount ;
balances[player].betOnNumber = _betOnNumber;
}
And this is how I call it from ReactJS
async function stakeBet() {
const amount = ethers.utils.parseEther("10");
const maxAmount = ethers.utils.parseEther("1000000");
// approve token once so player can save on gas in future
await token.approve(stakingContract.address, maxAmount);
// bet 10 tokens on number 20
await lottery.enterLottery(20, {value: amount,});
}
There are 2 problems with this code:
I have to approve the contract every time, even I'm approving maxAmount higher than betting amount. How do I let Metamask know that contract was already approved?
After approving the ERC20 token, the token for transfer is actually ETH and not ERC20 token defined in Token.sol, how do I specify that ERC20 is the one to transfer?
I'm testing on Kovan test net.
In your stakeBet function, you are calling those functions in order:
await token.approve(stakingContract.address, maxAmount);
// bet 10 tokens on number 20
await lottery.enterLottery(20, {value: amount,});
When you call approve, you are actually updating the allowance mapping. Let the contract know that, you are allowing certain amount for the allowed address. It should be implemented like this:
function approve(address _spender, uint _value)public returns (bool success){
// allowance tells how many tokens can be sent
allowance[msg.sender][_spender]=_value;
// This event must trigger when a successful call is made to the approve function.
emit Approval(msg.sender,_spender,_value);
return true;
}
Token transfer or coin transfer is actually updating the state inside the contracts. With the approve function you updated the allowance. Now
token.transferFrom should be implemented like this:
// my address is allowing your address for this much token
mapping(address=>mapping(address=>uint)) public allowance;
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success){
// check the allowance
require(_value <=allowance[_from][msg.sender]);
// update the balances
balanceOf[_to]+=_value;
balanceOf[_from]-=_value;
allowance[_from][msg.sender]-=_value;
// emitting an event
emit Transfer(_from,_to,_value);
return true;
}
ETH is not ERC20 token. Instead you have to implement WETH token and transfer WETH token. WETH is wrapped version of eth. https://weth.io/
All Ethereum wallet addresses are ERC20 compatible. Moreover, this means that every ERC20 transfer can happen between two Ethereum wallet addresses, or ERC20-compatible addresses. This typically includes all EVM-compatible blockchains. You send weth token and user can swap in metamask:
For the 1st part, you have done what you intended to do, i.e, you have set the allowance to the maximum amount so that the user doesn't have to pay for calling transaction for allow() each time.
The MetaMask is asking your permission for gas fees (in ETH) for sending "betAmount" amount of tokens to the contract.
I am new to ethereum, but I have faced similar circumstances in my projects. So, this is according to my understanding.
Also, for the 2nd problem, as I said earlier, MetaMask is asking for your permission for paying the gas fees (which it takes in ETH, but actual transfer of tokens must also be taking place. Only, some amount of ETH is spent for the "Gas Fees".
I got a real good article discussing the same thing.
Link: https://medium.com/ethex-market/erc20-approve-allow-explained-88d6de921ce9
You can see the demonstration of what I just said. You can see that the gas fees are taken in ETH. Also, if you had not set the allowance earlier to maximum, you would have to pay gas fees two times for a transaction to your contract to happen, first to call approve() for allowance, then to get the "betAmount" amount of tokens transferred to the contract.
Related
I'm working on a smart contract that allows users to pay for monthly subscriptions like Netflix, Amazon, etc... with ERC20 tokens.
Everything works well when I tested it. but when I implemented the pay function inside the front-end, so users can interact with the smart contract. the problem that I've faced is that when I clicked on a button to fire off the pay function, I had Metamask asking to confirm the approval, then once the approval is confirmed, I had to confirm the transfer of tokens. **I had to confirm the transfer of tokens. then I got another transaction to confirm the equivalent amount of tokens in ETH.
I made a lot of research but I got stuck, on can I allow the pay function to transfer tokens between two addresses only once.
am I missing something?
PS: blockchain.account & blockchain.smartContract are imported from redux object state.
solidity function
function pay(uint planId) external {
Subscription storage subscription = subscriptions[subscriber][planId];
Plan storage plan = plans[planId];
IERC20 token = IERC20(plan.token);
require(
block.timestamp > subscription.nextPayment,
'not due yet'
);
token.transferFrom(subscriber, plan.merchant, plan.amount);
emit PaymentSent(
msg.sender,
plan.merchant,
plan.amount,
planId,
block.timestamp
);
subscription.nextPayment = subscription.nextPayment + plan.frequency;
}
React function
async function pay() {
showAlert(true, "Happy to see you, Your payment is processing...!");
const data = await blockchain.smartContract.methods.subscriptions(Id).call();
let monthlyPayment = String(data.monthlyPayment);
let tokenAddress = data.amount;
// instance of ERC20 contract
let currency = new web3.eth.Contract(tokenIbi, tokenAddress);
currency.methods.approve("0x1b4eAe2DC7Ca0b68643A26177bfC9c069B3D6E04",
amount).send({from: blockchain.account})
.then(
await currency.methods.transfer("0x1b4eAe2DC7Ca0b68643A26177bfC9c069B3D6E04",
amount).send({from:blockchain.account})
)
blockchain.smartContract.methods.pay(Id).send({from: blockchain.account})
.once("error", (err)=> {
console.log(err);
showAlert(true, "Something went wrong...!");
})
.then((receipt)=> {
console.log(receipt);
showAlert(true, "Congratulations, You monthly payment has been submitted successfully");
dispatch(fetchData(blockchain.account));
})
}
Pay button
<button className="btn"
id="launchApp-btn"
onClick={(e)=> {
e.preventDefault();
pay();
}}>
You are making two transaction on each monthly purchase
Approve the allowance of erc20 tokens
Transfer of erc20 token
Each one has its own gas cost. You may multiply the allowance of erc20 on user's first purchase (Which is not fair and brings the security issue) or continue doing the same scenario Approve&Transfer
In the simplified (incomplete) code below, the runIt() function in contract B makes a transfer to contract A. This works fine, I can check the balance of contract A afterwards and it receives the funds fine at this stage alone.
Immediately after this transfer, the runIt() function in contract B calls the randomEvent() function in contract A. This function in contract A is meant to make another transfer, sending the entire contract A balance to contract C (not shown). For some reason I can never get this part to work. I tried to simplify the code by changing the target of randomEvent() to a wallet address instead of a contract address but I experience the same problem there. MetaMask flags the transaction as having issues and it won't process if you try.
In this code example it might seem like I'm sending funds in circles but that's just because I've simplified it for ease of explanation. Can anyone please help me figure out how I can execute a second transfer from contract A to contract C, following the receiving of funds from contract B? I initially tried to call the randomEvent() function from contract A directly, inside the payable function so that it fires every time it receives a transfer from anywhere. This would work perfectly fine, too, except I ran into the same issue and error. It didn't work that way, either.
I'm new to Solidity so I'm probably missing something stupid but any advice would be much appreciated. Yes, I know I'm using a very old version of Solidity, I'll eventually refactor it to something more modern.
pragma solidity ^0.4.17;
contract A {
function () public payable {
}
function randomEvent() public payable {
address wallet = 0x0000; // Random Contract C Address
wallet.transfer(address(this).balance);
}
}
}
contract B {
address public platform;
platform = 0x000; // Contract A Address
uint someValue = 1000000; // Irrelevant Example Amount
function runIt() public payable {
platform.transfer(someValue); // This works fine, funds in Contract A are received.
A ContractA = A(platform);
ContractA.randomEvent(); // This transaction fails and won't process, funds aren't moved from Contract A to Contract C.
}
}
I have deployed a smart contract on a local truffle project and I am trying to interact with it in a React project using web3. The following solidity function should send Ether what was previously deposited in the contract to a user address on a boolean condition:
function Payout() public{
require( voteEndTime< block.timestamp, "Voting Time is not up. Please come back later" );
Voter storage sender = voters[msg.sender];
if (negativeVotes > positiveVotes){
require(!sender.option, "Wrong Vote. Stake is distributed among winners");
payable(address(msg.sender)).transfer((stakes*sender.amount) / negativeStakes);
}
else if (positiveVotes > negativeVotes){
require(sender.option, "Wrong Vote. Stake is distributed among winners");
payable(address(msg.sender)).transfer((stakes*sender.amount) / positiveStakes);
}
else{
payable(address(msg.sender)).transfer((stakes*sender.amount) / stakes);
}
}
The contract is definitely able to read the user's address using msg.sender because it has worked in the other functions I have. Every other function in the contract is also working fine. I can interact with it and I am able to send Ether to it. The problem occurs when I am trying to return the Ether stored in the contract to an account. I am trying to call my Payout() function using the following web3 call in React on button click:
var response = await BallotContract.methods.Payout().send({ from: account, gas: 310000 })
I have specified a higher gas limit, because the contract runs out of gas if I try to use the gas estimation seen below. The function this call is present in looks like this:
const giveMeMoney = async (e) => {
const web3 = await new Web3(window.ethereum);
await window.ethereum.enable();
var Accounts = await web3.eth.getAccounts()
account = Accounts[0]
console.log(account)
const gas = await BallotContract.methods.Payout().estimateGas();
console.log(gas)
var response = await BallotContract.methods.Payout().send({ from: account, gas: 310000 })
}
I am able to access the function from the frontend and it is returning the correct string if a "require" condition is not met. My problem is that the contract does not return any Ether if the conditions are met and this line:
payable(address(msg.sender)).transfer((stakes*sender.amount) / positiveStakes);
...is accessed. I am getting the following error:
Uncaught (in promise) Error: Returned error: VM Exception while processing transaction: revert
at Object.ErrorResponse (errors.js:30)
at onJsonrpcResult (index.js:162)
at XMLHttpRequest.request.onreadystatechange (index.js:123)
ErrorResponse # errors.js:30
Now I am unsure what could be the problem, because the contract is running perfectly fine if I test it in Remix. Does anybody see the problem or have a workaround for this kind of problem?
The "out of gas" error is caused by the transfer function. This function has a gas limit of 2100; I recommend you use call, you can check how here.
Also the boolean value of the Voter struct defaults to false. So if the negative votes win, but the user didn't vote, they still can try to claim the reward, even if the amount is 0. I recommend you check how to use an enum, it can be very useful.
I'm using NextJS with Firebase, and PayPal is 100x easier to implement client-side. The only worry I have is somebody potentially brute-forcing the amount before the token is sent to PayPal. If I JWT sign with a secret key, is that secure enough (within reason) to dissuade people from attempting to manipulate the prices?
I thought about writing a serverless function, but it would still have to pass the values to the client to finish the transaction (the prices are baked into a statically-generated site). I'm not sure if PayPal's IPN listener is still even a thing, or even the NVP (name-value-pairs). My options as I see them:
Verify the prices and do payment server-side (way more complex)
Sign the prices with a secret key at build time, and reference those prices when sending to PayPal.
I should also mention that I'm completely open to ideas, and in no way think that these are the 'best' as it were.
Pseudo-code:
cart = {
product: [ obj1, obj2, obj3 where obj = { price, sale_price, etc.}],
total: cart.products.length
}
create an order with PayPal, using the cart array, and mapping over values
cart.products.map( prod => { return prod.sale_price || prod.price } etc.
Someone could easily modify the object to make price '.01' instead of '99.99' (for example)
I'm creating my own BEP20 token and want to implement a function to airdrop tokens to multiple addresses at once to reduce gas fees. Use case would be a giveaway of free tokens to selected users after the launch.
This is the code that I have so far, however there seems to be something missing for it to work properly:
contract Airdrop is Ownable {
IERC20 token;
struct PaymentInfo {
address payable payee;
uint256 amount;
}
constructor(address _token) public {
token = IERC20(_token);
}
function batchPayout(PaymentInfo[] calldata info) external onlyOwner {
for (uint i=0; i < info.length; i++) {
token.transfer(info[i].payee,info[i].amount);
}
}
function transfer(address to, uint256 amount) external onlyOwner {
token.transfer(to, amount);
}
}
Can I use code snippets from ERC20 examples? Will they work with BEP20?
Ethereum and Binance Smart Chain use slightly different token standards, so most of the Solidity code designed for Ethereum virtual machine need minor changes, including replacing mentions of IERC20 with IBEP20 and using the correct Solidity file for IBEP20 interface.
If you use correct version of Solidity compiler, it should tell if the code needs further changes. For real life testing, it's better to test the code on testnet of Binance Smart Chain.
You do not need to include batch send in the token itself. Because smart contracts are composable, there exist third-party smart contracts that can do batch send on behalf of any token.
One example service with open-source smart contracts is Token BulkSender. The source for the bulk send smart contract is here.