Skip to content

Commit 79df46d

Browse files
authored
Merge pull request #1 from usecube/exchange-v2
[feat][exchange-v2]: Updated contracts to Exchange V2
2 parents fa21bc6 + f5bad23 commit 79df46d

File tree

4 files changed

+347
-45
lines changed

4 files changed

+347
-45
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
## Contracts
77

88
1. Registry.sol: [0x2b4836d81370e37030727e4dcbd9cc5a772cf43a](https://sepolia.basescan.org/address/0x2b4836d81370e37030727e4dcbd9cc5a772cf43a)
9-
2. Exchange.sol: [0xd9004Edc4bdEB308C4A40fdCbE320bbE5DF4AF77](https://sepolia.basescan.org/address/0xd9004edc4bdeb308c4a40fdcbe320bbe5df4af77)
10-
3. Vault.sol: [0xd580248163CDD5AE3225A700E9f4e7CD525b27b0](https://sepolia.basescan.org/address/0xd580248163cdd5ae3225a700e9f4e7cd525b27b0)
11-
4. XSGD.sol [0xd7260d7063fE5A62A90E6A8DD5A39Ab27A05986B](https://sepolia.basescan.org/token/0xd7260d7063fe5a62a90e6a8dd5a39ab27a05986b)
9+
2. Exchange.sol (V1): [0xd9004Edc4bdEB308C4A40fdCbE320bbE5DF4AF77](https://sepolia.basescan.org/address/0xd9004edc4bdeb308c4a40fdcbe320bbe5df4af77)
10+
3. Exchange.sol (V2): [0x92F5D70ffBE0988DEcD5c1E7A6cb8A048a3Fe75D](https://sepolia.basescan.org/address/0x92F5D70ffBE0988DEcD5c1E7A6cb8A048a3Fe75D)
11+
4. Vault.sol: [0xd580248163CDD5AE3225A700E9f4e7CD525b27b0](https://sepolia.basescan.org/address/0xd580248163cdd5ae3225a700e9f4e7cd525b27b0)
12+
5. XSGD.sol: [0xd7260d7063fE5A62A90E6A8DD5A39Ab27A05986B](https://sepolia.basescan.org/token/0xd7260d7063fe5a62a90e6a8dd5a39ab27a05986b)
1213

1314
## Deployment
1415

script/deploy/Exchange.s.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ contract DeployExchange is Script {
1111

1212
address registryAddress = 0x2b4836d81370e37030727E4DCbd9cC5a772cf43A;
1313
address usdcAddress = 0x036CbD53842c5426634e7929541eC2318f3dCF7e;
14+
address xsgdAddress = 0xd7260d7063fE5A62A90E6A8DD5A39Ab27A05986B;
1415
address vaultAddress = 0xd580248163CDD5AE3225A700E9f4e7CD525b27b0;
1516

16-
Exchange exchange = new Exchange(registryAddress, usdcAddress, vaultAddress);
17+
Exchange exchange = new Exchange(registryAddress, usdcAddress, xsgdAddress, vaultAddress);
1718

1819
console.log("Exchange deployed at:", address(exchange));
1920

src/Exchange.sol

Lines changed: 134 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,36 @@ contract Exchange is ReentrancyGuard, Ownable {
1919

2020
Registry public immutable registry;
2121
IERC20 public immutable usdcToken;
22+
IERC20 public immutable xsgdToken;
2223
IERC4626 public immutable vault;
2324

2425
uint256 public fee = 100; // 100 basis points fee (1%)
2526
address public feeCollector;
2627

28+
uint256 public lastUpdateTime;
29+
uint256 public lastPricePerShare;
30+
2731
event Transfer(address indexed from, address indexed to, uint256 amount, string uen);
2832
event FeeUpdated(uint256 newFee);
2933
event FeeCollectorUpdated(address newFeeCollector);
3034
event FeesWithdrawn(address indexed to, uint256 amount);
3135
event VaultDeposit(address indexed merchant, uint256 assets, uint256 shares);
3236
event VaultWithdraw(address indexed merchant, uint256 assets, uint256 shares);
3337

34-
constructor(address _registryAddress, address _usdcAddress, address _vaultAddress) Ownable(msg.sender) {
38+
constructor(address _registryAddress, address _usdcAddress, address _xsgdAddress, address _vaultAddress)
39+
Ownable(msg.sender)
40+
{
3541
require(_registryAddress != address(0), "Invalid registry address");
3642
require(_usdcAddress != address(0), "Invalid USDC address");
43+
require(_xsgdAddress != address(0), "Invalid xSGD address");
3744
require(_vaultAddress != address(0), "Invalid vault address");
3845
registry = Registry(_registryAddress);
3946
usdcToken = IERC20(_usdcAddress);
47+
xsgdToken = IERC20(_xsgdAddress);
4048
feeCollector = address(this);
4149
vault = IERC4626(_vaultAddress);
50+
lastUpdateTime = block.timestamp;
51+
lastPricePerShare = vault.convertToAssets(1e6);
4252
}
4353

4454
/////////////////////////
@@ -50,7 +60,7 @@ contract Exchange is ReentrancyGuard, Ownable {
5060
* @param _uen Merchant's UEN.
5161
* @param _amount Amount of USDC to transfer to merchant.
5262
*/
53-
function transferToMerchant(string memory _uen, uint256 _amount) external nonReentrant {
63+
function transferUsdcToMerchant(string memory _uen, uint256 _amount) external nonReentrant {
5464
require(_amount > 0, "Amount must be greater than zero");
5565

5666
address merchantWalletAddress = registry.getMerchantByUEN(_uen).wallet_address;
@@ -67,6 +77,23 @@ contract Exchange is ReentrancyGuard, Ownable {
6777
emit Transfer(msg.sender, merchantWalletAddress, merchantAmount, _uen);
6878
}
6979

80+
function transferXsgdToMerchant(string memory _uen, uint256 _amount) external nonReentrant {
81+
require(_amount > 0, "Amount must be greater than zero");
82+
83+
address merchantWalletAddress = registry.getMerchantByUEN(_uen).wallet_address;
84+
require(merchantWalletAddress != address(0), "Invalid merchant wallet address");
85+
86+
uint256 feeAmount = (_amount * fee) / 10000;
87+
uint256 merchantAmount = _amount - feeAmount;
88+
89+
xsgdToken.safeTransferFrom(msg.sender, merchantWalletAddress, merchantAmount);
90+
if (feeAmount > 0) {
91+
xsgdToken.safeTransferFrom(msg.sender, feeCollector, feeAmount);
92+
}
93+
94+
emit Transfer(msg.sender, merchantWalletAddress, merchantAmount, _uen);
95+
}
96+
7097
/**
7198
* @notice Transfer USDC to vault.
7299
* @param _uen Merchant's UEN.
@@ -149,16 +176,119 @@ contract Exchange is ReentrancyGuard, Ownable {
149176
}
150177

151178
/**
152-
* @notice Withdraw fees.
179+
* @notice Withdraw USDC fees.
153180
* @param _to Withdrawal address.
154181
* @param _amount Amount of USDC to withdraw.
155182
*/
156-
function withdrawFees(address _to, uint256 _amount) external onlyOwner {
183+
function withdrawUsdcFees(address _to, uint256 _amount) external onlyOwner {
157184
require(_to != address(0), "Invalid withdrawal address");
158185
require(_amount > 0, "Withdrawal amount must be greater than zero");
159186
require(_amount <= usdcToken.balanceOf(address(this)), "Insufficient balance");
160187

161188
usdcToken.safeTransfer(_to, _amount);
162189
emit FeesWithdrawn(_to, _amount);
163190
}
191+
192+
/**
193+
* @notice Withdraw XSGD fees.
194+
* @param _to Withdrawal address.
195+
* @param _amount Amount of XSGD to withdraw.
196+
*/
197+
function withdrawXsgdFees(address _to, uint256 _amount) external onlyOwner {
198+
require(_to != address(0), "Invalid withdrawal address");
199+
require(_amount > 0, "Withdrawal amount must be greater than zero");
200+
require(_amount <= xsgdToken.balanceOf(address(this)), "Insufficient balance");
201+
202+
xsgdToken.safeTransfer(_to, _amount);
203+
emit FeesWithdrawn(_to, _amount);
204+
}
205+
206+
///////////////////
207+
// VAULT HELPERS //
208+
///////////////////
209+
210+
/**
211+
* @notice Get current price per share
212+
* @return Current price of 1 share in terms of assets (USDC)
213+
*/
214+
function getCurrentPricePerShare() public view returns (uint256) {
215+
return vault.convertToAssets(1e6);
216+
}
217+
218+
/**
219+
* @notice Get vault metrics
220+
* @return totalAssets Total assets in vault
221+
* @return totalShares Total shares issued
222+
* @return pricePerShare Current price per share
223+
*/
224+
function getVaultMetrics() public view returns (uint256 totalAssets, uint256 totalShares, uint256 pricePerShare) {
225+
totalAssets = vault.totalAssets();
226+
totalShares = vault.totalSupply();
227+
pricePerShare = getCurrentPricePerShare();
228+
}
229+
230+
/**
231+
* @notice Calculate APY between two price points
232+
* @param startPrice Starting price per share
233+
* @param endPrice Ending price per share
234+
* @param timeElapsedInSeconds Time elapsed between prices in seconds
235+
* @return apy Annual Percentage Yield in basis points (1% = 100)
236+
*/
237+
function calculateAPY(uint256 startPrice, uint256 endPrice, uint256 timeElapsedInSeconds)
238+
public
239+
pure
240+
returns (uint256 apy)
241+
{
242+
require(timeElapsedInSeconds > 0, "Time elapsed must be > 0");
243+
require(startPrice > 0, "Start price must be > 0");
244+
245+
// Calculate yield for the period
246+
uint256 yield = ((endPrice - startPrice) * 1e6) / startPrice;
247+
248+
// Annualize it (multiply by seconds in year and divide by elapsed time)
249+
uint256 secondsInYear = 365 days;
250+
apy = (yield * secondsInYear) / timeElapsedInSeconds;
251+
252+
return apy;
253+
}
254+
255+
/**
256+
* @notice Get current APY based on last update
257+
* @return Current APY in basis points (1% = 100)
258+
*/
259+
function getCurrentAPY() external view returns (uint256) {
260+
uint256 currentPrice = getCurrentPricePerShare();
261+
uint256 timeElapsed = block.timestamp - lastUpdateTime;
262+
263+
return calculateAPY(lastPricePerShare, currentPrice, timeElapsed);
264+
}
265+
266+
/**
267+
* @notice Update the stored price per share
268+
* @dev This can be called periodically to update the reference point for APY calculations
269+
*/
270+
function updatePricePerShare() external {
271+
lastPricePerShare = getCurrentPricePerShare();
272+
lastUpdateTime = block.timestamp;
273+
}
274+
275+
/**
276+
* @notice Get detailed yield information
277+
* @return currentPrice Current price per share
278+
* @return lastPrice Last recorded price per share
279+
* @return timeSinceLastUpdate Seconds since last update
280+
* @return currentAPY Current APY in basis points
281+
*/
282+
function getYieldInfo()
283+
external
284+
view
285+
returns (uint256 currentPrice, uint256 lastPrice, uint256 timeSinceLastUpdate, uint256 currentAPY)
286+
{
287+
currentPrice = getCurrentPricePerShare();
288+
lastPrice = lastPricePerShare;
289+
timeSinceLastUpdate = block.timestamp - lastUpdateTime;
290+
currentAPY = calculateAPY(lastPrice, currentPrice, timeSinceLastUpdate);
291+
292+
return (currentPrice, lastPrice, timeSinceLastUpdate, currentAPY);
293+
}
164294
}

0 commit comments

Comments
 (0)