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

Alex Lebed
Good Audience
Published in
5 min readSep 11, 2018

--

TL;DR

Gemini USDG is a new centralized stablecoin (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 https://etherscan.io/address/0x056fd409e1d7a124bd7017459dfea2f387b6d5cd#code.

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 trustless 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 https://bitcointalk.org/index.php?topic=5025759.0 and https://www.reddit.com/r/ethereum/comments/9eng9i/winklevoss_brothers_launch_ethereum_token_backed/

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: https://etherscan.io/address/0x056fd409e1d7a124bd7017459dfea2f387b6d5cd

Code overview

To see the code you can open the “code” tab on https://etherscan.io/address/0x056fd409e1d7a124bd7017459dfea2f387b6d5cd#code

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

contract LockRequestablecontract CustodianUpgradeable is LockRequestablecontract ERC20ImplUpgradeable is CustodianUpgradeablecontract ERC20Interfacecontract ERC20Proxy is ERC20Interface, ERC20ImplUpgradeablecontract ERC20Impl is CustodianUpgradeablecontract 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:

// PUBLIC FUNCTIONS
// (UPGRADE)

/** @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

// MODIFIERS
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

https://etherscan.io/address/0x9a7b5f6e453d0cda978163cb4a9a88367250a52d#code

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

// CONSTRUCTOR
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 paramsrequire(_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’t show the values of map data structures. 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 a transaction on custodian address https://etherscan.io/tx/0x536cad5dc484a0a2efaff4a60a4d2ed7038bbf6e17c846b95b37c6ddf3fe1b5f we can find that there are currently 6 addresses (+ first address is a primary):

d24400ae8bfebb18ca49be86258a3c749cf468530000000000000000000000000000000000000000000000000000000000000006000000000000000000000000d7c14ebd217ce757041dab619a99d740cda02dc500000000000000000000000035d13b3e90ea179cf572945e36aab8b5443bfc69000000000000000000000000cf269986da781407b0eeeac3ea79ac1c9d857d380000000000000000000000003dfa83b9cb39d88c6dace9744d0f709532082296000000000000000000000000ec426164c0a2f89fa70942ca2499decff306ac5a000000000000000000000000f43c8e5ca6072505e4cc8f74228e4d37740d2221

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:

D24400ae8bfebb18ca49be86258a3c749cf46853
D7c14ebd217ce757041dab619a99d740cda02dc5
35d13b3e90ea179cf572945e36aab8b5443bfc69
Cf269986da781407b0eeeac3ea79ac1c9d857d38
3dfa83b9cb39d88c6dace9744d0f709532082296
Ec426164c0a2f89fa70942ca2499decff306ac5a
f43c8e5ca6072505e4cc8f74228e4d37740d2221

Conclusion

The custodian can generate infinite amount of tokens, and every 48 hours it can totally change the implementation, making all tokens non-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.”

Do 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 company which has to follow all the regulations of the US government.

Authors: Alex Lebed, Alexey Akhunov

Special thanks: Emil Lerner, James Key

p.s. This is only the beginning. If you care about the future of truly decentralized and censorship-resistant monetary systems — please increase awareness by sharing this post. Thanks!

Update. I think it’s necessary to point out that Gemini doesn’t hide this fact. Nowadays this is considered a best practice for evolving smart-contracts, especially for the asset-backed token. And Geimini made an excellent job by explicitly mentioning that it in their whitepaper. The point of this article is to understand such a limitation and be able independently find it in the smart contact.

Update2. I didn’t know how Good Audience medium channel works, so they apparently inset this Raven popup below. I know nothing about this Raven Token. Do your own research!

Connect with the Raven team on Telegram

--

--

Dev @ https://twitter.com/stableunitdao. ex 1inch, Amazon, Facebook. Math degree. Developed $1b+ smart-contracts. Prizewinner eth(Boston/NY/Denver/Lisbon)