Skip to content

Commit

Permalink
V1.2.0 - Integrate ERC2612 Permit and Replace Signature points with B…
Browse files Browse the repository at this point in the history
…yteArray for Burn Function (#42)

* feat(ERC2612+ERC1271): adding permit function and helper ; removing signature points from burn function in favor of a byte array

* Update README.md with ERC2612 documentation

* fix(HasNoToken): removing deprecated dead code

* fix(duplicated hash string): using constant and remove duplicated code

* fix(standardController):  removing unused import

* fix(PolygonPosTokenFrontend): removing commented out code

* fix(MintableController): migrating interface to a dedicated file

* fix(MintableController): removing dead code in burnFrom_withCaller
  • Loading branch information
KristenPire authored Mar 7, 2024
1 parent 1c6a99c commit bbdacb2
Show file tree
Hide file tree
Showing 32 changed files with 2,308 additions and 37,080 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ Once approved, your contract can call the `transferFrom` method in the Monerium

`transferAndCall` transfers money and calls the receiving contract's `onTokenTransfer` method with additional data and triggers an event Transfer.

### ERC2612: Permit
ERC2612 introduces a method for token holders to approve spenders with a signed permission, streamlining transactions without on-chain approve calls. This functionality enables efficient gas-less approvals, leveraging EIP-712 signed messages for secure delegation of spending rights.

The Permit function, while not directly accessible through the Token interface, can be invoked via the `controller`.

For a step-by-step implementation guide, refer to the detailed tutorial in [Permit Function Documentation](./docs/permit.md).


## Token design

Four cooperating Ethereum smart-contracts are deployed for each e-money currency token that is [ERC20](https://eips.ethereum.org/EIPS/eip-20) compliant.
Expand Down
29 changes: 29 additions & 0 deletions contracts/ITokenFrontend.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: apache-2.0 */
/**
* Copyright 2022 Monerium ehf.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

pragma solidity 0.8.11;

interface ITokenFrontend {
function burnFrom(
address from,
uint256 amount,
bytes32 h,
uint8 v,
bytes32 r,
bytes32 s
) external returns (bool);
}
41 changes: 23 additions & 18 deletions contracts/MintableController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pragma solidity 0.8.11;

import "./StandardController.sol";
import "./MintableTokenLib.sol";
import "./ITokenFrontend.sol";

/**
* @title MintableController
Expand Down Expand Up @@ -100,25 +101,19 @@ contract MintableController is StandardController {
* @dev Burns tokens from token owner.
* This removes the burned tokens from circulation.
* @param caller Address of the caller passed through the frontend.
* @param from Address of the token owner.
* @param amount Number of tokens to burn.
* @param h Hash which the token owner signed.
* @param v Signature component.
* @param r Signature component.
* @param s Sigature component.
*/
function burnFrom_withCaller(
address caller,
address from,
uint256 amount,
bytes32 h,
uint8 v,
bytes32 r,
bytes32 s
) public onlyFrontend onlySystemAccount(caller) returns (bool) {
address,
uint256,
bytes32,
uint8,
bytes32,
bytes32
) public view onlyFrontend returns (bool) {
require(
token.burn(from, amount, h, v, r, s),
"MintableController: burn failed"
caller == address(this),
"only allow this contract to be the caller"
);
return true;
}
Expand All @@ -131,9 +126,19 @@ contract MintableController is StandardController {
*/
function burnFrom(
address from,
uint256 amount
) public onlyFrontend onlySystemAccount(msg.sender) returns (bool) {
require(token.burn(from, amount), "MintableController: burn failed");
uint256 amount,
bytes32 h,
bytes memory signature
) public onlySystemAccount(msg.sender) returns (bool) {
require(
token.burn(from, amount, h, signature),
"MintableController: burn failed"
);
ITokenFrontend tokenFrontend = ITokenFrontend(frontend);
require(
tokenFrontend.burnFrom(from, amount, h, 0, 0, 0),
"MintableController: TokenFrontend burn call failed"
);
return true;
}

Expand Down
12 changes: 2 additions & 10 deletions contracts/MintableTokenLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,15 @@ library MintableTokenLib {
* @param from The address holding tokens.
* @param amount The amount of tokens to burn.
* @param h Hash which the token owner signed.
* @param v Signature component.
* @param r Signature component.
* @param s Sigature component.
* @param signature Signature component.
*/
function burn(
TokenStorage db,
address from,
uint256 amount,
bytes32 h,
uint8 v,
bytes32 r,
bytes32 s
bytes memory signature
) external returns (bool) {
bytes memory signature;
if (r != bytes32(0) || s != bytes32(0)) {
signature = bytes(abi.encodePacked(r, s, v));
}
require(
from.isValidSignatureNow(h, signature),
"signature/hash does not match"
Expand Down
3 changes: 1 addition & 2 deletions contracts/PolygonPosTokenFrontend.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,11 @@ abstract contract PolygonPosTokenFrontend is
}

/**
* This Function has beed left out. The controller doesn't implement this function anymore.
* @notice Polygon Bridge Mechanism. Called when user wants to withdraw tokens back to root chain
* @dev Should burn user's tokens. This transaction will be verified when exiting on root chain
* @param amount amount of tokens to withdraw
*/
function withdraw(uint256 amount) external override {
controller.burnFrom(msg.sender, amount);
emit Transfer(msg.sender, address(0x0), amount);
}
}
101 changes: 100 additions & 1 deletion contracts/StandardController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
pragma solidity 0.8.11;

import "./TokenStorage.sol";
import "./IERC20.sol";
import "./ERC20Lib.sol";
import "./ERC677Lib.sol";
import "./ClaimableSystemRole.sol";
Expand All @@ -37,6 +36,9 @@ contract StandardController is ClaimableSystemRole {
address internal frontend;
mapping(address => bool) internal bridgeFrontends;
uint8 public decimals = 18;

bytes32 private constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

/**
* @dev Emitted when updating the frontend.
Expand Down Expand Up @@ -74,6 +76,17 @@ contract StandardController is ClaimableSystemRole {
_;
}

/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/

uint256 internal immutable INITIAL_CHAIN_ID;

bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

mapping(address => uint256) public nonces;


/**
* @dev Contract constructor.
* @param storage_ Address of the token storage for the controller.
Expand All @@ -92,6 +105,9 @@ contract StandardController is ClaimableSystemRole {
token = TokenStorage(storage_);
}
frontend = frontend_;

INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}

/**
Expand Down Expand Up @@ -236,6 +252,89 @@ contract StandardController is ClaimableSystemRole {
return token.approve(caller, spender, amount);
}

function getPermitDigest(
address owner,
address spender,
uint256 value,
uint256 nonce,
uint256 deadline
) public view returns (bytes32) {
return keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
PERMIT_TYPEHASH,
owner,
spender,
value,
nonce,
deadline
)
)
)
);
}

function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
PERMIT_TYPEHASH,
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);

require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
}
token.approve(owner, spender, value);
}

function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}

function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("standardController")),
keccak256("1"),
block.chainid,
address(this)
)
);
}

/**
* @dev Transfers tokens and subsequently calls a method on the recipient [ERC677].
* If the recipient is a non-contract address this method behaves just like transfer.
Expand Down
9 changes: 9 additions & 0 deletions contracts/SystemRole.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ abstract contract SystemRole is AccessControl, Ownable {
return hasRole(SYSTEM_ROLE, account);
}

/**
* @dev Checks wether an address is a admin account.
* @param account The address of the account.
* @return true if admin account.
*/
function isAdminAccount(address account) public view returns (bool) {
return hasRole(ADMIN_ROLE, account);
}

/**
* @dev add system account.
* @param account The address of the account.
Expand Down
30 changes: 0 additions & 30 deletions contracts/ownership/HasNoTokens.sol

This file was deleted.

3 changes: 1 addition & 2 deletions contracts/ownership/NoOwner.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity 0.8.11;

import "./HasNoEther.sol";
import "./HasNoTokens.sol";
import "./HasNoContracts.sol";

/**
Expand All @@ -11,6 +10,6 @@ import "./HasNoContracts.sol";
* @dev Solves a class of errors where a contract accidentally becomes owner of Ether, Tokens or
* Owned contracts. See respective base contracts for details.
*/
contract NoOwner is HasNoEther, HasNoTokens, HasNoContracts {
contract NoOwner is HasNoEther, HasNoContracts {

}
Loading

0 comments on commit bbdacb2

Please sign in to comment.