Gemini can make GUSD non-transferrable at any moment (code review)


Gemini can make GUSD non-transferrable at any moment (code review)


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

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

The current implementation gives Gemini the ability to completely change the implementation of the token every 48 hours. Despite tokens being freely transferable, custodian can at any moment swap the implementation to make them non-transferrable.


The code of the smart contract is available at

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

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

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:

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 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 smart contract and check variable #3:

172800 seconds is 172000/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 these N chosen from this multisig?

Unfortunately, right now etherscan can’t show values of map data structure. You can do it via rpc but that’s 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 transaction on custodian address we can find that there are currenly 6 addresses (+ first address is a primary):


In the ABI 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 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:









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 any more.”

You think this is impossible because it’s a big company with 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 company which has to follow all the regulations of the US government.

Authors: Alex lebed, Alexey Akhunov

Special thanks: Emil Lerner, James Key


Wonderful review. I think with all Type-I stable coins you have the issue that it is against the law to create a financial instrument that a terrorist or money launderer can use, so the work around is to make sure that users are being KYC’ed and have functions built in to the currency that allow a freezing of assets from a central party. The result is that it looses much of the utility that it requires - why would a DAPP developer pick GUSD for their platform if they cannot prevent their whole platform from being shut down by Gemini?


I can’t confirm that, would be interesting to hear official position about that. How does Makerdao, for example, resolve that while being an American company as well?

can’t confirm it either, so far it’s just ERC20 token so I can totally transfer my tokens to you and now you are an owner of GUSD without being KYC’d. They might have some questions to cash in and cash out but that makes tokens essentially non-fungible. It’s very complicated to do technically. What do you think?

agree here, I see some the utility here but looks like DAI (or SU when we release it) after widespread adoption (sufficient for the ATM/exchanges to sell/buy it directly for fiat) will have a significant advantage.