cbBTC & BTC-on-Tron Blacklisting
Does a wrapped bitcoin by another name smell as sweet?
Coinbase just announced their cbBTC product and immediately the operator-owner-manager of some competing wrapped BTC products came out swinging.
That tweet contains 3 specific complaints:
- Lack of reserves transparency
- No audits
- A central freeze function
Here we will compare the freeze functionality between cbBTC and BTC-on-Tron (a product in the Justin Sun space). Unlike discussions of reserves transparency and auditing we can prove, with certainty, whether there are real differences between these products in the way freezing is handled. We will do this by comparing the short-and-very-readable code.
Note that while the tweet above uses the word “freeze” the code in both contracts uses blacklist instead.
BTC-on-Tron
Here is the blacklisting code:
contract BlackList is Ownable {
/////// Getter 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];
}
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);
}
event AddedBlackList(address indexed _user);
event RemovedBlackList(address indexed _user);
}
This defines:
- A way to check the blacklisting status of an address
- Functions to blacklist and unblacklist an address
- Events to emit when an address is (un)blacklisted
Changing the blacklist is gated by the onlyOwner modifier — this means onl the contract owner can call those functions. And therefore whatever processes are used to manage/transfer/renounce ownership on the contract as a whole also apply to blacklist administrative control.
cbBTC
Now we will look at the cbBTC code. What you are going to see is that Coinbase employs a dedicated “blacklister” address which holds administrative control over the blacklisting. This is different from BTC-on-Tron where the overall contract owner is also the blacklist admin. But beyond this one difference the code looks otherwise identical.
Here is the blacklisting code:
contract Blacklistable is Ownable {
address public blacklister;
mapping(address => bool) internal blacklisted;
event Blacklisted(address indexed _account);
event UnBlacklisted(address indexed _account);
event BlacklisterChanged(address indexed newBlacklister);
/**
* @dev Throws if called by any account other than the blacklister
*/
modifier onlyBlacklister() {
require(
msg.sender == blacklister,
"Blacklistable: caller is not the blacklister"
);
_;
}
/**
* @dev Throws if argument account is blacklisted
* @param _account The address to check
*/
modifier notBlacklisted(address _account) {
require(
!blacklisted[_account],
"Blacklistable: account is blacklisted"
);
_;
}
/**
* @dev Checks if account is blacklisted
* @param _account The address to check
*/
function isBlacklisted(address _account) external view returns (bool) {
return blacklisted[_account];
}
/**
* @dev Adds account to blacklist
* @param _account The address to blacklist
*/
function blacklist(address _account) external onlyBlacklister {
blacklisted[_account] = true;
emit Blacklisted(_account);
}
/**
* @dev Removes account from blacklist
* @param _account The address to remove from the blacklist
*/
function unBlacklist(address _account) external onlyBlacklister {
blacklisted[_account] = false;
emit UnBlacklisted(_account);
}
function updateBlacklister(address _newBlacklister) external onlyOwner {
require(
_newBlacklister != address(0),
"Blacklistable: new blacklister is the zero address"
);
blacklister = _newBlacklister;
emit BlacklisterChanged(blacklister);
}
}
Are These The Same?
The data structure used to manage the blacklist is identical. That’s the “mapping(address => bool)” bit at the top of both blocks. And there are functions for adding to, removing from, and checking whether an address is on that blacklist. We also find a pair of add/remove events. This is the same as BTC-on-Tron so far.
Then we have the administrative control apparatus around the “blacklister” address. As mentioned above these are not completely identical. But notice that the “updateBlacklister” function has the “onlyOwner” modifier. Only the overall contract owner can set or change the blacklister. Morally this is the same as BTC-on-Tron. The change is that a vice-owner gets appointed by the overall owner to handle the blacklist bit. Not meaningfully different.
So yes they are functionally the same.
Also notice the comment above “getBlackListStatus” in the BTC-on-Tron code. It says “Getter to allow the same blacklist to be used also by other contracts (including upgraded Tether).” The intent here is explicitly to share blacklists across various tokens and have blacklisting as a central feature of an entire ecosystem. And, for the record, BTC-on-Tron and USDT-on-Tron use precisely the same code for this bit. Line-for-line.