Reputation: 21
To context. I want to make a contract that receives USDC and send custom tokens to the buyer. After that, in the same call, the contract forwards those USDCs to another wallet. I have done tests, and calls. When I do it through Remix, I'm able to get it working nicely, Rate works, forward funds, everything. However, once I try to call it through Metamask, it returns me a absurd amound of gas required (10 matic) to call buyTokens(). Could someone help me find if something is wrong, or should it be this expensive?
ps: With buyTokens(), we are also calling approve() from the buyer, to approve Crowdsale to take the right amount of USDC
I have two contracts to demonstrate. First, the custom token:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./ERC777P.sol";
import "./MinterRole.sol";
contract TokenMBP is ERC777P, MinterRole {
constructor(
string memory name,
string memory symbol,
address[] memory defaultOperators,
uint256 initialSupply
) ERC777P(name, symbol, defaultOperators)
{
_mint(msg.sender, initialSupply, "", "", "");
}
function mint(address account, uint256 amount, bytes memory userData, bytes memory operatorData, bytes32 proof) public onlyMinter returns (bool) {
_mint(account, amount, userData, operatorData, proof);
return true;
}
function burn(uint256 amount, bytes memory userData, bytes memory operatorData) public returns (bool) {
_burn(msg.sender, amount, userData, operatorData);
return true;
}
}
Then, the crowdsale:
pragma solidity ^0.5.0;
import "./Context.sol";
import "./IERC20.sol";
import "./StableCoin.sol";
import "./SafeMath.sol";
import "./SafeERC20.sol";
import "./ReentrancyGuard.sol";
/**
* @title Crowdsale
* @dev Crowdsale is a base contract for managing a token crowdsale,
* allowing investors to purchase tokens with ether. This contract implements
* such functionality in its most fundamental form and can be extended to provide additional
* functionality and/or custom behavior.
* The external interface represents the basic interface for purchasing tokens, and conforms
* the base architecture for crowdsales. It is *not* intended to be modified / overridden.
* The internal interface conforms the extensible and modifiable surface of crowdsales. Override
* the methods to add functionality. Consider using 'super' where appropriate to concatenate
* behavior.
*/
contract Crowdsale is Context, ReentrancyGuard {
using SafeMath for uint256;
using SafeERC20 for IERC20;
// The token being sold
IERC20 private _token;
STABLE private _stableCoin = _stableCoin;
// Address where funds are collected
address payable private _wallet;
// Contract owner address
address _owner;
// How many token units a buyer gets per usdc.
// The rate is the conversion between usdc and the smallest and indivisible token unit.
// So, if you are using a rate of 1 with a ERC20Detailed token with 3 decimals called TOK
// 1 usdc will give you 1 unit, or 0.001 TOK.
uint256 private _rate;
// Amount of usdc raised
uint256 private _usdcRaised;
/**
* Event for token purchase logging
* @param purchaser who paid for the tokens
* @param beneficiary who got the tokens
* @param value usdcs paid for purchase
* @param amount amount of tokens purchased
*/
event TokensPurchased(address indexed purchaser, address indexed beneficiary, uint256 value, uint256 amount);
/**
* @param rate Number of token units a buyer gets per wei
* @dev The rate is the conversion between wei and the smallest and indivisible
* token unit. So, if you are using a rate of 1 with a ERC20Detailed token
* with 3 decimals called TOK, 1 wei will give you 1 unit, or 0.001 TOK.
* @param wallet Address where collected funds will be forwarded to
* @param token Address of the token being sold
*/
constructor (uint256 rate, address payable wallet, IERC20 token, address stableCoin) public {
require(rate > 0, "Crowdsale: rate is 0");
require(wallet != address(0), "Crowdsale: wallet is the zero address");
require(address(token) != address(0), "Crowdsale: token is the zero address");
require(address(stableCoin) != address(0), "Crowdsale: stable is the zero address");
_owner = _msgSender();
_rate = rate;
_wallet = wallet;
_token = token;
_stableCoin = STABLE(stableCoin);
}
modifier onlyOwner() {
require(_msgSender() == _owner, "OwnerRole: Only owner can make this change");
_;
}
/**
* @return the token being sold.
*/
function token() public view returns (IERC20) {
return _token;
}
/**
* @return the stable token being required.
*/
function stable() public view returns (STABLE) {
return _stableCoin;
}
/**
* @return the address where funds are collected.
*/
function wallet() public view returns (address payable) {
return _wallet;
}
/**
* @return the number of token units a buyer gets per wei.
*/
function rate() public view returns (uint256) {
return _rate;
}
/**
* @return the amount of wei raised.
*/
function usdcRaised() public view returns (uint256) {
return _usdcRaised;
}
/**
* @dev Change contract rate
* @param newRate New price for minimum amount of token
* @return true if rate was modifie
*/
function changeRate(uint256 newRate) public onlyOwner returns (bool) {
_rate = newRate;
return true;
}
/**
* @dev low level token purchase ***DO NOT OVERRIDE***
* This function has a non-reentrancy guard, so it shouldn't be called by
* another `nonReentrant` function.
* @param beneficiary Recipient of the token purchase
* @param tokenAmount Stable coin quantity for the purchase
*/
function buyTokens(address beneficiary, uint256 tokenAmount) public nonReentrant payable {
// calculate token amount to be created
// tokenAmount = tokenAmount * 10 ** 18;
uint256 tokens = _getTokenAmount(tokenAmount);
_preValidatePurchase(beneficiary, tokens);
// update state
_usdcRaised = _usdcRaised.add(tokenAmount);
bool stableStatus = _stableCoin.transferFrom(beneficiary, address(this), tokenAmount);
require(stableStatus, "Crowdsale: Stable token transfer failed");
_processPurchase(beneficiary, tokens);
emit TokensPurchased(_msgSender(), beneficiary, tokenAmount, tokens);
_updatePurchasingState(beneficiary, tokenAmount);
_forwardFunds(tokenAmount);
_postValidatePurchase(beneficiary, tokenAmount);
}
/**
* @dev Validation of an incoming purchase. Use require statements to revert state when conditions are not met.
* Use `super` in contracts that inherit from Crowdsale to extend their validations.
* Example from CappedCrowdsale.sol's _preValidatePurchase method:
* super._preValidatePurchase(beneficiary, tokenAmount);
* require(weiRaised().add(tokenAmount) <= cap);
* @param beneficiary Address performing the token purchase
* @param tokenAmount Value in wei involved in the purchase
*/
function _preValidatePurchase(address beneficiary, uint256 tokenAmount) internal view {
require(beneficiary != address(0), "Crowdsale: beneficiary is the zero address");
require(tokenAmount != 0, "Crowdsale: tokenAmount is 0");
require(!(_stableCoin.balanceOf(beneficiary) < tokenAmount), "Crowdsale: Not enough balance");
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
}
/**
* @dev Validation of an executed purchase. Observe state and use revert statements to undo rollback when valid
* conditions are not met.
* @param beneficiary Address performing the token purchase
* @param weiAmount Value in wei involved in the purchase
*/
function _postValidatePurchase(address beneficiary, uint256 weiAmount) internal view {
// solhint-disable-previous-line no-empty-blocks
}
/**
* @dev Source of tokens. Override this method to modify the way in which the crowdsale ultimately gets and sends
* its tokens.
* @param beneficiary Address performing the token purchase
* @param tokenAmount Number of tokens to be emitted
*/
function _deliverTokens(address beneficiary, uint256 tokenAmount) internal {
_token.safeTransfer(beneficiary, tokenAmount);
}
/**
* @dev Executed when a purchase has been validated and is ready to be executed. Doesn't necessarily emit/send
* tokens.
* @param beneficiary Address receiving the tokens
* @param tokenAmount Number of tokens to be purchased
*/
function _processPurchase(address beneficiary, uint256 tokenAmount) internal {
_deliverTokens(beneficiary, tokenAmount);
}
/**
* @dev Override for extensions that require an internal state to check for validity (current user contributions,
* etc.)
* @param beneficiary Address receiving the tokens
* @param weiAmount Value in wei involved in the purchase
*/
function _updatePurchasingState(address beneficiary, uint256 weiAmount) internal {
// solhint-disable-previous-line no-empty-blocks
}
/**
* @dev Override to extend the way in which ether is converted to tokens.
* @param stableCoinAmount Value in wei to be converted into tokens
* @return Number of tokens that can be purchased with the specified _weiAmount
*/
function _getTokenAmount(uint256 stableCoinAmount) internal view returns (uint256) {
return stableCoinAmount.div(_rate);
}
/**
* @dev Determines how ETH is stored/forwarded on purchases.
*/
function _forwardFunds(uint256 tokenAmount) internal returns (bool){
require(_stableCoin.transfer(_wallet, tokenAmount), "Crowdsale: Stable token transfer failed");
return true;
}
}
I used those contracts and with remix gas was as low as 0.01 matic.
Upvotes: 1
Views: 110