Skip to content

Commit

Permalink
Merge pull request #53 from ibdotxyz/unused_native_amount
Browse files Browse the repository at this point in the history
use unusedNativeToken to track native token amount
  • Loading branch information
ibsunhub committed Jun 28, 2023
2 parents 02d0e03 + 2c1cd16 commit 95500d7
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 31 deletions.
12 changes: 9 additions & 3 deletions src/extensions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ Help user wrap Ether into WETH and supply it into Iron Bank.

Action name: `ACTION_SUPPLY_NATIVE_TOKEN`

Action data: None, but `msg.value` should be the supply amount.
Action data:
| Type | Description |
|------|-------------|
| uint256 | the supply amount |

### Borrow Native Token

Expand All @@ -48,11 +51,14 @@ Action data:

### Repay Native Token

Help user wrap Ether into WETH and repay it into Iron Bank. If user repays more than borrow balance, the excessive amount will return to user.
Help user wrap Ether into WETH and repay it into Iron Bank.

Action name: `ACTION_REPAY_NATIVE_TOKEN`

Action data: None, but `msg.value` should be the repay amount.
Action data:
| Type | Description |
|------|-------------|
| uint256 | the repay amount, -1 will repay full |

### Supply

Expand Down
57 changes: 34 additions & 23 deletions src/extensions/TxBuilderExtension.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ contract TxBuilderExtension is ReentrancyGuard, Ownable2Step, DeferLiquidityChec
/// @notice The action for redeeming pToken
bytes32 public constant ACTION_REDEEM_PTOKEN = "ACTION_REDEEM_PTOKEN";

/// @dev Transient storage variable used for native token amount
uint256 private unusedNativeToken;

/// @notice The address of IronBank
IronBankInterface public immutable ironBank;

Expand Down Expand Up @@ -98,6 +101,8 @@ contract TxBuilderExtension is ReentrancyGuard, Ownable2Step, DeferLiquidityChec
* @param actions The list of actions
*/
function execute(Action[] calldata actions) external payable {
unusedNativeToken = msg.value;

executeInternal(msg.sender, actions, 0);
}

Expand Down Expand Up @@ -139,7 +144,8 @@ contract TxBuilderExtension is ReentrancyGuard, Ownable2Step, DeferLiquidityChec
* @param index The index of the action to start with
*/
function executeInternal(address user, Action[] memory actions, uint256 index) internal {
for (uint256 i = index; i < actions.length;) {
uint256 i = index;
while (i < actions.length) {
Action memory action = actions[i];
if (action.name == ACTION_DEFER_LIQUIDITY_CHECK) {
deferLiquidityCheck(user, abi.encode(user, actions, i + 1));
Expand All @@ -159,15 +165,19 @@ contract TxBuilderExtension is ReentrancyGuard, Ownable2Step, DeferLiquidityChec
(address asset, uint256 amount) = abi.decode(action.data, (address, uint256));
repay(user, asset, amount);
} else if (action.name == ACTION_SUPPLY_NATIVE_TOKEN) {
supplyNativeToken(user);
uint256 supplyAmount = abi.decode(action.data, (uint256));
supplyNativeToken(user, supplyAmount);
unusedNativeToken -= supplyAmount;
} else if (action.name == ACTION_BORROW_NATIVE_TOKEN) {
uint256 borrowAmount = abi.decode(action.data, (uint256));
borrowNativeToken(user, borrowAmount);
} else if (action.name == ACTION_REDEEM_NATIVE_TOKEN) {
uint256 redeemAmount = abi.decode(action.data, (uint256));
redeemNativeToken(user, redeemAmount);
} else if (action.name == ACTION_REPAY_NATIVE_TOKEN) {
repayNativeToken(user);
uint256 repayAmount = abi.decode(action.data, (uint256));
repayAmount = repayNativeToken(user, repayAmount);
unusedNativeToken -= repayAmount;
} else if (action.name == ACTION_SUPPLY_STETH) {
uint256 amount = abi.decode(action.data, (uint256));
supplyStEth(user, amount);
Expand All @@ -194,6 +204,13 @@ contract TxBuilderExtension is ReentrancyGuard, Ownable2Step, DeferLiquidityChec
i++;
}
}

// Refund unused native token back to user if the action list is fully executed.
if (i == actions.length && unusedNativeToken > 0) {
(bool sent,) = user.call{value: unusedNativeToken}("");
require(sent, "failed to send native token");
unusedNativeToken = 0;
}
}

/**
Expand Down Expand Up @@ -248,11 +265,12 @@ contract TxBuilderExtension is ReentrancyGuard, Ownable2Step, DeferLiquidityChec
/**
* @dev Wraps the native token and supplies it to Iron Bank.
* @param user The address of the user
* @param supplyAmount The amount of the wrapped native token to supply
*/
function supplyNativeToken(address user) internal nonReentrant {
WethInterface(weth).deposit{value: msg.value}();
IERC20(weth).safeIncreaseAllowance(address(ironBank), msg.value);
ironBank.supply(address(this), user, weth, msg.value);
function supplyNativeToken(address user, uint256 supplyAmount) internal nonReentrant {
WethInterface(weth).deposit{value: supplyAmount}();
IERC20(weth).safeIncreaseAllowance(address(ironBank), supplyAmount);
ironBank.supply(address(this), user, weth, supplyAmount);
}

/**
Expand Down Expand Up @@ -285,25 +303,18 @@ contract TxBuilderExtension is ReentrancyGuard, Ownable2Step, DeferLiquidityChec

/**
* @dev Wraps the native token and repays it to Iron Bank.
* @dev If the amount of the native token is greater than the borrow balance, the excess amount will be sent back to the user.
* @param user The address of the user
* @param repayAmount The amount of the wrapped native token to repay, -1 means repay all
*/
function repayNativeToken(address user) internal nonReentrant {
uint256 repayAmount = msg.value;

ironBank.accrueInterest(weth);
uint256 borrowBalance = ironBank.getBorrowBalance(user, weth);
if (repayAmount > borrowBalance) {
WethInterface(weth).deposit{value: borrowBalance}();
IERC20(weth).safeIncreaseAllowance(address(ironBank), borrowBalance);
ironBank.repay(address(this), user, weth, borrowBalance);
(bool sent,) = user.call{value: repayAmount - borrowBalance}("");
require(sent, "failed to send native token");
} else {
WethInterface(weth).deposit{value: repayAmount}();
IERC20(weth).safeIncreaseAllowance(address(ironBank), repayAmount);
ironBank.repay(address(this), user, weth, repayAmount);
function repayNativeToken(address user, uint256 repayAmount) internal nonReentrant returns (uint256) {
if (repayAmount == type(uint256).max) {
ironBank.accrueInterest(weth);
repayAmount = ironBank.getBorrowBalance(user, weth);
}
WethInterface(weth).deposit{value: repayAmount}();
IERC20(weth).safeIncreaseAllowance(address(ironBank), repayAmount);
ironBank.repay(address(this), user, weth, repayAmount);
return repayAmount;
}

/**
Expand Down
119 changes: 114 additions & 5 deletions test/extensions/TestTxBuilderExtension_integration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ contract TxBuilderExtensionIntegrationTest is Test, Common {

vm.prank(user1);
TxBuilderExtension.Action[] memory actions = new TxBuilderExtension.Action[](1);
actions[0] = TxBuilderExtension.Action({name: "ACTION_SUPPLY_NATIVE_TOKEN", data: bytes("")});
actions[0] = TxBuilderExtension.Action({name: "ACTION_SUPPLY_NATIVE_TOKEN", data: abi.encode(supplyAmount)});
extension.execute{value: supplyAmount}(actions);

uint256 poolWethAfter = IERC20(WETH).balanceOf(address(ib));
Expand Down Expand Up @@ -159,7 +159,7 @@ contract TxBuilderExtensionIntegrationTest is Test, Common {

vm.prank(user1);
TxBuilderExtension.Action[] memory actions = new TxBuilderExtension.Action[](1);
actions[0] = TxBuilderExtension.Action({name: "ACTION_SUPPLY_NATIVE_TOKEN", data: bytes("")});
actions[0] = TxBuilderExtension.Action({name: "ACTION_SUPPLY_NATIVE_TOKEN", data: abi.encode(supplyAmount)});
extension.execute{value: supplyAmount}(actions);

uint256 poolWethBefore = IERC20(WETH).balanceOf(address(ib));
Expand All @@ -183,7 +183,7 @@ contract TxBuilderExtensionIntegrationTest is Test, Common {

vm.prank(user1);
TxBuilderExtension.Action[] memory actions = new TxBuilderExtension.Action[](1);
actions[0] = TxBuilderExtension.Action({name: "ACTION_SUPPLY_NATIVE_TOKEN", data: bytes("")});
actions[0] = TxBuilderExtension.Action({name: "ACTION_SUPPLY_NATIVE_TOKEN", data: abi.encode(supplyAmount)});
extension.execute{value: supplyAmount}(actions);

uint256 poolWethBefore = IERC20(WETH).balanceOf(address(ib));
Expand Down Expand Up @@ -219,7 +219,7 @@ contract TxBuilderExtensionIntegrationTest is Test, Common {

vm.prank(user1);
actions = new TxBuilderExtension.Action[](1);
actions[0] = TxBuilderExtension.Action({name: "ACTION_REPAY_NATIVE_TOKEN", data: bytes("")});
actions[0] = TxBuilderExtension.Action({name: "ACTION_REPAY_NATIVE_TOKEN", data: abi.encode(repayAmount)});
extension.execute{value: repayAmount}(actions);

uint256 poolWethAfter = IERC20(WETH).balanceOf(address(ib));
Expand All @@ -245,7 +245,7 @@ contract TxBuilderExtensionIntegrationTest is Test, Common {

vm.prank(user1);
actions = new TxBuilderExtension.Action[](1);
actions[0] = TxBuilderExtension.Action({name: "ACTION_REPAY_NATIVE_TOKEN", data: bytes("")});
actions[0] = TxBuilderExtension.Action({name: "ACTION_REPAY_NATIVE_TOKEN", data: abi.encode(type(uint256).max)});
extension.execute{value: repayAmount}(actions);

uint256 poolWethAfter = IERC20(WETH).balanceOf(address(ib));
Expand All @@ -254,6 +254,23 @@ contract TxBuilderExtensionIntegrationTest is Test, Common {
assertEq(user1EthBefore - user1EthAfter, 10e18);
}

function testRefundUnusedEther() public {
uint256 poolWethBefore = IERC20(WETH).balanceOf(address(ib));
uint256 user1EthBefore = user1.balance;
uint256 nativeTokenAmount = 15e18;
uint256 supplyAmount = 10e18;

vm.prank(user1);
TxBuilderExtension.Action[] memory actions = new TxBuilderExtension.Action[](1);
actions[0] = TxBuilderExtension.Action({name: "ACTION_SUPPLY_NATIVE_TOKEN", data: abi.encode(supplyAmount)});
extension.execute{value: nativeTokenAmount}(actions);

uint256 poolWethAfter = IERC20(WETH).balanceOf(address(ib));
uint256 user1EthAfter = user1.balance;
assertEq(poolWethAfter - poolWethBefore, supplyAmount);
assertEq(user1EthBefore - user1EthAfter, supplyAmount);
}

function testSupplyBorrowRedeemRepay() public {
/**
* Supply 10,000 DAI to borrow 5,000 USDT and repay and redeem full.
Expand Down Expand Up @@ -514,6 +531,98 @@ contract TxBuilderExtensionIntegrationTest is Test, Common {
extension.execute(actions);
}

function testDeferLiquidityCheckWithSupplyEther() public {
uint256 poolWethBefore = IERC20(WETH).balanceOf(address(ib));
uint256 user1EthBefore = user1.balance;
uint256 supplyAmount = 10e18;

vm.prank(user1);
TxBuilderExtension.Action[] memory actions = new TxBuilderExtension.Action[](2);
actions[0] = TxBuilderExtension.Action({name: "ACTION_DEFER_LIQUIDITY_CHECK", data: bytes("")});
actions[1] = TxBuilderExtension.Action({name: "ACTION_SUPPLY_NATIVE_TOKEN", data: abi.encode(supplyAmount)});
extension.execute{value: supplyAmount}(actions);

uint256 poolWethAfter = IERC20(WETH).balanceOf(address(ib));
uint256 user1EthAfter = user1.balance;
assertEq(poolWethAfter - poolWethBefore, supplyAmount);
assertEq(user1EthBefore - user1EthAfter, supplyAmount);
}

function testDeferLiquidityCheckWithSupplyEther2() public {
uint256 poolWethBefore = IERC20(WETH).balanceOf(address(ib));
uint256 user1EthBefore = user1.balance;
uint256 nativeTokenAmount = 25e18;
uint256 supplyAmount = 10e18;

vm.prank(user1);
TxBuilderExtension.Action[] memory actions = new TxBuilderExtension.Action[](3);
actions[0] = TxBuilderExtension.Action({name: "ACTION_DEFER_LIQUIDITY_CHECK", data: bytes("")});
actions[1] = TxBuilderExtension.Action({name: "ACTION_SUPPLY_NATIVE_TOKEN", data: abi.encode(supplyAmount)});
actions[2] = TxBuilderExtension.Action({name: "ACTION_SUPPLY_NATIVE_TOKEN", data: abi.encode(supplyAmount)});
extension.execute{value: nativeTokenAmount}(actions);

uint256 poolWethAfter = IERC20(WETH).balanceOf(address(ib));
uint256 user1EthAfter = user1.balance;
assertEq(poolWethAfter - poolWethBefore, 20e18); // supply twice
assertEq(user1EthBefore - user1EthAfter, 20e18); // 5 eth will be refunded
}

function testDeferLiquidityCheckWithRepayEther() public {
prepareBorrow();

uint256 borrowAmount = 10e18;

vm.prank(user1);
TxBuilderExtension.Action[] memory actions = new TxBuilderExtension.Action[](1);
actions[0] = TxBuilderExtension.Action({name: "ACTION_BORROW_NATIVE_TOKEN", data: abi.encode(borrowAmount)});
extension.execute(actions);

uint256 poolWethBefore = IERC20(WETH).balanceOf(address(ib));
uint256 user1EthBefore = user1.balance;

uint256 repayAmount = 5e18;

vm.prank(user1);
actions = new TxBuilderExtension.Action[](2);
actions[0] = TxBuilderExtension.Action({name: "ACTION_DEFER_LIQUIDITY_CHECK", data: bytes("")});
actions[1] = TxBuilderExtension.Action({name: "ACTION_REPAY_NATIVE_TOKEN", data: abi.encode(repayAmount)});
extension.execute{value: repayAmount}(actions);

uint256 poolWethAfter = IERC20(WETH).balanceOf(address(ib));
uint256 user1EthAfter = user1.balance;
assertEq(poolWethAfter - poolWethBefore, repayAmount);
assertEq(user1EthBefore - user1EthAfter, repayAmount);
}

function testDeferLiquidityCheckWithRepayEther2() public {
prepareBorrow();

uint256 borrowAmount = 12e18;

vm.prank(user1);
TxBuilderExtension.Action[] memory actions = new TxBuilderExtension.Action[](1);
actions[0] = TxBuilderExtension.Action({name: "ACTION_BORROW_NATIVE_TOKEN", data: abi.encode(borrowAmount)});
extension.execute(actions);

uint256 poolWethBefore = IERC20(WETH).balanceOf(address(ib));
uint256 user1EthBefore = user1.balance;

uint256 nativeTokenAmount = 15e18;
uint256 repayAmount = 5e18;

vm.prank(user1);
actions = new TxBuilderExtension.Action[](3);
actions[0] = TxBuilderExtension.Action({name: "ACTION_DEFER_LIQUIDITY_CHECK", data: bytes("")});
actions[1] = TxBuilderExtension.Action({name: "ACTION_REPAY_NATIVE_TOKEN", data: abi.encode(repayAmount)});
actions[2] = TxBuilderExtension.Action({name: "ACTION_REPAY_NATIVE_TOKEN", data: abi.encode(type(uint256).max)}); // repay 7 eth
extension.execute{value: nativeTokenAmount}(actions);

uint256 poolWethAfter = IERC20(WETH).balanceOf(address(ib));
uint256 user1EthAfter = user1.balance;
assertEq(poolWethAfter - poolWethBefore, borrowAmount);
assertEq(user1EthBefore - user1EthAfter, borrowAmount);
}

function testDeferLiquidityCheckWithPToken() public {
uint256 supplyAmount = 10000e18;
uint256 borrowAmount = 5000e6; // USDT
Expand Down

0 comments on commit 95500d7

Please sign in to comment.