The MultiAssetProxy
was first proposed in ZEIP-23. Please refer to the ZEIP for information and discussion about how this contract works at a higher level.
The MultiAssetProxy
can transfer arbitrary bundles of assets in a single smart contract call. It expects a values
(uint256
array) and a nestedAssetData
(array of assetData
byte arrays) to be encoded within its own assetData
. Each element of values
corresponds to an element at the same index of nestedAssetData
. The MultiAssetProxy
will multiply each values
element by the amount
passed into MultiAssetProxy.transferFrom
and then dispatch the corresponding element of nestedAssetProxy
to the relevant AssetProxy
contract with the resulting totalAmount
. This contract does not perform any transferFrom
calls to assets directly and therefore does not require any additional user approvals.
This contract may dispatch transfers to other AssetProxy
contracts if its transferFrom
method is called from an authorized address.
/// @dev Transfers assets. Either succeeds or throws.
/// @param assetData Byte array encoded for the respective asset proxy.
/// @param from Address to transfer asset from.
/// @param to Address to transfer asset to.
/// @param amount Amount of asset to transfer.
function transferFrom(
bytes assetData,
address from,
address to,
uint256 amount
)
external;
Calling MultiAssetProxy.transferFrom
will perform the following steps:
- Decode
values
andnestedAssetData
fromassetData
- For each element of
values
, given indexi
:- Multiply
values[i]
byamount
, resulting in ascaledValue
- Decode an
assetProxyId
fromnestedAssetData[i]
- Load an
assetProxyAddress
that corresponds to theassetProxyId
- Call
AssetProxy(assetProxyAddress).transferFrom(nestedAssetData[i], from, to, scaledValue)
- Revert if the call was unsuccessful
- Multiply
The transferFrom
method may revert with the following errors:
Error | Condition |
---|---|
StandardError("SENDER_NOT_AUTHORIZED") | msg.sender has not been authorized |
StandardError("INVALID_ASSET_DATA_LENGTH") | The assetData is shorter than 68 bytes or is not a multiple of 32 (exluding the 4 byte id) |
StandardError("INVALID_ASSET_DATA_END") | The offset to assetData points to outside the end of calldata |
StandardError("LENGTH_MISMATCH") | The lengths of values and nestedAssetData are not equal |
StandardError("UINT256_OVERFLOW) | The multiplication of an element of values and amount resulted in an overflow |
StandardError("LENGTH_GREATER_THAN_3_REQUIRED") | An element of nestedAssetData is shorter than 4 bytes |
StandardError("ASSET_PROXY_DOES_NOT_EXIST") | No AssetProxy contract exists for the given assetProxyId of an element of nestedAssetData |
* | This contract will rethrow any revert data received from an unsuccessful call of an AssetProxy contract |
This contract expects MultiAsset assetData
to be encoded using ABIv2 with the following function signature. The id of this contract is 0x94cfcdd7
, which can be calculated as the 4 byte function selector of the same signature.
/// @dev Function signature for encoding MultiAsset assetData.
/// @param values Array of values that correspond to each asset to be transferred.
/// Note that each value will be multiplied by the amount being filled in the order before transferring.
/// @param nestedAssetData Array of assetData fields that will be be dispatched to their correspnding AssetProxy contract.
function MultiAsset(
uint256[] calldata values,
bytes[] calldata nestedAssetData
)
external;
In Solidity, this data can be encoded with:
bytes memory data = abi.encodeWithSelector(
0x94cfcdd7,
values,
nestedAssetData
);
Each element of nestedAssetData
must be encoded according to the specification of the corresponding AssetProxy
contract. Note that initially, the MultiAssetProxy
will not support dispatching a transfer to itself.
The MultiAssetProxy
has the following interface for managing which addresses are allowed to call this contract's transferFrom
method. These authorization functions can only be called by the contract's owner
(currently, the ZeroExGovernor
contract).
contract IAuthorizable {
/// @dev Gets all authorized addresses.
/// @return Array of authorized addresses.
function getAuthorizedAddresses()
external
view
returns (address[]);
/// @dev Authorizes an address.
/// @param target Address to authorize.
function addAuthorizedAddress(address target)
external;
/// @dev Removes authorizion of an address.
/// @param target Address to remove authorization from.
function removeAuthorizedAddress(address target)
external;
/// @dev Removes authorizion of an address.
/// @param target Address to remove authorization from.
/// @param index Index of target in authorities array.
function removeAuthorizedAddressAtIndex(
address target,
uint256 index
)
external;
}
The contracts that are currently authorized to call the MultiAssetProxy
contract's transferFrom
method are:
The MultiAssetProxy
can only dispatch transfers to other AssetProxy
contracts that are registered within this contract. The MultiAssetProxy
has the following interface for managing which AssetProxy
contracts it is allowed to call. New registrations can only be initiated by the owner
of this contract (currently, the ZeroExGovernor
contract).
contract IAssetProxyDispatcher {
// Logs registration of new asset proxy
event AssetProxyRegistered(
bytes4 id, // Id of new registered AssetProxy.
address assetProxy // Address of new registered AssetProxy.
);
/// @dev Registers an asset proxy to its asset proxy id.
/// Once an asset proxy is registered, it cannot be unregistered.
/// @param assetProxy Address of new asset proxy to register.
function registerAssetProxy(address assetProxy)
external;
/// @dev Gets an asset proxy.
/// @param assetProxyId Id of the asset proxy.
/// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered.
function getAssetProxy(bytes4 assetProxyId)
external
view
returns (address);
}
The AssetProxy
contracts that are currently registered withing the MultiAssetProxy
are: