How the Tether USDT Token works

Rosario Borgesi
Coinmonks
22 min readNov 12, 2023

--

In the ever-evolving landscape of cryptocurrency, where digital assets often exhibit significant price volatility, Tether tokens emerge as a stabilizing force. Tether tokens are a unique breed in the realm of blockchain-based currencies, designed to bridge the gap between the decentralized world of cryptocurrencies and the stability of real-world fiat currencies.

If you like you can also check out the following video in which I have discussed the topics of this story in great detail.

Table of Contents

· Table of Contents
· Understanding Tether tokens
· Key Considerations Before Getting Started
· Deep Dive into Tether (USDT) Code
SafeMath library
Ownable contract
ERC20Basic and ERC20 Contracts
BasicToken and StandardToken contracts
Pausable contract
BlackList contract
UpgradedStandardToken contract
TetherToken contract
· Upgradeable Pattern
Hands On Demonstration
· Further Exploration
· Conclusions
· Resources

Understanding Tether tokens

Tether tokens, launched in 2014, are pioneering the stablecoin model and currently hold the distinction of being the most extensively traded in the digital currency market.
Unlike many other blockchain-based digital currencies, Tether tokens set themselves apart by maintaining a direct 1-to-1 peg to real-world fiat currencies (e.g., 1 USD₮ equals 1 USD) and are backed 100% by Tether’s reserves.
These tokens, often categorized as stablecoins, derive their name from the remarkable stability they offer. This stability arises from their connection to a specific fiat currency that offers traders, merchants and funds a low volatility solution when exiting positions in the market.
Tether supports US dollar (USD), euro (EUR), mexican peso (MXN), and offshore chinese yuan (CNH). Represented by ₮, Tether tokens are denoted as USD₮, EUR₮, MXN₮, CNH₮.
Furthermore Tether tokens are built on multiple blockchains: Algorand, Avalanche, Bitcoin, Ethereum, EOS, Kava, Polka, Polygon, Solana, TRON and Tezos.

Key Considerations Before Getting Started

This story aims to provide a detailed examination of the code behind the Tether token (USDT) deployed on Ethereum.
Therefore, before delving into this content, I recommend reviewing the following two articles. Doing so will enhance your understanding of the concepts discussed in this piece:

Deep Dive into Tether (USDT) Code

The code for the Tether token (USDT), deployed on Ethereum, can be found on Etherscan (*):

/**
*Submitted for verification at Etherscan.io on 2017-11-28
*/

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);
}

The code consists of various contracts and libraries, each responsible for implementing specific features. These components are consolidated in the primary contract, TetherToken, utilizing inheritance through the use of the “is” keyword.

The Solidity version is quite old (0.4.17) and it’s noticeable that there are two deprecated syntax elements in newer Solidity versions:

  1. declaring the constructor as a function with the same name of the contract.
  2. invoking the events without the emit prefix.

In the following sections, we will delve into each part of the code in greater detail.

SafeMath library

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;
}
}

The SafeMath library contains wrappers over Solidity’s arithmetic operations (mul, div, sub and add) with added overflow checks. It is generally not needed starting with Solidity 0.8, since the compiler has built in overflow checking.

Ownable contract

contract Ownable {
address public owner;

function Ownable() public {
owner = msg.sender;
}

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

function transferOwnership(address newOwner) public onlyOwner {
if (newOwner != address(0)) {
owner = newOwner;
}
}

}

The Ownable contract serves as a fundamental means of access control by designating a specific account (in the variabile owner) as the contract’s owner, granting it authority to perform administrative tasks.

In the Ownable constructor the initial contract owner is set as msg.sender which corresponds to the contract that is deploying the contract.

This contract defines the modifier onlyOwner which it is used to limit the function use to the contract owner.

Furthermore it defines the function transferOwnership which allows to change the contract owner which must be different from the 0 address.

ERC20Basic and ERC20 Contracts

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);
}

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);
}

It is clear that the Tether token adheres to the ERC-20 standard, in fact the ERC20 contract, which extends the ERC20Basic, defines the methods totalSupply, balanceOf, transfer, allowance, transferFrom and approve and the events Transfer and Approval, collectively constituting the ERC20 Standard.

The only methods that are missing are name, symbol and decimals that have been implemented in the TetherToken contract.

BasicToken and StandardToken contracts

contract BasicToken is Ownable, ERC20Basic {
using SafeMath for uint;
mapping(address => uint) public balances;
uint public basisPointsRate = 0;
uint public maximumFee = 0;

modifier onlyPayloadSize(uint size) {
require(!(msg.data.length < size + 4));
_;
}

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);
}

function balanceOf(address _owner) public constant returns (uint balance) {
return balances[_owner];
}

}

contract StandardToken is BasicToken, ERC20 {
mapping (address => mapping (address => uint)) public allowed;
uint public constant MAX_UINT = 2**256 - 1;

function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) {
var _allowance = allowed[_from][msg.sender];
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);
}

function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
}

function allowance(address _owner, address _spender) public constant returns (uint remaining) {
return allowed[_owner][_spender];
}
}

The methods outlined in the ERC20 interface have been implemented within the contract BasicToken and StandardToken.

The BasicToken contract extends the Ownable and ERC20Basic contracts and implements the methods transfer and balanceOf, while the StandardToken contract, which extends the BasicToken and ERC20 contracts, implements the methods transferFrom, approve and allowance.

Notably, the StandardToken contract represents a more comprehensive implementation, as it inherits the method implementations from the BasicToken contract, but it is missing the implementation of the method totalSupply which has been implemented in the TetherToken contract.

The BasicToken contract also defines the onlyPayloadSize modifier to fix the ERC20 short address attack.

It is noteworthy that both the variables balances, which contains the accounts balances, and allowed, which contains the accounts allowances, have been implemented as mappings. In fact this particular data structure is an hash table which allows fast retrival of its elements by key (O(1)).

Pausable contract

contract Pausable is Ownable {
event Pause();
event Unpause();

bool public paused = false;

modifier whenNotPaused() {
require(!paused);
_;
}

modifier whenPaused() {
require(paused);
_;
}

function pause() onlyOwner whenNotPaused public {
paused = true;
Pause();
}

function unpause() onlyOwner whenPaused public {
paused = false;
Unpause();
}
}

The Pausable contract allows to implement an emergency stop mechanism which can be useful for scenarios such as preventing trades until the end of an evaluation period or having an emergency switch to freeze all transactions in the event of a large bug.

It extends the Ownable contract and defines two modifiers: whenNotPaused and whenPaused and two functions pause and unpause, designed to stop and reactivate the contract, respectively.

BlackList contract

contract BlackList is Ownable, BasicToken {

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);
}

The BlackList contract manages a list of blacklisted addresses, allowing the addition or removal of addresses from this list.

This contract extends both the Ownable and the BasicToken contracts.

The variable isBlackListed holds the blacklist status of each account: also in this case has been chosen the mapping data structure to allow fast data retrival.

The contract defines the function getBlackListStatus which returns the blacklist status of an account and the functions addBlackList and removeBlackList to add and remove an address from the blacklist, respectively.

The function destroyBlackFunds sets the balance of the blacklisted account to zero and deducts its corresponding token from the total supply.

UpgradedStandardToken contract

contract UpgradedStandardToken is StandardToken{
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;
}

This is an interface which allows to upgrade the TetherToken contract in the future. This will be discussed in greater detail in the next section.

TetherToken contract

contract TetherToken is Pausable, StandardToken, BlackList {

string public name;
string public symbol;
uint public decimals;
address public upgradedAddress;
bool public deprecated;

function TetherToken(uint _initialSupply, string _name, string _symbol, uint _decimals) public {
_totalSupply = _initialSupply;
name = _name;
symbol = _symbol;
decimals = _decimals;
balances[owner] = _initialSupply;
deprecated = false;
}

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);
}
}

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);
}
}

function balanceOf(address who) public constant returns (uint) {
if (deprecated) {
return UpgradedStandardToken(upgradedAddress).balanceOf(who);
} else {
return super.balanceOf(who);
}
}

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);
}
}

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);
}
}

function deprecate(address _upgradedAddress) public onlyOwner {
deprecated = true;
upgradedAddress = _upgradedAddress;
Deprecate(_upgradedAddress);
}

function totalSupply() public constant returns (uint) {
if (deprecated) {
return StandardToken(upgradedAddress).totalSupply();
} else {
return _totalSupply;
}
}

function issue(uint amount) public onlyOwner {
require(_totalSupply + amount > _totalSupply);
require(balances[owner] + amount > balances[owner]);

balances[owner] += amount;
_totalSupply += amount;
Issue(amount);
}

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 {
require(newBasisPoints < 20);
require(newMaxFee < 50);

basisPointsRate = newBasisPoints;
maximumFee = newMaxFee.mul(10**decimals);

Params(basisPointsRate, maximumFee);
}

event Issue(uint amount);
event Redeem(uint amount);
event Deprecate(address newAddress);
event Params(uint feeBasisPoints, uint maxFee);
}

TetherTokenserves as the core contract for the Tether token,inheriting from the preceding contracts: Pausable, StandardToken, BlackList.

In the constructor it initializes the main variables of the ERC20 standard, including the initial supply, name, ticker symbol and decimals.
From EtherScan we can observe the arguments that were passed during the token deployment:

-----Decoded View---------------
Arg [0] : _initialSupply (uint256): 100000000000
Arg [1] : _name (string): Tether USD
Arg [2] : _symbol (string): USDT
Arg [3] : _decimals (uint256): 6

The initial supply was 10,000 tokens because there are 6 decimals and during deployment they were all sent to the owner’s account.

If we look at the issue function, it allows to mint new tokens and all of them are transferred to the owner’s account. However a supply cap has not been set, allowing for the creation of an essentially infinite number of tokens.
In Etherscan, the maximum total supply, as of the day of writing this article, is 39,022,689,732.746729 tokens, indicating that a considerable number of tokens have been generated since the initial deployment.

On the other hand the redeem function enables the burning of tokens, thereby reducing the total amount of tokens in circulation.

What we have observed is in perfect agreement with the Tether policy: as it maintains a 1:1 peg to the dollar, the value of each token remains constant, eliminating the need for a token cap. Additionally, when new customers purchase tokens using their funds, the contract owner can issue more tokens; conversely, when customers sell their tokens to retrieve their funds, the tokens can be burned.

Upgradeable Pattern

The TetherToken contract can be updated: even though the blockchain is immutable and once deployed a contract cannot be modified, the upgradeable pattern allows to upgrade the contract.

This is crucial because in the field of software engineering, it is common for developers to discover bugs in the code or for the code to require upgrades for various reasons. This pattern enables what may seem impossible due to the inherent nature of the blockchain.

In fact if we look at the functions transfer, transferFrom, balanceOf and approve we observe that they operate in a similar manner: if the TetherToken contract is deprecated they invoke the functions of the UpgradedStandardToken contract, otherwise they call the functions of the TetherToken contract.

To deprecate the TetherToken contract we must call the deprecate function, which sets the variable deprecated to true and sets the upgradedAddress variable to the address of the contract that will host the updated code.

If the TetherToken contract gets deprecated it acts like a proxy and fowards the call to the functions of the UpgradedStandardToken contract.

Let’s examine the transfer function of TetherToken:

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);
}
}

If the TetherToken contract is deprecated and the transfer function gets called it will foward the call to the transferByLegacy function of the UpgradedStandardToken contract at the address upgradedAddress. This means that we can write the updated code in the function transferByLegacy of UpgradedStandardToken which will replace the old code of the transfer function inside StandardToken and inherited by TetherToken.

The same applies to the TetherToken functions allowance and totalSupply which will foward the call to the methods of the StandardToken contract containing the updated code.

The UpgradedStandardToken contract is just an interface and if we plan to change the code we are forced to implement the three functions transferByLegacy, transferFromByLegacy and approveByLegacy. Nonetheless it is also possibile to change the implementation of the method balanceOf.

Hands On Demonstration

To illustrate how the upgradeable pattern works, let’s initially deploy a TetherToken contract with the complete code obtained from Etherscan and reported at (*) in the Sepolia test network. The contract has been deployed at the following address: 0xd41Cb299e7855D8348211Ce22730243974F02020.

Now let’s deploy a new UpgradedStandardToken contract that incorporates the updated code to upgrade the TetherToken contract:

pragma solidity ^0.4.17;

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) {
uint256 c = a / b;
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;
}
}

contract Ownable {
address public owner;

function Ownable() public {
owner = msg.sender;
}

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

function transferOwnership(address newOwner) public onlyOwner {
if (newOwner != address(0)) {
owner = newOwner;
}
}

}

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);
}

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);
}

contract BasicToken is Ownable, ERC20Basic {
using SafeMath for uint;
mapping(address => uint) public balances;
uint public basisPointsRate = 0;
uint public maximumFee = 0;

modifier onlyPayloadSize(uint size) {
require(!(msg.data.length < size + 4));
_;
}

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);
}

function balanceOf(address _owner) public constant returns (uint balance) {
return balances[_owner];
}

}

contract StandardToken is BasicToken, ERC20 {

mapping (address => mapping (address => uint)) public allowed;
uint public constant MAX_UINT = 2**256 - 1;

function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) {
var _allowance = allowed[_from][msg.sender];
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);
}

function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {

require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));

allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
}

function allowance(address _owner, address _spender) public constant returns (uint remaining) {
return allowed[_owner][_spender];
}

}

contract Pausable is Ownable {
event Pause();
event Unpause();

bool public paused = false;


modifier whenNotPaused() {
require(!paused);
_;
}

modifier whenPaused() {
require(paused);
_;
}

function pause() onlyOwner whenNotPaused public {
paused = true;
Pause();
}

function unpause() onlyOwner whenPaused public {
paused = false;
Unpause();
}
}

contract BlackList is Ownable, BasicToken {

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 {
event TransferByLegacy(address from, address to, uint value);
event TransferFromByLegacy(address sender, address from, address spender, uint value);
event ApproveByLegacy(address from, address spender, uint value);

function transferByLegacy(address from, address to, uint value) public {
TransferByLegacy(from, to, value);
}

function transferFromByLegacy(address sender, address from, address spender, uint value) public {
TransferFromByLegacy(sender, from, spender, value);
}

function approveByLegacy(address from, address spender, uint value) public {
ApproveByLegacy(from, spender, value);
}

function totalSupply() public constant returns (uint) {
return _totalSupply;
}
}

The code is identical to (*): with the exception of deleting the TetherToken contract and implementing the functions transferByLegacy, transferFromByLegacy and approveByLegacy in the UpgradedStandardToken contract. In these new functions, the primary purpose is to record an event on the blockchain, thus confirming the execution of the functions themselves.

Hence the new UpgradedStandardToken has been deployed at the following address: 0x0cAdB67a9a953bCc72A2f4dFf833aEb82752fd3A.

The next step is to invoke the deprecate function of the TetherToken contract passing as argument the address of the last deployed contract: 0x0cAdB67a9a953bCc72A2f4dFf833aEb82752fd3A.

Afterwards we call the transfer function of the TetherToken contract with these two arguments: 0x350a97Aa777CcfE518197C34342C5bA262825B35 as the receiver address and 10000000000000000000 as the amount of tokens to transfer.

In the following it has been reported the transaction receipt:

status 0x1 Transaction mined and execution succeed
transaction hash 0x41e6db403442720d45441a5198f4da62fff8993f9c9a8285717c92ff70298e11
block hash 0xe6f16b8b7319658929849a192a3095f8a09f7ba0577778b6a5374b0558943350
block number 4677909
from 0x1f0c72e13718d9136ffe51b89289b239a1bcfe28
to TetherToken.transfer(address,uint256) 0xd41cb299e7855d8348211ce22730243974f02020
gas gas
transaction cost 34102 gas
input 0xa90...80000
decoded input {
"address _to": "0x350a97Aa777CcfE518197C34342C5bA262825B35",
"uint256 _value": "10000000000000000000"
}
decoded output -
logs [
{
"from": "0x0cadb67a9a953bcc72a2f4dff833aeb82752fd3a",
"topic": "0x19848106c7b199207bd4396dafb7de1ced6d41715e8c50d49c7f020b213b402e",
"event": "TransferByLegacy",
"args": {
"0": "0x1F0c72E13718D9136FfE51b89289b239A1BcfE28",
"1": "0x350a97Aa777CcfE518197C34342C5bA262825B35",
"2": "10000000000000000000",
"from": "0x1F0c72E13718D9136FfE51b89289b239A1BcfE28",
"to": "0x350a97Aa777CcfE518197C34342C5bA262825B35",
"value": "10000000000000000000"
}
}
]

As we can see in the logs section the event parameter contains TransferByLegacy which is the event that emitted by transferByLegacy function of the last deployed UpgradedStandardToken contract.

Further Exploration

For those eager to dive into coding Solidity smart contracts, I recommend exploring the following resources:

For those who want to understand more about how the blockchain works I can recommend the following article:

Conclusions

In conclusion, our exploration into the Tether (USDT) code deployed on the Ethereum blockchain has unveiled the intricate mechanisms that underpin this widely-used stablecoin. By dissecting the contract structure, scrutinizing key functions, and understanding the upgradeability pattern employed, we’ve gained valuable insights into how Tether maintains its peg to the US dollar while offering a versatile and dynamic framework.

The presence of features such as the ability to mint and burn tokens, coupled with the utilization of the upgradeable pattern, demonstrates the adaptability of Tether’s smart contract design. The transparent nature of the code, accessible on platforms like Etherscan, allows for comprehensive audits and contributes to the overall trustworthiness of the Tether ecosystem.

As we navigate the complexities of blockchain technology, the Tether code serves as a noteworthy example of how stability, transparency, and functionality can coexist within a decentralized financial ecosystem. It is a testament to the ongoing evolution of smart contract development, paving the way for further innovations and refinements in the realm of digital assets.

Resources

--

--