Skip to content

Commit

Permalink
feat: separating deployLockbox and deployXERC20 (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
excaliborr authored May 26, 2023
1 parent 36c6b4e commit d4d2c46
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 142 deletions.
1 change: 1 addition & 0 deletions solidity/contracts/XERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ contract XERC20 is ERC20, Ownable, IXERC20, ERC20Permit {
bool _receiverIsBridge = bridges[_to].isBridge;

if (_senderIsBridge && _receiverIsBridge) {
_spendAllowance(_from, msg.sender, _amount);
_mintWithCaller(msg.sender, msg.sender, _amount);
_burnWithCaller(_to, msg.sender, _amount);

Expand Down
72 changes: 35 additions & 37 deletions solidity/contracts/XERC20Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,65 +26,50 @@ contract XERC20Factory is IXERC20Factory {
EnumerableSet.AddressSet internal _xerc20RegistryArray;

/**
* @notice Deploys an XERC20 contract using CREATE2
* @notice Deploys an XERC20 contract using CREATE3
* @dev _limits and _minters must be the same length
* @param _name The name of the token
* @param _symbol The symbol of the token
* @param _minterLimits The array of limits that you are adding (optional, can be an empty array)
* @param _burnerLimits The array of limits that you are adding (optional, can be an empty array)
* @param _bridges The array of bridges that you are adding (optional, can be an empty array)
* @param _baseToken The address of the base ERC20 token if you are deploying a lockbox (optional, put address(0) if you dont want to deploy one)
*/

function deploy(
function deployXERC20(
string memory _name,
string memory _symbol,
uint256[] memory _minterLimits,
uint256[] memory _burnerLimits,
address[] memory _bridges,
address _baseToken
) external returns (address _xerc20, address _lockbox) {
if (_baseToken != address(0)) {
bytes32 _salt = keccak256(abi.encodePacked(_name, _symbol, msg.sender));
address _preComputedXERC20 = CREATE3.getDeployed(_salt);
_lockbox = _deployLockbox(_preComputedXERC20, _baseToken);
}

_xerc20 = _deployXERC20(_name, _symbol, _minterLimits, _burnerLimits, _bridges, _lockbox);
address[] memory _bridges
) external returns (address _xerc20) {
_xerc20 = _deployXERC20(_name, _symbol, _minterLimits, _burnerLimits, _bridges);

emit XERC20Deployed(_xerc20, _lockbox);
emit XERC20Deployed(_xerc20);
}

/**
* @notice Deploys an XERC20Lockbox contract using CREATE3
*
* @param _xerc20 The address of the xerc20 that you want to deploy a lockbox for
* @param _baseToken The address of the base token that you want to lock
* @param _isNative Whether or not the base token is native
*/

function deployLockbox(address _xerc20, address _baseToken) external returns (address _lockbox) {
if (_baseToken == address(0) || !EnumerableSet.contains(_xerc20RegistryArray, _xerc20)) {
revert IXERC20Factory_BadTokenAddress();
}
function deployLockbox(
address _xerc20,
address _baseToken,
bool _isNative
) external returns (address payable _lockbox) {
if (_baseToken == address(0) && !_isNative) revert IXERC20Factory_BadTokenAddress();

if (XERC20(_xerc20).owner() != msg.sender) revert IXERC20Factory_NotOwner();
if (lockboxRegistry[_xerc20] != address(0)) revert IXERC20Factory_LockboxAlreadyDeployed();

_lockbox = _deployLockbox(_xerc20, _baseToken);
_lockbox = _deployLockbox(_xerc20, _baseToken, _isNative);

emit LockboxDeployed(_lockbox);
}

/**
* @notice Checks if an XERC20 is registered
*
* @param _xerc20 The address of the xerc20 that you want to check
* @return _result True if the xerc20 is registered, false if not
*/

function isXERC20(address _xerc20) external view returns (bool _result) {
_result = EnumerableSet.contains(_xerc20RegistryArray, _xerc20);
}

/**
* @notice Loops through the xerc20RegistryArray
*
Expand Down Expand Up @@ -135,6 +120,17 @@ contract XERC20Factory is IXERC20Factory {
}
}

/**
* @notice Returns if an XERC20 is registered
*
* @param _xerc20 The address of the XERC20
* @return _result If the XERC20 is registered
*/

function isRegisteredXERC20(address _xerc20) external view returns (bool _result) {
_result = EnumerableSet.contains(_xerc20RegistryArray, _xerc20);
}

/**
* @notice Deploys an XERC20 contract using CREATE3
* @dev _limits and _minters must be the same length
Expand All @@ -143,16 +139,14 @@ contract XERC20Factory is IXERC20Factory {
* @param _minterLimits The array of limits that you are adding (optional, can be an empty array)
* @param _burnerLimits The array of limits that you are adding (optional, can be an empty array)
* @param _bridges The array of burners that you are adding (optional, can be an empty array)
* @param _lockbox The address of the lockbox (If no lockbox is deployed will be address(0))
*/

function _deployXERC20(
string memory _name,
string memory _symbol,
uint256[] memory _minterLimits,
uint256[] memory _burnerLimits,
address[] memory _bridges,
address _lockbox
address[] memory _bridges
) internal returns (address _xerc20) {
bytes32 _salt = keccak256(abi.encodePacked(_name, _symbol, msg.sender));
bytes memory _creation = type(XERC20).creationCode;
Expand All @@ -171,17 +165,21 @@ contract XERC20Factory is IXERC20Factory {
XERC20(_xerc20).createBridgeBurningLimits(_burnerLimits, _bridges);
}

XERC20(_xerc20).setLockbox(_lockbox);
XERC20(_xerc20).transferOwnership(msg.sender);
}

function _deployLockbox(address _xerc20, address _baseToken) internal returns (address _lockbox) {
function _deployLockbox(
address _xerc20,
address _baseToken,
bool _isNative
) internal returns (address payable _lockbox) {
bytes32 _salt = keccak256(abi.encodePacked(_xerc20, _baseToken, msg.sender));
bytes memory _creation = type(XERC20Lockbox).creationCode;
bytes memory _bytecode = abi.encodePacked(_creation, abi.encode(_xerc20, _baseToken));
bytes memory _bytecode = abi.encodePacked(_creation, abi.encode(_xerc20, _baseToken, _isNative));

_lockbox = CREATE3.deploy(_salt, _bytecode, 0);
_lockbox = payable(CREATE3.deploy(_salt, _bytecode, 0));

XERC20(_xerc20).setLockbox(address(_lockbox));
EnumerableSet.add(_lockboxRegistryArray, _lockbox);
lockboxRegistry[_xerc20] = _lockbox;
}
Expand Down
34 changes: 32 additions & 2 deletions solidity/contracts/XERC20Lockbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,34 @@ contract XERC20Lockbox is IXERC20Lockbox {
*/
IERC20 public immutable ERC20;

/**
* @notice Whether the ERC20 token is the native gas token of this chain
*/

bool public immutable IS_NATIVE;

/**
* @notice Constructor
*
* @param _xerc20 The address of the XERC20 contract
* @param _erc20 The address of the ERC20 contract
*/

constructor(address _xerc20, address _erc20) {
constructor(address _xerc20, address _erc20, bool _isNative) {
XERC20 = IXERC20(_xerc20);
ERC20 = IERC20(_erc20);
IS_NATIVE = _isNative;
}

/**
* @notice Deposit native tokens into the lockbox
*/

function deposit() public payable {
if (!IS_NATIVE) revert IXERC20Lockbox_NotNative();
XERC20.mint(msg.sender, msg.value);

emit Deposit(msg.sender, msg.value);
}

/**
Expand All @@ -38,6 +56,8 @@ contract XERC20Lockbox is IXERC20Lockbox {
*/

function deposit(uint256 _amount) external {
if (IS_NATIVE) revert IXERC20Lockbox_Native();

ERC20.safeTransferFrom(msg.sender, address(this), _amount);
XERC20.mint(msg.sender, _amount);

Expand All @@ -51,9 +71,19 @@ contract XERC20Lockbox is IXERC20Lockbox {
*/

function withdraw(uint256 _amount) external {
ERC20.safeTransfer(msg.sender, _amount);
XERC20.burn(msg.sender, _amount);

if (IS_NATIVE) {
(bool _success,) = payable(msg.sender).call{value: _amount}('');
if (!_success) revert IXERC20Lockbox_WithdrawFailed();
} else {
ERC20.safeTransfer(msg.sender, _amount);
}

emit Withdraw(msg.sender, _amount);
}

receive() external payable {
deposit();
}
}
37 changes: 20 additions & 17 deletions solidity/interfaces/IXERC20Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface IXERC20Factory {
* @notice Emitted when a new XERC20 is deployed
*/

event XERC20Deployed(address _xerc20, address _lockbox);
event XERC20Deployed(address _xerc20);

/**
* @notice Emitted when a new XERC20Lockbox is deployed
Expand All @@ -33,41 +33,35 @@ interface IXERC20Factory {
error IXERC20Factory_LockboxAlreadyDeployed();

/**
* @notice Deploys an XERC20 contract using CREATE2
* @notice Deploys an XERC20 contract using CREATE3
* @dev _limits and _minters must be the same length
* @param _name The name of the token
* @param _symbol The symbol of the token
* @param _burnerLimits The array of limits that you are adding (optional, can be an empty array)
* @param _bridges The array of burners that you are adding (optional, can be an empty array)
* @param _baseToken The address of the base ERC20 token if you are deploying a lockbox (optional, put address(0) if you dont want to deploy one)
*/

function deploy(
function deployXERC20(
string memory _name,
string memory _symbol,
uint256[] memory _minterLimits,
uint256[] memory _burnerLimits,
address[] memory _bridges,
address _baseToken
) external returns (address _xerc20, address _lockbox);
address[] memory _bridges
) external returns (address _xerc20);

/**
* @notice Deploys an XERC20Lockbox contract using CREATE3
*
* @param _xerc20 The address of the xerc20 that you want to deploy a lockbox for
* @param _baseToken The address of the base token that you want to lock
* @param _isNative Whether or not the base token is native
*/

function deployLockbox(address _xerc20, address _baseToken) external returns (address _lockbox);

/**
* @notice Checks if an XERC20 is registered
*
* @param _xerc20 The address of the xerc20 that you want to check
* @return _result True if the xerc20 is registered, false if not
*/

function isXERC20(address _xerc20) external view returns (bool _result);
function deployLockbox(
address _xerc20,
address _baseToken,
bool _isNative
) external returns (address payable _lockbox);

/**
* @notice Loops through the xerc20RegistryArray
Expand All @@ -89,6 +83,15 @@ interface IXERC20Factory {

function getRegisteredXERC20(uint256 _start, uint256 _amount) external view returns (address[] memory _xerc20s);

/**
* @notice Returns if an XERC20 is registered
*
* @param _xerc20 The address of the XERC20
* @return _result If the XERC20 is registered
*/

function isRegisteredXERC20(address _xerc20) external view returns (bool _result);

/**
* @notice Returns the address of the lockbox for a given XERC20
*
Expand Down
19 changes: 19 additions & 0 deletions solidity/interfaces/IXERC20Lockbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,25 @@ interface IXERC20Lockbox {
*/

event Withdraw(address _sender, uint256 _amount);

/**
* @notice Reverts when a user tries to deposit native tokens on a non-native lockbox
*/

error IXERC20Lockbox_NotNative();

/**
* @notice Reverts when a user tries to deposit non-native tokens on a native lockbox
*/

error IXERC20Lockbox_Native();

/**
* @notice Reverts when a user tries to withdraw and the call fails
*/

error IXERC20Lockbox_WithdrawFailed();

/**
* @notice Deposit ERC20 tokens into the lockbox
*
Expand Down
10 changes: 8 additions & 2 deletions solidity/scripts/MultichainCreateXERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ contract MultichainCreateXERC20 is Script, ScriptingLibrary {
// NOTE: This is an array of the addresses of the ERC20 contract you are deploying the lockbox for, if you dont want to deploy a lockbox leave this as is
// NOTE: You must add the token address of your token for each chain you are deploying to in order of how the chains are listed in chains.txt, if no address is listed we will not deplyo a lockbox
address[] public erc20 = [address(0)];
// NOTE: Please also for each add a boolean to this array, if you are deploying a lockbox for the native token set it to true, if not set it to false for each iteration of an erc20
bool[] public isNative = [false];

function run() public {
address[] memory tokens = new address[](chains.length);
Expand All @@ -42,15 +44,19 @@ contract MultichainCreateXERC20 is Script, ScriptingLibrary {

vm.createSelectFork(vm.rpcUrl(vm.envString(chains[i])));
address _erc20 = i < erc20.length ? erc20[i] : address(0);
bool _isNative = i < isNative.length ? isNative[i] : false;
vm.startBroadcast(deployer);
// If this chain does not have a factory we will revert
require(
keccak256(address(factory).code) == keccak256(type(XERC20Factory).runtimeCode),
'There is no factory deployed on this chain'
);

(address xerc20, address lockbox) =
factory.deploy(name, symbol, minterLimits[i], burnLimits[i], bridges[i], _erc20);
address xerc20 = factory.deployXERC20(name, symbol, minterLimits[i], burnLimits[i], bridges[i]);
address lockbox;
if (_erc20 != address(0) && !_isNative) {
lockbox = factory.deployLockbox(xerc20, _erc20, _isNative);
}
vm.stopBroadcast();
console.log(chains[i], 'token deployed to: ', xerc20);
console.log(chains[i], 'lockbox deployed to: ', lockbox);
Expand Down
5 changes: 3 additions & 2 deletions solidity/test/e2e/Common.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ contract CommonE2EBase is Test {

vm.startPrank(_owner);
_xerc20Factory = new XERC20Factory();
(address _token, address _lock) =
_xerc20Factory.deploy(_dai.name(), _dai.symbol(), _minterLimits, _burnerLimits, _bridges, address(_dai));
address _token = _xerc20Factory.deployXERC20(_dai.name(), _dai.symbol(), _minterLimits, _burnerLimits, _bridges);
address payable _lock = _xerc20Factory.deployLockbox(_token, address(_dai), false);

_xerc20 = XERC20(_token);
_lockbox = XERC20Lockbox(_lock);
vm.stopPrank();
Expand Down
8 changes: 4 additions & 4 deletions solidity/test/e2e/XERC20Factory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ contract E2EDeployment is CommonE2EBase {
uint256[] memory _limits = new uint256[](0);
address[] memory _minters = new address[](0);

(address _token,) = _xerc20Factory.deploy('Test', 'TST', _limits, _limits, _minters, address(0));
address _lock = _xerc20Factory.deployLockbox(_token, address(_dai));
address _token = _xerc20Factory.deployXERC20('Test', 'TST', _limits, _limits, _minters);
address _lock = _xerc20Factory.deployLockbox(_token, address(_dai), false);

assertEq(address(XERC20Lockbox(_lock).XERC20()), address(_token));
assertEq(address(XERC20Lockbox(_lock).ERC20()), address(_dai));
assertEq(address(XERC20Lockbox(payable(_lock)).XERC20()), address(_token));
assertEq(address(XERC20Lockbox(payable(_lock)).ERC20()), address(_dai));
}
}
Loading

0 comments on commit d4d2c46

Please sign in to comment.