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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions contracts/asset-proxy/CHANGELOG.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
{
"note": "Remove unused dependency on IAuthorizable in IAssetProxy",
"pr": 1910
},
{
"note": "Add `ERC20BridgeProxy`",
"pr": 2220
}
]
},
Expand Down
126 changes: 126 additions & 0 deletions contracts/asset-proxy/contracts/src/ERC20BridgeProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*

Copyright 2019 ZeroEx Intl.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/

pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;

import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "@0x/contracts-utils/contracts/src/Authorizable.sol";
import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
import "./interfaces/IAssetProxy.sol";
import "./interfaces/IERC20Bridge.sol";


contract ERC20BridgeProxy is
IAssetProxy,
Authorizable
{
using LibBytes for bytes;
using LibSafeMath for uint256;

// @dev Id of this proxy. Also the result of a successful bridge call.
// bytes4(keccak256("ERC20Bridge(address,address,bytes)"))
bytes4 constant private PROXY_ID = 0xdc1600f3;

/// @dev Calls a bridge contract to transfer `amount` of ERC20 from `from`
/// to `to`. Asserts that the balance of `to` has increased by `amount`.
/// @param assetData Abi-encoded data for this asset proxy encoded as:
/// abi.encodeWithSelector(
/// bytes4 PROXY_ID,
/// address tokenAddress,
/// address bridgeAddress,
/// bytes bridgeData
/// )
/// @param from Address to transfer asset from.
/// @param to Address to transfer asset to.
/// @param amount Amount of asset to transfer.
function transferFrom(
bytes calldata assetData,
address from,
address to,
uint256 amount
)
external
onlyAuthorized
{
// Extract asset data fields.
(
address tokenAddress,
address bridgeAddress,
bytes memory bridgeData
) = abi.decode(
assetData.sliceDestructive(4, assetData.length),
(address, address, bytes)
);

// Remember the balance of `to` before calling the bridge.
uint256 balanceBefore = balanceOf(tokenAddress, to);
// Call the bridge, who should transfer `amount` of `tokenAddress` to
// `to`.
bytes4 success = IERC20Bridge(bridgeAddress).withdrawTo(
tokenAddress,
from,
to,
amount,
bridgeData
);
// Bridge must return the proxy ID to indicate success.
require(success == PROXY_ID, "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.

"BRIDGE_UNDERPAY"
);
}

/// @dev Gets the proxy id associated with this asset proxy.
/// @return proxyId The proxy id.
function getProxyId()
external
pure
returns (bytes4 proxyId)
{
return PROXY_ID;
}

/// @dev Retrieves the balance of `owner` for this asset.
/// @return balance The balance of the ERC20 token being transferred by this
/// asset proxy.
function balanceOf(bytes calldata assetData, address owner)
external
view
returns (uint256 balance)
{
(address tokenAddress) = abi.decode(
assetData.sliceDestructive(4, assetData.length),
(address)
);
return balanceOf(tokenAddress, owner);
}

/// @dev Retrieves the balance of `owner` given an ERC20 address.
/// @return balance The balance of the ERC20 token for `owner`.
function balanceOf(address tokenAddress, address owner)
private
view
returns (uint256 balance)
{
return IERC20Token(tokenAddress).balanceOf(owner);
}
}
11 changes: 11 additions & 0 deletions contracts/asset-proxy/contracts/src/interfaces/IAssetData.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,15 @@ interface IAssetData {
bytes32 expectedReturnDataHash
)
external;

/// @dev Function signature for encoding ERC20Bridge assetData.
/// @param tokenAddress Address of token to transfer.
/// @param bridgeAddress Address of the bridge contract.
/// @param bridgeData Arbitrary data to be passed to the bridge contract.
function ERC20Bridge(
address tokenAddress,
address bridgeAddress,
bytes calldata bridgeData
)
external;
}
43 changes: 43 additions & 0 deletions contracts/asset-proxy/contracts/src/interfaces/IERC20Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*

Copyright 2019 ZeroEx Intl.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/

pragma solidity ^0.5.9;


contract IERC20Bridge {

// @dev Result of a successful bridge call.
bytes4 constant internal BRIDGE_SUCCESS = 0xdc1600f3;

/// @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 `0x37708e9b` if successful.
function withdrawTo(
address tokenAddress,
address from,
address to,
uint256 amount,
bytes calldata bridgeData
)
external
returns (bytes4 success);
}
108 changes: 108 additions & 0 deletions contracts/asset-proxy/contracts/test/TestERC20Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*

Copyright 2019 ZeroEx Intl.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/

pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;

import "../src/interfaces/IERC20Bridge.sol";


/// @dev Test bridge token
contract TestERC20BridgeToken {
mapping (address => uint256) private _balances;

function addBalance(address owner, int256 amount)
external
{
setBalance(owner, uint256(int256(balanceOf(owner)) + amount));
}

function setBalance(address owner, uint256 balance)
public
{
_balances[owner] = balance;
}

function balanceOf(address owner)
public
view
returns (uint256)
{
return _balances[owner];
}
}


/// @dev Test bridge contract.
contract TestERC20Bridge is
IERC20Bridge
{
TestERC20BridgeToken public testToken;

event BridgeWithdrawTo(
address tokenAddress,
address from,
address to,
uint256 amount,
bytes bridgeData
);

constructor() public {
testToken = new TestERC20BridgeToken();
}

function setTestTokenBalance(address owner, uint256 balance)
external
{
testToken.setBalance(owner, balance);
}

function withdrawTo(
address tokenAddress,
address from,
address to,
uint256 amount,
bytes calldata bridgeData
)
external
returns (bytes4)
{
emit BridgeWithdrawTo(
tokenAddress,
from,
to,
amount,
bridgeData
);
// Unpack the bridgeData.
(
int256 transferAmount,
bytes memory revertData,
bytes memory returnData
) = abi.decode(bridgeData, (int256, bytes, bytes));

// If `revertData` is set, revert.
if (revertData.length != 0) {
assembly { revert(add(revertData, 0x20), mload(revertData)) }
}
// Increase `to`'s balance by `transferAmount`.
TestERC20BridgeToken(tokenAddress).addBalance(to, transferAmount);
// Return `returnData`.
assembly { return(add(returnData, 0x20), mload(returnData)) }
}
}
2 changes: 1 addition & 1 deletion contracts/asset-proxy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"compile:truffle": "truffle compile"
},
"config": {
"abis": "./generated-artifacts/@(ERC1155Proxy|ERC20Proxy|ERC721Proxy|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|MixinAssetProxyDispatcher|MixinAuthorizable|MultiAssetProxy|Ownable|StaticCallProxy|TestStaticCallTarget).json",
"abis": "./generated-artifacts/@(ERC1155Proxy|ERC20BridgeProxy|ERC20Proxy|ERC721Proxy|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|IERC20Bridge|MixinAssetProxyDispatcher|MixinAuthorizable|MultiAssetProxy|Ownable|StaticCallProxy|TestERC20Bridge|TestStaticCallTarget).json",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
},
"repository": {
Expand Down
6 changes: 6 additions & 0 deletions contracts/asset-proxy/src/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,27 @@
import { ContractArtifact } from 'ethereum-types';

import * as ERC1155Proxy from '../generated-artifacts/ERC1155Proxy.json';
import * as ERC20BridgeProxy from '../generated-artifacts/ERC20BridgeProxy.json';
import * as ERC20Proxy from '../generated-artifacts/ERC20Proxy.json';
import * as ERC721Proxy from '../generated-artifacts/ERC721Proxy.json';
import * as IAssetData from '../generated-artifacts/IAssetData.json';
import * as IAssetProxy from '../generated-artifacts/IAssetProxy.json';
import * as IAssetProxyDispatcher from '../generated-artifacts/IAssetProxyDispatcher.json';
import * as IAuthorizable from '../generated-artifacts/IAuthorizable.json';
import * as IERC20Bridge from '../generated-artifacts/IERC20Bridge.json';
import * as MixinAssetProxyDispatcher from '../generated-artifacts/MixinAssetProxyDispatcher.json';
import * as MixinAuthorizable from '../generated-artifacts/MixinAuthorizable.json';
import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json';
import * as Ownable from '../generated-artifacts/Ownable.json';
import * as StaticCallProxy from '../generated-artifacts/StaticCallProxy.json';
import * as TestERC20Bridge from '../generated-artifacts/TestERC20Bridge.json';
import * as TestStaticCallTarget from '../generated-artifacts/TestStaticCallTarget.json';
export const artifacts = {
MixinAssetProxyDispatcher: MixinAssetProxyDispatcher as ContractArtifact,
MixinAuthorizable: MixinAuthorizable as ContractArtifact,
Ownable: Ownable as ContractArtifact,
ERC1155Proxy: ERC1155Proxy as ContractArtifact,
ERC20BridgeProxy: ERC20BridgeProxy as ContractArtifact,
ERC20Proxy: ERC20Proxy as ContractArtifact,
ERC721Proxy: ERC721Proxy as ContractArtifact,
MultiAssetProxy: MultiAssetProxy as ContractArtifact,
Expand All @@ -31,5 +35,7 @@ export const artifacts = {
IAssetProxy: IAssetProxy as ContractArtifact,
IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact,
IAuthorizable: IAuthorizable as ContractArtifact,
IERC20Bridge: IERC20Bridge as ContractArtifact,
TestERC20Bridge: TestERC20Bridge as ContractArtifact,
TestStaticCallTarget: TestStaticCallTarget as ContractArtifact,
};
3 changes: 3 additions & 0 deletions contracts/asset-proxy/src/wrappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@
* -----------------------------------------------------------------------------
*/
export * from '../generated-wrappers/erc1155_proxy';
export * from '../generated-wrappers/erc20_bridge_proxy';
export * from '../generated-wrappers/erc20_proxy';
export * from '../generated-wrappers/erc721_proxy';
export * from '../generated-wrappers/i_asset_data';
export * from '../generated-wrappers/i_asset_proxy';
export * from '../generated-wrappers/i_asset_proxy_dispatcher';
export * from '../generated-wrappers/i_authorizable';
export * from '../generated-wrappers/i_erc20_bridge';
export * from '../generated-wrappers/mixin_asset_proxy_dispatcher';
export * from '../generated-wrappers/mixin_authorizable';
export * from '../generated-wrappers/multi_asset_proxy';
export * from '../generated-wrappers/ownable';
export * from '../generated-wrappers/static_call_proxy';
export * from '../generated-wrappers/test_erc20_bridge';
export * from '../generated-wrappers/test_static_call_target';
Loading