Transaction Hash:
Block:
13303756 at Sep-26-2021 09:10:43 PM +UTC
Transaction Fee:
0.00319164869163704 ETH
$7.87
Gas Used:
52,490 Gas / 60.804890296 Gwei
Emitted Events:
123 |
TetherToken.Transfer( from=[Sender] 0x97acbe609cba0ab16a388337792be3d113aa203c, to=[Receiver] TeleportCustody, value=525000000 )
|
124 |
TeleportCustody.Locked( amount=525000000, flowAddress=System.Byte[], ethereumAddress=[Sender] 0x97acbe609cba0ab16a388337792be3d113aa203c )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x7F101fE4...353f2B90c
Miner
| (Flexpool.io) | 2,307.03364120237702429 Eth | 2,307.03371521327702429 Eth | 0.0000740109 | |
0x97acBe60...113aA203C |
0.132986218357740505 Eth
Nonce: 2
|
0.129794569666103465 Eth
Nonce: 3
| 0.00319164869163704 | ||
0xdAC17F95...13D831ec7 |
Execution Trace
TeleportCustody.lock( amount=525000000, flowAddress=System.Byte[] )
-
TetherToken.transferFrom( _from=0x97acBe609cba0AB16a388337792BE3D113aA203C, _to=0xf8F12fE1B51D1398019C4faCd4D00aDAb5fEF746, _value=525000000 )
lock[TeleportCustody (ln:272)]
_msgSender[TeleportCustody (ln:276)]
transferFrom[TeleportCustody (ln:279)]
Locked[TeleportCustody (ln:281)]
File 1 of 2: TeleportCustody
File 2 of 2: TetherToken
// File: @openzeppelin/contracts/GSN/Context.sol pragma solidity ^0.6.0; /* * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with GSN meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address payable) { return msg.sender; } function _msgData() internal view virtual returns (bytes memory) { this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 return msg.data; } } // File: @openzeppelin/contracts/access/Ownable.sol pragma solidity ^0.6.0; /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor () internal { address msgSender = _msgSender(); _owner = msgSender; emit OwnershipTransferred(address(0), msgSender); } /** * @dev Returns the address of the current owner. */ function owner() public view returns (address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(_owner == _msgSender(), "Ownable: caller is not the owner"); _; } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions anymore. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { emit OwnershipTransferred(_owner, address(0)); _owner = address(0); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; } } // File: contracts/teleport/ethereum/TeleportAdmin.sol pragma solidity 0.6.12; /** * @dev Contract module which provides a basic access control mechanism, where * there are multiple accounts (admins) that can be granted exclusive access to * specific functions. * * This module is used through inheritance. It will make available the modifier * `consumeAuthorization`, which can be applied to your functions to restrict * their use to the admins. */ contract TeleportAdmin is Ownable { // Marks that the contract is frozen or unfrozen (safety kill-switch) bool private _isFrozen; mapping(address => uint256) private _allowedAmount; event AdminUpdated(address indexed account, uint256 allowedAmount); // Modifiers /** * @dev Throw if contract is currently frozen. */ modifier notFrozen() { require( !_isFrozen, "TeleportAdmin: contract is frozen by owner" ); _; } /** * @dev Throw if caller does not have sufficient authorized amount. */ modifier consumeAuthorization(uint256 amount) { address sender = _msgSender(); require( allowedAmount(sender) >= amount, "TeleportAdmin: caller does not have sufficient authorization" ); _; // reduce authorization amount. Underflow cannot occur because we have // already checked that admin has sufficient allowed amount. _allowedAmount[sender] -= amount; emit AdminUpdated(sender, _allowedAmount[sender]); } /** * @dev Checks the authorized amount of an admin account. */ function allowedAmount(address account) public view returns (uint256) { return _allowedAmount[account]; } /** * @dev Returns if the contract is currently frozen. */ function isFrozen() public view returns (bool) { return _isFrozen; } /** * @dev Owner freezes the contract. */ function freeze() public onlyOwner { _isFrozen = true; } /** * @dev Owner unfreezes the contract. */ function unfreeze() public onlyOwner { _isFrozen = false; } /** * @dev Updates the admin status of an account. * Can only be called by the current owner. */ function updateAdmin(address account, uint256 newAllowedAmount) public virtual onlyOwner { emit AdminUpdated(account, newAllowedAmount); _allowedAmount[account] = newAllowedAmount; } /** * @dev Overrides the inherited method from Ownable. * Disable ownership resounce. */ function renounceOwnership() public override onlyOwner { revert("TeleportAdmin: ownership cannot be renounced"); } } // File: contracts/teleport/ethereum/TetherToken.sol pragma solidity 0.6.12; /** * @dev Method signature contract for Tether (USDT) because it's not a standard * ERC-20 contract and have different method signatures. */ interface TetherToken { function transfer(address _to, uint _value) external; function transferFrom(address _from, address _to, uint _value) external; } // File: contracts/teleport/ethereum/TeleportCustody.sol // SPDX-License-Identifier: MIT pragma solidity 0.6.12; // import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; /** * @dev Implementation of the TeleportCustody contract. * * There are two priviledged roles for the contract: "owner" and "admin". * * Owner: Has the ultimate control of the contract and the funds stored inside the * contract. Including: * 1) "freeze" and "unfreeze" the contract: when the TeleportCustody is frozen, * all deposits and withdrawals with the TeleportCustody is disabled. This * should only happen when a major security risk is spotted or if admin access * is comprimised. * 2) assign "admins": owner has the authority to grant "unlock" permission to * "admins" and set proper "unlock limit" for each "admin". * * Admin: Has the authority to "unlock" specific amount to tokens to receivers. */ contract TeleportCustody is TeleportAdmin { // USDC // ERC20 internal _tokenContract = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); // USDT TetherToken internal _tokenContract = TetherToken(0xdAC17F958D2ee523a2206206994597C13D831ec7); // Records that an unlock transaction has been executed mapping(bytes32 => bool) internal _unlocked; // Emmitted when user locks token and initiates teleport event Locked(uint256 amount, bytes8 indexed flowAddress, address indexed ethereumAddress); // Emmitted when teleport completes and token gets unlocked event Unlocked(uint256 amount, address indexed ethereumAddress, bytes32 indexed flowHash); /** * @dev User locks token and initiates teleport request. */ function lock(uint256 amount, bytes8 flowAddress) public notFrozen { address sender = _msgSender(); // NOTE: Return value should be checked. However, Tether does not have return value. _tokenContract.transferFrom(sender, address(this), amount); emit Locked(amount, flowAddress, sender); } // Admin methods /** * @dev TeleportAdmin unlocks token upon receiving teleport request from Flow. */ function unlock(uint256 amount, address ethereumAddress, bytes32 flowHash) public notFrozen consumeAuthorization(amount) { _unlock(amount, ethereumAddress, flowHash); } // Owner methods /** * @dev Owner unlocks token upon receiving teleport request from Flow. * There is no unlock limit for owner. */ function unlockByOwner(uint256 amount, address ethereumAddress, bytes32 flowHash) public notFrozen onlyOwner { _unlock(amount, ethereumAddress, flowHash); } // Internal methods /** * @dev Internal function for processing unlock requests. * * There is no way TeleportCustody can check the validity of the target address * beforehand so user and admin should always make sure the provided information * is correct. */ function _unlock(uint256 amount, address ethereumAddress, bytes32 flowHash) internal { require(ethereumAddress != address(0), "TeleportCustody: ethereumAddress is the zero address"); require(!_unlocked[flowHash], "TeleportCustody: same unlock hash has been executed"); _unlocked[flowHash] = true; // NOTE: Return value should be checked. However, Tether does not have return value. _tokenContract.transfer(ethereumAddress, amount); emit Unlocked(amount, ethereumAddress, flowHash); } }
File 2 of 2: TetherToken
pragma solidity ^0.4.17; /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ library SafeMath { function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) { return 0; } uint256 c = a * b; assert(c / a == b); return c; } function div(uint256 a, uint256 b) internal pure returns (uint256) { // assert(b > 0); // Solidity automatically throws when dividing by 0 uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } } /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ function Ownable() public { owner = msg.sender; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(msg.sender == owner); _; } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) public onlyOwner { if (newOwner != address(0)) { owner = newOwner; } } } /** * @title ERC20Basic * @dev Simpler version of ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract ERC20Basic { uint public _totalSupply; function totalSupply() public constant returns (uint); function balanceOf(address who) public constant returns (uint); function transfer(address to, uint value) public; event Transfer(address indexed from, address indexed to, uint value); } /** * @title ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract ERC20 is ERC20Basic { function allowance(address owner, address spender) public constant returns (uint); function transferFrom(address from, address to, uint value) public; function approve(address spender, uint value) public; event Approval(address indexed owner, address indexed spender, uint value); } /** * @title Basic token * @dev Basic version of StandardToken, with no allowances. */ contract BasicToken is Ownable, ERC20Basic { using SafeMath for uint; mapping(address => uint) public balances; // additional variables for use if transaction fees ever became necessary uint public basisPointsRate = 0; uint public maximumFee = 0; /** * @dev Fix for the ERC20 short address attack. */ modifier onlyPayloadSize(uint size) { require(!(msg.data.length < size + 4)); _; } /** * @dev transfer token for a specified address * @param _to The address to transfer to. * @param _value The amount to be transferred. */ function transfer(address _to, uint _value) public onlyPayloadSize(2 * 32) { uint fee = (_value.mul(basisPointsRate)).div(10000); if (fee > maximumFee) { fee = maximumFee; } uint sendAmount = _value.sub(fee); balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(sendAmount); if (fee > 0) { balances[owner] = balances[owner].add(fee); Transfer(msg.sender, owner, fee); } Transfer(msg.sender, _to, sendAmount); } /** * @dev Gets the balance of the specified address. * @param _owner The address to query the the balance of. * @return An uint representing the amount owned by the passed address. */ function balanceOf(address _owner) public constant returns (uint balance) { return balances[_owner]; } } /** * @title Standard ERC20 token * * @dev Implementation of the basic standard token. * @dev https://github.com/ethereum/EIPs/issues/20 * @dev Based oncode by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol */ contract StandardToken is BasicToken, ERC20 { mapping (address => mapping (address => uint)) public allowed; uint public constant MAX_UINT = 2**256 - 1; /** * @dev Transfer tokens from one address to another * @param _from address The address which you want to send tokens from * @param _to address The address which you want to transfer to * @param _value uint the amount of tokens to be transferred */ function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) { var _allowance = allowed[_from][msg.sender]; // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met // if (_value > _allowance) throw; uint fee = (_value.mul(basisPointsRate)).div(10000); if (fee > maximumFee) { fee = maximumFee; } if (_allowance < MAX_UINT) { allowed[_from][msg.sender] = _allowance.sub(_value); } uint sendAmount = _value.sub(fee); balances[_from] = balances[_from].sub(_value); balances[_to] = balances[_to].add(sendAmount); if (fee > 0) { balances[owner] = balances[owner].add(fee); Transfer(_from, owner, fee); } Transfer(_from, _to, sendAmount); } /** * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. * @param _spender The address which will spend the funds. * @param _value The amount of tokens to be spent. */ function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) { // To change the approve amount you first have to reduce the addresses` // allowance to zero by calling `approve(_spender, 0)` if it is not // already 0 to mitigate the race condition described here: // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 require(!((_value != 0) && (allowed[msg.sender][_spender] != 0))); allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); } /** * @dev Function to check the amount of tokens than an owner allowed to a spender. * @param _owner address The address which owns the funds. * @param _spender address The address which will spend the funds. * @return A uint specifying the amount of tokens still available for the spender. */ function allowance(address _owner, address _spender) public constant returns (uint remaining) { return allowed[_owner][_spender]; } } /** * @title Pausable * @dev Base contract which allows children to implement an emergency stop mechanism. */ contract Pausable is Ownable { event Pause(); event Unpause(); bool public paused = false; /** * @dev Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPaused() { require(!paused); _; } /** * @dev Modifier to make a function callable only when the contract is paused. */ modifier whenPaused() { require(paused); _; } /** * @dev called by the owner to pause, triggers stopped state */ function pause() onlyOwner whenNotPaused public { paused = true; Pause(); } /** * @dev called by the owner to unpause, returns to normal state */ function unpause() onlyOwner whenPaused public { paused = false; Unpause(); } } contract BlackList is Ownable, BasicToken { /////// Getters to allow the same blacklist to be used also by other contracts (including upgraded Tether) /////// function getBlackListStatus(address _maker) external constant returns (bool) { return isBlackListed[_maker]; } function getOwner() external constant returns (address) { return owner; } mapping (address => bool) public isBlackListed; function addBlackList (address _evilUser) public onlyOwner { isBlackListed[_evilUser] = true; AddedBlackList(_evilUser); } function removeBlackList (address _clearedUser) public onlyOwner { isBlackListed[_clearedUser] = false; RemovedBlackList(_clearedUser); } function destroyBlackFunds (address _blackListedUser) public onlyOwner { require(isBlackListed[_blackListedUser]); uint dirtyFunds = balanceOf(_blackListedUser); balances[_blackListedUser] = 0; _totalSupply -= dirtyFunds; DestroyedBlackFunds(_blackListedUser, dirtyFunds); } event DestroyedBlackFunds(address _blackListedUser, uint _balance); event AddedBlackList(address _user); event RemovedBlackList(address _user); } contract UpgradedStandardToken is StandardToken{ // those methods are called by the legacy contract // and they must ensure msg.sender to be the contract address function transferByLegacy(address from, address to, uint value) public; function transferFromByLegacy(address sender, address from, address spender, uint value) public; function approveByLegacy(address from, address spender, uint value) public; } contract TetherToken is Pausable, StandardToken, BlackList { string public name; string public symbol; uint public decimals; address public upgradedAddress; bool public deprecated; // The contract can be initialized with a number of tokens // All the tokens are deposited to the owner address // // @param _balance Initial supply of the contract // @param _name Token Name // @param _symbol Token symbol // @param _decimals Token decimals function TetherToken(uint _initialSupply, string _name, string _symbol, uint _decimals) public { _totalSupply = _initialSupply; name = _name; symbol = _symbol; decimals = _decimals; balances[owner] = _initialSupply; deprecated = false; } // Forward ERC20 methods to upgraded contract if this one is deprecated function transfer(address _to, uint _value) public whenNotPaused { require(!isBlackListed[msg.sender]); if (deprecated) { return UpgradedStandardToken(upgradedAddress).transferByLegacy(msg.sender, _to, _value); } else { return super.transfer(_to, _value); } } // Forward ERC20 methods to upgraded contract if this one is deprecated function transferFrom(address _from, address _to, uint _value) public whenNotPaused { require(!isBlackListed[_from]); if (deprecated) { return UpgradedStandardToken(upgradedAddress).transferFromByLegacy(msg.sender, _from, _to, _value); } else { return super.transferFrom(_from, _to, _value); } } // Forward ERC20 methods to upgraded contract if this one is deprecated function balanceOf(address who) public constant returns (uint) { if (deprecated) { return UpgradedStandardToken(upgradedAddress).balanceOf(who); } else { return super.balanceOf(who); } } // Forward ERC20 methods to upgraded contract if this one is deprecated function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) { if (deprecated) { return UpgradedStandardToken(upgradedAddress).approveByLegacy(msg.sender, _spender, _value); } else { return super.approve(_spender, _value); } } // Forward ERC20 methods to upgraded contract if this one is deprecated function allowance(address _owner, address _spender) public constant returns (uint remaining) { if (deprecated) { return StandardToken(upgradedAddress).allowance(_owner, _spender); } else { return super.allowance(_owner, _spender); } } // deprecate current contract in favour of a new one function deprecate(address _upgradedAddress) public onlyOwner { deprecated = true; upgradedAddress = _upgradedAddress; Deprecate(_upgradedAddress); } // deprecate current contract if favour of a new one function totalSupply() public constant returns (uint) { if (deprecated) { return StandardToken(upgradedAddress).totalSupply(); } else { return _totalSupply; } } // Issue a new amount of tokens // these tokens are deposited into the owner address // // @param _amount Number of tokens to be issued function issue(uint amount) public onlyOwner { require(_totalSupply + amount > _totalSupply); require(balances[owner] + amount > balances[owner]); balances[owner] += amount; _totalSupply += amount; Issue(amount); } // Redeem tokens. // These tokens are withdrawn from the owner address // if the balance must be enough to cover the redeem // or the call will fail. // @param _amount Number of tokens to be issued function redeem(uint amount) public onlyOwner { require(_totalSupply >= amount); require(balances[owner] >= amount); _totalSupply -= amount; balances[owner] -= amount; Redeem(amount); } function setParams(uint newBasisPoints, uint newMaxFee) public onlyOwner { // Ensure transparency by hardcoding limit beyond which fees can never be added require(newBasisPoints < 20); require(newMaxFee < 50); basisPointsRate = newBasisPoints; maximumFee = newMaxFee.mul(10**decimals); Params(basisPointsRate, maximumFee); } // Called when new token are issued event Issue(uint amount); // Called when tokens are redeemed event Redeem(uint amount); // Called when contract is deprecated event Deprecate(address newAddress); // Called if contract ever adds fees event Params(uint feeBasisPoints, uint maxFee); }