Transaction Hash:
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 | ||
---|---|---|---|---|---|
0x15EB0c76...85Cc48275 | |||||
0x20398aD6...a0b860C1f | |||||
0x85a33A53...4D8403BDA |
0.18721368398199773 Eth
Nonce: 133
|
0.18539193690099773 Eth
Nonce: 134
| 0.001821747081 | ||
0xEA674fdD...16B898ec8
Miner
| (Ethermine) | 2,733.319770460405834208 Eth | 2,733.321592207486834208 Eth | 0.001821747081 |
Execution Trace
FlashProtocol.unstakeEarly( _id=42A4EEA38B153645656CFE49B0B4193C68DD35B7CDD23CFFE1DD3ABDEEC93848 ) => ( withdrawAmount=21831287919145364033467 )
unstakeEarly[FlashProtocol (ln:380)]
File 1 of 2: FlashProtocol
File 2 of 2: FlashToken
// 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); } }