diff --git a/contracts/ConfidentialERC20.sol b/contracts/ConfidentialERC20.sol deleted file mode 100644 index 655f160..0000000 --- a/contracts/ConfidentialERC20.sol +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause-Clear - -pragma solidity ^0.8.20; - -import "fhevm/abstracts/Reencrypt.sol"; -import "fhevm/lib/TFHE.sol"; -import "@openzeppelin/contracts/access/Ownable2Step.sol"; - -contract ConfidentialERC20 is Reencrypt, Ownable2Step { - event Transfer(address indexed from, address indexed to); - event Approval(address indexed owner, address indexed spender); - event Mint(address indexed to, uint32 amount); - - uint32 private _totalSupply; - string private _name; - string private _symbol; - uint8 public constant decimals = 0; - - // A mapping from address to an encrypted balance. - mapping(address => euint32) internal balances; - - // A mapping of the form mapping(owner => mapping(spender => allowance)). - mapping(address => mapping(address => euint32)) internal allowances; - - constructor(string memory name_, string memory symbol_) Ownable(msg.sender) { - _name = name_; - _symbol = symbol_; - } - - // Returns the name of the token. - function name() public view virtual returns (string memory) { - return _name; - } - - // Returns the symbol of the token, usually a shorter version of the name. - function symbol() public view virtual returns (string memory) { - return _symbol; - } - - // Returns the total supply of the token - function totalSupply() public view virtual returns (uint32) { - return _totalSupply; - } - - // Sets the balance of the owner to the given encrypted balance. - function mint(uint32 mintedAmount) public virtual onlyOwner { - balances[owner()] = TFHE.add(balances[owner()], mintedAmount); // overflow impossible because of next line - _totalSupply = _totalSupply + mintedAmount; - emit Mint(owner(), mintedAmount); - } - - // Transfers an encrypted amount from the message sender address to the `to` address. - function transfer(address to, bytes calldata encryptedAmount) public virtual returns (bool) { - transfer(to, TFHE.asEuint32(encryptedAmount)); - return true; - } - - // Transfers an amount from the message sender address to the `to` address. - function transfer(address to, euint32 amount) public virtual returns (bool) { - // makes sure the owner has enough tokens - ebool canTransfer = TFHE.le(amount, balances[msg.sender]); - _transfer(msg.sender, to, amount, canTransfer); - return true; - } - - // Returns the balance of the caller encrypted under the provided public key. - function balanceOf( - address wallet, - bytes32 publicKey, - bytes calldata signature - ) public view virtual onlySignedPublicKey(publicKey, signature) returns (bytes memory) { - if (wallet == msg.sender) { - return TFHE.reencrypt(balances[wallet], publicKey, 0); - } - return TFHE.reencrypt(TFHE.asEuint32(0), publicKey, 0); - } - - // Sets the `encryptedAmount` as the allowance of `spender` over the caller's tokens. - function approve(address spender, bytes calldata encryptedAmount) public virtual returns (bool) { - approve(spender, TFHE.asEuint32(encryptedAmount)); - return true; - } - - // Sets the `amount` as the allowance of `spender` over the caller's tokens. - function approve(address spender, euint32 amount) public virtual returns (bool) { - address owner = msg.sender; - _approve(owner, spender, amount); - emit Approval(owner, spender); - return true; - } - - // Returns the remaining number of tokens that `spender` is allowed to spend - // on behalf of the caller. The returned ciphertext is under the caller public FHE key. - function allowance( - address owner, - address spender, - bytes32 publicKey, - bytes calldata signature - ) public view virtual onlySignedPublicKey(publicKey, signature) returns (bytes memory) { - require(owner == msg.sender || spender == msg.sender, "Caller must be owner or spender"); - return TFHE.reencrypt(_allowance(owner, spender), publicKey); - } - - // Transfers `encryptedAmount` tokens using the caller's allowance. - function transferFrom(address from, address to, bytes calldata encryptedAmount) public virtual returns (bool) { - transferFrom(from, to, TFHE.asEuint32(encryptedAmount)); - return true; - } - - // Transfers `amount` tokens using the caller's allowance. - function transferFrom(address from, address to, euint32 amount) public virtual returns (bool) { - address spender = msg.sender; - ebool isTransferable = _updateAllowance(from, spender, amount); - _transfer(from, to, amount, isTransferable); - return true; - } - - function _approve(address owner, address spender, euint32 amount) internal virtual { - allowances[owner][spender] = amount; - } - - function _allowance(address owner, address spender) internal view virtual returns (euint32) { - if (TFHE.isInitialized(allowances[owner][spender])) { - return allowances[owner][spender]; - } else { - return TFHE.asEuint32(0); - } - } - - function _updateAllowance(address owner, address spender, euint32 amount) internal virtual returns (ebool) { - euint32 currentAllowance = _allowance(owner, spender); - // makes sure the allowance suffices - ebool allowedTransfer = TFHE.le(amount, currentAllowance); - // makes sure the owner has enough tokens - ebool canTransfer = TFHE.le(amount, balances[owner]); - ebool isTransferable = TFHE.and(canTransfer, allowedTransfer); - _approve(owner, spender, TFHE.cmux(isTransferable, currentAllowance - amount, currentAllowance)); - return isTransferable; - } - - // Transfers an encrypted amount. - function _transfer(address from, address to, euint32 amount, ebool isTransferable) internal virtual { - // Add to the balance of `to` and subract from the balance of `from`. - balances[to] = balances[to] + TFHE.cmux(isTransferable, amount, TFHE.asEuint32(0)); - balances[from] = balances[from] - TFHE.cmux(isTransferable, amount, TFHE.asEuint32(0)); - emit Transfer(from, to); - } -} diff --git a/contracts/RandomInConstructor.sol b/contracts/RandomInConstructor.sol new file mode 100644 index 0000000..e904091 --- /dev/null +++ b/contracts/RandomInConstructor.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.20; + +import "fhevm/lib/TFHE.sol"; + +contract RandomInConstructor { + euint32 private random; + + constructor() { + random = TFHE.rem(TFHE.randEuint32(),100); + } + + // Returns the name of the token. + function result() public view virtual returns (uint32) { + return TFHE.decrypt(random); + } + +} diff --git a/contracts/RandomInConstructorFactory.sol b/contracts/RandomInConstructorFactory.sol new file mode 100644 index 0000000..33b74a7 --- /dev/null +++ b/contracts/RandomInConstructorFactory.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/proxy/Clones.sol"; +import { RandomInConstructorInitializable } from "./RandomInConstructorInitializable.sol"; + +contract RandomInConstructorFactory{ + address private immutable implementation; + + constructor(address _implementation) { + implementation = _implementation; + } + + function clone(bytes32 salt) public returns (address cloneAdd){ + cloneAdd = Clones.cloneDeterministic(implementation,salt); + RandomInConstructorInitializable(cloneAdd).initialize(); + } + + function predictAddress(bytes32 salt) public view returns (address predicted){ + predicted = Clones.predictDeterministicAddress(implementation,salt); + } +} \ No newline at end of file diff --git a/contracts/RandomInConstructorInitializable.sol b/contracts/RandomInConstructorInitializable.sol new file mode 100644 index 0000000..f0e146f --- /dev/null +++ b/contracts/RandomInConstructorInitializable.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear + +pragma solidity ^0.8.20; + +import "fhevm/lib/TFHE.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +contract RandomInConstructorInitializable is Initializable{ + euint32 private random; + + constructor() { + _disableInitializers(); + } + + function initialize() external initializer{ + random = TFHE.randEuint32(); + } + + // Returns the name of the token. + function result() public view virtual returns (uint32) { + return TFHE.decrypt(random); + } + +} diff --git a/package.json b/package.json index 157fb6a..c59b884 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,8 @@ "fhevm:faucet:dave": "docker exec -i fhevm faucet $(npx hardhat task:getEthereumAddressDave)" }, "dependencies": { - "@openzeppelin/contracts": "^5.0.1" + "@openzeppelin-contracts-upgradeable": "link:@openzeppelin-contracts-upgradeable", + "@openzeppelin/contracts": "^5.0.1", + "@openzeppelin/contracts-upgradeable": "^5.0.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b79e8e..744b205 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,9 +5,15 @@ settings: excludeLinksFromLockfile: false dependencies: + '@openzeppelin-contracts-upgradeable': + specifier: link:@openzeppelin-contracts-upgradeable + version: link:@openzeppelin-contracts-upgradeable '@openzeppelin/contracts': specifier: ^5.0.1 version: 5.0.1 + '@openzeppelin/contracts-upgradeable': + specifier: ^5.0.1 + version: 5.0.1(@openzeppelin/contracts@5.0.1) devDependencies: '@nomicfoundation/hardhat-chai-matchers': @@ -1157,6 +1163,14 @@ packages: '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.1 dev: true + /@openzeppelin/contracts-upgradeable@5.0.1(@openzeppelin/contracts@5.0.1): + resolution: {integrity: sha512-MvaLoPnVcoZr/qqZP+4cl9piuR4gg0iIGgxVSZ/AL1iId3M6IdEHzz9Naw5Lirl4KKBI6ciTVnX07yL4dOMIJg==} + peerDependencies: + '@openzeppelin/contracts': 5.0.1 + dependencies: + '@openzeppelin/contracts': 5.0.1 + dev: false + /@openzeppelin/contracts@5.0.1: resolution: {integrity: sha512-yQJaT5HDp9hYOOp4jTYxMsR02gdFZFXhewX5HW9Jo4fsqSVqqyIO/xTHdWDaKX5a3pv1txmf076Lziz+sO7L1w==} diff --git a/test/confidentialERC20/ConfidentialERC20.fixture.ts b/test/confidentialERC20/ConfidentialERC20.fixture.ts deleted file mode 100644 index fb45afd..0000000 --- a/test/confidentialERC20/ConfidentialERC20.fixture.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ethers } from "hardhat"; - -import type { ConfidentialERC20 } from "../../types"; -import { getSigners } from "../signers"; - -export async function deployEncryptedERC20Fixture(): Promise { - const signers = await getSigners(); - - const contractFactory = await ethers.getContractFactory("ConfidentialERC20"); - const contract = await contractFactory.connect(signers.alice).deploy("Naraggara", "NARA"); // City of Zama's battle - await contract.waitForDeployment(); - - return contract; -} diff --git a/test/confidentialERC20/ConfidentialERC20.ts b/test/confidentialERC20/ConfidentialERC20.ts deleted file mode 100644 index 820c3f3..0000000 --- a/test/confidentialERC20/ConfidentialERC20.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { createInstances } from "../instance"; -import { getSigners, initSigners } from "../signers"; -import { deployEncryptedERC20Fixture } from "./ConfidentialERC20.fixture"; - -describe("ConfidentialERC20", function () { - before(async function () { - await initSigners(); - this.signers = await getSigners(); - }); - - beforeEach(async function () { - const contract = await deployEncryptedERC20Fixture(); - this.contractAddress = await contract.getAddress(); - this.erc20 = contract; - this.instances = await createInstances(this.contractAddress, ethers, this.signers); - }); - - it("should mint the contract", async function () { - const transaction = await this.erc20.mint(1000); - await transaction.wait(); - // Call the method - const token = this.instances.alice.getPublicKey(this.contractAddress) || { - signature: "", - publicKey: "", - }; - const encryptedBalance = await this.erc20.balanceOf(this.signers.alice, token.publicKey, token.signature); - // Decrypt the balance - const balance = this.instances.alice.decrypt(this.contractAddress, encryptedBalance); - expect(balance).to.equal(1000); - - const totalSupply = await this.erc20.totalSupply(); - // Decrypt the total supply - expect(totalSupply).to.equal(1000); - }); - - it("should transfer tokens between two users", async function () { - const transaction = await this.erc20.mint(10000); - await transaction.wait(); - - const encryptedTransferAmount = this.instances.alice.encrypt32(1337); - const tx = await this.erc20["transfer(address,bytes)"](this.signers.bob.address, encryptedTransferAmount); - await tx.wait(); - - const tokenAlice = this.instances.alice.getPublicKey(this.contractAddress)!; - - const encryptedBalanceAlice = await this.erc20.balanceOf( - this.signers.alice, - tokenAlice.publicKey, - tokenAlice.signature, - ); - - // Decrypt the balance - const balanceAlice = this.instances.alice.decrypt(this.contractAddress, encryptedBalanceAlice); - - expect(balanceAlice).to.equal(10000 - 1337); - - const bobErc20 = this.erc20.connect(this.signers.bob); - - const tokenBob = this.instances.bob.getPublicKey(this.contractAddress)!; - - const encryptedBalanceBob = await bobErc20.balanceOf(this.signers.bob, tokenBob.publicKey, tokenBob.signature); - - // Decrypt the balance - const balanceBob = this.instances.bob.decrypt(this.contractAddress, encryptedBalanceBob); - - expect(balanceBob).to.equal(1337); - }); - - it("should not transfer tokens between two users", async function () { - const transaction = await this.erc20.mint(1000); - await transaction.wait(); - - const encryptedTransferAmount = this.instances.alice.encrypt32(1337); - const tx = await this.erc20["transfer(address,bytes)"](this.signers.bob.address, encryptedTransferAmount); - await tx.wait(); - - const tokenAlice = this.instances.alice.getPublicKey(this.contractAddress)!; - - const encryptedBalanceAlice = await this.erc20.balanceOf( - this.signers.alice, - tokenAlice.publicKey, - tokenAlice.signature, - ); - - // Decrypt the balance - const balanceAlice = this.instances.alice.decrypt(this.contractAddress, encryptedBalanceAlice); - - expect(balanceAlice).to.equal(1000); - - const bobErc20 = this.erc20.connect(this.signers.bob); - - const tokenBob = this.instances.bob.getPublicKey(this.contractAddress)!; - - const encryptedBalanceBob = await bobErc20.balanceOf(this.signers.bob, tokenBob.publicKey, tokenBob.signature); - - // Decrypt the balance - const balanceBob = this.instances.bob.decrypt(this.contractAddress, encryptedBalanceBob); - - expect(balanceBob).to.equal(0); - }); - - it("should be able to transferFrom only if allowance is sufficient", async function () { - const transaction = await this.erc20.mint(10000); - await transaction.wait(); - - const encryptedAllowanceAmount = this.instances.alice.encrypt32(1337); - const tx = await this.erc20["approve(address,bytes)"](this.signers.bob.address, encryptedAllowanceAmount); - await tx.wait(); - - const bobErc20 = this.erc20.connect(this.signers.bob); - const encryptedTransferAmount = this.instances.bob.encrypt32(1338); // above allowance so next tx should actually not send any token - const tx2 = await bobErc20["transferFrom(address,address,bytes)"]( - this.signers.alice.address, - this.signers.bob.address, - encryptedTransferAmount, - ); - await tx2.wait(); - - const tokenAlice = this.instances.alice.getPublicKey(this.contractAddress)!; - const encryptedBalanceAlice = await this.erc20.balanceOf( - this.signers.alice, - tokenAlice.publicKey, - tokenAlice.signature, - ); - - // Decrypt the balance - const balanceAlice = this.instances.alice.decrypt(this.contractAddress, encryptedBalanceAlice); - expect(balanceAlice).to.equal(10000); // check that transfer did not happen, as expected - - const tokenBob = this.instances.bob.getPublicKey(this.contractAddress)!; - const encryptedBalanceBob = await bobErc20.balanceOf(this.signers.bob, tokenBob.publicKey, tokenBob.signature); - // Decrypt the balance - const balanceBob = this.instances.bob.decrypt(this.contractAddress, encryptedBalanceBob); - expect(balanceBob).to.equal(0); // check that transfer did not happen, as expected - - const encryptedTransferAmount2 = this.instances.bob.encrypt32(1337); // below allowance so next tx should send token - const tx3 = await bobErc20["transferFrom(address,address,bytes)"]( - this.signers.alice.address, - this.signers.bob.address, - encryptedTransferAmount2, - ); - await tx3.wait(); - - const encryptedBalanceAlice2 = await this.erc20.balanceOf( - this.signers.alice, - tokenAlice.publicKey, - tokenAlice.signature, - ); - // Decrypt the balance - const balanceAlice2 = this.instances.alice.decrypt(this.contractAddress, encryptedBalanceAlice2); - expect(balanceAlice2).to.equal(10000 - 1337); // check that transfer did happen this time - - const encryptedBalanceBob2 = await bobErc20.balanceOf(this.signers.bob, tokenBob.publicKey, tokenBob.signature); - const balanceBob2 = this.instances.bob.decrypt(this.contractAddress, encryptedBalanceBob2); - expect(balanceBob2).to.equal(1337); // check that transfer did happen this time - }); -}); diff --git a/test/randomInConstructor/RandomInConstructor.fixture.ts b/test/randomInConstructor/RandomInConstructor.fixture.ts new file mode 100644 index 0000000..51cee3a --- /dev/null +++ b/test/randomInConstructor/RandomInConstructor.fixture.ts @@ -0,0 +1,14 @@ +import { ethers } from "hardhat"; + +import type { RandomInConstructor } from "../../types"; +import { getSigners } from "../signers"; + +export async function deployRandomInConstructorFixture(): Promise { + const signers = await getSigners(); + + const contractFactory = await ethers.getContractFactory("RandomInConstructor"); + const contract = await contractFactory.connect(signers.alice).deploy(); + await contract.waitForDeployment(); + + return contract; +} diff --git a/test/randomInConstructor/RandomInConstructor.ts b/test/randomInConstructor/RandomInConstructor.ts new file mode 100644 index 0000000..99a6c52 --- /dev/null +++ b/test/randomInConstructor/RandomInConstructor.ts @@ -0,0 +1,24 @@ +import { ethers } from "hardhat"; + +import { createInstances } from "../instance"; +import { getSigners, initSigners } from "../signers"; +import { deployRandomInConstructorFixture } from "./RandomInConstructor.fixture"; + +describe("RandomInConstructor", function () { + before(async function () { + await initSigners(); + this.signers = await getSigners(); + }); + + beforeEach(async function () { + const contract = await deployRandomInConstructorFixture(); + this.contractAddress = await contract.getAddress(); + this.random = contract; + this.instances = await createInstances(this.contractAddress, ethers, this.signers); + }); + + it("Random in constructor", async function () { + console.log(await this.random.result()); + }); + +}); diff --git a/test/randomInConstructor/RandomInConstructorFactory.fixture.ts b/test/randomInConstructor/RandomInConstructorFactory.fixture.ts new file mode 100644 index 0000000..e66dfcd --- /dev/null +++ b/test/randomInConstructor/RandomInConstructorFactory.fixture.ts @@ -0,0 +1,18 @@ +import { ethers } from "hardhat"; + +import type { RandomInConstructorFactory } from "../../types"; +import { getSigners } from "../signers"; + +export async function deployRandomInConstructorFactoryFixture(): Promise { + const signers = await getSigners(); + + const contractFactory = await ethers.getContractFactory("RandomInConstructorInitializable"); + const contract = await contractFactory.connect(signers.alice).deploy(); + await contract.waitForDeployment(); + + const cloneFactoryFactory = await ethers.getContractFactory("RandomInConstructorFactory"); + const cloneFactory = await cloneFactoryFactory.connect(signers.alice).deploy(await contract.getAddress()); + await cloneFactory.waitForDeployment(); + + return cloneFactory; +} diff --git a/test/randomInConstructor/RandomInConstructorFactory.ts b/test/randomInConstructor/RandomInConstructorFactory.ts new file mode 100644 index 0000000..d5dd1e2 --- /dev/null +++ b/test/randomInConstructor/RandomInConstructorFactory.ts @@ -0,0 +1,28 @@ +import { ethers } from "hardhat"; + +import { createInstances } from "../instance"; +import { getSigners, initSigners } from "../signers"; +import { deployRandomInConstructorFactoryFixture } from "./RandomInConstructorFactory.fixture"; + +describe("RandomInConstructorFactory", function () { + before(async function () { + await initSigners(); + this.signers = await getSigners(); + }); + + beforeEach(async function () { + const contract = await deployRandomInConstructorFactoryFixture(); + this.contractAddress = await contract.getAddress(); + this.factory = contract; + this.instances = await createInstances(this.contractAddress, ethers, this.signers); + }); + + it("Random in constructor of a Clone", async function () { + const tx = await this.factory.clone("0xf172873c63909462ac4de545471fd3ad3e9eeadeec4608b92d16ce6b500704cc") + await tx.wait(); + const cloneAddress = await this.factory.predictAddress("0xf172873c63909462ac4de545471fd3ad3e9eeadeec4608b92d16ce6b500704cc") + const clone = await ethers.getContractAt("RandomInConstructorInitializable", cloneAddress); + console.log(await clone.result()); + }); + +});