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

Fix DLLR EIP-2612 bug with Permit2 #18

Merged
merged 19 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
94 changes: 92 additions & 2 deletions contracts/integration/MoC/MocIntegration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
import "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol";
import { IMocMintRedeemDoc } from "./IMoC.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "../../meta-asset-token/DLLR.sol";
import "../../interfaces/IMassetManager.sol";
import { IDLLR, PermitParams } from "../../interfaces/IDLLR.sol";
import { IPermit2, ISignatureTransfer } from "../../permit2/interfaces/IPermit2.sol";


/// @notice This contract provides compound functions with Money On Chain wrapping them in one transaction for convenience and to save on gas

Check warning on line 15 in contracts/integration/MoC/MocIntegration.sol

View workflow job for this annotation

GitHub Actions / build

Line length must be no more than 140 but current length is 141
contract MocIntegration is OwnableUpgradeable, ERC1967UpgradeUpgradeable {
using Counters for Counters.Counter;
// Money On Chain DoC redeem interface at MoC main contract address
IMocMintRedeemDoc public immutable moc;
// IERC20@[DoC token]
Expand All @@ -20,6 +24,10 @@

address public mocVendorAccount;

IPermit2 public immutable permit2;

mapping(address => Counters.Counter) private _permit2Nonces;

event GetDocFromDllrAndRedeemRBTC(address indexed from, uint256 fromDLLR, uint256 toRBTC);
event MocVendorAccountSet(address newMocVendorAccount);

Expand All @@ -29,18 +37,20 @@
* @param _dllr DLLR contract address
* @param _massetManager MassetManager contract address
*/
constructor(address _moc, address _doc, address _dllr, address _massetManager) {
constructor(address _moc, address _doc, address _dllr, address _massetManager, address _permit2) {

Check warning on line 40 in contracts/integration/MoC/MocIntegration.sol

View workflow job for this annotation

GitHub Actions / build

Explicitly mark visibility in function
require(
_moc != address(0) &&
_doc != address(0) &&
_dllr != address(0) &&
_massetManager != address(0),
_massetManager != address(0) &&
_permit2 != address(0),
"MocIntegration:: no null addresses allowed"
);
moc = IMocMintRedeemDoc(_moc);

Check warning on line 49 in contracts/integration/MoC/MocIntegration.sol

View workflow job for this annotation

GitHub Actions / build

Explicitly mark all external contracts as trusted or untrusted
doc = IERC20(_doc);

Check warning on line 50 in contracts/integration/MoC/MocIntegration.sol

View workflow job for this annotation

GitHub Actions / build

Explicitly mark all external contracts as trusted or untrusted
dllr = IDLLR(_dllr);

Check warning on line 51 in contracts/integration/MoC/MocIntegration.sol

View workflow job for this annotation

GitHub Actions / build

Explicitly mark all external contracts as trusted or untrusted
massetManager = IMassetManager(_massetManager);

Check warning on line 52 in contracts/integration/MoC/MocIntegration.sol

View workflow job for this annotation

GitHub Actions / build

Explicitly mark all external contracts as trusted or untrusted
permit2 = IPermit2(_permit2);

Check warning on line 53 in contracts/integration/MoC/MocIntegration.sol

View workflow job for this annotation

GitHub Actions / build

Explicitly mark all external contracts as trusted or untrusted
}

function initialize(address payable _mocVendorAccount) external initializer {
Expand All @@ -49,7 +59,7 @@
}

///@dev the contract requires receiving funds temporarily before transferring them to users
receive() external payable {}

Check warning on line 62 in contracts/integration/MoC/MocIntegration.sol

View workflow job for this annotation

GitHub Actions / build

Explicitly mark visibility of state

/**
* @notice how getDocFromDllrAndRedeemRBTC function works:
Expand Down Expand Up @@ -98,7 +108,47 @@
emit GetDocFromDllrAndRedeemRBTC(msg.sender, _dllrAmount, rbtcAmount);
}

/**
* @notice how getDocFromDllrAndRedeemRBTC function works:
* -------------------------------------------------------------------------------------------
* | Mynt | Money On Chain |
* -------------------------------------------------------------------------------------------
* | get DLLR (EIP-2612) -> convert DLLR to DoC | -> get RBTC from DoC -> send RBTC to user |
* -------------------------------------------------------------------------------------------
*
* @param permit permit data, in form of PermitTransferFrom struct.
* @param signature signatue of the permit data.
*/
function getDocFromDllrAndRedeemRBTCWithPermit2(
tjcloa marked this conversation as resolved.
Show resolved Hide resolved
ISignatureTransfer.PermitTransferFrom memory permit,
bytes memory signature
) external {
address thisAddress = address(this);
uint256 _dllrAmount = permit.permitted.amount;

ISignatureTransfer.SignatureTransferDetails memory transferDetails = _generateTransferDetails(thisAddress, _dllrAmount);

permit2.permitTransferFrom(permit, transferDetails, msg.sender, signature);

_useNonce(msg.sender);
dharkmattr marked this conversation as resolved.
Show resolved Hide resolved

// redeem DoC from DLLR
require(
massetManager.redeemTo(address(doc), _dllrAmount, thisAddress) == _dllrAmount,
"MocIntegration:: redeemed incorrect DoC amount"
);

// redeem RBTC from DoC using Money On Chain and send to the user
uint256 rbtcBalanceBefore = thisAddress.balance;
moc.redeemFreeDocVendors(_dllrAmount, payable(mocVendorAccount));
uint256 rbtcAmount = thisAddress.balance - rbtcBalanceBefore;
(bool success, ) = msg.sender.call{ value: rbtcAmount }("");
require(success, "MocIntegration:: error transferring redeemed RBTC");

emit GetDocFromDllrAndRedeemRBTC(msg.sender, _dllrAmount, rbtcAmount);
}

/// Set MoC registered Vendor account to receive markup fees https://docs.moneyonchain.com/main-rbtc-contract/integration-with-moc-platform/vendors

Check warning on line 151 in contracts/integration/MoC/MocIntegration.sol

View workflow job for this annotation

GitHub Actions / build

Line length must be no more than 140 but current length is 151
function setMocVendorAccount(address payable newMocVedorAccount) external onlyOwner {
_setMocVendorAccount(newMocVedorAccount);
}
Expand All @@ -116,4 +166,44 @@
function getProxyImplementation() external view returns (address) {
return ERC1967UpgradeUpgradeable._getImplementation();
}

/**
* @dev view function to construct SignatureTransferDetails struct to be used by Permit2
*
* @param _to ultimate recipient
* @param _amount amount of transfer
*
* @return SignatureTransferDetails struct object
*/
function _generateTransferDetails(address _to, uint256 _amount) private view returns (ISignatureTransfer.SignatureTransferDetails memory) {
ISignatureTransfer.SignatureTransferDetails memory transferDetails = ISignatureTransfer.SignatureTransferDetails({
to: _to,
requestedAmount: _amount
});

return transferDetails;
}

/**
* @dev "Consume a nonce": return the current value and increment.
*
* @param owner address of owner
*
* @return current nonce of the owner's address
*/
function _useNonce(address owner) internal virtual returns (uint256 current) {
Counters.Counter storage nonce = _permit2Nonces[owner];
current = nonce.current();
nonce.increment();
}
tjcloa marked this conversation as resolved.
Show resolved Hide resolved

/**
* @dev getter for currernt nonce
*
* @param owner address of owner
* @return current nonce of the owner's address
*/
function nonces(address owner) public view returns (uint256) {
return _permit2Nonces[owner].current();
}
tjcloa marked this conversation as resolved.
Show resolved Hide resolved
}
7 changes: 6 additions & 1 deletion contracts/interfaces/IApproveAndCall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,10 @@ interface IApproveAndCall {
* @param _token The address of token.
* @param _data The data will be used for low level call.
* */
function receiveApproval(address _sender, uint256 _amount, address _token, bytes calldata _data) external;
function receiveApproval(
address _sender,
uint256 _amount,
address _token,
bytes calldata _data
) external;
}
162 changes: 162 additions & 0 deletions contracts/permit2/AllowanceTransfer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import { ERC20 } from "./ERC20.sol";
import { SafeTransferLib } from "./libraries/SafeTransferLib.sol";
import { PermitHash } from "./libraries/PermitHash.sol";
import { SignatureVerification } from "./libraries/SignatureVerification.sol";
import { EIP712 } from "./EIP712.sol";
import { IAllowanceTransfer } from "./interfaces/IAllowanceTransfer.sol";
import { SignatureExpired, InvalidNonce } from "./PermitErrors.sol";
import { Allowance } from "./libraries/Allowance.sol";

contract AllowanceTransfer is IAllowanceTransfer, EIP712 {
using SignatureVerification for bytes;
using SafeTransferLib for ERC20;
using PermitHash for PermitSingle;
using PermitHash for PermitBatch;
using Allowance for PackedAllowance;

/// @notice Maps users to tokens to spender addresses and information about the approval on the token
/// @dev Indexed in the order of token owner address, token address, spender address
/// @dev The stored word saves the allowed amount, expiration on the allowance, and nonce
mapping(address => mapping(address => mapping(address => PackedAllowance))) public allowance;

/// @inheritdoc IAllowanceTransfer
function approve(address token, address spender, uint160 amount, uint48 expiration) external {
PackedAllowance storage allowed = allowance[msg.sender][token][spender];
allowed.updateAmountAndExpiration(amount, expiration);
emit Approval(msg.sender, token, spender, amount, expiration);
}

/// @inheritdoc IAllowanceTransfer
function permit(
address owner,
PermitSingle memory permitSingle,
bytes calldata signature
) external {
if (block.timestamp > permitSingle.sigDeadline)
revert SignatureExpired(permitSingle.sigDeadline);

// Verify the signer address from the signature.
signature.verify(_hashTypedData(permitSingle.hash()), owner);

_updateApproval(permitSingle.details, owner, permitSingle.spender);
}

/// @inheritdoc IAllowanceTransfer
function permit(
address owner,
PermitBatch memory permitBatch,
bytes calldata signature
) external {
if (block.timestamp > permitBatch.sigDeadline)
revert SignatureExpired(permitBatch.sigDeadline);

// Verify the signer address from the signature.
signature.verify(_hashTypedData(permitBatch.hash()), owner);

address spender = permitBatch.spender;
unchecked {
uint256 length = permitBatch.details.length;
for (uint256 i = 0; i < length; ++i) {
_updateApproval(permitBatch.details[i], owner, spender);
}
}
}

/// @inheritdoc IAllowanceTransfer
function transferFrom(address from, address to, uint160 amount, address token) external {
_transfer(from, to, amount, token);
}

/// @inheritdoc IAllowanceTransfer
function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external {
unchecked {
uint256 length = transferDetails.length;
for (uint256 i = 0; i < length; ++i) {
AllowanceTransferDetails memory transferDetail = transferDetails[i];
_transfer(
transferDetail.from,
transferDetail.to,
transferDetail.amount,
transferDetail.token
);
}
}
}

/// @notice Internal function for transferring tokens using stored allowances
/// @dev Will fail if the allowed timeframe has passed
function _transfer(address from, address to, uint160 amount, address token) private {
PackedAllowance storage allowed = allowance[from][token][msg.sender];

if (block.timestamp > allowed.expiration) revert AllowanceExpired(allowed.expiration);

uint256 maxAmount = allowed.amount;
if (maxAmount != type(uint160).max) {
if (amount > maxAmount) {
revert InsufficientAllowance(maxAmount);
} else {
unchecked {
allowed.amount = uint160(maxAmount) - amount;
}
}
}

// Transfer the tokens from the from address to the recipient.
ERC20(token).safeTransferFrom(from, to, amount);
}

/// @inheritdoc IAllowanceTransfer
function lockdown(TokenSpenderPair[] calldata approvals) external {
address owner = msg.sender;
// Revoke allowances for each pair of spenders and tokens.
unchecked {
uint256 length = approvals.length;
for (uint256 i = 0; i < length; ++i) {
address token = approvals[i].token;
address spender = approvals[i].spender;

allowance[owner][token][spender].amount = 0;
emit Lockdown(owner, token, spender);
}
}
}

/// @inheritdoc IAllowanceTransfer
function invalidateNonces(address token, address spender, uint48 newNonce) external {
uint48 oldNonce = allowance[msg.sender][token][spender].nonce;

if (newNonce <= oldNonce) revert InvalidNonce();

// Limit the amount of nonces that can be invalidated in one transaction.
unchecked {
uint48 delta = newNonce - oldNonce;
if (delta > type(uint16).max) revert ExcessiveInvalidation();
}

allowance[msg.sender][token][spender].nonce = newNonce;
emit NonceInvalidation(msg.sender, token, spender, newNonce, oldNonce);
}

/// @notice Sets the new values for amount, expiration, and nonce.
/// @dev Will check that the signed nonce is equal to the current nonce and then incrememnt the nonce value by 1.
/// @dev Emits a Permit event.
function _updateApproval(
PermitDetails memory details,
address owner,
address spender
) private {
uint48 nonce = details.nonce;
address token = details.token;
uint160 amount = details.amount;
uint48 expiration = details.expiration;
PackedAllowance storage allowed = allowance[owner][token][spender];

if (allowed.nonce != nonce) revert InvalidNonce();

allowed.updateAll(amount, expiration, nonce);
emit Permit(owner, token, spender, amount, expiration, nonce);
}
}
45 changes: 45 additions & 0 deletions contracts/permit2/EIP712.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import { IEIP712 } from "./interfaces/IEIP712.sol";

/// @notice EIP712 helpers for permit2
/// @dev Maintains cross-chain replay protection in the event of a fork
/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol
contract EIP712 is IEIP712 {
// Cache the domain separator as an immutable value, but also store the chain id that it
// corresponds to, in order to invalidate the cached domain separator if the chain id changes.
bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
uint256 private immutable _CACHED_CHAIN_ID;

bytes32 private constant _HASHED_NAME = keccak256("Permit2");
bytes32 private constant _TYPE_HASH =
keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");

constructor() {
_CACHED_CHAIN_ID = block.chainid;
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME);
}

/// @notice Returns the domain separator for the current chain.
/// @dev Uses cached version if chainid and address are unchanged from construction.
function DOMAIN_SEPARATOR() public view override returns (bytes32) {
return
block.chainid == _CACHED_CHAIN_ID
? _CACHED_DOMAIN_SEPARATOR
: _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME);
}

/// @notice Builds a domain separator using the current chainId and contract address.
function _buildDomainSeparator(
bytes32 typeHash,
bytes32 nameHash
) private view returns (bytes32) {
return keccak256(abi.encode(typeHash, nameHash, block.chainid, address(this)));
}

/// @notice Creates an EIP-712 typed data hash
function _hashTypedData(bytes32 dataHash) internal view returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), dataHash));
}
}
Loading
Loading