crypto

Gemini can make GUSD non-transferrable (code review)

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 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 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 https://bitcointalk.org/index.php?topic=5025759.0and 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

The Code overview:

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

crypto

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.

crypto

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

crypto

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::

crypto

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

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. Required fields are marked *

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