4

I would like to create a smart contract that will take care of locking and/or vesting funds to several beneficiaries (inputted by the owner of the smart contract). The funds that it will vest and lock needs to come from another token smart contract. Is that even possible? My question is how do you get funds from a ERC20 token smart contract to my vesting/locking smart contract so that It can then manipulate the funds according to some set rules.

Thanks a lot <3

1 Answer 1

8

Yes, you can do it. It will be too involved to show a complete implementation, so I will just assume you know your way around an ERC20 contract.

Generally, when an ERC20 is deployed, the initial supply is "minted" to the deployer. The next step is to transfer some tokens to a time-lock contract. The time-lock contract can simply hold balances for specific addresses until a certain time or block number has passed, then permit the claimant addresses to withdraw.

This is a really quick and untested scribble to give you some ideas. Starting with the ERC20 interface by openzeppelin: https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/IERC20.sol

pragma solidity ^0.4.24;

/**
 * @title ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/20
 */
interface IERC20 {
  function totalSupply() external view returns (uint256);

  function balanceOf(address who) external view returns (uint256);

  function allowance(address owner, address spender)
    external view returns (uint256);

  function transfer(address to, uint256 value) external returns (bool);

  function approve(address spender, uint256 value)
    external returns (bool);

  function transferFrom(address from, address to, uint256 value)
    external returns (bool);

  event Transfer(
    address indexed from,
    address indexed to,
    uint256 value
  );

  event Approval(
    address indexed owner,
    address indexed spender,
    uint256 value
  );
}

Then, carry on with the time-lock contract, using the ERC20 interface defined above.

We'll make numbered lockBoxes. Users are required to specify the number of the box containing their funds. This is easily discovered by looking at event logs. Numbered boxes mean the contract can easily address multiple tranches for a single address, with different amounts and deadlines.

In case you haven't seen it before, the deposit function uses transferFrom. This presumes that the user first signs an approve authorizing this contract to help itself to a certain amount of funds. That's usually coordinated from the client-side. It goes, client sends approve(addressTimeLock, uintAmount) to the token contract, then deposit(addressBeneficiary, uintAmount, uintDeadline) to the timeLock contract.

contract TimeLock {
    IERC20 token;

    struct LockBoxStruct {
        address beneficiary;
        uint balance;
        uint releaseTime;
    }

    LockBoxStruct[] public lockBoxStructs; // This could be a mapping by address, but these numbered lockBoxes support possibility of multiple tranches per address

    event LogLockBoxDeposit(address sender, uint amount, uint releaseTime);   
    event LogLockBoxWithdrawal(address receiver, uint amount);

    constructor(address tokenContract) public {
        token = IERC20(tokenContract);
    }

    function deposit(address beneficiary, uint amount, uint releaseTime) public returns(bool success) {
        require(token.transferFrom(msg.sender, address(this), amount));
        LockBoxStruct memory l;
        l.beneficiary = beneficiary;
        l.balance = amount;
        l.releaseTime = releaseTime;
        lockBoxStructs.push(l);
        emit LogLockBoxDeposit(msg.sender, amount, releaseTime);
        return true;
    }

    function withdraw(uint lockBoxNumber) public returns(bool success) {
        LockBoxStruct storage l = lockBoxStructs[lockBoxNumber];
        require(l.beneficiary == msg.sender);
        require(l.releaseTime <= now);
        uint amount = l.balance;
        l.balance = 0;
        emit LogLockBoxWithdrawal(msg.sender, amount);
        require(token.transfer(msg.sender, amount));
        return true;
    }    

}

There is no notion of an owner and no way to recover tokens in the case that the beneficiary is unable to sign a transaction when it's time to claim tokens deposited in this contract. Input validations could be strenthened but I wanted to keep it as short as possible for clarity.

Hope it helps.

6
  • That is very very helpful thank you @rob. So the logic of time lock doesn't have to live on the token smart contract right? In my case the token smart contract is already deployed so vesting and timelocking contract logic needs to live on it's own.
    – Cyzanfar
    Nov 11, 2018 at 19:56
  • 1
    I would argue that the logic of the lockup shouldn't live in the token in the same way that the logic of a dollar is separate from the logic of a time-locked safe. Having said that, I'm aware of proposals that mingle the two concerns, particularly in the realm of security token issuance. You might look at ERC1400 tranches, for example. Nov 11, 2018 at 20:06
  • 1
    That is just amazing. Thank you 🙌. One thing I noticed tho in the deposit function: You are calling tranferFrom the beneficiary to the smart contract meaning the beneficiary must already hold the tokens to be locked correct?
    – Cyzanfar
    Nov 11, 2018 at 20:08
  • You know what? That's a mistake. I forgot the issuer would have them all to start with and the deployment "ceremony" would include depositing the vested tokens into locked boxes. I'll fix it so this is a better reference for someone else who happens along. Nov 11, 2018 at 21:01
  • For sure thanks a lot. So you changed to msg.sender which could be the token smart contract address that holds all the token right?
    – Cyzanfar
    Nov 11, 2018 at 21:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.