Skip to content

Commit

Permalink
EIP712 domain and version adjustment, correction to transferWithAutho…
Browse files Browse the repository at this point in the history
…rization method, Version 02 of the contract
  • Loading branch information
VladimirKlimo committed Jun 27, 2024
1 parent a3c6aba commit d909b4c
Show file tree
Hide file tree
Showing 10 changed files with 1,380 additions and 25 deletions.
914 changes: 914 additions & 0 deletions .openzeppelin/base-sepolia.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Presearch Token for Base/Optimism network

Presearch ERC-20 Token. Deployed on the Base Sepolia at
Presearch ERC-20 Token. Deployed on the Base Sepolia at
**Token Address**: [0xc0C034725e4eC6DDd23B8D4e6412094BcfB3F5D6](https://sepolia.basescan.org/token/0xc0c034725e4ec6ddd23b8d4e6412094bcfb3f5d6)

Presearch ERC-20 Token. Deployed on the Base Mainnet at
Presearch ERC-20 Token. Deployed on the Base Mainnet at
**Token Address**: [0x3816dD4bd44c8830c2FA020A5605bAC72FA3De7A](https://basescan.org/token/0x3816dD4bd44c8830c2FA020A5605bAC72FA3De7A)

## Overview
Expand All @@ -15,9 +15,9 @@ https://presearch.io/vision.pdf
## Audit
The PRE Token Base smart contract was audited by MantisecLabs from March 28th - April 4th, 2024 (based on initial commit e3f8792 and revalidtion commit db3b0ce).

- MantisecLabs checked all aspects related to the ERC20 standard compatibility and other known ERC20 pitfalls/vulnerabilities, and no issues were found in these areas.
- MantisecLabs checked all aspects related to the ERC20 standard compatibility and other known ERC20 pitfalls/vulnerabilities, and no issues were found in these areas.
- MantisecLabs also examined other areas such as coding practices and business logic. 1 High priority finding were found in EIP3009.sol function CancelAuthorization, which was a typo during rewritting contract for solidity 0.8.24.
- Overall, MantisecLabs reported also 3 Low priority findings related to gas optimizations of contract execution.
- Overall, MantisecLabs reported also 3 Low priority findings related to gas optimizations of contract execution.

Presearch implemented fix for typo in CancelAuthorization (High priority finding) and all of the Low priority findings of MantisecLabs on April 3rd, 2024 (commit db3b0ce) prior to deploying the token smart contract on April 04th, 2024 to the Base Mainnet.

Expand All @@ -33,4 +33,4 @@ The final audit report reflecting the token deployment was completed on April 04
1. run `hardhat compile --all`
2. run `hardhat test`

BTW contract itself does not mint any tokens by default so for testing of the contract features/functionality its good to mint some tokens during inicialization or utilize the native bridge functionality of its more convenient
BTW contract itself does not mint any tokens by default so for testing of the contract features/functionality its good to mint some tokens during initialization or utilize the native bridge functionality of its more convenient
14 changes: 9 additions & 5 deletions contracts/EIP3009.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
* Ported to 0.8.24 solidity by Vladimir Klimo, bitCore,s.r.o.
* 20.03.2024 - https://www.bitcore.sk
*/
Expand All @@ -42,7 +42,7 @@ import { AccessControlEnumerableUpgradeable } from "@openzeppelin/contracts-upgr
* @title Presearch Commom ERC20
* @author Vladimir Klimo
* @notice Direct port of PRE Token V3 contract from solidity 0.6.2 to 0.8.24 with customizations for EIP3009 (previously PRETransferAuthorizableERC20.sol)
*
*
* @dev Implementation of EIP3009
* Modification for DOMAIN_SEPARATOR library was handled by using internal generator for domain separator of EIP712Upgradeable
* main EIP3009 contract is extended with AccessControl and allow only TRANSFER_AUTHORIZABLE_ROLE members (preferably contracts)
Expand Down Expand Up @@ -99,6 +99,7 @@ abstract contract EIP3009 is ContextUpgradeable, EIP712Upgradeable, ERC20Upgrade
string internal constant _CALLER_MUST_BE_PAYEEE_ERROR = "EIP3009: caller must be the payee";
string internal constant _AUTHORIZATION_NOT_YET_VALID_ERROR = "EIP3009: authorization is not yet valid";
string internal constant _AUTHORIZATION_EXPIRED_ERROR = "EIP3009: authorization is expired";
string internal constant _AUTHORIZATION_FROM_ERROR = "EIP3009: from address is not authorized for transferWithAuthorization";

/**
* @dev Ported function to provide same interfaces as PRE Token V3 contract on L1 for signing
Expand Down Expand Up @@ -144,7 +145,9 @@ abstract contract EIP3009 is ContextUpgradeable, EIP712Upgradeable, ERC20Upgrade
uint8 v,
bytes32 r,
bytes32 s
) external virtual onlyRole(TRANSFER_AUTHORIZER_ROLE) {
) external virtual {
require(hasRole(TRANSFER_AUTHORIZER_ROLE, from), _AUTHORIZATION_FROM_ERROR);

_transferWithAuthorization(
TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
from,
Expand Down Expand Up @@ -184,8 +187,9 @@ abstract contract EIP3009 is ContextUpgradeable, EIP712Upgradeable, ERC20Upgrade
uint8 v,
bytes32 r,
bytes32 s
) external virtual onlyRole(TRANSFER_AUTHORIZER_ROLE) {
) external virtual {
require(to == _msgSender(), _CALLER_MUST_BE_PAYEEE_ERROR);
require(hasRole(TRANSFER_AUTHORIZER_ROLE, from), _AUTHORIZATION_FROM_ERROR);

_transferWithAuthorization(
RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
Expand Down Expand Up @@ -215,7 +219,7 @@ abstract contract EIP3009 is ContextUpgradeable, EIP712Upgradeable, ERC20Upgrade
uint8 v,
bytes32 r,
bytes32 s
) external virtual onlyRole(TRANSFER_AUTHORIZER_ROLE) {
) external virtual {
EIP3009Storage storage $ = _getEIP3009Storage();
require(
!$._authorizationStates[authorizer][nonce],
Expand Down
14 changes: 8 additions & 6 deletions contracts/PRETokenBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @title Presearch Commom ERC20
* @author Vladimir Klimo
* @notice Presearch ERC20 Token for Optimism/Base network
*
*
*/

pragma solidity ^0.8.24;
Expand All @@ -23,7 +23,7 @@ import { EIP3009 } from "./EIP3009.sol";
*
* Supply capped at 1B tokens to match the maximum on Eth/PRETokenV3 contract
* Only bridge could mint/burn the token on base/optimism L2 network
* No minting is done during inicialization
* No minting is done during initialization
*/
contract PRETokenBase is PresearchCommonERC20, ILegacyMintableERC20, IOptimismMintableERC20, ISemver {

Expand All @@ -46,17 +46,19 @@ contract PRETokenBase is PresearchCommonERC20, ILegacyMintableERC20, IOptimismMi

function initialize(string memory name, string memory symbol, address _bridge, address _remoteToken) public initializer {
__Context_init_unchained();
// token name and symbol inicialization
// token name and symbol initialization
__ERC20_init_unchained(name, symbol);
// max supply inicialization 1 billion token 1000000000*10**18
// max supply initialization 1 billion token 1000000000*10**18
__ERC20Capped_init_unchained(1e27);
// implementation of pauseable token
__Pausable_init_unchained();
// implementation of AccessControl
// implementation of AccessControl
__AccessControl_init_unchained();
__AccessControlEnumerable_init_unchained();
// EIP712 domain initialization
//__EIP712_init_unchained(name, "1");
// EIP9001 implementation of TransferAuthorizable
// adds access control of TOKEN_TRANSFER_AUTHORIZE to contract owner during inicialization
// adds access control of TOKEN_TRANSFER_AUTHORIZE to contract owner during initialization
__EIP3009_init_unchained();
// inicialize underlying contract
__PresearchCommonERC20_init_unchained();
Expand Down
40 changes: 40 additions & 0 deletions contracts/PRETokenBaseV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2020 CENTRE SECZ
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Ported to 0.8.24 solidity by Vladimir Klimo, bitCore,s.r.o.
* 20.03.2024 - https://www.bitcore.sk - initial realease
* 25.06.2024 - ability to change domain and version during upgrade (reinitializer)
*/

pragma solidity ^0.8.24;

//import { EnhancedEIP712Upgradeable } from "./EnhancedEIP712Upgradeable.sol";
import { PRETokenBase } from "./PRETokenBase.sol";

contract PRETokenBaseV2 is PRETokenBase {

function reinitialize(string memory name, string memory version) public reinitializer(2) {
super.__EIP712_init(name, version);
}

}
6 changes: 3 additions & 3 deletions contracts/PresearchCommonERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { EIP3009 } from "./EIP3009.sol";
* @dev PresearchCommonERC20 is ported version of PRETokenV3 contract
* MINTER_ROLE is not necessary as Base/Optimism L2 would utilize the native bridge to mint/burn token on L2
* PAUSER_ROLE is implemented for security reason to stop/pause any transaction (red button case)
* TRANSFER_AUTHORIZABLE_ROLE is set during EIP9001 inicialization
* TRANSFER_AUTHORIZABLE_ROLE is set during EIP9001 initialization
*/
abstract contract PresearchCommonERC20 is Initializable, ERC20CappedUpgradeable, EIP3009, PausableUpgradeable {
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
Expand Down Expand Up @@ -71,7 +71,7 @@ abstract contract PresearchCommonERC20 is Initializable, ERC20CappedUpgradeable,
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransferBatch() internal virtual whenNotPaused { }
function _beforeTokenTransferBatch() internal virtual whenNotPaused { }

/**
* @dev Send multiple transfers as a batch within a single transaction
Expand Down Expand Up @@ -101,7 +101,7 @@ abstract contract PresearchCommonERC20 is Initializable, ERC20CappedUpgradeable,
uint amount = amounts[i];
address recipient = recipients[i];

if(sender != recipient){
if(sender != recipient){
_transfer(sender, recipient, amount);
}
}
Expand Down
39 changes: 39 additions & 0 deletions scripts/deploy-V2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ethers, upgrades } from "hardhat";

let tokenName: string = 'Presearch';
let tokenSymbol: string = 'PRE';

async function main() {
const networkName = (await ethers.provider.getNetwork()).name;
let PROXY_ADDRESS: string;

// get fixed/optimized PRETokenBase after audit
const pre = await ethers.getContractFactory("PRETokenBaseV2");
console.log("Re-Deploying PRETokenBaseV2 ...");

// base deployed transparent PROXY address
switch (networkName) {
case 'base-mainnet': { PROXY_ADDRESS = '0x3816dD4bd44c8830c2FA020A5605bAC72FA3De7A'; break; }
case 'base-sepolia': { PROXY_ADDRESS = '0xc0C034725e4eC6DDd23B8D4e6412094BcfB3F5D6'; break; }
case 'localhost': { PROXY_ADDRESS = '0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1'; break; }
default: { throw new TypeError('Unknown network for deployment'); break; }
}

// execute the upgrade
const upgraded = await upgrades.upgradeProxy(PROXY_ADDRESS, pre);
console.log("Reinitializing...");
await upgraded.reinitialize('Presearch', '1');
console.log("Upgrade and reinitialization complete!");

// log output
console.log("V2 Contract deployed / upgraded to:", await upgraded.getAddress());
console.log(await upgrades.erc1967.getImplementationAddress(await upgraded.getAddress())," getImplementationAddress");
console.log(await upgrades.erc1967.getAdminAddress(await upgraded.getAddress()), " getAdminAddress");
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
5 changes: 3 additions & 2 deletions scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ async function main() {

// decides where to deploy and which tokens to inicialize contract with
switch (networkName) {
case 'localhost': { L1TokenAddress = '0x0765C04EF390A63E8b7Bb6A5FCA2F42169C7bB19'; break; }
case 'base-sepolia': { L1TokenAddress = '0x0765C04EF390A63E8b7Bb6A5FCA2F42169C7bB19'; break; }
case 'base-mainnet': { L1TokenAddress = '0xEC213F83defB583af3A000B1c0ada660b1902A0F'; break; }
case 'optimism-mainnet': { L1TokenAddress = '0xEC213F83defB583af3A000B1c0ada660b1902A0F'; break; }
default: { throw new TypeError('Unknown network for deployment'); break; }
}

const pre = await ethers.getContractFactory("PRETokenBase");
console.log("Deploying PRETokenBase...");
const proxy = await upgrades.deployProxy(pre, [
tokenName,
tokenName,
tokenSymbol,
'0x4200000000000000000000000000000000000010', // Standard Bridge address on L2 minting source
L1TokenAddress // presearch token address on L1
Expand Down
89 changes: 85 additions & 4 deletions test/PRE-Token-Base-Tests.ts → test/01-PRE-Token-Base-Tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,36 @@ import { expect } from "chai";
import { ethers, upgrades } from "hardhat";
import { PRETokenBase } from "../typechain-types";
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
import { randomBytes } from "ethers";

describe("Presearch Token", function() {
describe("Presearch Token - version 01", function() {
let tokenAddress: string;
let con: PRETokenBase;
let owner: SignerWithAddress;
let w1: SignerWithAddress;
let w2: SignerWithAddress;

const tokenName = 'Presearch';

before (async function () {
const pre = await ethers.getContractFactory("PRETokenBase");
//console.log("Deploying PRETokenBase...");
const contract = await upgrades.deployProxy(pre, [
"Presearch Token",
tokenName,
"PRE",
'0x4200000000000000000000000000000000000010', // Standard Bridge address on L2 minting source
'0xEC213F83defB583af3A000B1c0ada660b1902A0F' // presearch token address on L1
] );
await contract.waitForDeployment();
tokenAddress = await contract.getAddress();
console.log("V1 Contract deployed to:", tokenAddress);
console.log("V1 Contract deployed to:", tokenAddress);
con = await ethers.getContractAt("PRETokenBase", tokenAddress);

[owner, w1, w2] = await ethers.getSigners();
});

it('Check token/contract assigned name', async () => {
expect((await con.name()).toString()).to.equal('Presearch Token');
expect((await con.name()).toString()).to.equal('Presearch');
});

it('Check symbol', async () => {
Expand Down Expand Up @@ -178,6 +182,83 @@ describe("Presearch Token", function() {
await con.getRoleAdmin(role)
).be.equal('0x0000000000000000000000000000000000000000000000000000000000000000');
});

it('Test the EIP712 Domain', async () => {
const domain = await con.eip712Domain();
const contractAddress = await con.getAddress()
await expect(
domain[1]
).be.equal("");
await expect(
domain[2]
).be.equal("");
await expect(
domain[4]
).be.equal(contractAddress);
});


it('Execute TransferWithAuthorization', async () => {

const amountBN = 100;
const validTill = BigInt(Math.floor(Date.now() / 1000) + 3600); // Valid for an hour
const nonce = ethers.randomBytes(32);
//console.log( ethers.hexlify(nonce));

const domain = {
name: "",
version: "",
chainId: 31337,
verifyingContract: tokenAddress,
};
const types = {
TransferWithAuthorization: [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
{ name: "validAfter", type: "uint256" },
{ name: "validBefore", type: "uint256" },
{ name: "nonce", type: "bytes32" },
]
};
const message = {
from: owner.address,
to: w1.address,
value: BigInt(100),
validAfter: BigInt(0),
validBefore: validTill, // Valid for an hour
nonce: nonce,
}

const signature = await owner.signTypedData(
domain, types, message
);

const v = "0x" + signature.slice(130, 132);
const r = signature.slice(0, 66);
const s = "0x" + signature.slice(66, 130);

const b1 = await con.balanceOf(w1.address);
const callContract = await con.transferWithAuthorization(
owner.address,
w1.address,
BigInt(100),
BigInt(0),
validTill,
nonce,
v,
r,
s
);
const b2 = await con.balanceOf(w1.address);

console.log(b1)
console.log(b2)

await expect(
b2-b1
).be.equal(100);

});

});
Loading

0 comments on commit d909b4c

Please sign in to comment.