How to request tokens from msg.sender within functions

transferring ethereum from the user of your smart contract is simple enough, you simply use msg.value and can use this within your contract’s functions. But doing the same thing with tokens is a bit more tricky consider the following contract

pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "../node_modules/zeppelin-solidity/contracts/math/SafeMath.sol";
import "../node_modules/zeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "../node_modules/zeppelin-solidity/contracts/token/ERC20/SafeERC20.sol";
contract TokenExchange{
  enum State {OPEN, FILLED, CLOSED, CANCELED}
  struct Order {
    uint id;
    State state;
    address buyer;
    address seller;
    uint antAmmount;
    uint daiAmmount;
  }
  ERC20 public token1;
  ERC20 public token2;
  mapping (uint => Order) public globalOrders;      
  uint private nextOrderId;
  constructor () public {
	  token1 = ERC20(0x1111111111111111111111111111111111111111); 
	  token2 = ERC20(0x2222222222222222222222222222222222222222); 
  }
  function createBuyOrder(uint _buyAmmount, uint _sellAmmount) external payable {
    uint deposit = _buyAmmount.div(2);    

    // How to transfer tokens from message sender to this contract
  
    Order memory newOrder = Order({
			id: 0, 
			state: State.OPEN, 
			buyer: msg.sender, 
			seller: 0x0000000000000000000000000000000000000000, 
			token1Ammount: _buyAmmount, 
			token2Ammount: _sellAmmount 
		});
    globalOrders[nextBuyOrderId] = newOrder;
    nextBuyOrderId++;
  }
}

if we were dealing with eth we could simply require msg.sender to equal the deposit vaue. Also we cant use token.aprove() and transferFrom() within the function as this would be aproving the contracts balance and not msg.sender

so my question is this:
how do I request the message sender to send the deposit to the contract and how do I test if the message sender has sent the required amount?

Interesting question!

You could either:

  1. transfer tokens from msg.sender to contract address in the createBuyOrder() function
  2. transfer tokens from msg.sender to contract BEFORE calling createBuyOrder() function, in another deposit() function
  3. call the approve() function of the token BEFORE calling createBuyOrder() function

I would recommend doing 2.

For this you need to:

  • import the interface of ERC20 token into contract (IERC20 in openzeppelin, using import keyword)
  • import the address of the ERC20 token into contract (in constructor for example, assign an address variable with the correct value)
  • call the transfer() function on the token using this notation:
IERC20Token(tokenAddress).deposit(/* arguments here */);
1 Like

If I’m understanding correctly I create another function and within that

  1. call deposit() which contains the logic to transfer tokens from msg.sender using the transfer() function of ierc20?
  2. call createBuyOrder()?

when calling transfer() within deposit(), wouldn’t the msg.sender be the contract it’s self and not the caller of the contract?

also, I don’t understand this line of code. deposit() isn’t a member of ierc20

EDIT: so my main confusion was I didn’t realise approve() needs to be called on the token contract outside my contract all together. If the user has not called this first. I’m assuming the checks for this are on the UI level?

Sorry Aaron, I was confused in my original answer. Here is the correct one.

(I am assuming you trade only 1 asset in your contract…otherwise you need to generalize my solution, see the contract of my DEX course here)

Step 1: User approve the amount of tokens to be sent to DEX
This is done between frontend and erc20 token, by calling the approve() function.

Step 2: User transfer tokens to DEX contract.
This is done by calling a deposit() function on the DEX, which itself calls the transferFrom() function of the erc20 token. This will work because we called approve() just before with the address of the DEX as argument.

function deposit(uint amount) external {
    IERC20(contractAddr).transferFrom(msg.sender, address(this), amount);
    balances[msg.sender] += amount;
  }

Step 3: User creates order in DEX
This is where you call your createBuyOrder() function. It does not manipulate tokens. It just creates an entry in your contract that user with such address created an order at a certain price, for a certain quantity of tokens.

Note that Step 1 and Step 2 can be done with a single user action. I.e in the event handler of a Deposit button, you would first await the transaction to approve() token transfer, then you await the transaction to deposit() token to DEX.