Withdraw function issue USDT?

Hi,
I'm hoping someone can point me in the right direction. Im very new at this and very eager to learn. I am having trouble writing a Withdraw function for a contract that Deposits/ Withdraws USDT. I am using SafeERC20, but i think im getting confused on the approval process. I've read that SafeApprove should be replaced with SafeIncrease, but then again, i think im getting confused with approvals for the contract all together. Anyway here is my code I included the deposit feature that works as well.

function adminDeposit(uint256 _value) external _onlyOwner{        /// WORKED
    require(_value > 0, "Deposit amount must be greater than 0");
    uint256 allowance =  IERC20(token).allowance(msg.sender, address(this));
                                                ///Token Owner, Token Spender
    require(allowance >= _value, "Do not have enough allowance");
    uint256 prevBalance = balances[address(this)];
    bool successful = _transferFrom(msg.sender, address(this), _value);
    require(successful, "Transfer Failed");   
    balances[address(this)] = balances[address(this)].add(_value);
    emit AdminDeposit(address(this), _value);
    } 

function _transferFrom(address _from, address _to, uint _value) private returns(bool){
      token.safeTransferFrom(_from, _to, _value);
      return true;

}

function adminWithdraw(uint256 _value) external _onlyOwner{ 
    require(msg.sender != address(0), "Zero Address not permitted"); 
    uint256 prevBalance = balances[address(this)];
    
    uint256 allowance =  IERC20(token).allowance(address(this), msg.sender);

    if (allowance < _value){
    token.safeIncreaseAllowance(owner, _value); /// wallet / value
    }

    bool success = _transferFrom(address(this), owner, _value);
    require(success, "Transfer Failed");

    balances[address(this)] = balances[address(this)].sub(_value);
    uint256 currentBalance = balances[address(this)];
    require(prevBalance - currentBalance == _value, "Tx Failed");
  
    emit AdminWithdraw(owner, _value); }

this code gives me a low Level error when trying to withdraw.
execution reverted: SafeERC20: low-level call failed"

again, im new at this and i might be overcomplicating it for sure. Any direction would be very appreciated.

thanks,
John Coffee.

Dear, @John_Coffee
Could you share your whole contract source?

This seems to be some sort of hybrid between "a contract which Deposits/ Withdraws some ERC20" (as described in the question), and a contract which actually implements an ERC20.

In order to implement a contract which accepts deposits and allows withdrawals, you don't need most of what's written here.

Specifically, you don't need that balances mapping, which is used as the ledger of an ERC20 contract (since you're not looking to implement such contract).

Your two functions should be as simple as:

function adminDeposit(uint256 _value) external _onlyOwner{
    token.safeTransferFrom(msg.sender, address(this), _value);
}

function adminWithdraw(uint256 _value) external _onlyOwner{
    token.safeTransfer(msg.sender, _value);
}

Before calling adminDeposit, the admin will need to approve this contract of course.

2 Likes

@ barakman Interesting! Thank you for responding. I really appreciate it. Here's another question if you don't mind.

If i wanted to make a public withdrawal, does the approve function play a role or would it simply look like this (based off your response)

function withdraw() external  {
    require(balances[msg.sender] > 0, "Sender has no funds to withdraw"); 
    require(balances[address(this)] > 0, "Contract has Insufficient funds");

    address holder = msg.sender; 
    uint256 withdrawAmount = balances[holder];

    balances[holder] = 0;
     token.safeTransfer(holder, withdrawAmount);
    
    balances[address(this)] = balances[address(this)].sub(withdrawAmount);
    emit Withdrawal(holder, withdrawAmount);
}

@ warrior_dragon Full Test Contract. The purpose of this contract is to Deposit USDT(ERC20) then the balance divided into NFT Holders equally. They can then withdraw their own share when they are ready. I have an admin desposit and withdraw function in here that does not get distributed, rather - its suppose to work as it does. Again im new at this and thought this would be a great start - my code might look very clunky. :grimacing:


contract TestUSDT {   

using SafeERC20 for IERC20;
using SafeMath for uint256;

address public owner;
IERC20 public token;
IERC721 public nft;
bool public withdrawalIsEnable;
bool public withdrawalInProgress;

mapping(address => uint256) public balances;

modifier _onlyOwner{
    require(msg.sender == owner, "Not Owner");
    _;
}

modifier _withdrawalIsEnabled {
    require(withdrawalIsEnable == true, "Withdrawal temp paused");
    _;
}

event AdminWithdraw(address indexed _to, uint256 _amount);
event AdminDeposit(address indexed _to, uint256 _amount);
event Deposit(address indexed _to, uint256 _amount);
event Approval(address indexed owner, address indexed spender, uint256 value);
event NFTAddressUpdate(address _newAddress);
event TokenAddressUpdate(address _newAddress);
event Withdrawal(address indexed _to, uint256 _amount);


constructor() public { 
    owner = msg.sender;
    token = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7);  ///  Tether
    nft = IERC721(0x0EF343f73DbBFA84a1d6D63c369084859EB858b5); /// NFT

    withdrawalIsEnable = true;
}


function _transferFrom(address _from, address _to, uint _value) private returns(bool){
      token.safeTransferFrom(_from, _to, _value);
      return true;

}
//// ADMIN Desposit

function adminDeposit(uint256 _value) external _onlyOwner{        
    require(_value > 0, "Deposit amount must be greater than 0");

    uint256 allowance =  IERC20(token).allowance(msg.sender, address(this));
                                                
    require(allowance >= _value, "Do not have enough allowance");

    uint256 prevBalance = balances[address(this)];
    bool successful = _transferFrom(msg.sender, address(this), _value);

    require(successful, "Transfer Failed");   
    balances[address(this)] = balances[address(this)].add(_value);

    emit AdminDeposit(address(this), _value);
    } 


function adminWithdraw(uint256 _value) external _onlyOwner{ 

    uint256 prevBalance = balances[address(this)];   

    bool success = _transferFrom(address(this), owner, _value);
    require(success, "Transfer Failed");

    balances[address(this)] = balances[address(this)].sub(_value);
    uint256 currentBalance = balances[address(this)];
    require(prevBalance - currentBalance == _value, "Tx Failed");
  
    emit AdminWithdraw(owner, _value);
}

////////////////////////////


function DepNDis(uint256 _value) external _onlyOwner{    
    require(_value > 0, "Deposit amount must be greater than 0");

    uint256 allowance =  IERC20(token).allowance(msg.sender, address(this));                                               
    require(allowance >= _value, "Do not have enough allowance");

    uint256 prevBalance = balances[address(this)];
    token.safeTransferFrom(msg.sender, address(this), _value);

    balances[address(this)] = balances[address(this)].add(_value);

     uint256 totalSupply = IERC721(nft).totalSupply();
    uint256 distributionAmount = _value.div(totalSupply);

    for(uint256 i=0; i < totalSupply; ++i){
    address holder = IERC721(nft).ownerOf(i+1);
    balances[holder] = balances[holder].add(distributionAmount);

    }

    emit Deposit(address(this), _value);
    } 


function withdraw() external _withdrawalIsEnabled {
    require(balances[msg.sender] > 0, "Sender has no funds to withdraw"); 
    require(balances[address(this)] > 0, "Contract has Insufficient funds");
    require(!withdrawalInProgress,  "Another Withdrawal is in progress");
    
    withdrawalInProgress = true;  

    address holder = msg.sender; 
    uint256 withdrawAmount = balances[holder];

    balances[holder] = 0;
    token.safeTransferFrom(address(this), holder, withdrawAmount);
    
    balances[address(this)] = balances[address(this)].sub(withdrawAmount);
    emit Withdrawal(holder, withdrawAmount);
    withdrawalInProgress = false;
}

///////////////////////////////


function contractBalance() external view returns(uint256){
    return balances[address(this)];
}

function balanceOf(address _holder) external view returns(uint256){
    return balances[_holder];
}


function updateNFTAddress(address _newAddress) external _onlyOwner{
     nft = IERC721(_newAddress);
     emit NFTAddressUpdate(_newAddress);
}


function updateTokenAddress(address _newAddress) external _onlyOwner{
    token = IERC20(_newAddress);
    emit TokenAddressUpdate(_newAddress);
}


function totalSupply() external view returns(uint256){
    return IERC721(nft).totalSupply();
}

function nftHolderOf(uint256 _i) external view returns(address){
    return IERC721(nft).ownerOf(_i);
}


function num_of_NFTs(address _holder) external view returns(uint256){
    return IERC721(nft).balanceOf(_holder);
}


}  /// contract end

It seems fine, assuming that upon deposit (or any other function), you always do this:

token.safeTransferFrom(msg.sender, address(this), depositAmount);

Before you do this:

balances[msg.sender] += depositAmount;

Aside from that, your balances[address(this)] management seems completely redundant.

1 Like

@barakman So when would you call an approve function?

I want to get this right in my head or else I wont really learn...

I launch this contract, then i go over to USDT and approve the owner to let the contract move USDT over.
that i have understand..

But when it comes to anyone else interacting with the contract i have 2 questions -

    1. do they need to run an approve or is it because you suggest using safeTransfer, the contract already has the approval to move its own tokens around?
  • 2 I have read that the SafeApprove is depreciating. Instead of SafeApprove, would you just replace the SafeApprove with SafeIncreaseAllowance? There would be no use to call an approve?

Totally appreciate you helping out. Thank you.

With regards to the SafeERC20 library, see my answer to a different question.

Other than that, the rules of engaging with an ERC20-Token contract are pretty simple:

  1. An entity can transfer its own tokens using function transfer (or safeTransfer)
  2. An entity can transfer the tokens of another entity using function transferFrom (or safeTransferFrom), but only after the latter has approved the former to do so, using function approve (or safeApprove)

An entity can be either one of the following:

  • An EOA (externally-owned account, aka wallet)
  • An SCA (smart-contract account, aka contract)

Every entity is associated with an Ethereum address of course.

1 Like

Thank you. this worked well. I appreciate your direction and help on this. I understand it a bit better after trying and reading some more.

1 Like