Assignment - ERC20

Hi @Riki

You are missing the verification in transferFrom, double check that one and be sure to verify:

  • The allowance;
  • The balance.

Cheers,
Dani

I see, thank you! This should be good to go then.

pragma solidity 0.8.0;
import "/.SafeMath.sol";
/**
 * @title ERC20 standard token implementation.
 * @dev Standard ERC20 token. This contract follows the implementation at https://goo.gl/mLbAPJ.
 */
contract Token {
    using Safemath for uint256;
  /*
   * Token name.
   */
  string internal tokenName;

  /*
   * Token symbol.
   */
  string internal tokenSymbol;

  /*
   * Number of decimals.
   */
  uint8 internal tokenDecimals;

  /*
   * Total supply of tokens.
   */
  uint256 internal tokenTotalSupply;

  /*
   * Balance information map.
   */
  mapping (address => uint256) internal balances;

  /*
   * Token allowance mapping.
   */
  mapping (address => mapping (address => uint256)) internal allowed;

  /*
   * @dev Trigger when tokens are transferred, including zero value transfers.
   */
  event Transfer(address indexed _from,address indexed _to,uint256 _value);

  /*
   * @dev Trigger on any successful call to approve(address _spender, uint256 _value).
   */
  event Approval(address indexed _owner,address indexed _spender,uint256 _value);
  
  constructor(string memory _name, string memory _symbol, uint8 _decimals, uint _initialOwnerBalance) {
      tokenName = _name;
      tokenSymbol = _symbol;
      tokenDecimals = _decimals;
      tokenTotalSupply = _initialOwnerBalance;
      balances[msg.sender] = _initialOwnerBalance;
  }

  /*
   * @dev Returns the name of the token.
   */
  function name() external view returns (string memory _name){
    _name = tokenName;
  }

  /*
   * @dev Returns the symbol of the token.
   */
  function symbol() external view returns (string memory _symbol){
    _symbol = tokenSymbol;
  }

  /*
   * @dev Returns the number of decimals the token uses.
   */
  function decimals() external view returns (uint8 _decimals){
    _decimals = tokenDecimals;
  }

  /*
   * @dev Returns the total token supply.
   */
  function totalSupply()external view returns (uint256 _totalSupply){
    _totalSupply = tokenTotalSupply;
  }

  /*
   * @dev Returns the account balance of another account with address _owner.
   * @param _owner The address from which the balance will be retrieved.
   */
  function balanceOf(address _owner) external view returns (uint256 _balance){
    _balance = balances[_owner];
  }

  /*
   * @dev Transfers _value amount of tokens to address _to, and MUST fire the Transfer event. The
   * function SHOULD throw if the "from" account balance does not have enough tokens to spend.
   * @param _to The address of the recipient.
   * @param _value The amount of token to be transferred.
   */
  function transfer(address payable _to, uint256 _value) public returns (bool _success){
    require(balances[msg.sender] >= _value, "not enough tokens bro");
    balances[msg.sender] = balances[msg.sender].sub(_value);
    balances[_to] = balances[_to].add(_value);
    emit Transfer(msg.sender, _to, _value);
    _success = true;
  }

  /*
   * @dev Allows _spender to withdraw from your account multiple times, up to the _value amount. If
   * this function is called again it overwrites the current allowance with _value. SHOULD emit the Approval event.
   * @param _spender The address of the account able to transfer the tokens.
   * @param _value The amount of tokens to be approved for transfer.
   */
  function approve(address _spender,uint256 _value) public returns (bool _success) {
    allowed[msg.sender][_spender] = _value;
    emit Approval(msg.sender, _spender, _value);
    _success = true;
  }

  /*
   * @dev Returns the amount which _spender is still allowed to withdraw from _owner.
   * @param _owner The address of the account owning tokens.
   * @param _spender The address of the account able to transfer the tokens.
   */
  function allowance(address _owner,address _spender) external view returns (uint256 _remaining){
    _remaining = allowed[_owner][_spender];
  }

  /*
   * @dev Transfers _value amount of tokens from address _from to address _to, and MUST fire the
   * Transfer event.
   * @param _from The address of the sender.
   * @param _to The address of the recipient.
   * @param _value The amount of token to be transferred.
   */
  function transferFrom(address _from,address _to,uint256 _value) public returns (bool _success){
      require(_value <= balances[_from]);
      require(_value <= allowed[_from][msg.sender]);
      
    balances[_from] = balances[_from].sub(_value);
    balances[_to] = balances[_to].add(_value);
    allowed[_from][msg.sender] -=_value;
    emit Transfer(_from, _to, _value);
    _success = true;

  }

}
1 Like

Well done @Riki, this is correct indeed!

This is my code.

pragma solidity 0.8.0;
import "./SafeMath.sol";

/**
 * @title ERC20 standard token implementation.
 * @dev Standard ERC20 token. This contract follows the implementation at https://goo.gl/mLbAPJ.
 */
contract Token {
    
    using SafeMath for uint256;
    
  /*
   * Token name.
   */
  string internal tokenName;

  /*
   * Token symbol.
   */
  string internal tokenSymbol;

  /*
   * Number of decimals.
   */
  uint8 internal tokenDecimals;

  /*
   * Total supply of tokens.
   */
  uint256 internal tokenTotalSupply;

  /*
   * Balance information map.
   */
  mapping (address => uint256) internal balances;

  /*
   * Token allowance mapping.
   */
  mapping (address => mapping (address => uint256)) internal allowed;

  /*
   * @dev Trigger when tokens are transferred, including zero value transfers.
   */
  event Transfer(address indexed _from,address indexed _to,uint256 _value);

  /*
   * @dev Trigger on any successful call to approve(address _spender, uint256 _value).
   */
  event Approval(address indexed _owner,address indexed _spender,uint256 _value);
  
  constructor(string memory _name, string memory _symbol, uint8 _decimals, uint _initialOwnerBalance) {
      tokenName = _name;
      tokenSymbol = _symbol;
      tokenDecimals = _decimals;
      tokenTotalSupply = _initialOwnerBalance;
      balances[msg.sender] = _initialOwnerBalance;
  }

  /*
   * @dev Returns the name of the token.
   */
  function name() external view returns (string memory _name){
    _name = tokenName;
  }

  /*
   * @dev Returns the symbol of the token.
   */
  function symbol() external view returns (string memory _symbol){
    _symbol = tokenSymbol;
  }

  /*
   * @dev Returns the number of decimals the token uses.
   */
  function decimals() external view returns (uint8 _decimals){
    _decimals = tokenDecimals;
  }

  /*
   * @dev Returns the total token supply.
   */
  function totalSupply()external view returns (uint256 _totalSupply){
    _totalSupply = tokenTotalSupply;
  }

  /*
   * @dev Returns the account balance of another account with address _owner.
   * @param _owner The address from which the balance will be retrieved.
   */
  function balanceOf(address _owner) external view returns (uint256 _balance){
    _balance = balances[_owner];
  }

  /*
   * @dev Transfers _value amount of tokens to address _to, and MUST fire the Transfer event. The
   * function SHOULD throw if the "from" account balance does not have enough tokens to spend.
   * @param _to The address of the recipient.
   * @param _value The amount of token to be transferred.
   */
  function transfer(address payable _to, uint256 _value) public returns (bool _success){
    require(balances[msg.sender] >= _value, "Not enough funds");
    balances[msg.sender] = balances[msg.sender].sub(_value);
    balances[_to] = balances[_to].add(_value);
    emit Transfer(msg.sender, _to, _value);
    return true;
  }

  /*
   * @dev Allows _spender to withdraw from your account multiple times, up to the _value amount. If
   * this function is called again it overwrites the current allowance with _value. SHOULD emit the Approval event.
   * @param _spender The address of the account able to transfer the tokens.
   * @param _value The amount of tokens to be approved for transfer.
   */
  function approve(address _spender,uint256 _value) public returns (bool _success) {
    allowed[msg.sender][_spender] = _value;
    emit Approval(msg.sender, _spender, _value);
    return true;
  }

  /*
   * @dev Returns the amount which _spender is still allowed to withdraw from _owner.
   * @param _owner The address of the account owning tokens.
   * @param _spender The address of the account able to transfer the tokens.
   */
  function allowance(address _owner,address _spender) external view returns (uint256 _remaining){
    _remaining = allowed[_owner][_spender];
  }

  /*
   * @dev Transfers _value amount of tokens from address _from to address _to, and MUST fire the
   * Transfer event.
   * @param _from The address of the sender.
   * @param _to The address of the recipient.
   * @param _value The amount of token to be transferred.
   */
  function transferFrom(address _from,address _to,uint256 _value) public returns (bool _success){
    require(allowed[_from][msg.sender] >= _value, "You are not allowed to transfer funds");
    require(balances[_from] >= _value, "Not enough funds");
    allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
    balances[_from] = balances[_from].sub(_value);
    balances[_to] = balances[_to].add(_value);
    emit Transfer(_from, _to, _value);
    return true;
  }

}
1 Like

Hi @dan-i,

I am not actually sure by that. Shoudn’t be SafeMath require itself? Because SafeMath checks for underflow, so it can’t go under the 0. In other words you can’t spend more than your balance. I think you don’t need require in this case, isn’t it?

Also I have read from you earlier that in 0.8.0 operators + - : * comes with built-in under/overflow check. So basically you only need + - : * without require or SafeMath.

So where is catch? I guess fallback is not possible either, because fallback comes only with call/send/transfer? And fallback does not have enough of gas anyway in 0.8.0. So what is it? :smiley: Thanks.

Here is my solution to the erc20 assignment.

The main idea is that msg.sender is the owner when calling transfer() and approve(), and msg.sender is the spender when calling transferFrom().

pragma solidity 0.8.0;

/**
 * @title ERC20 standard token implementation.
 * @dev Standard ERC20 token. This contract follows the implementation at https://goo.gl/mLbAPJ.
 */
contract Token {
  /*
   * Token name.
   */
  string internal tokenName;

  /*
   * Token symbol.
   */
  string internal tokenSymbol;

  /*
   * Number of decimals.
   */
  uint8 internal tokenDecimals;

  /*
   * Total supply of tokens.
   */
  uint256 internal tokenTotalSupply;

  /*
   * Balance information map.
   */
  mapping (address => uint256) internal balances;

  /*
   * Token allowance mapping.
   */
  mapping (address => mapping (address => uint256)) internal allowed;

  /*
   * @dev Trigger when tokens are transferred, including zero value transfers.
   */
  event Transfer(address indexed _from,address indexed _to,uint256 _value);

  /*
   * @dev Trigger on any successful call to approve(address _spender, uint256 _value).
   */
  event Approval(address indexed _owner,address indexed _spender,uint256 _value);
  
  constructor(string memory _name, string memory _symbol, uint8 _decimals, uint _initialOwnerBalance) {
      tokenName = _name;
      tokenSymbol = _symbol;
      tokenDecimals = _decimals;
      tokenTotalSupply = _initialOwnerBalance;
      balances[msg.sender] = _initialOwnerBalance;
  }

  /*
   * @dev Returns the name of the token.
   */
  function name() external view returns (string memory _name){
    _name = tokenName;
  }

  /*
   * @dev Returns the symbol of the token.
   */
  function symbol() external view returns (string memory _symbol){
    _symbol = tokenSymbol;
  }

  /*
   * @dev Returns the number of decimals the token uses.
   */
  function decimals() external view returns (uint8 _decimals){
    _decimals = tokenDecimals;
  }

  /*
   * @dev Returns the total token supply.
   */
  function totalSupply()external view returns (uint256 _totalSupply){
    _totalSupply = tokenTotalSupply;
  }

  /*
   * @dev Returns the account balance of another account with address _owner.
   * @param _owner The address from which the balance will be retrieved.
   */
  function balanceOf(address _owner) external view returns (uint256 _balance){
    _balance = balances[_owner];
  }

  /*
   * @dev Transfers _value amount of tokens to address _to, and MUST fire the Transfer event. The
   * function SHOULD throw if the "from" account balance does not have enough tokens to spend.
   * @param _to The address of the recipient.
   * @param _value The amount of token to be transferred.
   */
  function transfer(address payable _to, uint256 _value) public returns (bool _success){
    require(balances[msg.sender] >= _value, "Value exceeds balance");
    
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    
    emit Transfer(msg.sender, _to, _value);
    
    return true;
  }

  /*
   * @dev Allows _spender to withdraw from your account multiple times, up to the _value amount. If
   * this function is called again it overwrites the current allowance with _value. SHOULD emit the Approval event.
   * @param _spender The address of the account able to transfer the tokens.
   * @param _value The amount of tokens to be approved for transfer.
   */
  function approve(address _spender,uint256 _value) public returns (bool _success) {
    allowed[msg.sender][_spender] = _value;
    
    emit Approval(msg.sender, _spender, _value);
    
    return true;
  }

  /*
   * @dev Returns the amount which _spender is still allowed to withdraw from _owner.
   * @param _owner The address of the account owning tokens.
   * @param _spender The address of the account able to transfer the tokens.
   */
  function allowance(address _owner,address _spender) external view returns (uint256 _remaining){
    _remaining = allowed[_owner][_spender];
  }

  /*
   * @dev Transfers _value amount of tokens from address _from to address _to, and MUST fire the
   * Transfer event.
   * @param _from The address of the sender.
   * @param _to The address of the recipient.
   * @param _value The amount of token to be transferred.
   */
  function transferFrom(address _from,address _to,uint256 _value) public returns (bool _success){
    require(allowed[_from][msg.sender] >= _value, "Value exceeds allowance");
    require(balances[_from] >= _value, "Value exceeds balance");
    
    balances[_from] -= _value;
    balances[_to] += _value;
    allowed[_from][msg.sender] -= _value;
    
    emit Transfer(_from, _to, _value);
    
    return true;
  }

}
1 Like
transfer
  function transfer(address payable _to, uint256 _value) public returns (bool _success){
        require(_value <= balances[msg.sender], "You do not have enough tokens to spend.");
        
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        
        emit Transfer(msg.sender, _to, _value);
        
       _success = true;
  }
approve
  function approve(address _spender,uint256 _value) public returns (bool _success) {
        allowed[msg.sender][_spender] = _value;
        
        emit Approval(msg.sender, _spender, _value);
        
        _success = true;
  }
transferFrom
  function transferFrom(address _from,address _to,uint256 _value) public returns (bool _success){
        require(_value <= balances[_from] , "The address you are sending funds from does not have enough funds.");
        require(_value <= allowed[_from][msg.sender], "You are not allowed to spend more than the allowed value.");
        
        balances[_from] = balances[_from].sub(_value);
        allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        
        emit Transfer(_from, _to, _value);
        
        _success = true;
  }
1 Like

Hey @Tomahawk

Some funny stuff for your:

pragma solidity 0.6.0;

contract Underflow {
    
    function underflow(uint _x) public view returns (uint) {
        return 1-_x;
    }
}

Run this one and use the number 2 as parameter to the function underflow :slight_smile:
Check the result.
Screenshot 2021-04-19 at 11.27.52

Also I have read from you earlier that in 0.8.0 operators + - : * comes with built-in under/overflow check. So basically you only need + - : * without require or SafeMath.

This is true IF you are using sol 0.8.
You will not always be able to use that solidity version, for example in case you are implementing an oracle contract the has been wrote with an older version of Soldity.
This is why it’s important to learn how safemath works.

Happy learning,
Dani

1 Like

@dan-i

That is good to know, thanks. I didn’t realize you would sometimes actually need an older version

Well okay, I understand SafeMath is needed in pragma < 0.8. However if you use SafeMath in <0.8 or normal operators in >=0.8, what about my second question? Do you really need require? Because you can’t underflow, so you can’t spend more funds than your balance, therefore you shoudn’t need ā€œrequireā€ statement.

1 Like
pragma solidity 0.8.0;

/**
 * @title ERC20 standard token implementation.
 * @dev Standard ERC20 token. This contract follows the implementation at https://goo.gl/mLbAPJ.
 */
contract Token {
  /*
   * Token name.
   */
  string internal tokenName;

  /*
   * Token symbol.
   */
  string internal tokenSymbol;

  /*
   * Number of decimals.
   */
  uint8 internal tokenDecimals;

  /*
   * Total supply of tokens.
   */
  uint256 internal tokenTotalSupply;

  /*
   * Balance information map.
   */
  mapping (address => uint256) internal balances;

  /*
   * Token allowance mapping.
   */
  mapping (address => mapping (address => uint256)) internal allowed;

  /*
   * @dev Trigger when tokens are transferred, including zero value transfers.
   */
  event Transfer(address indexed _from,address indexed _to,uint256 _value);

  /*
   * @dev Trigger on any successful call to approve(address _spender, uint256 _value).
   */
  event Approval(address indexed _owner,address indexed _spender,uint256 _value);
  
  constructor(string memory _name, string memory _symbol, uint8 _decimals, uint _initialOwnerBalance) {
      tokenName = _name;
      tokenSymbol = _symbol;
      tokenDecimals = _decimals;
      tokenTotalSupply = _initialOwnerBalance;
      balances[msg.sender] = _initialOwnerBalance;
  }

  /*
   * @dev Returns the name of the token.
   */
  function name() external view returns (string memory _name){
    _name = tokenName;
  }

  /*
   * @dev Returns the symbol of the token.
   */
  function symbol() external view returns (string memory _symbol){
    _symbol = tokenSymbol;
  }

  /*
   * @dev Returns the number of decimals the token uses.
   */
  function decimals() external view returns (uint8 _decimals){
    _decimals = tokenDecimals;
  }

  /*
   * @dev Returns the total token supply.
   */
  function totalSupply()external view returns (uint256 _totalSupply){
    _totalSupply = tokenTotalSupply;
  }

  /*
   * @dev Returns the account balance of another account with address _owner.
   * @param _owner The address from which the balance will be retrieved.
   */
  function balanceOf(address _owner) external view returns (uint256 _balance){
    _balance = balances[_owner];
  }

  /*
   * @dev Transfers _value amount of tokens to address _to, and MUST fire the Transfer event. The
   * function SHOULD throw if the "from" account balance does not have enough tokens to spend.
   * @param _to The address of the recipient.
   * @param _value The amount of token to be transferred.
   */
  function transfer(address payable _to, uint256 _value) public returns (bool _success){
    //checks if the sender of the transfer has enough funds.
    require( balances[msg.sender] >= _value, "Not enough funds!");
    
    //make the transaction happen.
    balances[msg.sender] = balances[msg.sender] - _value;
    balances[_to] = balances[_to] + _value;
    
    //emit the event.
    emit Transfer(msg.sender, _to, _value);
    
    //return true to end the function.
    return true;
  }

  /*
   * @dev Allows _spender to withdraw from your account multiple times, up to the _value amount. If
   * this function is called again it overwrites the current allowance with _value. SHOULD emit the Approval event.
   * @param _spender The address of the account able to transfer the tokens.
   * @param _value The amount of tokens to be approved for transfer.
   */
  function approve(address _spender,uint256 _value) public returns (bool _success) {
      
    //set the person that msg.sender wants to approve for this function.
    allowed[msg.sender][_spender] = _value;
    
    //emit the event.
    emit Approval(msg.sender, _spender, _value);
    
    //return true to end the funciton. 
    return true;
  }

  /*
   * @dev Returns the amount which _spender is still allowed to withdraw from _owner.
   * @param _owner The address of the account owning tokens.
   * @param _spender The address of the account able to transfer the tokens.
   */
  function allowance(address _owner,address _spender) external view returns (uint256 _remaining){
    _remaining = allowed[_owner][_spender];
  }

  /*
   * @dev Transfers _value amount of tokens from address _from to address _to, and MUST fire the
   * Transfer event.
   * @param _from The address of the sender.
   * @param _to The address of the recipient.
   * @param _value The amount of token to be transferred.
   */
  function transferFrom(address _from,address _to,uint256 _value) public returns (bool _success){
    //check if there are enough funds in the from balance.
    require(_value >= balances[_from], "Not enough funds!");
    
    //check if the caller is allowed to spend the amount
    require(_value >= allowed[_from][msg.sender], "Not enough alowed funds!");
    
    //remove the spent amount from the allowance 
    allowed[_from][msg.sender] = allowed[_from][msg.sender] - _value;
    
    //move the balance from sender to receiver
    balances[_from] = balances[_from] - _value;
    balances[_to] = balances[_to] + _value;
    
    //emit the transfer
    emit Transfer(_from, _to, _value);
    
    //return true to finish the funciton.
    return true;
  }

}

When i try to deploy this contract i get: creation of Token errored: Error encoding arguments: Error: invalid BigNumber string (argument="value", value="", code=INVALID_ARGUMENT, version=bignumber/5.0.8) I’ve tried to search it but can’t find any good solution.
Even when I copied the solution from Philip into my remix it still gives this error. I also refreshed the remix site but that also didn’t help.

Hi @Tomahawk

Do you really need require? Because you can’t underflow, so you can’t spend more funds than your balance, therefore you shoudn’t need ā€œrequireā€ statement.

I highly suggest you the Smart Contract Security course as soon as you’ve finished this one.
One of the most famous hacks in Solidity has been made by overflowing a uint.

Because you can’t underflow, so you can’t spend more funds than your balance

This is not the point unfortunately.
It is true that I cannot send more than my balance, however keep in mind that ā€˜my balance’ is saved in a mapping 99.99% of the times.

mapping(address => uint) balances;

This is where SafeMath is fundamental.

If you are using solidity < 0.8 without SafeMath, your contract will surely be hacked :slight_smile:

Nice questions anyway, take the Security course I am sure you will love it.

Cheers,
Dani

Hi @jelle_van_der_meer

When i try to deploy this contract i get: creation of Token errored: Error encoding arguments: Error: invalid BigNumber string (argument="value", value="", code=INVALID_ARGUMENT, version=bignumber/5.0.8)

Can you please post your migration file where this error is generated?

Thanks
Dani

1 Like

Sorry :sweat_smile: buuut if you use SafeMath you can’t overflow as well, so I still can’t see why require statement is needed. Hope I will learn that in Security course then.

The issue is not in the transfer function, is in the mappings/ variable that hold the user balance.
Will be all clear once you take the course no worries, we will discuss then if you want :slight_smile:

Happy learning and let me know if you need help!

1 Like

I would love to do that but I can’t find how to upload migration files in this forum.
I was trying something out but now I have deleted all my workspaces i thought it maybe would help(i downloaded them so no worry there) so I’ll first figure out how to set it all back :sweat_smile:.

Hey @jelle_van_der_meer

No need to upload a file, check this faq :slight_smile:

FAQ - How to post code in the forum

Cheers,
Dani

1 Like

I found it! My stupid ass head couldn’t figure out that I needed to put the values first before deploying the contract. Learned something new that you get that error when you don’t put any values in hence the argument="value", value=""

thank you for your time have a nice day !:grin:

1 Like

how does one tweak the test fixture so that the test cases are not dependant on order and that everything is reset between each test case?

doing this but its not right

contract("Dex - Market Orders", (accounts) => {
  let dex;
  let link;

  beforeEach(async function () {
    dex = await Dex.deployed();
    link = await Link.deployed();

    await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {
      from: accounts[0],
    });
  });

my mistake… forgot an await in some of my code :frowning:

1 Like

if account[0] wants to BUY Link… then why do I need to approve it for link? surely it only applied to spending the link ? @filip

await link.approve(dex.address,50,{from : accounts[0]})