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

Modify l2deposit using spender from user with permit #115

16 changes: 14 additions & 2 deletions contracts/L2Deposit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import "./interfaces/IL2Deposit.sol";
import "./interfaces/IOptimismL1StandardBridge.sol";
import "./interfaces/IPermanentStorage.sol";
import "./interfaces/ISpender.sol";
import "./utils/SpenderLibEIP712.sol";
import "./utils/StrategyBase.sol";
import "./utils/BaseLibEIP712.sol";
import "./utils/Ownable.sol";
Expand Down Expand Up @@ -49,8 +50,19 @@ contract L2Deposit is IL2Deposit, StrategyBase, ReentrancyGuard, BaseLibEIP712,
// PermanentStorage will revert when L2 deposit hash is already seen
permStorage.setL2DepositSeen(depositHash);

// Transfer token from sender to this contract
spender.spendFromUser(_params.deposit.sender, _params.deposit.l1TokenAddr, _params.deposit.amount);
// Create spendWithPermit for spender contract to verify
SpenderLibEIP712.SpendWithPermit memory l1TokenPermit = SpenderLibEIP712.SpendWithPermit({
tokenAddr: _params.deposit.l1TokenAddr,
requester: address(this),
user: _params.deposit.sender,
recipient: address(this),
amount: _params.deposit.amount,
actionHash: depositHash,
expiry: uint64(_params.deposit.expiry)
});

// Verfiy signature then transfer token from sender to this contract
spender.spendFromUserToWithPermit(l1TokenPermit, _params.l1TokenPermitSig);

// Bypass stack too deep
DepositInfo memory depositInfo = DepositInfo(
Expand Down
1 change: 1 addition & 0 deletions contracts/interfaces/IL2Deposit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface IL2Deposit is IStrategyBase {
struct DepositParams {
L2DepositLibEIP712.Deposit deposit;
bytes depositSig;
bytes l1TokenPermitSig;
}

/// @notice Deposit user's fund into L2 bridge
Expand Down
90 changes: 72 additions & 18 deletions contracts/test/forkMainnet/L2Deposit/Deposit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ pragma abicoder v2;
import "contracts/interfaces/IL2Deposit.sol";
import "contracts-test/forkMainnet/L2Deposit/Setup.t.sol";
import "contracts-test/utils/BalanceSnapshot.sol";
import "contracts-test/utils/Permit.sol";

interface IArbitrumBridge {
function delayedMessageCount() external view returns (uint256);
}

contract TestL2DepositTopUp is TestL2Deposit {
contract TestL2DepositTopUp is TestL2Deposit, Permit {
using BalanceSnapshot for BalanceSnapshot.Snapshot;

uint256 arbMaxSubmissionCost = 1e18;
Expand All @@ -20,18 +21,28 @@ contract TestL2DepositTopUp is TestL2Deposit {
function testCannotDepositIfExpired() public {
// overwrite expiry
DEFAULT_DEPOSIT.expiry = block.timestamp;
// sig is not relevent in this case
bytes memory sig = bytes("");
bytes memory payload = abi.encodeWithSelector(L2Deposit.deposit.selector, IL2Deposit.DepositParams(DEFAULT_DEPOSIT, sig));
// the signatures is not relevent in this case
bytes memory depositActionSig = bytes("");
bytes memory spenderPermitSig = bytes("");
bytes memory payload = abi.encodeWithSelector(
L2Deposit.deposit.selector,
IL2Deposit.DepositParams(DEFAULT_DEPOSIT, depositActionSig, spenderPermitSig)
);

vm.expectRevert("L2Deposit: Deposit is expired");
userProxy.toL2Deposit(payload);
}

function testCannotDepositWithInvalidSig() public {
// compose payload with signature from bob
bytes memory sig = _signDeposit(bobPrivateKey, DEFAULT_DEPOSIT);
bytes memory payload = abi.encodeWithSelector(L2Deposit.deposit.selector, IL2Deposit.DepositParams(DEFAULT_DEPOSIT, sig));
bytes memory depositActionSig = _signDeposit(bobPrivateKey, DEFAULT_DEPOSIT);
// spenderPermitSig is not relevent in this case
bytes memory spenderPermitSig = bytes("");

bytes memory payload = abi.encodeWithSelector(
L2Deposit.deposit.selector,
IL2Deposit.DepositParams(DEFAULT_DEPOSIT, depositActionSig, spenderPermitSig)
);

vm.expectRevert("L2Deposit: Invalid deposit signature");
userProxy.toL2Deposit(payload);
Expand All @@ -43,9 +54,22 @@ contract TestL2DepositTopUp is TestL2Deposit {
// overwrite deposit data with encoded arbitrum specific params
DEFAULT_DEPOSIT.data = abi.encode(arbMaxSubmissionCost, arbMaxGas, arbGasPriceBid);

// compose payload with signature
bytes memory sig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT);
bytes memory payload = abi.encodeWithSelector(L2Deposit.deposit.selector, IL2Deposit.DepositParams(DEFAULT_DEPOSIT, sig));
// sign the deposit action
bytes memory depositActionSig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT);
// create spendWithPermit using the deposit and sign it
SpenderLibEIP712.SpendWithPermit memory spendWithPermit = _createSpenderPermitFromL2Deposit(DEFAULT_DEPOSIT);
bytes memory spenderPermitSig = signSpendWithPermit(
userPrivateKey,
spendWithPermit,
spender.EIP712_DOMAIN_SEPARATOR(),
SignatureValidator.SignatureType.EIP712
);

bytes memory payload = abi.encodeWithSelector(
L2Deposit.deposit.selector,
IL2Deposit.DepositParams(DEFAULT_DEPOSIT, depositActionSig, spenderPermitSig)
);

uint256 callValue = arbMaxSubmissionCost + (arbMaxGas * arbGasPriceBid);

vm.expectEmit(true, true, true, true);
Expand All @@ -69,9 +93,19 @@ contract TestL2DepositTopUp is TestL2Deposit {
// overwrite deposit data with encoded arbitrum specific params
DEFAULT_DEPOSIT.data = abi.encode(arbMaxSubmissionCost, arbMaxGas, arbGasPriceBid);

// compose payload with signature
bytes memory sig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT);
bytes memory payload = abi.encodeWithSelector(L2Deposit.deposit.selector, IL2Deposit.DepositParams(DEFAULT_DEPOSIT, sig));
bytes memory depositActionSig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT);
SpenderLibEIP712.SpendWithPermit memory spendWithPermit = _createSpenderPermitFromL2Deposit(DEFAULT_DEPOSIT);
bytes memory spenderPermitSig = signSpendWithPermit(
userPrivateKey,
spendWithPermit,
spender.EIP712_DOMAIN_SEPARATOR(),
SignatureValidator.SignatureType.EIP712
);

bytes memory payload = abi.encodeWithSelector(
L2Deposit.deposit.selector,
IL2Deposit.DepositParams(DEFAULT_DEPOSIT, depositActionSig, spenderPermitSig)
);
uint256 callValue = arbMaxSubmissionCost + (arbMaxGas * arbGasPriceBid);

userProxy.toL2Deposit{ value: callValue }(payload);
Expand All @@ -88,9 +122,19 @@ contract TestL2DepositTopUp is TestL2Deposit {
// overwrite deposit data with encoded arbitrum specific params
DEFAULT_DEPOSIT.data = abi.encode(arbMaxSubmissionCost, arbMaxGas, arbGasPriceBid);

// compose payload with signature
bytes memory sig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT);
bytes memory payload = abi.encodeWithSelector(L2Deposit.deposit.selector, IL2Deposit.DepositParams(DEFAULT_DEPOSIT, sig));
bytes memory depositActionSig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT);
SpenderLibEIP712.SpendWithPermit memory spendWithPermit = _createSpenderPermitFromL2Deposit(DEFAULT_DEPOSIT);
bytes memory spenderPermitSig = signSpendWithPermit(
userPrivateKey,
spendWithPermit,
spender.EIP712_DOMAIN_SEPARATOR(),
SignatureValidator.SignatureType.EIP712
);

bytes memory payload = abi.encodeWithSelector(
L2Deposit.deposit.selector,
IL2Deposit.DepositParams(DEFAULT_DEPOSIT, depositActionSig, spenderPermitSig)
);
uint256 callValue = arbMaxSubmissionCost + (arbMaxGas * arbGasPriceBid);

vm.expectRevert("L2Deposit: Incorrect L2 token address");
Expand All @@ -101,9 +145,19 @@ contract TestL2DepositTopUp is TestL2Deposit {
// overwrite deposit data with encoded arbitrum specific params
DEFAULT_DEPOSIT.data = abi.encode(arbMaxSubmissionCost, arbMaxGas, arbGasPriceBid);

// compose payload with signature
bytes memory sig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT);
bytes memory payload = abi.encodeWithSelector(L2Deposit.deposit.selector, IL2Deposit.DepositParams(DEFAULT_DEPOSIT, sig));
bytes memory depositActionSig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT);
SpenderLibEIP712.SpendWithPermit memory spendWithPermit = _createSpenderPermitFromL2Deposit(DEFAULT_DEPOSIT);
bytes memory spenderPermitSig = signSpendWithPermit(
userPrivateKey,
spendWithPermit,
spender.EIP712_DOMAIN_SEPARATOR(),
SignatureValidator.SignatureType.EIP712
);

bytes memory payload = abi.encodeWithSelector(
L2Deposit.deposit.selector,
IL2Deposit.DepositParams(DEFAULT_DEPOSIT, depositActionSig, spenderPermitSig)
);

// compose inbox revert data with defined error
uint256 l2Callvalue = 0; // l2 call value 0 by default
Expand Down
21 changes: 17 additions & 4 deletions contracts/test/forkMainnet/L2Deposit/Optimism.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import "contracts/interfaces/IL2Deposit.sol";
import "contracts/utils/L2DepositLibEIP712.sol";
import "contracts-test/forkMainnet/L2Deposit/Setup.t.sol";
import "contracts-test/utils/BalanceSnapshot.sol";
import "contracts-test/utils/Permit.sol";

contract TestL2DepositOptimism is TestL2Deposit {
contract TestL2DepositOptimism is TestL2Deposit, Permit {
using BalanceSnapshot for BalanceSnapshot.Snapshot;

uint32 optL2Gas = 1e6;
Expand All @@ -24,9 +25,21 @@ contract TestL2DepositOptimism is TestL2Deposit {
// overwrite deposit data with encoded optimism specific params
DEFAULT_DEPOSIT.data = abi.encode(optL2Gas);

// compose payload with signature
bytes memory sig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT);
bytes memory payload = abi.encodeWithSelector(L2Deposit.deposit.selector, IL2Deposit.DepositParams(DEFAULT_DEPOSIT, sig));
// sign the deposit action
bytes memory depositActionSig = _signDeposit(userPrivateKey, DEFAULT_DEPOSIT);
// create spendWithPermit using the deposit and sign it
SpenderLibEIP712.SpendWithPermit memory spendWithPermit = _createSpenderPermitFromL2Deposit(DEFAULT_DEPOSIT);
bytes memory spenderPermitSig = signSpendWithPermit(
userPrivateKey,
spendWithPermit,
spender.EIP712_DOMAIN_SEPARATOR(),
SignatureValidator.SignatureType.EIP712
);

bytes memory payload = abi.encodeWithSelector(
L2Deposit.deposit.selector,
IL2Deposit.DepositParams(DEFAULT_DEPOSIT, depositActionSig, spenderPermitSig)
);

vm.expectEmit(true, true, true, true);
emit Deposited(
Expand Down
56 changes: 35 additions & 21 deletions contracts/test/forkMainnet/L2Deposit/Setup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "contracts/L2Deposit.sol";
import "contracts/utils/SignatureValidator.sol";
import "contracts/utils/L2DepositLibEIP712.sol";
import "contracts/utils/SpenderLibEIP712.sol";
import "contracts/interfaces/IArbitrumL1GatewayRouter.sol";
import "contracts/interfaces/IOptimismL1StandardBridge.sol";

Expand Down Expand Up @@ -53,34 +54,34 @@ contract TestL2Deposit is StrategySharedSetup {
// Set user token balance and approve
setEOABalanceAndApprove(user, tokens, 100);

DEFAULT_DEPOSIT = L2DepositLibEIP712.Deposit(
L2DepositLibEIP712.L2Identifier.Arbitrum, // l2Identifier
LON_ADDRESS, // l1TokenAddr
address(arbitrumLONAddr), // l2TokenAddr
user, // sender
user, // recipient
user, // arbitrumRefundAddr
1 ether, // amount
1234, // salt
DEFAULT_DEADLINE, // expiry
bytes("") // data
);
DEFAULT_DEPOSIT = L2DepositLibEIP712.Deposit({
l2Identifier: L2DepositLibEIP712.L2Identifier.Arbitrum,
l1TokenAddr: LON_ADDRESS,
l2TokenAddr: address(arbitrumLONAddr),
sender: user,
recipient: user,
arbitrumRefundAddr: user,
amount: 1 ether,
salt: 1234,
expiry: DEFAULT_DEADLINE,
data: bytes("")
});

// Label addresses for easier debugging
vm.label(user, "User");
vm.label(address(this), "TestingContract");
}

function _deployStrategyAndUpgrade() internal override returns (address) {
l2Deposit = new L2Deposit(
address(this), // This contract would be the owner
address(userProxy),
WETH_ADDRESS,
address(permanentStorage),
address(spender),
arbitrumL1GatewayRouter,
optimismL1StandardBridge
);
l2Deposit = new L2Deposit({
_owner: address(this), // This contract would be the owner
_userProxy: address(userProxy),
_weth: WETH_ADDRESS,
_permStorage: address(permanentStorage),
_spender: address(spender),
_arbitrumL1GatewayRouter: arbitrumL1GatewayRouter,
_optimismL1StandardBridge: optimismL1StandardBridge
});

// Hook up L2Deposit
userProxy.upgradeL2Deposit(address(l2Deposit), true);
Expand Down Expand Up @@ -129,4 +130,17 @@ contract TestL2Deposit is StrategySharedSetup {
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, EIP712SignDigest);
return abi.encodePacked(r, s, v, bytes32(0), uint8(SignatureValidator.SignatureType.EIP712));
}

function _createSpenderPermitFromL2Deposit(L2DepositLibEIP712.Deposit memory _deposit) internal view returns (SpenderLibEIP712.SpendWithPermit memory) {
return
SpenderLibEIP712.SpendWithPermit({
tokenAddr: _deposit.l1TokenAddr,
requester: address(l2Deposit),
user: _deposit.sender,
recipient: address(l2Deposit),
amount: _deposit.amount,
actionHash: getEIP712Hash(l2Deposit.EIP712_DOMAIN_SEPARATOR(), L2DepositLibEIP712._getDepositHash(_deposit)),
expiry: uint64(_deposit.expiry)
});
}
}