Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

ERC20BridgeProxy #2220

Merged
merged 11 commits into from
Oct 1, 2019
Merged

ERC20BridgeProxy #2220

merged 11 commits into from
Oct 1, 2019

Conversation

dorothy-zbornak
Copy link
Contributor

@dorothy-zbornak dorothy-zbornak commented Sep 27, 2019

Description

Say hello to the new ERC20BridgeProxy!

Overview

The ERC20BridgeProxy essentially does just two things:

  1. Call withdrawTo() on an instance of the IERC20Bridge contract.
  2. Ensure that the token balance of to has increased by amount after the call.

It is up to the IERC20Bridge contract to actually perform the transfer. If the bridge contract needs the taker funds in order to complete the transfer (like for Eth2Dai/Uniswap bridges), the order should be constructed with maker = IERC20Bridge.

AssetData

abi.encodeWithSelector(
    // bytes4(keccak256("ERC20BridgeProxy(address,address,bytes)"))
    ERC20BridgeProxy(0).PROXY_ID,
    // The address of the ERC20 token that will be transferred from `from` to `to`
    address tokenAddress,
    // The address of an instance of the `IERC20Bridge` contract that will handle
    // the transfer.
    address bridgeAddress,
    // Arbitrary data needed by the bridge contract.
    bytes bridgeData
)

IERC20Bridge

A valid bridge contract only has to export one function:

/// @dev Transfers `amount` of the ERC20 `tokenAddress` from `from` to `to`.
/// @param tokenAddress The address of the ERC20 token to transfer.
/// @param from Address to transfer asset from.
/// @param to Address to transfer asset to.
/// @param amount Amount of asset to transfer.
/// @param bridgeData Arbitrary asset data needed by the bridge contract.
/// @return success The magic bytes `0xb5d40d78` if successful.
function transfer(
    address tokenAddress,
    address from,
    address to,
    uint256 amount,
    bytes calldata bridgeData
)
    external
    returns (bytes4 success);

Testing instructions

Types of changes

Checklist:

  • Prefix PR title with [WIP] if necessary.
  • Add tests to cover changes as needed.
  • Update documentation as needed.
  • Add new entries to the relevant CHANGELOG.jsons.

Addresses 0xProject/ZEIPs/issues/47

@dorothy-zbornak dorothy-zbornak changed the title Feat/erc20 bridge/hello world ERC20BridgeProxy Sep 27, 2019
@dorothy-zbornak dorothy-zbornak marked this pull request as ready for review September 27, 2019 23:47
@coveralls
Copy link

coveralls commented Sep 27, 2019

Coverage Status

Coverage remained the same at 75.286% when pulling 6da48be on feat/erc20-bridge/hello-world into d21f978 on 3.0.

@dorothy-zbornak dorothy-zbornak mentioned this pull request Sep 28, 2019
4 tasks
Copy link
Member

@abandeali1 abandeali1 left a comment

Choose a reason for hiding this comment

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

So excited for this!

using LibSafeMath for uint256;

// @dev Result of a successful bridge call.
bytes4 constant public BRIDGE_SUCCESS = 0xb5d40d78;
Copy link
Member

Choose a reason for hiding this comment

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

How was this derived?

Copy link
Contributor Author

@dorothy-zbornak dorothy-zbornak Sep 28, 2019

Choose a reason for hiding this comment

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

crypto.randomBytes(4) :trollface:

Copy link
Member

Choose a reason for hiding this comment

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

Probably better to add some meaning to them. Maybe just reuse the PROXY_ID?

/// @param to Address to transfer asset to.
/// @param amount Amount of asset to transfer.
/// @return success The magic bytes `0xb5d40d78` if successful.
function transfer(
Copy link
Member

Choose a reason for hiding this comment

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

Some nits about the signature of this function:

transferFrom is more descriptive than transfer. It's somewhat awkward that this would be the 3rd different transferFrom used in the proxies, so maybe there is a better name... but I don't love transfer.

I also think bridgeData should be the last parameter. It is optional and probably won't be used in all bridge contracts. Making it the last parameter is consistent with all of the token standards that have a similar parameter in their own transfer functions.

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 would like to avoid transferFrom because it makes it really hard to explain this pattern to people (transferFrom calls transferFrom calls transferFrom). Maybe convertFrom? exchangeFrom? bridgeFrom? swapFrom? settleFrom? I'm at a loss.

Copy link
Member

Choose a reason for hiding this comment

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

withdrawTo perhaps?

require(success == BRIDGE_SUCCESS, "BRIDGE_FAILED");
// Ensure that the balance of `to` has increased by at least `amount`.
require(
balanceBefore.safeAdd(amount) <= balanceOf(tokenAddress, to),
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I feel like balanceOf(tokenAddress, to) >= balanceBefore.safeAdd(amount) is slightly more intuitive. Not a huge deal either way.

view
returns (uint256 balance)
{
(address tokenAddress, ,) = abi.decode(
Copy link
Member

Choose a reason for hiding this comment

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

This can be made more efficient with:

(address tokenAddress) = abi.decode(
    assetData.sliceDestructive(4, assetData.length),
    (address)
);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I was trying to sneak in some extra asset data validation this way, but I guess since it's not checking the selector, it's not even doing that very well.

// @dev Result of a successful bridge call.
bytes4 constant public BRIDGE_SUCCESS = 0xb5d40d78;
// @dev Id of this proxy.
// bytes4(keccak256("ERC20BridgeProxy(address,address,bytes)"))
Copy link
Member

Choose a reason for hiding this comment

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

This should probably use ERC20Bridge rather than ERC20BridgeProxy to be consistent with the other ids. We should also add an entry for this to IAssetData.

…C20Bridge.withdrawTo()`.

`@0x/contracts-asset-proxy`: Make `bridgeData` last parameter in `IERC20Bridge.withdrawTo()`.
`@0x/contracts-asset-proxy`: Reuse `PROXY_ID` as `BRIDGE_SUCCESS`.
@jalextowle jalextowle self-assigned this Sep 30, 2019
Copy link
Contributor

@jalextowle jalextowle left a comment

Choose a reason for hiding this comment

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

This is looking great! I just had a few nits on tests, and then this looks good to go

} from '../src';

blockchainTests.resets('ERC20BridgeProxy unit tests', env => {
const PROXY_ID = '0xdc1600f3';
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should add this to AssetProxyId in packages/types/

return (logs as any) as DecodedLogs;
}

it('succeeds if the bridge succeeds and balance increases', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit:

Suggested change
it('succeeds if the bridge succeeds and balance increases', async () => {
it('succeeds if the bridge succeeds and balance increases by `amount`', async () => {

return expect(tx).to.revertWith(revertError);
});

it('fails if balance of `to` increases by `amount - 1`', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit:

Suggested change
it('fails if balance of `to` increases by `amount - 1`', async () => {
it('fails if balance of `to` increases by less than `amount`', async () => {

For consistency with the above tests.

return expect(tx).to.revertWith('BRIDGE_UNDERPAY');
});

it('fails if balance of `to` decreases', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

🙏

return expect(tx).to.revertWith('BRIDGE_FAILED');
});

it('fails if bridge is an EOA', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!

Copy link
Contributor

@hysz hysz left a comment

Choose a reason for hiding this comment

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

looks great +1! Please hit http://merge.it (~‾▿‾)~

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants