From 16a5d597a631468d52f14ef3e44128cd0414fc73 Mon Sep 17 00:00:00 2001 From: YBM Date: Mon, 14 Oct 2024 11:32:32 -0500 Subject: [PATCH 1/3] feat: add todo notes on subscription fees --- src/contracts/facilitators/gsm/converter/GsmConverter.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/contracts/facilitators/gsm/converter/GsmConverter.sol b/src/contracts/facilitators/gsm/converter/GsmConverter.sol index 065394c5..b74a90a8 100644 --- a/src/contracts/facilitators/gsm/converter/GsmConverter.sol +++ b/src/contracts/facilitators/gsm/converter/GsmConverter.sol @@ -235,6 +235,7 @@ contract GsmConverter is Ownable, EIP712, IGsmConverter { IERC20(REDEEMED_ASSET).approve(SUBSCRIPTION_CONTRACT, redeemedAssetAmount); //TODO: replace with proper issuance implementation later MockBUIDLSubscription(SUBSCRIPTION_CONTRACT).issuance(redeemedAssetAmount); + // TODO: probably will be fees from issuance, so need to adjust the logic require( IERC20(ISSUED_ASSET).balanceOf(address(this)) == initialissuedAssetBalance + redeemedAssetAmount, @@ -243,6 +244,7 @@ contract GsmConverter is Ownable, EIP712, IGsmConverter { // reset approval after issuance IERC20(REDEEMED_ASSET).approve(SUBSCRIPTION_CONTRACT, 0); + // TODO: account for fees for sellAsset amount param IERC20(ISSUED_ASSET).approve(GSM, redeemedAssetAmount); (uint256 assetAmount, uint256 ghoBought) = IGsm(GSM).sellAsset(maxAmount, receiver); // reset approval after sellAsset From c943e73b28128aa4f84329e86a6188675659cb38 Mon Sep 17 00:00:00 2001 From: YBM Date: Mon, 14 Oct 2024 13:10:07 -0500 Subject: [PATCH 2/3] fix: account for subscription fee, resolve tests, param names --- .../gsm/converter/GsmConverter.sol | 48 ++++++++++++------- .../converter/interfaces/IGsmConverter.sol | 8 ++-- src/test/TestGsmConverter.t.sol | 2 +- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/contracts/facilitators/gsm/converter/GsmConverter.sol b/src/contracts/facilitators/gsm/converter/GsmConverter.sol index b74a90a8..a170eec2 100644 --- a/src/contracts/facilitators/gsm/converter/GsmConverter.sol +++ b/src/contracts/facilitators/gsm/converter/GsmConverter.sol @@ -185,20 +185,20 @@ contract GsmConverter is Ownable, EIP712, IGsmConverter { IGhoToken(GHO_TOKEN).transferFrom(originator, address(this), ghoAmount); IGhoToken(GHO_TOKEN).approve(address(GSM), ghoAmount); - (uint256 issuedAssetAmount, uint256 ghoSold) = IGsm(GSM).buyAsset(minAmount, address(this)); + (uint256 boughtAssetAmount, uint256 ghoSold) = IGsm(GSM).buyAsset(minAmount, address(this)); require(ghoAmount == ghoSold, 'INVALID_GHO_SOLD'); IGhoToken(GHO_TOKEN).approve(address(GSM), 0); - IERC20(ISSUED_ASSET).approve(address(REDEMPTION_CONTRACT), issuedAssetAmount); - IRedemption(REDEMPTION_CONTRACT).redeem(issuedAssetAmount); - // redeemedAssetAmount matches issuedAssetAmount because Redemption exchanges in 1:1 ratio + IERC20(ISSUED_ASSET).approve(address(REDEMPTION_CONTRACT), boughtAssetAmount); + IRedemption(REDEMPTION_CONTRACT).redeem(boughtAssetAmount); + // Redemption exchanges in 1:1 ratio between BUIDL and USDC require( IERC20(REDEEMED_ASSET).balanceOf(address(this)) == - initialRedeemedAssetBalance + issuedAssetAmount, + initialRedeemedAssetBalance + boughtAssetAmount, 'INVALID_REDEMPTION' ); IERC20(ISSUED_ASSET).approve(address(REDEMPTION_CONTRACT), 0); - IERC20(REDEEMED_ASSET).safeTransfer(receiver, issuedAssetAmount); + IERC20(REDEEMED_ASSET).safeTransfer(receiver, boughtAssetAmount); require( IGhoToken(GHO_TOKEN).balanceOf(address(this)) == initialGhoBalance, @@ -209,8 +209,8 @@ contract GsmConverter is Ownable, EIP712, IGsmConverter { 'INVALID_REMAINING_ISSUED_ASSET_BALANCE' ); - emit BuyAssetThroughRedemption(originator, receiver, issuedAssetAmount, ghoSold); - return (issuedAssetAmount, ghoSold); + emit BuyAssetThroughRedemption(originator, receiver, boughtAssetAmount, ghoSold); + return (boughtAssetAmount, ghoSold); } /** @@ -227,29 +227,37 @@ contract GsmConverter is Ownable, EIP712, IGsmConverter { address receiver ) internal returns (uint256, uint256) { uint256 initialGhoBalance = IGhoToken(GHO_TOKEN).balanceOf(address(this)); - uint256 initialissuedAssetBalance = IERC20(ISSUED_ASSET).balanceOf(address(this)); + uint256 initialIssuedAssetBalance = IERC20(ISSUED_ASSET).balanceOf(address(this)); uint256 initialRedeemedAssetBalance = IERC20(REDEEMED_ASSET).balanceOf(address(this)); - (uint256 redeemedAssetAmount, , , ) = IGsm(GSM).getGhoAmountForSellAsset(maxAmount); - IERC20(REDEEMED_ASSET).transferFrom(originator, address(this), redeemedAssetAmount); - IERC20(REDEEMED_ASSET).approve(SUBSCRIPTION_CONTRACT, redeemedAssetAmount); + (uint256 assetAmount, , , ) = IGsm(GSM).getGhoAmountForSellAsset(maxAmount); // asset is BUIDL + IERC20(REDEEMED_ASSET).transferFrom(originator, address(this), assetAmount); + IERC20(REDEEMED_ASSET).approve(SUBSCRIPTION_CONTRACT, assetAmount); //TODO: replace with proper issuance implementation later - MockBUIDLSubscription(SUBSCRIPTION_CONTRACT).issuance(redeemedAssetAmount); + MockBUIDLSubscription(SUBSCRIPTION_CONTRACT).issuance(assetAmount); + uint256 subscribedAssetAmount = IERC20(ISSUED_ASSET).balanceOf(address(this)) - + initialIssuedAssetBalance; // TODO: probably will be fees from issuance, so need to adjust the logic + // only use this require only if preview of issuance is possible, otherwise it is redundant require( IERC20(ISSUED_ASSET).balanceOf(address(this)) == - initialissuedAssetBalance + redeemedAssetAmount, + initialIssuedAssetBalance + subscribedAssetAmount, 'INVALID_ISSUANCE' ); // reset approval after issuance IERC20(REDEEMED_ASSET).approve(SUBSCRIPTION_CONTRACT, 0); // TODO: account for fees for sellAsset amount param - IERC20(ISSUED_ASSET).approve(GSM, redeemedAssetAmount); - (uint256 assetAmount, uint256 ghoBought) = IGsm(GSM).sellAsset(maxAmount, receiver); + (assetAmount, , , ) = IGsm(GSM).getGhoAmountForSellAsset(subscribedAssetAmount); // recalculate based on actual issuance amount, < maxAmount + IERC20(ISSUED_ASSET).approve(GSM, assetAmount); + (uint256 soldAssetAmount, uint256 ghoBought) = IGsm(GSM).sellAsset( + subscribedAssetAmount, + receiver + ); // reset approval after sellAsset IERC20(ISSUED_ASSET).approve(GSM, 0); + // by the end of the transaction, this contract should not retain any of the assets require( IGhoToken(GHO_TOKEN).balanceOf(address(this)) == initialGhoBalance, 'INVALID_REMAINING_GHO_BALANCE' @@ -258,8 +266,12 @@ contract GsmConverter is Ownable, EIP712, IGsmConverter { IERC20(REDEEMED_ASSET).balanceOf(address(this)) == initialRedeemedAssetBalance, 'INVALID_REMAINING_REDEEMED_ASSET_BALANCE' ); + require( + IERC20(ISSUED_ASSET).balanceOf(address(this)) == initialIssuedAssetBalance, + 'INVALID_REMAINING_ISSUED_ASSET_BALANCE' + ); - emit SellAssetThroughSubscription(originator, receiver, redeemedAssetAmount, ghoBought); - return (assetAmount, ghoBought); + emit SellAssetThroughSubscription(originator, receiver, soldAssetAmount, ghoBought); + return (soldAssetAmount, ghoBought); } } diff --git a/src/contracts/facilitators/gsm/converter/interfaces/IGsmConverter.sol b/src/contracts/facilitators/gsm/converter/interfaces/IGsmConverter.sol index 7f2c7ee0..ebc7580d 100644 --- a/src/contracts/facilitators/gsm/converter/interfaces/IGsmConverter.sol +++ b/src/contracts/facilitators/gsm/converter/interfaces/IGsmConverter.sol @@ -12,13 +12,13 @@ interface IGsmConverter { * @dev Emitted when a user buys an asset (selling GHO) in the GSM after a redemption * @param originator The address of the buyer originating the request * @param receiver The address of the receiver of the underlying asset - * @param issuedAssetAmount The amount of the issued asset converted + * @param boughtAssetAmount The amount of the asset bought * @param ghoAmount The amount of total GHO sold, inclusive of fee */ event BuyAssetThroughRedemption( address indexed originator, address indexed receiver, - uint256 issuedAssetAmount, + uint256 boughtAssetAmount, uint256 ghoAmount ); @@ -26,13 +26,13 @@ interface IGsmConverter { * @dev Emitted when a user sells an asset (buying GHO) in the GSM after an asset subscription * @param originator The address of the seller originating the request * @param receiver The address of the receiver of GHO - * @param redeemedAssetAmount The amount of the redeemed asset converted + * @param soldAssetAmount The amount of the asset sold * @param ghoAmount The amount of GHO bought, inclusive of fee */ event SellAssetThroughSubscription( address indexed originator, address indexed receiver, - uint256 redeemedAssetAmount, + uint256 soldAssetAmount, uint256 ghoAmount ); diff --git a/src/test/TestGsmConverter.t.sol b/src/test/TestGsmConverter.t.sol index b594e640..e2de64ff 100644 --- a/src/test/TestGsmConverter.t.sol +++ b/src/test/TestGsmConverter.t.sol @@ -549,7 +549,7 @@ contract TestGsmConverter is TestGhoBase { vm.startPrank(ALICE); USDC_TOKEN.approve(address(gsmConverter), DEFAULT_GSM_BUIDL_AMOUNT); - vm.expectRevert('INVALID_ISSUANCE'); + vm.expectRevert('INVALID_AMOUNT'); gsmConverter.sellAsset(DEFAULT_GSM_BUIDL_AMOUNT, ALICE); } From 0201eea2dabc908e4e2ff9a8258716b56d42c609 Mon Sep 17 00:00:00 2001 From: YBM Date: Tue, 15 Oct 2024 08:13:50 -0500 Subject: [PATCH 3/3] test: end prank --- src/test/TestGsmConverter.t.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/TestGsmConverter.t.sol b/src/test/TestGsmConverter.t.sol index e2de64ff..2e7de5df 100644 --- a/src/test/TestGsmConverter.t.sol +++ b/src/test/TestGsmConverter.t.sol @@ -544,13 +544,14 @@ contract TestGsmConverter is TestGhoBase { address(USDC_TOKEN) ); - vm.prank(FAUCET); + vm.startPrank(FAUCET); USDC_TOKEN.mint(ALICE, DEFAULT_GSM_BUIDL_AMOUNT); vm.startPrank(ALICE); USDC_TOKEN.approve(address(gsmConverter), DEFAULT_GSM_BUIDL_AMOUNT); vm.expectRevert('INVALID_AMOUNT'); gsmConverter.sellAsset(DEFAULT_GSM_BUIDL_AMOUNT, ALICE); + vm.stopPrank(); } function testRevertSellAssetInvalidRemainingGhoBalance() public {