TrancheEscrow

0xcf6a256e504a5a62717a2d4379894143b1083450

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
4,614 bytes
ABI entries
36
8 read · 7 write
License
none

Contract information

Address
0xcf6a256e504a5a62717a2d4379894143b1083450
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x3578ea6545…4882cf17cc

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (8)

SWEEP_DELAY()uint256
factory()address
getProgram(address)tuple
keeper()address
owner()address
pendingOwner()address
programs(address)uint64
renounceOwnership()

Events (7)

FactorySetKeeperChangedOwnershipTransferStartedOwnershipTransferredRegisteredSweptTrancheDistributed

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "_keeper",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "AlreadyRegistered",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "CapExceeded",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "FactoryAlreadySet",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "LengthMismatch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotFactory",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotKeeper",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotRegistered",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "RenounceDisabled",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SweepTooEarly",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TrancheLocked",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "factory",
        "type": "address"
      }
    ],
    "name": "FactorySet",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "newKeeper",
        "type": "address"
      }
    ],
    "name": "KeeperChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferStarted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint64",
        "name": "launchTime",
        "type": "uint64"
      },
      {
        "indexed": false,
        "internalType": "uint128[4]",
        "name": "caps",
        "type": "uint128[4]"
      },
      {
        "indexed": false,
        "internalType": "uint32[4]",
        "name": "delays",
        "type": "uint32[4]"
      }
    ],
    "name": "Registered",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "Swept",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint8",
        "name": "trancheId",
        "type": "uint8"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "TrancheDistributed",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "SWEEP_DELAY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "acceptOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint8",
        "name": "trancheId",
        "type": "uint8"
      },
      {
        "internalType": "address[]",
        "name": "to",
        "type": "address[]"
      },
      {
        "internalType": "uint256[]",
        "name": "amounts",
        "type": "uint256[]"
      }
    ],
    "name": "distributeTranche",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "factory",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "getProgram",
    "outputs": [
      {
        "components": [
          {
            "internalType": "uint64",
            "name": "launchTime",
            "type": "uint64"
          },
          {
            "internalType": "uint128[4]",
            "name": "caps",
            "type": "uint128[4]"
          },
          {
            "internalType": "uint128[4]",
            "name": "spent",
            "type": "uint128[4]"
          },
          {
            "internalType": "uint32[4]",
            "name": "delays",
            "type": "uint32[4]"
          }
        ],
        "internalType": "struct TrancheEscrow.Program",
        "name": "",
        "type": "tuple"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "keeper",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pendingOwner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "programs",
    "outputs": [
      {
        "internalType": "uint64",
        "name": "launchTime",
        "type": "uint64"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint64",
        "name": "launchTime",
        "type": "uint64"
      },
      {
        "internalType": "uint128[4]",
        "name": "caps",
        "type": "uint128[4]"
      },
      {
        "internalType": "uint32[4]",
        "name": "delays",
        "type": "uint32[4]"
      }
    ],
    "name": "register",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "f",
        "type": "address"
      }
    ],
    "name": "setFactory",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "a",
        "type": "address"
      }
    ],
    "name": "setKeeper",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "sweepExpired",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IERC20 as IERC20OZ} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract TrancheEscrow is Ownable2Step {
    using SafeERC20 for IERC20OZ;

    uint256 public constant SWEEP_DELAY = 30 days;

    address public factory;
    address public keeper;

    struct Program {
        uint64 launchTime;
        uint128[4] caps;
        uint128[4] spent;
        uint32[4] delays;
    }
    mapping(address => Program) public programs;

    event Registered(address indexed token, uint64 launchTime, uint128[4] caps, uint32[4] delays);
    event TrancheDistributed(address indexed token, uint8 indexed trancheId, address indexed to, uint256 amount);
    event Swept(address indexed token, address to, uint256 amount);
    event KeeperChanged(address indexed newKeeper);
    event FactorySet(address indexed factory);

    error NotFactory();
    error NotKeeper();
    error FactoryAlreadySet();
    error AlreadyRegistered();
    error NotRegistered();
    error TrancheLocked();
    error CapExceeded();
    error LengthMismatch();
    error SweepTooEarly();
    error RenounceDisabled();

    constructor(address _keeper) Ownable(msg.sender) {
        keeper = _keeper;
        emit KeeperChanged(_keeper);
    }

    function setFactory(address f) external onlyOwner {
        if (factory != address(0)) revert FactoryAlreadySet();
        factory = f;
        emit FactorySet(f);
    }

    function setKeeper(address a) external onlyOwner {
        keeper = a;
        emit KeeperChanged(a);
    }

    function getProgram(address token) external view returns (Program memory) {
        return programs[token];
    }

    function register(address token, uint64 launchTime, uint128[4] calldata caps, uint32[4] calldata delays)
        external
    {
        if (msg.sender != factory) revert NotFactory();
        if (programs[token].launchTime != 0) revert AlreadyRegistered();
        programs[token] = Program(launchTime, caps, [uint128(0), 0, 0, 0], delays);
        emit Registered(token, launchTime, caps, delays);
    }

    function distributeTranche(address token, uint8 trancheId, address[] calldata to, uint256[] calldata amounts)
        external
    {
        if (msg.sender != keeper) revert NotKeeper();
        if (to.length != amounts.length) revert LengthMismatch();
        Program storage p = programs[token];
        if (p.launchTime == 0) revert NotRegistered();
        if (block.timestamp < uint256(p.launchTime) + p.delays[trancheId]) revert TrancheLocked();
        uint256 total;
        for (uint256 i; i < amounts.length; ++i) total += amounts[i];
        if (uint256(p.spent[trancheId]) + total > p.caps[trancheId]) revert CapExceeded();
        p.spent[trancheId] += uint128(total);
        for (uint256 i; i < to.length; ++i) {
            IERC20OZ(token).safeTransfer(to[i], amounts[i]);
            emit TrancheDistributed(token, trancheId, to[i], amounts[i]);
        }
    }

    function sweepExpired(address token, address to) external onlyOwner {
        Program storage p = programs[token];
        if (p.launchTime == 0) revert NotRegistered();
        if (block.timestamp < uint256(p.launchTime) + SWEEP_DELAY) revert SweepTooEarly();
        uint256 bal = IERC20OZ(token).balanceOf(address(this));
        IERC20OZ(token).safeTransfer(to, bal);
        emit Swept(token, to, bal);
    }

    function renounceOwnership() public view override onlyOwner {
        revert RenounceDisabled();
    }
}
Chain explorer3635msChain nodeok