From 6792432a2dc08eec52d3bf0550bb8a3425982fde Mon Sep 17 00:00:00 2001 From: onahprosperity Date: Sun, 15 Sep 2024 00:37:19 +0100 Subject: [PATCH] feat: add deposit functionality to Gateway contract --- contracts/Gateway.sol | 33 +++++- contracts/interfaces/IGateway.sol | 40 ++++++++ test/gateway/gateway.deposit.test.js | 146 +++++++++++++++++++++++++++ test/utils/utils.manager.js | 19 +++- 4 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 test/gateway/gateway.deposit.test.js diff --git a/contracts/Gateway.sol b/contracts/Gateway.sol index c1c5801..8ed1672 100644 --- a/contracts/Gateway.sol +++ b/contracts/Gateway.sol @@ -19,6 +19,7 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable { mapping(bytes32 => Order) private order; mapping(address => uint256) private _nonce; uint256[50] private __gap; + mapping(address => mapping(address => uint256)) private depositedBalances; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -42,6 +43,20 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable { _; } + /** + * @dev Modifier that checks if the token is supported. + * @param _token The address of the token to be checked. + */ + modifier isTokenApproved(address _token) { + require(_isTokenSupported[_token] == 1, 'TokenNotSupported'); + _; + } + + modifier isValidAmount(uint256 _amount) { + require(_amount != 0, 'AmountIsZero'); + _; + } + /* ################################################################## OWNER FUNCTIONS ################################################################## */ @@ -128,9 +143,7 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable { address _refundAddress, address _senderFeeRecipient, uint256 _senderFee - ) internal view { - require(_isTokenSupported[_token] == 1, 'TokenNotSupported'); - require(_amount != 0, 'AmountIsZero'); + ) internal isTokenApproved(_token) isValidAmount(_amount) view { require(_refundAddress != address(0), 'ThrowZeroAddress'); if (_senderFee != 0) { @@ -225,6 +238,15 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable { return true; } + /** @dev See {deposit-IGateway}. */ + function deposit(address _token, uint256 _amount) external isTokenApproved(_token) isValidAmount(_amount) returns (bool) { + address sender = _msgSender(); + IERC20(_token).transferFrom(sender, address(this), _amount); + depositedBalances[_token][sender] += _amount; + emit Deposit(sender, _token, _amount); + return true; + } + /* ################################################################## VIEW CALLS ################################################################## */ @@ -243,4 +265,9 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable { function getFeeDetails() external view returns (uint64, uint256) { return (protocolFeePercent, MAX_BPS); } + + /** @dev See {getProviderStakedBalance-IGateway}. */ + function getProviderDepositBalance(address _token, address _provider) external view returns (uint256) { + return depositedBalances[_token][_provider]; + } } diff --git a/contracts/interfaces/IGateway.sol b/contracts/interfaces/IGateway.sol index 5b4573d..d2c6938 100644 --- a/contracts/interfaces/IGateway.sol +++ b/contracts/interfaces/IGateway.sol @@ -58,6 +58,14 @@ interface IGateway { */ event SenderFeeTransferred(address indexed sender, uint256 indexed amount); + /** + * @dev Emitted when a deposit is made by a provider. + * @param sender The address of the sender. + * @param token The address of the deposited token. + * @param amount The amount of the deposit. + */ + event Deposit(address indexed sender, address indexed token, uint256 indexed amount); + /* ################################################################## STRUCTS ################################################################## */ @@ -141,6 +149,30 @@ interface IGateway { */ function refund(uint256 _fee, bytes32 _orderId) external returns (bool); + /** + * @notice Allow Provider to deposit assets. + * @dev Reqirements: + * - The amount must be greater than minimum. + * - The asset must be supported. + * - The provider must approve Gateway contract on `_token` of at least `_amount` before function call + * @param _token The address of the asset. + * @param _amount The amount to be deposited. + * @return bool the deposit is successful. + */ + function deposit(address _token, uint256 _amount) external returns (bool); + + /** + * @notice Escrowed assets from provider to the sender. + * @param _orderId The ID of the transaction. + * @param _signature The signature of the provider. + * @param _provider The address of the provider. + * @param _senderAddress The address of the sender. + * @param _token The address of the asset. + * @param _amount The amount to be transferred. + * @return bool the withdrawal is successful. + */ + + /** * @notice Checks if a token is supported by Gateway. * @param _token The address of the token to check. @@ -161,4 +193,12 @@ interface IGateway { * @return max_bps The maximum basis points. */ function getFeeDetails() external view returns (uint64 protocolReward, uint256 max_bps); + + /** + * @notice Gets provider staked balance. + * @param _provider The address of the provider. + * @param _asset The address of the asset. + * @return uint256 The staked balance of the provider. + */ + function getProviderDepositBalance(address _asset, address _provider) external view returns (uint256); } diff --git a/test/gateway/gateway.deposit.test.js b/test/gateway/gateway.deposit.test.js new file mode 100644 index 0000000..7cd680f --- /dev/null +++ b/test/gateway/gateway.deposit.test.js @@ -0,0 +1,146 @@ +const { ethers } = require("hardhat"); +const { BigNumber } = require("@ethersproject/bignumber"); + +const { gatewayFixture } = require("../fixtures/gateway.js"); + +const { + deployContract, + ZERO_AMOUNT, + Errors, + Events, + mockMintDeposit, + assertBalance, + assertDepositBalance, +} = require("../utils/utils.manager.js"); +const { expect } = require("chai"); + +describe("Gateway Provider deposit", function () { + beforeEach(async function () { + [ + this.deployer, + this.treasuryAddress, + this.alice, + this.bob, + this.Eve, + this.hacker, + ...this.accounts + ] = await ethers.getSigners(); + + ({ gateway, mockUSDT } = await gatewayFixture()); + + this.mockDAI = await deployContract("MockUSDT"); + + this.mockUSDT = mockUSDT; + this.gateway = gateway; + + this.depositAmount = ethers.utils.parseEther("1000000"); + + await mockMintDeposit(gateway, this.alice, mockUSDT, this.depositAmount); + await mockMintDeposit(gateway, this.bob, mockUSDT, this.depositAmount); + await mockMintDeposit(gateway, this.Eve, mockUSDT, this.depositAmount); + await mockMintDeposit( + gateway, + this.alice, + this.mockDAI, + this.depositAmount + ); + await mockMintDeposit(gateway, this.bob, this.mockDAI, this.depositAmount); + await mockMintDeposit(gateway, this.Eve, this.mockDAI, this.depositAmount); + + await assertBalance( + this.mockUSDT, + this.mockDAI, + this.alice.address, + this.depositAmount + ); + await assertBalance( + this.mockUSDT, + this.mockDAI, + this.bob.address, + this.depositAmount + ); + await assertBalance( + this.mockUSDT, + this.mockDAI, + this.Eve.address, + this.depositAmount + ); + + const token = ethers.utils.formatBytes32String("token"); + + await expect( + this.gateway + .connect(this.deployer) + .settingManagerBool(token, this.mockUSDT.address, BigNumber.from(1)) + ) + .to.emit(this.gateway, Events.Gateway.SettingManagerBool) + .withArgs(token, this.mockUSDT.address, BigNumber.from(1)); + }); + + it("Should be able to deposit and update user balance", async function () { + await assertDepositBalance( + this.gateway, + this.mockUSDT.address, + this.alice.address, + ZERO_AMOUNT + ); + await expect( + this.gateway + .connect(this.alice) + .deposit(this.mockUSDT.address, this.depositAmount) + ) + .to.emit(this.gateway, Events.Gateway.Deposit) + .withArgs( + this.alice.address, + this.mockUSDT.address, + BigNumber.from(this.depositAmount) + ); + + await assertDepositBalance( + this.gateway, + this.mockUSDT.address, + this.alice.address, + this.depositAmount + ); + }); + + it("SHould fail when amount deposited is zero", async function () { + await assertDepositBalance( + this.gateway, + this.mockUSDT.address, + this.bob.address, + ZERO_AMOUNT + ); + await expect( + this.gateway.connect(this.bob).deposit(this.mockUSDT.address, ZERO_AMOUNT) + ).to.be.revertedWith(Errors.Gateway.AmountIsZero); + + await assertDepositBalance( + this.gateway, + this.mockUSDT.address, + this.bob.address, + ZERO_AMOUNT + ); + }); + + it("Should fail when token is not supported", async function () { + await assertDepositBalance( + this.gateway, + this.mockDAI.address, + this.hacker.address, + ZERO_AMOUNT + ); + await expect( + this.gateway + .connect(this.hacker) + .deposit(this.mockDAI.address, this.depositAmount) + ).to.be.revertedWith(Errors.Gateway.TokenNotSupported); + + await assertDepositBalance( + this.gateway, + this.mockDAI.address, + this.hacker.address, + ZERO_AMOUNT + ); + }); +}); diff --git a/test/utils/utils.manager.js b/test/utils/utils.manager.js index 558b135..1a56361 100644 --- a/test/utils/utils.manager.js +++ b/test/utils/utils.manager.js @@ -1,5 +1,6 @@ const { ethers } = require("hardhat"); const { BigNumber } = require("@ethersproject/bignumber"); +const { expect } = require("chai"); const ZERO_AMOUNT = BigNumber.from("0"); const ZERO_ADDRESS = ethers.constants.AddressZero; @@ -33,6 +34,7 @@ const Events = { SettingManagerBool: "SettingManagerBool", ProtocolFeeUpdated: "ProtocolFeeUpdated", ProtocolAddressUpdated: "ProtocolAddressUpdated", + Deposit: "Deposit", }, }; @@ -66,9 +68,18 @@ async function getSupportedInstitutions() { }; } -async function mockMintDeposit(gateway, account, usdc, amount) { - await usdc.connect(account).mint(amount); - await usdc.connect(account).approve(gateway.address, amount); +async function mockMintDeposit(gateway, account, token, amount) { + await token.connect(account).mint(amount); + await token.connect(account).approve(gateway.address, amount); +} + +async function assertBalance(mockUSDT, mockDAI, account, depositAmount) { + expect(await mockDAI.balanceOf(account)).to.eq(depositAmount); + expect(await mockUSDT.balanceOf(account)).to.eq(depositAmount); +} + +async function assertDepositBalance(gateway, token, account, amount) { + expect(await gateway.getProviderDepositBalance(token, account)).to.eq(amount); } module.exports = { @@ -80,5 +91,7 @@ module.exports = { Events, deployContract, mockMintDeposit, + assertBalance, + assertDepositBalance, getSupportedInstitutions, };