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

use unusedNativeToken to track native token amount #53

Merged
merged 1 commit into from
Jun 28, 2023
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
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