From d005fab9c8cc391d1d802d81b13afcf35baffbd5 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Thu, 3 Aug 2023 10:24:02 -0600 Subject: [PATCH 1/3] feat: add lockbox interface --- .../shared/IXERC20Lockbox/IXERC20Lockbox.sol | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 contracts/shared/IXERC20Lockbox/IXERC20Lockbox.sol diff --git a/contracts/shared/IXERC20Lockbox/IXERC20Lockbox.sol b/contracts/shared/IXERC20Lockbox/IXERC20Lockbox.sol new file mode 100644 index 0000000..50ecb27 --- /dev/null +++ b/contracts/shared/IXERC20Lockbox/IXERC20Lockbox.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.4 <0.9.0; + +interface IXERC20Lockbox { + /** + * @notice Emitted when tokens are deposited into the lockbox + */ + + event Deposit(address _sender, uint256 _amount); + + /** + * @notice Emitted when tokens are withdrawn from the lockbox + */ + + 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 + * + * @param _amount The amount of tokens to deposit + */ + + function deposit(uint256 _amount) external; + + /** + * @notice Withdraw ERC20 tokens from the lockbox + * + * @param _amount The amount of tokens to withdraw + */ + + function withdraw(uint256 _amount) external; +} From 8a4c09f3627eb2a03c3b3cb934e57ad1f2c8aa66 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Thu, 3 Aug 2023 23:38:38 -0600 Subject: [PATCH 2/3] feat: lockbox adapter --- .../DappRadar/DappRadarLockboxAdapter.sol | 74 +++++++++++ contracts/shared/IXERC20/IXERC20.sol | 115 ++++++++++++++++++ .../IXERC20Lockbox.sol | 0 foundry.toml | 3 +- .../DeployDappRadarLockBoxAdapter.s.sol | 15 +++ 5 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 contracts/integration/DappRadar/DappRadarLockboxAdapter.sol create mode 100644 contracts/shared/IXERC20/IXERC20.sol rename contracts/shared/{IXERC20Lockbox => IXERC20}/IXERC20Lockbox.sol (100%) create mode 100644 script/DappRadar/DeployDappRadarLockBoxAdapter.s.sol diff --git a/contracts/integration/DappRadar/DappRadarLockboxAdapter.sol b/contracts/integration/DappRadar/DappRadarLockboxAdapter.sol new file mode 100644 index 0000000..ab2bb4f --- /dev/null +++ b/contracts/integration/DappRadar/DappRadarLockboxAdapter.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IXERC20} from "../../shared/IXERC20/IXERC20.sol"; +import {IXERC20Lockbox} from "../../shared/IXERC20/IXERC20Lockbox.sol"; +import {IXReceiver} from "@connext/interfaces/core/IXReceiver.sol"; + +contract DappRadarLockboxAdapter is IXReceiver { + IXERC20Lockbox public lockbox; + IERC20 public erc20; + IXERC20 public xerc20; + + constructor(address _lockbox, address _erc20, address _xerc20) { + lockbox = IXERC20Lockbox(_lockbox); + erc20 = IERC20(_erc20); + xerc20 = IXERC20(_xerc20); + } + + /// @notice Deposit ERC20s into the Lockbox + /// @param _amount Amount of ERC20s to use + /// @param _recipient Recipient of the xERC20s + function deposit(uint256 _amount, address _recipient) internal { + require(_amount > 0, "Zero amount"); + IERC20 _xerc20 = IERC20(address(xerc20)); + + if (erc20.allowance(address(this), address(lockbox)) < _amount) { + erc20.approve(address(lockbox), type(uint256).max); + } + lockbox.deposit(_amount); + + // Transfer the xERC20s to the recipient + SafeERC20.safeTransfer(_xerc20, _recipient, _amount); + } + + /// @notice Withdraw ERC20s from the Lockbox + /// @param _amount Amount of xERC20s to use + /// @param _recipient Recipient of the ERC20s + function withdraw(uint256 _amount, address _recipient) internal { + require(_amount > 0, "Zero amount"); + IERC20 _xerc20 = IERC20(address(xerc20)); + + if (_xerc20.allowance(address(this), address(lockbox)) < _amount) { + _xerc20.approve(address(lockbox), type(uint256).max); + } + lockbox.withdraw(_amount); + + // Transfer the ERC20s to the recipient + SafeERC20.safeTransfer(erc20, _recipient, _amount); + } + + /// @dev This function receives xERC20s from Connext and calls the Lockbox + /// @param _amount The amount of funds that will be received. + /// @param _asset The address of the asset that will be received. + /// @param _transferId The id of the transfer. + /// @param _callData The data that will be sent to the targets. + function xReceive( + bytes32 _transferId, + uint256 _amount, + address _asset, + address _originSender, + uint32 _origin, + bytes memory _callData + ) external returns (bytes memory) { + // Check for the right xerc20 + require(_asset == address(xerc20), "Wrong asset received"); + + // Unpack the _callData to get the recipient's address + address _recipient = abi.decode(_callData, (address)); + + withdraw(_amount, _recipient); + } +} diff --git a/contracts/shared/IXERC20/IXERC20.sol b/contracts/shared/IXERC20/IXERC20.sol new file mode 100644 index 0000000..4adbf9c --- /dev/null +++ b/contracts/shared/IXERC20/IXERC20.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.4 <0.9.0; + +interface IXERC20 { + /** + * @notice Emits when a lockbox is set + * + * @param _lockbox The address of the lockbox + */ + + event LockboxSet(address _lockbox); + + /** + * @notice Emits when a limit is set + * + * @param _mintingLimit The updated minting limit we are setting to the bridge + * @param _burningLimit The updated burning limit we are setting to the bridge + * @param _bridge The address of the bridge we are setting the limit too + */ + event BridgeLimitsSet(uint256 _mintingLimit, uint256 _burningLimit, address indexed _bridge); + + /** + * @notice Reverts when a user with too low of a limit tries to call mint/burn + */ + + error IXERC20_NotHighEnoughLimits(); + + /** + * @notice Reverts when caller is not the factory + */ + + error IXERC20_NotFactory(); + + struct Bridge { + BridgeParameters minterParams; + BridgeParameters burnerParams; + } + + struct BridgeParameters { + uint256 timestamp; + uint256 ratePerSecond; + uint256 maxLimit; + uint256 currentLimit; + } + + /** + * @notice Sets the lockbox address + * + * @param _lockbox The address of the lockbox + */ + + function setLockbox(address _lockbox) external; + + /** + * @notice Updates the limits of any bridge + * @dev Can only be called by the owner + * @param _mintingLimit The updated minting limit we are setting to the bridge + * @param _burningLimit The updated burning limit we are setting to the bridge + * @param _bridge The address of the bridge we are setting the limits too + */ + function setLimits(address _bridge, uint256 _mintingLimit, uint256 _burningLimit) external; + + /** + * @notice Returns the max limit of a minter + * + * @param _minter The minter we are viewing the limits of + * @return _limit The limit the minter has + */ + function mintingMaxLimitOf(address _minter) external view returns (uint256 _limit); + + /** + * @notice Returns the max limit of a bridge + * + * @param _bridge the bridge we are viewing the limits of + * @return _limit The limit the bridge has + */ + + function burningMaxLimitOf(address _bridge) external view returns (uint256 _limit); + + /** + * @notice Returns the current limit of a minter + * + * @param _minter The minter we are viewing the limits of + * @return _limit The limit the minter has + */ + + function mintingCurrentLimitOf(address _minter) external view returns (uint256 _limit); + + /** + * @notice Returns the current limit of a bridge + * + * @param _bridge the bridge we are viewing the limits of + * @return _limit The limit the bridge has + */ + + function burningCurrentLimitOf(address _bridge) external view returns (uint256 _limit); + + /** + * @notice Mints tokens for a user + * @dev Can only be called by a minter + * @param _user The address of the user who needs tokens minted + * @param _amount The amount of tokens being minted + */ + + function mint(address _user, uint256 _amount) external; + + /** + * @notice Burns tokens for a user + * @dev Can only be called by a minter + * @param _user The address of the user who needs tokens burned + * @param _amount The amount of tokens being burned + */ + + function burn(address _user, uint256 _amount) external; +} diff --git a/contracts/shared/IXERC20Lockbox/IXERC20Lockbox.sol b/contracts/shared/IXERC20/IXERC20Lockbox.sol similarity index 100% rename from contracts/shared/IXERC20Lockbox/IXERC20Lockbox.sol rename to contracts/shared/IXERC20/IXERC20Lockbox.sol diff --git a/foundry.toml b/foundry.toml index 20fd330..064cbfc 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,4 +4,5 @@ out = 'out' libs = ['node_modules', 'lib'] test = 'test' cache_path = 'cache_forge' -solc = "0.8.19" \ No newline at end of file +solc = "0.8.19" +via_ir = true \ No newline at end of file diff --git a/script/DappRadar/DeployDappRadarLockBoxAdapter.s.sol b/script/DappRadar/DeployDappRadarLockBoxAdapter.s.sol new file mode 100644 index 0000000..4f6a36c --- /dev/null +++ b/script/DappRadar/DeployDappRadarLockBoxAdapter.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import {DappRadarLockboxAdapter} from "../../contracts/integration/DappRadar/DappRadarLockboxAdapter.sol"; + +contract DeployDappRadarLockboxAdapter is Script { + function run(address lockbox, address erc20, address xerc20) external { + vm.startBroadcast(); + + new DappRadarLockboxAdapter(lockbox, erc20, xerc20); + + vm.stopBroadcast(); + } +} From e414624a65c9e7c437305a300e542ddab769f756 Mon Sep 17 00:00:00 2001 From: just-a-node Date: Sat, 2 Sep 2023 16:35:11 -0600 Subject: [PATCH 3/3] feat: add NextLockboxAdapter --- .../Connext/NextLockboxAdapter.sol | 74 +++++++++++++++++++ script/Connext/DeployNextLockboxAdapter.s.sol | 15 ++++ 2 files changed, 89 insertions(+) create mode 100644 contracts/integration/Connext/NextLockboxAdapter.sol create mode 100644 script/Connext/DeployNextLockboxAdapter.s.sol diff --git a/contracts/integration/Connext/NextLockboxAdapter.sol b/contracts/integration/Connext/NextLockboxAdapter.sol new file mode 100644 index 0000000..577c486 --- /dev/null +++ b/contracts/integration/Connext/NextLockboxAdapter.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IXERC20} from "../../shared/IXERC20/IXERC20.sol"; +import {IXERC20Lockbox} from "../../shared/IXERC20/IXERC20Lockbox.sol"; +import {IXReceiver} from "@connext/interfaces/core/IXReceiver.sol"; + +contract NextLockboxAdapter is IXReceiver { + IXERC20Lockbox public lockbox; + IERC20 public erc20; + IXERC20 public xerc20; + + constructor(address _lockbox, address _erc20, address _xerc20) { + lockbox = IXERC20Lockbox(_lockbox); + erc20 = IERC20(_erc20); + xerc20 = IXERC20(_xerc20); + } + + /// @notice Deposit ERC20s into the Lockbox + /// @param _amount Amount of ERC20s to use + /// @param _recipient Recipient of the xERC20s + function deposit(uint256 _amount, address _recipient) internal { + require(_amount > 0, "Zero amount"); + IERC20 _xerc20 = IERC20(address(xerc20)); + + if (erc20.allowance(address(this), address(lockbox)) < _amount) { + erc20.approve(address(lockbox), type(uint256).max); + } + lockbox.deposit(_amount); + + // Transfer the xERC20s to the recipient + SafeERC20.safeTransfer(_xerc20, _recipient, _amount); + } + + /// @notice Withdraw ERC20s from the Lockbox + /// @param _amount Amount of xERC20s to use + /// @param _recipient Recipient of the ERC20s + function withdraw(uint256 _amount, address _recipient) internal { + require(_amount > 0, "Zero amount"); + IERC20 _xerc20 = IERC20(address(xerc20)); + + if (_xerc20.allowance(address(this), address(lockbox)) < _amount) { + _xerc20.approve(address(lockbox), type(uint256).max); + } + lockbox.withdraw(_amount); + + // Transfer the ERC20s to the recipient + SafeERC20.safeTransfer(erc20, _recipient, _amount); + } + + /// @dev This function receives xERC20s from Connext and calls the Lockbox + /// @param _amount The amount of funds that will be received. + /// @param _asset The address of the asset that will be received. + /// @param _transferId The id of the transfer. + /// @param _callData The data that will be sent to the targets. + function xReceive( + bytes32 _transferId, + uint256 _amount, + address _asset, + address _originSender, + uint32 _origin, + bytes memory _callData + ) external returns (bytes memory) { + // Check for the right xerc20 + require(_asset == address(xerc20), "Wrong asset received"); + + // Unpack the _callData to get the recipient's address + address _recipient = abi.decode(_callData, (address)); + + withdraw(_amount, _recipient); + } +} diff --git a/script/Connext/DeployNextLockboxAdapter.s.sol b/script/Connext/DeployNextLockboxAdapter.s.sol new file mode 100644 index 0000000..d47955c --- /dev/null +++ b/script/Connext/DeployNextLockboxAdapter.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import {NextLockboxAdapter} from "../../contracts/integration/Connext/NextLockboxAdapter.sol"; + +contract DeployNextLockboxAdapter is Script { + function run(address lockbox, address erc20, address xerc20) external { + vm.startBroadcast(); + + new NextLockboxAdapter(lockbox, erc20, xerc20); + + vm.stopBroadcast(); + } +}