Gemini can make GUSD non-transferrable (code review)

Gemini USDG is a new centralized stablecoin (it’s similar to Tether) implemented as an ERC20 token on the Ethereum blockchain.

The current implementation gives Gemini the ability to freeze any account or make all tokens non-transferrable. The custodian is able to completely change the implementation of the token every 48 hours.

In this article, we review the code of the smart contract and show how to reproduce the results.

Let’s begin:

The code of the smart contract is available at

This code is uploaded by Gemini but according to etherscan, it’s an exact match with the code compiled and deployed by address 0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd

How do we know that this address is the only address Gemini uses? Strictly speaking, there are no trust-less ways to know. The company should publish the address somewhere and people have to agree that yes, this is the real address. I found it on

Also, we can check it indirectly because lots of money can be seen coming from gemini_1 in the transaction history of the same contract:

The Code overview:

To see the code you can open the “code” tab on


The code is 1028 lines long, so for simplicity let’s start from contract declarations

contract LockRequestable
contract CustodianUpgradeable is LockRequestable
contract ERC20ImplUpgradeable is CustodianUpgradeable
contract ERC20Interface
contract ERC20Proxy is ERC20Interface, ERC20ImplUpgradeable
contract ERC20Impl is CustodianUpgradeable
contract ERC20Store is ERC20ImplUpgradeable

So, the user uses the ERC20Proxy contract which offers an ERC20 interface, i.e. standard functions balance, total supply, etc which call erc20Impl object for actual implementation. For example:

/** @notice  Returns the account balance of another account with address
  * `_owner`.
  * @return  balance  the balance of account with address `_owner`.
function balanceOf(address _owner) public view returns (uint256 balance) {
    return erc20Impl.balanceOf(_owner);

Where does this object erc20Impl come from? Here we have to pay attention to the second part of the ERC20Proxy declaration: ERC20ImplUpgradeable. This object has an actual implementation of the ERC20 interface. Why is it called …Upgradable?

Because contract ERC20ImplUpgradeable is CustodianUpgradeable.


This contract “CustodianUpgradeable” has two functions:


/** @notice  Requests a change of the custodian associated with this contract.
  * @dev  Returns a unique lock id associated with the request.
  * Anyone can call this function, but confirming the request is authorized
  * by the custodian.
  * @param  _proposedCustodian  The address of the new custodian.
  * @return  lockId  A unique identifier for this request.
function requestCustodianChange(address _proposedCustodian) public returns (bytes32 lockId) {
    require(_proposedCustodian != address(0));

    lockId = generateLockId();

    custodianChangeReqs[lockId] = CustodianChangeRequest({
        proposedNew: _proposedCustodian

    emit CustodianChangeRequested(lockId, msg.sender, _proposedCustodian);

/** @notice  Confirms a pending change of the custodian associated with this contract.
  * @dev  When called by the current custodian with a lock id associated with a
  * pending custodian change, the `address custodian` member will be updated with the
  * requested address.
  * @param  _lockId  The identifier of a pending change request.
function confirmCustodianChange(bytes32 _lockId) public onlyCustodian {
    custodian = getCustodianChangeReq(_lockId);

    delete custodianChangeReqs[_lockId];

    emit CustodianChangeConfirmed(_lockId, custodian);

Pay attention to the public onlyCustodian modifier

modifier onlyCustodian {
    require(msg.sender == custodian);

This gives “onlyCustodian” the ability to change whatever it wants. Who is this custodian and does it have any limitations?

Open tab “Read contract” and click the link at variable #4: Custodian and open the code tab there


This code is 2/N multisig. There is something interesting about it. In the constructor, it has a special parameter, defaultTimeLock

function Custodian(
    address[] _signers,
    uint256 _defaultTimeLock,
    uint256 _extendedTimeLock,
    address _primary

Open the tab ‘Read Contract’ and check variable #3::


172800 seconds is 172800/60/60=48 hours.

// validate time lock params
require(_defaultTimeLock <= _extendedTimeLock);
defaultTimeLock = _defaultTimeLock;
extendedTimeLock = _extendedTimeLock;
if (request.extended && ((block.timestamp - request.timestamp) < extendedTimeLock)) {
    emit TimeLocked(request.timestamp + extendedTimeLock, _requestMsgHash);
    return false;
} else if ((block.timestamp - request.timestamp) < defaultTimeLock) {
    emit TimeLocked(request.timestamp + defaultTimeLock, _requestMsgHash);
    return false;
} else {

In plain English, it means that this function completeUnlock cannot be completed faster than once per 48h and this parameter can be extended in the future.

Who are this N chosen from this multisig?

Unfortunately, right now etherscan can not show the values of map data structures. You can do it via RPC but that is out of the scope of this short analysis. Alternatively, you can check all data of the transaction on this address and aggregate from it as plain text.

For example, from a transaction on custodian address we can find that there are currently 6 addresses (+ first address is a primary):


In the ABI, the signers parameter should be at the end because it has dynamic size and all other parameters are fixed. There are lots of zeros in these fields but an address is just 20 bytes from the 32-byte field (you can see 24 zeros after addresses — it’s 12 bytes ie. 32–20). By the way, this primary address can extend timeLock.

For your convenience, for further investigation:


In Conclusion:

The custodian can generate infinite amount of tokens, and every 48 hours it can totally change the implementation, making all tokens transferable or pretty much anything else.

But this actually doesn’t matter.

This project has another single point of failure: the company. They can just say one day: “you know what, sorry, we don’t want to change your tokens for dollars anymore.”

You think this is impossible because it’s a big company with a reputation? History has a precedent when the whole country with the largest economy in the world did this in 1971. And here we speak about just a private co which has to follow all the regulations of the U.S government.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.