Extendable token proxy
The ExtendableTokenProxy
is the smart contract responsible for managing both the EIP1967 Proxy logic as well as the extension registration/execution logic. This contract also has functions to manage roles through the TokenRoles
contract.
This is the contract you must inherit from to build custom token implementations. The repo comes with both ERC20.sol
and ERC721.sol
implementations.
Building a custom token¶
To build a custom token implementation, you must inherit from the ExtendableTokenProxy
contract. The ExtendableTokenProxy
contract requires that you:
- Invoke its constructor with the following arguments:
- ABI encoded initialization data to send to logic contract’s
_onInitialize
function. This data usually comes from the constructor arguments.
- ABI encoded initialization data to send to logic contract’s
- Protect any token standard functions so extensions don’t override them with their own functions:
- This can be done with the
protectFunction(bytes4)
function or theprotectFunctions(bytes4[])
function.
- This can be done with the
- Implement the
function _domainName() internal override view returns(bytes memory)
function:- This function is used for building the EIP712 domain separator and should return a unique bytes. For example this can be the token name.
- Implement the
function tokenStandard() external pure override returns (TokenStandard)
:- This function should return the token standard this contract implements.
- This function is a pure function, so it shouldn’t rely on storage or any input for its return value.
Example ERC20 implementation¶
pragma solidity ^0.8.0;
import {ExtendableTokenProxy} from "@consensys-software/UniversalToken-extendable/contracts/tokens/proxy/ExtendableTokenProxy.sol";
contract CustomERC20 is ExtendableTokenProxy {
constructor(
string memory _name,
string memory _symbol,
address _owner,
address _logicAddress
)
ExtendableTokenProxy(
abi.encode(_name, _symbol),
_logicAddress,
_owner
)
{
// Allocate an array of function selectors to protect
bytes4[] memory _protectedFunctions = new bytes4[](6);
_protectedFunctions[0] = IERC20.totalSupply.selector;
_protectedFunctions[1] = IERC20.balanceOf.selector;
_protectedFunctions[2] = IERC20.transfer.selector;
_protectedFunctions[3] = IERC20.transferFrom.selector;
_protectedFunctions[4] = IERC20.approve.selector;
_protectedFunctions[5] = IERC20.allowance.selector;
// Mark the ERC20 standard functions as protected, meaning extensions cannot override them
_protectFunctions(_protectedFunctions);
// ... constructor logic ...
//Update the doamin seperator now that
//we've setup everything
_updateDomainSeparator();
}
/**
* @notice This Token Proxy supports the ERC20 standard
* @dev This value does not change, will always return TokenStandard.ERC20
* @return The name of this Token standard
*/
function tokenStandard() external pure override returns (TokenStandard) {
return TokenStandard.ERC20;
}
/**
* @dev The domain name of this ERC20 Token Proxy will be the ERC20 Token name().
* This value does not change.
* @return The domain name of this ERC20 Token Proxy
*/
function _domainName()
internal
view
virtual
override
returns (bytes memory)
{
// TODO You could also grab the name using the name() function
return abi.encode("TokenName");
}