Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add deposit functionality to Gateway contract #47

Merged
merged 5 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions contracts/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 balance;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
Expand All @@ -42,6 +43,24 @@ 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');
_;
}

/**
* @dev Modifier that checks if the deposit amount is valid.
* @param _amount The amount to be checked.
*/
modifier isValidAmount(uint256 _amount) {
require(_amount != 0, 'AmountIsZero');
_;
}

/* ##################################################################
OWNER FUNCTIONS
################################################################## */
Expand Down Expand Up @@ -128,9 +147,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) {
Expand Down Expand Up @@ -225,6 +242,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);
balance[_token][sender] += _amount;
emit Deposit(sender, _token, _amount);
return true;
}

/* ##################################################################
VIEW CALLS
################################################################## */
Expand All @@ -243,4 +269,9 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable {
function getFeeDetails() external view returns (uint64, uint256) {
return (protocolFeePercent, MAX_BPS);
}

/** @dev See {getBalance-IGateway}. */
function getBalance(address _token, address _provider) external view returns (uint256) {
return balance[_token][_provider];
}
}
44 changes: 36 additions & 8 deletions contracts/interfaces/IGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface IGateway {
EVENTS
################################################################## */
/**
* @dev Emitted when a deposit is made.
* @notice Emitted when an order is created.
* @param sender The address of the sender.
* @param token The address of the deposited token.
* @param amount The amount of the deposit.
Expand All @@ -31,7 +31,7 @@ interface IGateway {
);

/**
* @dev Emitted when an aggregator settles a transaction.
* @notice Emitted when an aggregator settles a transaction.
* @param splitOrderId The ID of the split order.
* @param orderId The ID of the order.
* @param liquidityProvider The address of the liquidity provider.
Expand All @@ -45,24 +45,32 @@ interface IGateway {
);

/**
* @dev Emitted when an aggregator refunds a transaction.
* @notice Emitted when an aggregator refunds a transaction.
* @param fee The fee deducted from the refund amount.
* @param orderId The ID of the order.
*/
event OrderRefunded(uint256 fee, bytes32 indexed orderId);

/**
* @dev Emitted when the sender's fee is transferred.
* @notice Emitted when the sender's fee is transferred.
* @param sender The address of the sender.
* @param amount The amount of the fee transferred.
*/
event SenderFeeTransferred(address indexed sender, uint256 indexed amount);

/**
* @notice 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
################################################################## */
/**
* @dev Struct representing an order.
* @notice Struct representing an order.
* @param sender The address of the sender.
* @param token The address of the token.
* @param senderFeeRecipient The address of the sender fee recipient.
Expand Down Expand Up @@ -117,12 +125,12 @@ interface IGateway {
) external returns (bytes32 _orderId);

/**
* @notice Settles a transaction and distributes rewards accordingly.
* @notice Settles a transaction and distributes fees accordingly.
* @param _splitOrderId The ID of the split order.
* @param _orderId The ID of the transaction.
* @param _liquidityProvider The address of the liquidity provider.
* @param _settlePercent The rate at which the transaction is settled.
* @return bool the settlement is successful.
* @return bool The settlement is successful.
*/
function settle(
bytes32 _splitOrderId,
Expand All @@ -132,7 +140,7 @@ interface IGateway {
) external returns (bool);

/**
* @notice Refunds to the specified refundable address.
* @notice Refunds to the specified refund address.
* @dev Requirements:
* - Only aggregators can call this function.
* @param _fee The amount to be deducted from the amount to be refunded.
Expand All @@ -141,6 +149,18 @@ interface IGateway {
*/
function refund(uint256 _fee, bytes32 _orderId) external returns (bool);

/**
* @notice Allow a provider to deposit an asset into Gateway.
* @dev Requirements:
* - 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 Checks if a token is supported by Gateway.
* @param _token The address of the token to check.
Expand All @@ -161,4 +181,12 @@ interface IGateway {
* @return max_bps The maximum basis points.
*/
function getFeeDetails() external view returns (uint64 protocolReward, uint256 max_bps);

/**
* @notice Gets the balance of a provider.
* @param _provider The address of the provider.
* @param _asset The address of the asset.
* @return uint256 The provider's balance.
*/
function getBalance(address _asset, address _provider) external view returns (uint256);
}
146 changes: 146 additions & 0 deletions test/gateway/gateway.deposit.test.js
Original file line number Diff line number Diff line change
@@ -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
);
});
});
19 changes: 16 additions & 3 deletions test/utils/utils.manager.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -33,6 +34,7 @@ const Events = {
SettingManagerBool: "SettingManagerBool",
ProtocolFeeUpdated: "ProtocolFeeUpdated",
ProtocolAddressUpdated: "ProtocolAddressUpdated",
Deposit: "Deposit",
},
};

Expand Down Expand Up @@ -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.getBalance(token, account)).to.eq(amount);
}

module.exports = {
Expand All @@ -80,5 +91,7 @@ module.exports = {
Events,
deployContract,
mockMintDeposit,
assertBalance,
assertDepositBalance,
getSupportedInstitutions,
};
Loading