According to the OpenZeppelin documentation (OpenZeppelin), following has to be satisfied for transfer function:
- recipient cannot be the zero address.
- the caller must have a balance of at least amount.
The caller means the msg.sender. Now when someone calls your contract, their address is the msg.sender. When transfer function is invoked, it checks if the msg.sender has enough balance. However, all the token balance actually lies in the contract address, so that's why msg.sender has a balance of 0. And hence the transaction fails.
So in short words, what happens is, when Person A calls your testTra function, the contract tries to send the balance of Person A to the address specified in _to parameter.
The issue with your logic is that, if you mint tokens for the address of the contract, you cannot do anything with those tokens, because your contract will never be msg.sender in this case. Thus you can neither give allowance in your testTra function nor use transferFrom or transfer. What you can do however, is use another address (like another smart contract) as manager of tokens and mint the tokens to that address.
pragma solidity ^0.8.4;
import "@openzeppelin/contracts@4.5.0/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor(address _manager) ERC20("MyToken", "MTK") {
_mint(_manager, 500 * 10 ** decimals());
}
function testTra(address _to, uint _amount) external{
transfer(_to,_amount);
}
}
contract ManagerContract {
address public myTokenAddress;
address public owner;
constructor(){
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
modifier addressIsNotEmpty() {
require(myTokenAddress != address(0), "Address is empty!");
_;
}
function setMyTokenAddress(address _myTokenAddress) public onlyOwner {
myTokenAddress = _myTokenAddress;
}
function testTra(address _to, uint _amount) public addressIsNotEmpty {
(bool success, ) = myTokenAddress.call(abi.encodeWithSignature("testTra(address,uint256)",_to,_amount));
}
}
What will happen is that, you first deploy your ManagerContract. And then you are going to deploy the TokenContract by providing the address of the ManagerContract in the constructor. After that you will call setMyTokenAddress function with the address of the TokenContract. With this way, the user now has to call testTra function in manager contract to get tokens. After that, user can normally interact with TokenContract.