I have two contracts, A and B. Contract A defines my token. I want to transfer some of these tokens to Contract B.
If I understood correctly:
First, contract A should have some tokens to be able to transfer some tokens to B.
What I've done to achieve this, is setting initialSupply
for contract A in the constructor. Does this solve the problem?
Inside B contract, I have to call transfer
function of contract A. But this returns the error that: sender doesn't have enough token
. My assumption is that, sender
is contract A
, am I correct? If so, then it seems initialSupply
didn't assign some tokens to A, why and how to fix it?
Here is contract A, which defines my token:
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.28;
contract token {
string public tokenName = "ABC Coin";
string public tokenSymbol = "$abc";
uint8 public decimals = 18;
uint256 public totalSupply = 2000e27;
uint256 public initialSupply = 1000e27;
event Transfer(address indexed from, address indexed to, uint256 value);
mapping(address => uint256) public balances;
mapping(address => mapping(address => uint256)) public allowed;
constructor() {
balances[msg.sender] = initialSupply; // must change as Petr said
}
function transferFrom(address from, address to, uint256 value)
public returns (bool success)
{
require(value <= balances[from],
"Oops! Sender does not have enough token!");
require(value <= allowed[from][msg.sender],
"Oops! Sender is not allowed to send this amount of tokens!");
allowed[from][msg.sender] -= value;
balances[from] -= value;
balances[to] += value;
emit Transfer(from, to, value);
return true;
}
function approve(address spender, uint256 amount)
public returns (bool success)
{
require(msg.sender != address(0),
"Approve from the zero address");
require(msg.sender != address(0),
"Approve to the zero address");
allowed[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
}
And this is contract B:
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.28;
...
contract BContract is Pausable, Ownable(msg.sender) {
using SafeERC20 for IERC20;
IERC20 public immutable ABCToken;
mapping(address => uint256) public balances;
constructor() {
address _token = 0x4..8;
ABCToken = IERC20(_token);
}
function transferTokenFromAtoB(address to, uint amount) public {
ABCToken.transferFrom(address(this), to, amount);
}
function getSmartContractBalance() external view returns(uint) {
return ABCToken.balanceOf(address(this));
}
}
And here is my frontend code:
let AAddress = "0xf...d";//got it by: npx hardhat run deploy_A.js
let BAddress = "0x4...E";
const AContract = new ethers.Contract(AAddress, AAbi, signer);
try {
console.log("A approve: ", await AContract['approve'](BAddress, 100));
} catch (error) {
console.error(error);
}
const BContract = new ethers.Contract(BAddress, BAbi, signer);
try {
console.log("Initial balance: ",
await BContract['getSmartContractBalance']()); // logs 0n
console.log("Transfer some Token: ",
await BContract['transferTokenFromAtoB'](BAddress, 100)); // error
console.log("Current balance: ",
await BContract['getSmartContractBalance']());
} catch (error) {
console.error(error);
}
As mentioned above, when in frontend I call transferTokenFromAtoB
I get the error Oops! Sender does not have enough token to spend!
Contract A in your current code mints tokens to the deployer - not to the contract address itself.
constructor() {
// msg.sender is the deployer address
balances[msg.sender] = initialSupply;
}
You can use address(this)
to mint tokens to itself
constructor() {
// address(this) is the contract address
balances[address(this)] = initialSupply;
}
---
Contract B in your current code transfers tokens from the Contract B address.
function transferTokenFromAtoB(address to, uint amount) public {
// Contract B implicitly says "transfer `amount` of tokens that belong to me to the `to` recipient"
ABCToken.transfer(to, amount);
}
If you want the function transferTokenFromAtoB()
to effectively transfer A's tokens:
A gives approval to B to spend A's tokens = A calls A
.approve(B, amount)
B calls A
.transferFrom
(B, to, amount)
instead of the current A
.transfer()
Your ABCToken
is missing the standardized approve()
and transferFrom()
functions. You can find their example implementation here: https://www.cyfrin.io/glossary/erc-20-solidity-code-example or https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol
---
Note: Even though the Transfer
event is not required when minting new tokens (the ERC-20 standard uses the wording "SHOULD" and not "MUST", making it only recommended), it's generally good practice. Etherscan and other blockchain explorers use the Transfer
event to track the transfer history, number of holders, and other parameters that would show incorrect/incomplete data for your token if you don't use the Transfer
event for minting.