Skip to content

Commit

Permalink
feat: wrapWithPermit (#98)
Browse files Browse the repository at this point in the history
  • Loading branch information
deluca-mike authored Jan 21, 2025
1 parent 2db6f81 commit d0f6c8c
Show file tree
Hide file tree
Showing 8 changed files with 412 additions and 46 deletions.
26 changes: 26 additions & 0 deletions src/WrappedMToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,32 @@ contract WrappedMToken is IWrappedMToken, Migratable, ERC20Extended {
return _wrap(msg.sender, recipient_, _mBalanceOf(msg.sender));
}

/// @inheritdoc IWrappedMToken
function wrapWithPermit(
address recipient_,
uint256 amount_,
uint256 deadline_,
uint8 v_,
bytes32 r_,
bytes32 s_
) external returns (uint240 wrapped_) {
IMTokenLike(mToken).permit(msg.sender, address(this), amount_, deadline_, v_, r_, s_);

return _wrap(msg.sender, recipient_, UIntMath.safe240(amount_));
}

/// @inheritdoc IWrappedMToken
function wrapWithPermit(
address recipient_,
uint256 amount_,
uint256 deadline_,
bytes memory signature_
) external returns (uint240 wrapped_) {
IMTokenLike(mToken).permit(msg.sender, address(this), amount_, deadline_, signature_);

return _wrap(msg.sender, recipient_, UIntMath.safe240(amount_));
}

/// @inheritdoc IWrappedMToken
function unwrap(address recipient_, uint256 amount_) external returns (uint240 unwrapped_) {
return _unwrap(msg.sender, recipient_, UIntMath.safe240(amount_));
Expand Down
30 changes: 30 additions & 0 deletions src/interfaces/IMTokenLike.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,36 @@ pragma solidity 0.8.26;
interface IMTokenLike {
/* ============ Interactive Functions ============ */

/**
* @notice Approves `spender` to spend up to `amount` of the token balance of `owner`, via a signature.
* @param owner The address of the account who's token balance is being approved to be spent by `spender`.
* @param spender The address of an account allowed to spend on behalf of `owner`.
* @param value The amount of the allowance being approved.
* @param deadline The last timestamp where the signature is still valid.
* @param v An ECDSA secp256k1 signature parameter (EIP-2612 via EIP-712).
* @param r An ECDSA secp256k1 signature parameter (EIP-2612 via EIP-712).
* @param s An ECDSA secp256k1 signature parameter (EIP-2612 via EIP-712).
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;

/**
* @notice Approves `spender` to spend up to `amount` of the token balance of `owner`, via a signature.
* @param owner The address of the account who's token balance is being approved to be spent by `spender`.
* @param spender The address of an account allowed to spend on behalf of `owner`.
* @param value The amount of the allowance being approved.
* @param deadline The last timestamp where the signature is still valid.
* @param signature An arbitrary signature (EIP-712).
*/
function permit(address owner, address spender, uint256 value, uint256 deadline, bytes memory signature) external;

/**
* @notice Allows a calling account to transfer `amount` tokens to `recipient`.
* @param recipient The address of the recipient who's token balance will be incremented.
Expand Down
34 changes: 34 additions & 0 deletions src/interfaces/IWrappedMToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,40 @@ interface IWrappedMToken is IMigratable, IERC20Extended {
*/
function wrap(address recipient) external returns (uint240 wrapped);

/**
* @notice Wraps `amount` M from the caller into wM for `recipient`, using a permit.
* @param recipient The account receiving the minted wM.
* @param amount The amount of M deposited.
* @param deadline The last timestamp where the signature is still valid.
* @param v An ECDSA secp256k1 signature parameter (EIP-2612 via EIP-712).
* @param r An ECDSA secp256k1 signature parameter (EIP-2612 via EIP-712).
* @param s An ECDSA secp256k1 signature parameter (EIP-2612 via EIP-712).
* @return wrapped The amount of wM minted.
*/
function wrapWithPermit(
address recipient,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint240 wrapped);

/**
* @notice Wraps `amount` M from the caller into wM for `recipient`, using a permit.
* @param recipient The account receiving the minted wM.
* @param amount The amount of M deposited.
* @param deadline The last timestamp where the signature is still valid.
* @param signature An arbitrary signature (EIP-712).
* @return wrapped The amount of wM minted.
*/
function wrapWithPermit(
address recipient,
uint256 amount,
uint256 deadline,
bytes memory signature
) external returns (uint240 wrapped);

/**
* @notice Unwraps `amount` wM from the caller into M for `recipient`.
* @param recipient The account receiving the withdrawn M.
Expand Down
16 changes: 16 additions & 0 deletions test/integration/Protocol.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ contract ProtocolIntegrationTests is TestBase {
assertTrue(_mToken.isEarning(address(_wrappedMToken)));
}

function test_wrapWithPermits() external {
_giveM(_alice, 200_000000);

assertEq(_mToken.balanceOf(_alice), 200_000000);

_wrapWithPermitVRS(_alice, _aliceKey, _alice, 100_000000, 0, block.timestamp);

assertEq(_mToken.balanceOf(_alice), 100_000000);
assertEq(_wrappedMToken.balanceOf(_alice), 99_999999);

_wrapWithPermitSignature(_alice, _aliceKey, _alice, 100_000000, 1, block.timestamp);

assertEq(_mToken.balanceOf(_alice), 0);
assertEq(_wrappedMToken.balanceOf(_alice), 199_999999);
}

function test_integration_yieldAccumulation() external {
_giveM(_alice, 100_000000);

Expand Down
67 changes: 67 additions & 0 deletions test/integration/TestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
pragma solidity 0.8.26;

import { IERC20 } from "../../lib/common/src/interfaces/IERC20.sol";
import { IERC20Extended } from "../../lib/common/src/interfaces/IERC20Extended.sol";
import { IERC712 } from "../../lib/common/src/interfaces/IERC712.sol";

import { Test } from "../../lib/forge-std/src/Test.sol";

Expand Down Expand Up @@ -55,6 +57,8 @@ contract TestBase is Test {
address internal _ivan = makeAddr("ivan");
address internal _judy = makeAddr("judy");

uint256 internal _aliceKey = _makeKey("alice");

address[] internal _accounts = [_alice, _bob, _carol, _dave, _eric, _frank, _grace, _henry, _ivan, _judy];

address internal _implementationV2;
Expand Down Expand Up @@ -113,6 +117,34 @@ contract TestBase is Test {
_wrappedMToken.wrap(recipient_);
}

function _wrapWithPermitVRS(
address account_,
uint256 signerPrivateKey_,
address recipient_,
uint256 amount_,
uint256 nonce_,
uint256 deadline_
) internal {
(uint8 v_, bytes32 r_, bytes32 s_) = _getPermit(account_, signerPrivateKey_, amount_, nonce_, deadline_);

vm.prank(account_);
_wrappedMToken.wrapWithPermit(recipient_, amount_, deadline_, v_, r_, s_);
}

function _wrapWithPermitSignature(
address account_,
uint256 signerPrivateKey_,
address recipient_,
uint256 amount_,
uint256 nonce_,
uint256 deadline_
) internal {
(uint8 v_, bytes32 r_, bytes32 s_) = _getPermit(account_, signerPrivateKey_, amount_, nonce_, deadline_);

vm.prank(account_);
_wrappedMToken.wrapWithPermit(recipient_, amount_, deadline_, abi.encodePacked(r_, s_, v_));
}

function _unwrap(address account_, address recipient_, uint256 amount_) internal {
vm.prank(account_);
_wrappedMToken.unwrap(recipient_, amount_);
Expand Down Expand Up @@ -162,4 +194,39 @@ contract TestBase is Test {
vm.prank(_migrationAdmin);
_wrappedMToken.migrate(_migratorV1);
}

/* ============ utils ============ */

function _makeKey(string memory name_) internal returns (uint256 key_) {
(, key_) = makeAddrAndKey(name_);
}

function _getPermit(
address account_,
uint256 signerPrivateKey_,
uint256 amount_,
uint256 nonce_,
uint256 deadline_
) internal view returns (uint8 v_, bytes32 r_, bytes32 s_) {
return
vm.sign(
signerPrivateKey_,
keccak256(
abi.encodePacked(
"\x19\x01",
IERC712(address(_mToken)).DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
IERC20Extended(address(_mToken)).PERMIT_TYPEHASH(),
account_,
address(_wrappedMToken),
amount_,
nonce_,
deadline_
)
)
)
)
);
}
}
Loading

0 comments on commit d0f6c8c

Please sign in to comment.