ETH Price: $2,447.10 (+6.06%)

Transaction Decoder

Block:
12935413 at Jul-31-2021 08:30:44 PM +UTC
Transaction Fee:
0.001821747081 ETH $4.46
Gas Used:
42,173 Gas / 43.197 Gwei

Emitted Events:

342 FlashToken.Transfer( from=[Receiver] FlashProtocol, to=0x0000000000000000000000000000000000000000, value=4393101159872456325588 )
343 FlashToken.Transfer( from=[Receiver] FlashProtocol, to=[Sender] 0x85a33a536bb3d9479ea6fbd844ef1e14d8403bda, value=21831287919145364033467 )
344 FlashProtocol.Unstaked( _id=42A4EEA38B153645656CFE49B0B4193C68DD35B7CDD23CFFE1DD3ABDEEC93848, _amountIn=21831287919145364033467, _staker=[Sender] 0x85a33a536bb3d9479ea6fbd844ef1e14d8403bda )

Account State Difference:

  Address   Before After State Difference Code
0x15EB0c76...85Cc48275
0x20398aD6...a0b860C1f
0x85a33A53...4D8403BDA
0.18721368398199773 Eth
Nonce: 133
0.18539193690099773 Eth
Nonce: 134
0.001821747081
(Ethermine)
2,733.319770460405834208 Eth2,733.321592207486834208 Eth0.001821747081

Execution Trace

FlashProtocol.unstakeEarly( _id=42A4EEA38B153645656CFE49B0B4193C68DD35B7CDD23CFFE1DD3ABDEEC93848 ) => ( withdrawAmount=21831287919145364033467 )
  • FlashToken.burn( value=4393101159872456325588 ) => ( True )
  • FlashToken.transfer( to=0x85a33A536Bb3D9479EA6FBD844ef1e14D8403BDA, value=21831287919145364033467 ) => ( True )
    File 1 of 2: FlashProtocol
    // SPDX-License-Identifier: MIT
    pragma solidity 0.7.4;
    
    interface IFlashToken {
        function totalSupply() external view returns (uint256);
    
        function balanceOf(address account) external view returns (uint256);
    
        function allowance(address owner, address spender) external view returns (uint256);
    
        function approve(address spender, uint256 amount) external returns (bool);
    
        function transfer(address recipient, uint256 amount) external returns (bool);
    
        function transferFrom(
            address sender,
            address recipient,
            uint256 amount
        ) external returns (bool);
    
        function mint(address to, uint256 value) external returns (bool);
    
        function burn(uint256 value) external returns (bool);
    
        function permit(
            address owner,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external;
    }
    
    interface IFlashReceiver {
        function receiveFlash(
            bytes32 id,
            uint256 amountIn,
            uint256 expireAfter,
            uint256 mintedAmount,
            address staker,
            bytes calldata data
        ) external returns (uint256);
    }
    
    interface IFlashProtocol {
        enum LockedFunctions { SET_MATCH_RATIO, SET_MATCH_RECEIVER }
    
        function TIMELOCK() external view returns (uint256);
    
        function FLASH_TOKEN() external view returns (address);
    
        function matchRatio() external view returns (uint256);
    
        function matchReceiver() external view returns (address);
    
        function stakes(bytes32 _id)
            external
            view
            returns (
                uint256 amountIn,
                uint256 expiry,
                uint256 expireAfter,
                uint256 mintedAmount,
                address staker,
                address receiver
            );
    
        function stake(
            uint256 _amountIn,
            uint256 _days,
            address _receiver,
            bytes calldata _data
        )
            external
            returns (
                uint256 mintedAmount,
                uint256 matchedAmount,
                bytes32 id
            );
    
        function lockFunction(LockedFunctions _lockedFunction) external;
    
        function unlockFunction(LockedFunctions _lockedFunction) external;
    
        function timelock(LockedFunctions _lockedFunction) external view returns (uint256);
    
        function balances(address _staker) external view returns (uint256);
    
        function unstake(bytes32 _id) external returns (uint256 withdrawAmount);
    
        function unstakeEarly(bytes32 _id) external returns (uint256 withdrawAmount);
    
        function getFPY(uint256 _amountIn) external view returns (uint256);
    
        function setMatchReceiver(address _newMatchReceiver) external;
    
        function setMatchRatio(uint256 _newMatchRatio) external;
    
        function getMatchedAmount(uint256 mintedAmount) external view returns (uint256);
    
        function getMintAmount(uint256 _amountIn, uint256 _expiry) external view returns (uint256);
    
        function getPercentageStaked(uint256 _amountIn) external view returns (uint256 percentage);
    
        function calculateMaxStakePeriod(uint256 _amountIn) external view returns (uint256);
    
        function stakeWithPermit(
            address _receiver,
            uint256 _amountIn,
            uint256 _expiry,
            uint256 _deadline,
            uint8 _v,
            bytes32 _r,
            bytes32 _s,
            bytes calldata _data
        )
            external
            returns (
                uint256 mintedAmount,
                uint256 matchedAmount,
                bytes32 id
            );
    }
    
    library SafeMath {
        function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
            require((z = x + y) >= x, "MATH:: ADD_OVERFLOW");
        }
    
        function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
            require((z = x - y) <= x, "MATH:: SUB_UNDERFLOW");
        }
    
        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) {
                return 0;
            }
    
            uint256 c = a * b;
            require(c / a == b, "MATH:: MUL_OVERFLOW");
    
            return c;
        }
    
        function div(uint256 a, uint256 b) internal pure returns (uint256) {
            require(b > 0, "MATH:: DIVISION_BY_ZERO");
            uint256 c = a / b;
            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    
            return c;
        }
    }
    
    library Address {
        /**
         * @dev Returns true if `account` is a contract.
         *
         * [IMPORTANT]
         * ====
         * It is unsafe to assume that an address for which this function returns
         * false is an externally-owned account (EOA) and not a contract.
         *
         * Among others, `isContract` will return false for the following
         * types of addresses:
         *
         *  - an externally-owned account
         *  - a contract in construction
         *  - an address where a contract will be created
         *  - an address where a contract lived, but was destroyed
         * ====
         */
        function isContract(address account) internal view returns (bool) {
            // This method relies on extcodesize, which returns 0 for contracts in
            // construction, since the code is only stored at the end of the
            // constructor execution.
    
            uint256 size;
            // solhint-disable-next-line no-inline-assembly
            assembly {
                size := extcodesize(account)
            }
            return size > 0;
        }
    }
    
    
    contract FlashProtocol is IFlashProtocol {
        using SafeMath for uint256;
        using Address for address;
    
        struct Stake {
            uint256 amountIn;
            uint256 expiry;
            uint256 expireAfter;
            uint256 mintedAmount;
            address staker;
            address receiver;
        }
    
        uint256 public constant override TIMELOCK = 3 days;
        address public constant override FLASH_TOKEN = 0x20398aD62bb2D930646d45a6D4292baa0b860C1f;
    
        uint256 internal constant PRECISION = 1e18;
        uint256 internal constant MAX_FPY_FOR_1_YEAR = 5e17;
        uint256 internal constant SECONDS_IN_1_YEAR = 365 * 86400;
    
        uint256 public override matchRatio;
        address public override matchReceiver;
    
        mapping(bytes32 => Stake) public override stakes;
        mapping(LockedFunctions => uint256) public override timelock;
        mapping(address => uint256) public override balances;
    
        event Staked(
            bytes32 _id,
            uint256 _amountIn,
            uint256 _expiry,
            uint256 _expireAfter,
            uint256 _mintedAmount,
            address indexed _staker,
            address indexed _receiver
        );
    
        event Unstaked(bytes32 _id, uint256 _amountIn, address indexed _staker);
    
        modifier onlyMatchReceiver {
            require(msg.sender == matchReceiver, "FlashProtocol:: NOT_MATCH_RECEIVER");
            _;
        }
    
        modifier notLocked(LockedFunctions _lockedFunction) {
            require(
                timelock[_lockedFunction] != 0 && timelock[_lockedFunction] <= block.timestamp,
                "FlashProtocol:: FUNCTION_TIMELOCKED"
            );
            _;
        }
    
        constructor(address _initialMatchReceiver, uint256 _initialMatchRatio) {
            _setMatchRatio(_initialMatchRatio);
            _setMatchReceiver(_initialMatchReceiver);
        }
    
        function lockFunction(LockedFunctions _lockedFunction) external override onlyMatchReceiver {
            timelock[_lockedFunction] = type(uint256).max;
        }
    
        function unlockFunction(LockedFunctions _lockedFunction) external override onlyMatchReceiver {
            timelock[_lockedFunction] = block.timestamp + TIMELOCK;
        }
    
        function setMatchReceiver(address _newMatchReceiver)
            external
            override
            onlyMatchReceiver
            notLocked(LockedFunctions.SET_MATCH_RECEIVER)
        {
            _setMatchReceiver(_newMatchReceiver);
            timelock[LockedFunctions.SET_MATCH_RECEIVER] = 0;
        }
    
        function _setMatchReceiver(address _newMatchReceiver) internal {
            matchReceiver = _newMatchReceiver;
        }
    
        function setMatchRatio(uint256 _newMatchRatio)
            external
            override
            onlyMatchReceiver
            notLocked(LockedFunctions.SET_MATCH_RATIO)
        {
            _setMatchRatio(_newMatchRatio);
            timelock[LockedFunctions.SET_MATCH_RATIO] = 0;
        }
    
        function _setMatchRatio(uint256 _newMatchRatio) internal {
            require(_newMatchRatio >= 0 && _newMatchRatio <= 2000, "FlashProtocol:: INVALID_MATCH_RATIO");
            // can be 0 and cannot be above 20%
            require(_newMatchRatio <= 2000, "FlashProtocol:: INVALID_MATCH_RATIO");
            matchRatio = _newMatchRatio;
        }
    
        function stake(
            uint256 _amountIn,
            uint256 _expiry,
            address _receiver,
            bytes calldata _data
        )
            external
            override
            returns (
                uint256,
                uint256,
                bytes32
            )
        {
            return _stake(_amountIn, _expiry, _receiver, _data);
        }
    
        function stakeWithPermit(
            address _receiver,
            uint256 _amountIn,
            uint256 _expiry,
            uint256 _deadline,
            uint8 _v,
            bytes32 _r,
            bytes32 _s,
            bytes calldata _data
        )
            external
            override
            returns (
                uint256,
                uint256,
                bytes32
            )
        {
            IFlashToken(FLASH_TOKEN).permit(msg.sender, address(this), type(uint256).max, _deadline, _v, _r, _s);
            return _stake(_amountIn, _expiry, _receiver, _data);
        }
    
        function _stake(
            uint256 _amountIn,
            uint256 _expiry,
            address _receiver,
            bytes calldata _data
        )
            internal
            returns (
                uint256 mintedAmount,
                uint256 matchedAmount,
                bytes32 id
            )
        {
            require(_amountIn > 0, "FlashProtocol:: INVALID_AMOUNT");
            require(_receiver != address(this), "FlashProtocol:: INVALID_ADDRESS");
            require(_expiry <= calculateMaxStakePeriod(_amountIn), "FlashProtocol:: MAX_STAKE_PERIOD_EXCEEDS");
    
            address staker = msg.sender;
    
            uint256 expiration = block.timestamp.add(_expiry);
            balances[staker] = balances[staker].add(_amountIn);
    
            id = keccak256(abi.encodePacked(_amountIn, _expiry, _receiver, staker, block.timestamp));
    
            require(stakes[id].staker == address(0), "FlashProtocol:: STAKE_EXISTS");
    
            mintedAmount = getMintAmount(_amountIn, _expiry);
            matchedAmount = getMatchedAmount(mintedAmount);
    
            IFlashToken(FLASH_TOKEN).transferFrom(staker, address(this), _amountIn);
    
            IFlashToken(FLASH_TOKEN).mint(_receiver, mintedAmount);
            IFlashToken(FLASH_TOKEN).mint(matchReceiver, matchedAmount);
    
            stakes[id] = Stake(_amountIn, _expiry, expiration, mintedAmount, staker, _receiver);
    
            if (_receiver.isContract()) {
                IFlashReceiver(_receiver).receiveFlash(id, _amountIn, expiration, mintedAmount, staker, _data);
            }
    
            emit Staked(id, _amountIn, _expiry, expiration, mintedAmount, staker, _receiver);
        }
    
        function unstake(bytes32 _id) external override returns (uint256 withdrawAmount) {
            Stake memory s = stakes[_id];
            require(block.timestamp >= s.expireAfter, "FlashProtol:: STAKE_NOT_EXPIRED");
            balances[s.staker] = balances[s.staker].sub(s.amountIn);
            withdrawAmount = s.amountIn;
            delete stakes[_id];
            IFlashToken(FLASH_TOKEN).transfer(s.staker, withdrawAmount);
            emit Unstaked(_id, s.amountIn, s.staker);
        }
    
        function unstakeEarly(bytes32 _id) external override returns (uint256 withdrawAmount) {
            Stake memory s = stakes[_id];
            address staker = msg.sender;
            require(s.staker == staker, "FlashProtocol:: INVALID_STAKER");
            uint256 remainingTime = (s.expireAfter.sub(block.timestamp));
            require(s.expiry > remainingTime, "Flash Protocol:: INVALID_UNSTAKE_TIME");
            uint256 burnAmount = _calculateBurn(s.amountIn, remainingTime, s.expiry);
            assert(burnAmount <= s.amountIn);
            balances[staker] = balances[staker].sub(s.amountIn);
            withdrawAmount = s.amountIn.sub(burnAmount);
            delete stakes[_id];
            IFlashToken(FLASH_TOKEN).burn(burnAmount);
            IFlashToken(FLASH_TOKEN).transfer(staker, withdrawAmount);
            emit Unstaked(_id, withdrawAmount, staker);
        }
    
        function getMatchedAmount(uint256 _mintedAmount) public view override returns (uint256) {
            return _mintedAmount.mul(matchRatio).div(10000);
        }
    
        function getMintAmount(uint256 _amountIn, uint256 _expiry) public view override returns (uint256) {
            return _amountIn.mul(_expiry).mul(getFPY(_amountIn)).div(PRECISION * SECONDS_IN_1_YEAR);
        }
    
        function getFPY(uint256 _amountIn) public view override returns (uint256) {
            return (PRECISION.sub(getPercentageStaked(_amountIn))).div(2);
        }
    
        function getPercentageStaked(uint256 _amountIn) public view override returns (uint256) {
            uint256 locked = IFlashToken(FLASH_TOKEN).balanceOf(address(this)).add(_amountIn);
            return locked.mul(PRECISION).div(IFlashToken(FLASH_TOKEN).totalSupply());
        }
    
        function calculateMaxStakePeriod(uint256 _amountIn) public view override returns (uint256) {
            return MAX_FPY_FOR_1_YEAR.mul(SECONDS_IN_1_YEAR).div(getFPY(_amountIn));
        }
    
        function _calculateBurn(
            uint256 _amount,
            uint256 _remainingTime,
            uint256 _totalTime
        ) private pure returns (uint256) {
            return _amount.mul(_remainingTime).div(_totalTime);
        }
    }

    File 2 of 2: FlashToken
    // SPDX-License-Identifier: MIT
    pragma solidity 0.7.4;
    
    library SafeMath {
        function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
            require((z = x + y) >= x, "MATH:ADD_OVERFLOW");
        }
    
        function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
            require((z = x - y) <= x, "MATH:SUB_UNDERFLOW");
        }
    }
    
    interface IERC20 {
        function totalSupply() external view returns (uint256);
    
        function balanceOf(address account) external view returns (uint256);
    
        function allowance(address owner, address spender) external view returns (uint256);
    
        function approve(address spender, uint256 amount) external returns (bool);
    
        function transfer(address recipient, uint256 amount) external returns (bool);
    
        function transferFrom(
            address sender,
            address recipient,
            uint256 amount
        ) external returns (bool);
    }
    
    // Lightweight token modelled after UNI-LP:
    // https://github.com/Uniswap/uniswap-v2-core/blob/v1.0.1/contracts/UniswapV2ERC20.sol
    // Adds:
    //   - An exposed `mint()` with minting role
    //   - An exposed `burn()`
    //   - ERC-3009 (`transferWithAuthorization()`)
    contract FlashToken is IERC20 {
        using SafeMath for uint256;
    
        // bytes32 private constant EIP712DOMAIN_HASH =
        // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
        bytes32 private constant EIP712DOMAIN_HASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
    
        // bytes32 private constant NAME_HASH = keccak256("FLASH")
        bytes32 private constant NAME_HASH = 0x345b72c36b14f1cee01efb8ac4b299dc7b8d873e28b4796034548a3d371a4d2f;
    
        // bytes32 private constant VERSION_HASH = keccak256("2")
        bytes32 private constant VERSION_HASH = 0xad7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5;
    
        // bytes32 public constant PERMIT_TYPEHASH =
        // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
        bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
    
        // bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH =
        // keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)");
        bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267;
    
        string public constant name = "Flashstake";
        string public constant symbol = "FLASH";
        uint8 public constant decimals = 18;
    
        address public constant FLASH_PROTOCOL = 0x15EB0c763581329C921C8398556EcFf85Cc48275;
        address public constant FLASH_CLAIM = 0xf2319b6D2aB252d8D80D8CEC34DaF0079222A624;
    
        uint256 public override totalSupply;
    
        mapping(address => uint256) public override balanceOf;
        mapping(address => mapping(address => uint256)) public override allowance;
    
        // ERC-2612, ERC-3009 state
        mapping(address => uint256) public nonces;
        mapping(address => mapping(bytes32 => bool)) public authorizationState;
    
        event Approval(address indexed owner, address indexed spender, uint256 value);
        event Transfer(address indexed from, address indexed to, uint256 value);
        event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
    
        modifier onlyMinter {
            require(msg.sender == FLASH_PROTOCOL || msg.sender == FLASH_CLAIM, "FlashToken:: NOT_MINTER");
            _;
        }
    
        constructor() {
            // BlockZero Labs: Foundation Fund
            _mint(0x842f8f6fB524996d0b660621DA895166E1ceA691, 1200746000000000000000000);
            _mint(0x0945d9033147F27aDDFd3e7532ECD2100cb91032, 1000000000000000000000000);
        }
    
        function _validateSignedData(
            address signer,
            bytes32 encodeData,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) internal view {
            bytes32 digest = keccak256(abi.encodePacked("\x19\x01", getDomainSeparator(), encodeData));
            address recoveredAddress = ecrecover(digest, v, r, s);
            // Explicitly disallow authorizations for address(0) as ecrecover returns address(0) on malformed messages
            require(recoveredAddress != address(0) && recoveredAddress == signer, "FlashToken:: INVALID_SIGNATURE");
        }
    
        function _mint(address to, uint256 value) internal {
            totalSupply = totalSupply.add(value);
            balanceOf[to] = balanceOf[to].add(value);
            emit Transfer(address(0), to, value);
        }
    
        function _burn(address from, uint256 value) internal {
            // Balance is implicitly checked with SafeMath's underflow protection
            balanceOf[from] = balanceOf[from].sub(value);
            totalSupply = totalSupply.sub(value);
            emit Transfer(from, address(0), value);
        }
    
        function _approve(
            address owner,
            address spender,
            uint256 value
        ) private {
            allowance[owner][spender] = value;
            emit Approval(owner, spender, value);
        }
    
        function _transfer(
            address from,
            address to,
            uint256 value
        ) private {
            require(to != address(this) && to != address(0), "FlashToken:: RECEIVER_IS_TOKEN_OR_ZERO");
    
            // Balance is implicitly checked with SafeMath's underflow protection
            balanceOf[from] = balanceOf[from].sub(value);
            balanceOf[to] = balanceOf[to].add(value);
            emit Transfer(from, to, value);
        }
    
        function getChainId() public pure returns (uint256 chainId) {
            // solhint-disable-next-line no-inline-assembly
            assembly {
                chainId := chainid()
            }
        }
    
        function getDomainSeparator() public view returns (bytes32) {
            return keccak256(abi.encode(EIP712DOMAIN_HASH, NAME_HASH, VERSION_HASH, getChainId(), address(this)));
        }
    
        function mint(address to, uint256 value) external onlyMinter returns (bool) {
            _mint(to, value);
            return true;
        }
    
        function burn(uint256 value) external returns (bool) {
            _burn(msg.sender, value);
            return true;
        }
    
        function approve(address spender, uint256 value) external override returns (bool) {
            _approve(msg.sender, spender, value);
            return true;
        }
    
        function transfer(address to, uint256 value) external override returns (bool) {
            _transfer(msg.sender, to, value);
            return true;
        }
    
        function transferFrom(
            address from,
            address to,
            uint256 value
        ) external override returns (bool) {
            uint256 fromAllowance = allowance[from][msg.sender];
            if (fromAllowance != uint256(-1)) {
                // Allowance is implicitly checked with SafeMath's underflow protection
                allowance[from][msg.sender] = fromAllowance.sub(value);
            }
            _transfer(from, to, value);
            return true;
        }
    
        function permit(
            address owner,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external {
            require(deadline >= block.timestamp, "FlashToken:: AUTH_EXPIRED");
    
            bytes32 encodeData = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner], deadline));
            nonces[owner] = nonces[owner].add(1);
            _validateSignedData(owner, encodeData, v, r, s);
    
            _approve(owner, spender, value);
        }
    
        function transferWithAuthorization(
            address from,
            address to,
            uint256 value,
            uint256 validAfter,
            uint256 validBefore,
            bytes32 nonce,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external {
            require(block.timestamp > validAfter, "FlashToken:: AUTH_NOT_YET_VALID");
            require(block.timestamp < validBefore, "FlashToken:: AUTH_EXPIRED");
            require(!authorizationState[from][nonce], "FlashToken:: AUTH_ALREADY_USED");
    
            bytes32 encodeData = keccak256(abi.encode(TRANSFER_WITH_AUTHORIZATION_TYPEHASH, from, to, value, validAfter, validBefore, nonce));
            _validateSignedData(from, encodeData, v, r, s);
    
            authorizationState[from][nonce] = true;
            emit AuthorizationUsed(from, nonce);
    
            _transfer(from, to, value);
        }
    }