Skip to main content

MultiToken

1. Summary​

MultiToken is a solidity library unifying ERC20, ERC721 & ERC1155 transfers by wrapping them into a MultiToken Asset struct.

info

MultiToken supports CryptoKitties.

3. Contract Details​

  • MultiToken.sol contract is written in Solidity version 0.8.0 but can be compiled with any Solidity compiler version 0.8.*

Features​

The library defines a token asset as a struct of token identifiers. It wraps transfer, allowance and balance check calls of the following token standards:

Unifying the function calls used within the PWN context so we don't have to worry about handling these token standards individually.

Functions​

transferAssetFrom

Overview​

Function for transfer calls on various token interfaces.

This function takes two arguments:

  • Asset memoryasset - Asset struct defining all necessary context of a token
  • addresssource - Source address
  • addressdest - Destination address

Implementation​

function transferAssetFrom(Asset memory asset, address source, address dest) internal {
_transferAssetFrom(asset, source, dest, false);
}
safeTransferAssetFrom

Overview​

Function for safe transfer calls on various token interfaces.

This function takes two arguments:

  • Asset memoryasset - Asset struct defining all necessary context of a token
  • addresssource - Source address
  • addressdest - Destination address

Implementation​

function safeTransferAssetFrom(Asset memory asset, address source, address dest) internal {
_transferAssetFrom(asset, source, dest, true);
}
getTransferAmount

Overview​

Getter function to get the maximum amount of the supplied asset that can be transferred.

This function takes one argument:

  • Asset memoryasset - Asset struct defining all necessary context of a token

Implementation​

function getTransferAmount(Asset memory asset) internal pure returns (uint256) {
if (asset.category == Category.ERC20)
return asset.amount;
else if (asset.category == Category.ERC1155 && asset.amount > 0)
return asset.amount;
else // Return 1 for ERC721, CryptoKitties and ERC1155 used as NFTs (amount = 0)
return 1;
}
transferAssetFromCalldata

Overview​

Function for transfer calls on various token interfaces that can also handle calldata.

This function takes two arguments:

  • Asset memoryasset - Asset struct defining all necessary context of a token
  • addresssource - Account/address that should initiate the transfer
  • addressdest - Destination address
  • boolfromSender - Boolean defining if msg.sender is the same as source

Implementation​

function transferAssetFromCalldata(Asset memory asset, address source, address dest, bool fromSender) pure internal returns (bytes memory) {
return _transferAssetFromCalldata(asset, source, dest, fromSender, false);
}
safeTransferAssetFromCalldata

Overview​

Function for safe transfer calls on various token interfaces that can also handle calldata.

This function takes two arguments:

  • Asset memoryasset - Asset struct defining all necessary context of a token
  • addresssource - Account/address that should initiate the transfer
  • addressdest - Destination address
  • boolfromSender - Boolean defining if msg.sender is the same as source

Implementation​

function safeTransferAssetFromCalldata(Asset memory asset, address source, address dest, bool fromSender) pure internal returns (bytes memory) {
return _transferAssetFromCalldata(asset, source, dest, fromSender, true);
}
permit

Overview​

Wrapper function to allow grating approval using permit signature for ERC-20 (see EIP-2612).

This function takes four arguments:

  • Asset memoryasset - Asset struct defining all necessary context of a token
  • addressowner - Address that signed the permit
  • addressspender - Address that is getting the approval to transfer the _asset
  • bytes memorypermitData - The Permit data itself. The data must include the permit deadline (uint256) and permit signature. The signature can be standard (65 bytes) or compact (64 bytes) defined in EIP-2098. Lastly, the deadline and signature should be pack encoded together.

Implementation​

function permit(Asset memory asset, address owner, address spender, bytes memory permitData) internal {
if (asset.category == Category.ERC20) {

// Parse deadline and permit signature parameters
uint256 deadline;
bytes32 r;
bytes32 s;
uint8 v;

// Parsing signature parameters used from OpenZeppelins ECDSA library
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/83277ff916ac4f58fec072b8f28a252c1245c2f1/contracts/utils/cryptography/ECDSA.sol

// Deadline (32 bytes) + standard signature data (65 bytes) -> 97 bytes
if (permitData.length == 97) {
assembly {
deadline := mload(add(permitData, 0x20))
r := mload(add(permitData, 0x40))
s := mload(add(permitData, 0x60))
v := byte(0, mload(add(permitData, 0x80)))
}
}
// Deadline (32 bytes) + compact signature data (64 bytes) -> 96 bytes
else if (permitData.length == 96) {
bytes32 vs;

assembly {
deadline := mload(add(permitData, 0x20))
r := mload(add(permitData, 0x40))
vs := mload(add(permitData, 0x60))
}

s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
v = uint8((uint256(vs) >> 255) + 27);
} else {
revert("MultiToken::Permit: Invalid permit length");
}

// Call permit with parsed parameters
IERC20Permit(asset.assetAddress).permit(owner, spender, asset.amount, deadline, v, r, s);

} else {
// Currently supporting only ERC20 signed approvals via ERC2612
revert("MultiToken::Permit: Unsupported category");
}
}
balanceOf

Overview​

Function for checking balances on various token interfaces.

This function takes two arguments:

  • Asset memoryasset - Asset struct defining all necessary context of a token
  • addresstarget - Target address to be checked

Implementation​

function balanceOf(Asset memory asset, address target) internal view returns (uint256) {
if (asset.category == Category.ERC20) {
return IERC20(asset.assetAddress).balanceOf(target);

} else if (asset.category == Category.ERC721) {
return IERC721(asset.assetAddress).ownerOf(asset.id) == target ? 1 : 0;

} else if (asset.category == Category.ERC1155) {
return IERC1155(asset.assetAddress).balanceOf(target, asset.id);

} else if (asset.category == Category.CryptoKitties) {
return ICryptoKitties(asset.assetAddress).ownerOf(asset.id) == target ? 1 : 0;

} else {
revert("MultiToken: Unsupported category");
}
}
approveAsset

Overview​

Function for approving calls on various token interfaces.

This function takes two arguments:

  • Asset memoryasset - Asset struct defining all necessary context of a token
  • addresstarget - Target address to be checked

Implementation​

function approveAsset(Asset memory asset, address target) internal {
if (asset.category == Category.ERC20) {
IERC20(asset.assetAddress).safeApprove(target, asset.amount);

} else if (asset.category == Category.ERC721) {
IERC721(asset.assetAddress).approve(target, asset.id);

} else if (asset.category == Category.ERC1155) {
IERC1155(asset.assetAddress).setApprovalForAll(target, true);

} else if (asset.category == Category.CryptoKitties) {
ICryptoKitties(asset.assetAddress).approve(target, asset.id);

} else {
revert("MultiToken: Unsupported category");
}
}
isValid

Overview​

A view function to check the ID and amount values are valid for the asset category in the provided asset.

This function takes one argument:

  • Asset memoryasset - Asset struct defining all necessary context of a token

Implementation​

function isValid(Asset memory asset) internal view returns (bool) {
if (asset.category == Category.ERC20) {
// Check format
if (asset.id != 0)
return false;

// ERC20 has optional ERC165 implementation
if (asset.assetAddress.supportsERC165()) {
// If ERC20 implements ERC165, it has to return true for its interface id
return asset.assetAddress.supportsERC165InterfaceUnchecked(ERC20_INTERFACE_ID);

} else {
// In case token doesn't implement ERC165, its safe to assume that provided category is correct,
// because any other category have to implement ERC165.

// Check that asset address is contract
// Tip: asset address will return code length 0, if this code is called from the asset constructor
return asset.assetAddress.code.length > 0;
}

} else if (asset.category == Category.ERC721) {
// Check format
if (asset.amount != 0)
return false;

// Check it's ERC721 via ERC165
return asset.assetAddress.supportsInterface(ERC721_INTERFACE_ID);

} else if (asset.category == Category.ERC1155) {
// Check it's ERC1155 via ERC165
return asset.assetAddress.supportsInterface(ERC1155_INTERFACE_ID);

} else if (asset.category == Category.CryptoKitties) {
// Check format
if (asset.amount != 0)
return false;

// Check it's CryptoKitties via ERC165
return asset.assetAddress.supportsInterface(CRYPTO_KITTIES_INTERFACE_ID);

} else {
revert("MultiToken: Unsupported category");
}
}
isSameAs

Overview​

A view function to compare two assets (ignoring their amounts). Returns true if the assets are the same.

This function takes two arguments:

  • Asset memoryasset - Asset struct defining the first token to compare
  • Asset memoryotherAsset - Asset struct defining the second token to compare

Implementation​

function isSameAs(Asset memory asset, Asset memory otherAsset) internal pure returns (bool) {
return
asset.category == otherAsset.category &&
asset.assetAddress == otherAsset.assetAddress &&
asset.id == otherAsset.id;
}

Asset struct​

Each asset is defined by the Asset struct and has the following properties:

TypeNameComment
MultiToken.CategorycategoryCorresponding asset category
addressassetAddressAddress of the token contract defining the asset
uint256idTokenID of an NFT or 0
uint256amountAmount of fungible tokens or 0 -> 1

Events​

The MultiToken library does not define any events or custom errors.