Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V2.1 #317

Merged
merged 6 commits into from
Jan 25, 2021
Merged

V2.1 #317

merged 6 commits into from
Jan 25, 2021

Conversation

petejkim
Copy link
Contributor

@petejkim petejkim commented Dec 22, 2020

Changes:

  1. Moves locked USDC to a "lost and found" address and blacklists itself so that accidental sends will no longer be possible.
  2. Conform to EIP-3009 – Adds receiveWithAuthorization function, removes {approve,increaseAllowance,decreaseAllowance}WithAuthorization functions. According to the block explorer, only transferWithAuthorization and transferMultipleWithAuthorizations have been used so far (139 transactions) on the Mainnet, so it should be safe to remove the said functions.

*/
mapping(address => mapping(bytes32 => AuthorizationState))
private _authorizationStates;
Copy link
Contributor Author

@petejkim petejkim Dec 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've verified that this change is safe and backwards-compatible. EVM treats 0 as false and non-zero values as true.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the storage? Does solidity do any packing, or does it promise to always use a full slot for mappings? Thinking of what would happen if the old value was 2, but maybe its only reading the least significant bit for a bool for example.

What did you do to verify? Have you tried out an upgrade manually? This is hard to test, since the tests won't have access to the old code, so I want to be really sure.

Copy link
Contributor Author

@petejkim petejkim Dec 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it manually. Mappings always use full slots unless struct values are used, and the position is determined via a keccak256 hash of the key (k) and the position of the mapping itself (p): slot(authorizationStates[k1][k2]) = keccak256(k2 . keccak256(k1 . p). I also tested the casting via calling SLOAD directly:

pragma solidity 0.6.12;

contract Foo {
    enum AuthorizationState { Unused, Used, Canceled }
    
    AuthorizationState public a = AuthorizationState.Canceled;
    
    function authorizationState() external view returns (AuthorizationState) {
        return a; // returns 2
    }
    
    function authorizationStateBool() external view returns (bool) {
        bool x;
        
        assembly {
            x := sload(0)
        }
        return x; // returns true
    }
}


/**
* @title FiatToken V2
* @notice ERC20 Token backed by fiat reserves, version 2
*/
contract FiatTokenV2 is FiatTokenV1_1, GasAbstraction, Permit {
bool internal _initializedV2;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've verified that this change is safe and backwards compatible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, are you sure solidity didn't pack the storage that might no be compatible with the new type? How did you verify that it is safe?

Copy link
Contributor Author

@petejkim petejkim Dec 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uint8 will occupy the exact same number of bytes (1 byte) in the storage in the exact same position, and the previous value of true will become 1.

*/
mapping(address => mapping(bytes32 => AuthorizationState))
private _authorizationStates;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the storage? Does solidity do any packing, or does it promise to always use a full slot for mappings? Thinking of what would happen if the old value was 2, but maybe its only reading the least significant bit for a bool for example.

What did you do to verify? Have you tried out an upgrade manually? This is hard to test, since the tests won't have access to the old code, so I want to be really sure.


/**
* @title FiatToken V2
* @notice ERC20 Token backed by fiat reserves, version 2
*/
contract FiatTokenV2 is FiatTokenV1_1, GasAbstraction, Permit {
bool internal _initializedV2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, are you sure solidity didn't pack the storage that might no be compatible with the new type? How did you verify that it is safe?

if (lockedAmount > 0) {
_transfer(address(this), lostAndFound, lockedAmount);
}
blacklisted[address(this)] = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we emit a Blacklisted event?

Copy link
Contributor Author

@petejkim petejkim Dec 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't think it was necessary, also was worried that some unsophisticated people may report it as CENTRE blacklisting yet another address and cause some false alarm.

await expectRevert(
fiatToken.initializeV2_1(lostAndFound, { from: fiatTokenOwner })
);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shoudl we have some basic transfer/mint tests to/from other addresses to make sure they still work after the contract is blacklisted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I can add those.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added: 7e49288

*/
function authorizationState(address authorizer, bytes32 nonce)
external
view
returns (AuthorizationState)
returns (bool)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a contract was compiled against the old version, what does this map to in terms of the enum? Does true get interpreted as Used? which would be nice

Copy link
Contributor Author

@petejkim petejkim Dec 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enum gets compiled to an integer with possible values of 0, 1, and 2. When read as "bool", the value of 0 gets interpreted as false and others get interpreted as true.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that makes sense. I was thinking of the inverse - when a bool comes back but the caller reads it as an AuthorizationState. I think if true comes back, it should be as 1, which looks like it would map to AuthorizationState.Used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eztierney Yes that's correct:

pragma solidity 0.6.12;

interface Legacy {
    enum AuthorizationState { Unused, Used, Canceled }

    function authorizationState() external view returns (AuthorizationState);
}

contract New {
    bool public state = true;

    function authorizationState() external view returns (bool) {
        return state;
    }
    
    function legacyAuthorizationState() external view returns (Legacy.AuthorizationState) {
        Legacy.AuthorizationState x = Legacy(address(this)).authorizationState();
        
        return x; // returns 1 (Legacy.AuthorizationState.Used)
    }
}

@petejkim petejkim merged commit db0feab into circlefin:master Jan 25, 2021
@petejkim petejkim deleted the v2.1 branch January 25, 2021 20:42
@petejkim petejkim restored the v2.1 branch January 25, 2021 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants