From 937b24ec172493e1ff635f5ba7cc84da072575ee Mon Sep 17 00:00:00 2001 From: Arr00 <13561405+arr00@users.noreply.github.com> Date: Thu, 3 Jul 2025 11:42:26 -0400 Subject: [PATCH 1/6] Add extension that emits erc20 events for etherscan visibility --- .../ConfidentialFungibleTokenERC20Like.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 contracts/token/extensions/ConfidentialFungibleTokenERC20Like.sol diff --git a/contracts/token/extensions/ConfidentialFungibleTokenERC20Like.sol b/contracts/token/extensions/ConfidentialFungibleTokenERC20Like.sol new file mode 100644 index 00000000..7cda0ba9 --- /dev/null +++ b/contracts/token/extensions/ConfidentialFungibleTokenERC20Like.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {ConfidentialFungibleToken, euint64} from "../ConfidentialFungibleToken.sol"; + +abstract contract ConfidentialFungibleTokenERC20Like is ConfidentialFungibleToken { + function _update(address from, address to, euint64 amount) internal virtual override returns (euint64) { + emit IERC20.Transfer(from, to, 0); + return super._update(from, to, amount); + } +} From 6ed57a30153ab2c1434b2fbe0751c26773f159d8 Mon Sep 17 00:00:00 2001 From: Arr00 <13561405+arr00@users.noreply.github.com> Date: Tue, 8 Jul 2025 09:27:57 -0600 Subject: [PATCH 2/6] change contract name and emit non-zero amount --- .../ConfidentialFungibleTokenERC20Events.sol | 13 +++++++++++++ .../ConfidentialFungibleTokenERC20Like.sol | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol diff --git a/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol b/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol new file mode 100644 index 00000000..7cda0ba9 --- /dev/null +++ b/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {ConfidentialFungibleToken, euint64} from "../ConfidentialFungibleToken.sol"; + +abstract contract ConfidentialFungibleTokenERC20Like is ConfidentialFungibleToken { + function _update(address from, address to, euint64 amount) internal virtual override returns (euint64) { + emit IERC20.Transfer(from, to, 0); + return super._update(from, to, amount); + } +} diff --git a/contracts/token/extensions/ConfidentialFungibleTokenERC20Like.sol b/contracts/token/extensions/ConfidentialFungibleTokenERC20Like.sol index 7cda0ba9..26444b7d 100644 --- a/contracts/token/extensions/ConfidentialFungibleTokenERC20Like.sol +++ b/contracts/token/extensions/ConfidentialFungibleTokenERC20Like.sol @@ -5,9 +5,9 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ConfidentialFungibleToken, euint64} from "../ConfidentialFungibleToken.sol"; -abstract contract ConfidentialFungibleTokenERC20Like is ConfidentialFungibleToken { +abstract contract ConfidentialFungibleTokenERC20Events is ConfidentialFungibleToken { function _update(address from, address to, euint64 amount) internal virtual override returns (euint64) { - emit IERC20.Transfer(from, to, 0); + emit IERC20.Transfer(from, to, 1); return super._update(from, to, amount); } } From e9268c79c85470000625ffb45b4c337e8c7dff0a Mon Sep 17 00:00:00 2001 From: Arr00 <13561405+arr00@users.noreply.github.com> Date: Tue, 8 Jul 2025 11:15:06 -0600 Subject: [PATCH 3/6] fix --- .../ConfidentialFungibleTokenERC20Events.sol | 3 +-- .../ConfidentialFungibleTokenERC20Like.sol | 13 ------------- 2 files changed, 1 insertion(+), 15 deletions(-) delete mode 100644 contracts/token/extensions/ConfidentialFungibleTokenERC20Like.sol diff --git a/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol b/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol index 7cda0ba9..b7f34001 100644 --- a/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol +++ b/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol @@ -2,10 +2,9 @@ pragma solidity ^0.8.26; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - import {ConfidentialFungibleToken, euint64} from "../ConfidentialFungibleToken.sol"; -abstract contract ConfidentialFungibleTokenERC20Like is ConfidentialFungibleToken { +abstract contract ConfidentialFungibleTokenERC20Events is ConfidentialFungibleToken { function _update(address from, address to, euint64 amount) internal virtual override returns (euint64) { emit IERC20.Transfer(from, to, 0); return super._update(from, to, amount); diff --git a/contracts/token/extensions/ConfidentialFungibleTokenERC20Like.sol b/contracts/token/extensions/ConfidentialFungibleTokenERC20Like.sol deleted file mode 100644 index 26444b7d..00000000 --- a/contracts/token/extensions/ConfidentialFungibleTokenERC20Like.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.26; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import {ConfidentialFungibleToken, euint64} from "../ConfidentialFungibleToken.sol"; - -abstract contract ConfidentialFungibleTokenERC20Events is ConfidentialFungibleToken { - function _update(address from, address to, euint64 amount) internal virtual override returns (euint64) { - emit IERC20.Transfer(from, to, 1); - return super._update(from, to, amount); - } -} From 4ee8ea14fd615fb3a165f424c2e62089929fc664 Mon Sep 17 00:00:00 2001 From: Arr00 <13561405+arr00@users.noreply.github.com> Date: Tue, 8 Jul 2025 11:16:02 -0600 Subject: [PATCH 4/6] update amount --- .../token/extensions/ConfidentialFungibleTokenERC20Events.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol b/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol index b7f34001..a9635ef9 100644 --- a/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol +++ b/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol @@ -6,7 +6,7 @@ import {ConfidentialFungibleToken, euint64} from "../ConfidentialFungibleToken.s abstract contract ConfidentialFungibleTokenERC20Events is ConfidentialFungibleToken { function _update(address from, address to, euint64 amount) internal virtual override returns (euint64) { - emit IERC20.Transfer(from, to, 0); + emit IERC20.Transfer(from, to, 1); return super._update(from, to, amount); } } From ab43829c922f98b05e91bc1feabf336f1303b262 Mon Sep 17 00:00:00 2001 From: Arr00 <13561405+arr00@users.noreply.github.com> Date: Tue, 8 Jul 2025 18:04:04 -0600 Subject: [PATCH 5/6] Add test --- ...nfidentialFungibleTokenERC20EventsMock.sol | 17 ++++++++++ ...nfidentialFungibleTokenERC20Events.test.ts | 31 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 contracts/mocks/ConfidentialFungibleTokenERC20EventsMock.sol create mode 100644 test/token/extensions/ConfidentialFungibleTokenERC20Events.test.ts diff --git a/contracts/mocks/ConfidentialFungibleTokenERC20EventsMock.sol b/contracts/mocks/ConfidentialFungibleTokenERC20EventsMock.sol new file mode 100644 index 00000000..f10097f8 --- /dev/null +++ b/contracts/mocks/ConfidentialFungibleTokenERC20EventsMock.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {SepoliaConfig} from "@fhevm/solidity/config/ZamaConfig.sol"; +import {FHE, euint64, externalEuint64} from "@fhevm/solidity/lib/FHE.sol"; +import {ConfidentialFungibleTokenERC20Events} from "./../token/extensions/ConfidentialFungibleTokenERC20Events.sol"; + +// solhint-disable func-name-mixedcase +abstract contract ConfidentialFungibleTokenERC20EventsMock is ConfidentialFungibleTokenERC20Events, SepoliaConfig { + function $_mint( + address to, + externalEuint64 encryptedAmount, + bytes calldata inputProof + ) public returns (euint64 transferred) { + return _mint(to, FHE.fromExternal(encryptedAmount, inputProof)); + } +} diff --git a/test/token/extensions/ConfidentialFungibleTokenERC20Events.test.ts b/test/token/extensions/ConfidentialFungibleTokenERC20Events.test.ts new file mode 100644 index 00000000..f748a493 --- /dev/null +++ b/test/token/extensions/ConfidentialFungibleTokenERC20Events.test.ts @@ -0,0 +1,31 @@ +import { expect } from 'chai'; +import { ethers, fhevm } from 'hardhat'; + +const name = 'ConfidentialFungibleToken'; +const symbol = 'CFT'; +const uri = 'https://example.com/metadata'; + +describe('ConfidentialFungibleTokenERC20Events', function () { + beforeEach(async function () { + const [holder] = await ethers.getSigners(); + + const token = await ethers.deployContract('$ConfidentialFungibleTokenERC20EventsMock', [name, symbol, uri]); + this.token = token; + this.holder = holder; + }); + + it('should emit ERC20 transfer event', async function () { + const encryptedInput = await fhevm + .createEncryptedInput(this.token.target, this.holder.address) + .add64(100) + .encrypt(); + + await expect( + this.token + .connect(this.holder) + ['$_mint(address,bytes32,bytes)'](this.holder, encryptedInput.handles[0], encryptedInput.inputProof), + ) + .to.emit(this.token, 'Transfer') + .withArgs(ethers.ZeroAddress, this.holder.address, 1); + }); +}); From 9b30819ee2270c85c8f604c86612cdd51cd102ea Mon Sep 17 00:00:00 2001 From: Arr00 <13561405+arr00@users.noreply.github.com> Date: Tue, 8 Jul 2025 18:11:17 -0600 Subject: [PATCH 6/6] add changelog --- .changeset/stupid-dogs-bow.md | 5 +++++ contracts/token/README.adoc | 2 ++ .../extensions/ConfidentialFungibleTokenERC20Events.sol | 7 +++++++ 3 files changed, 14 insertions(+) create mode 100644 .changeset/stupid-dogs-bow.md diff --git a/.changeset/stupid-dogs-bow.md b/.changeset/stupid-dogs-bow.md new file mode 100644 index 00000000..2eda7adb --- /dev/null +++ b/.changeset/stupid-dogs-bow.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-confidential-contracts': minor +--- + +`ConfidentialFungibleTokenERC20Events`: Extension of `ConfidentialFungibleToken` that emits ERC20 events on transfers. diff --git a/contracts/token/README.adoc b/contracts/token/README.adoc index 88f9e110..1dd1a11a 100644 --- a/contracts/token/README.adoc +++ b/contracts/token/README.adoc @@ -7,6 +7,7 @@ This set of interfaces, contracts, and utilities are all related to the evolving - {ConfidentialFungibleToken}: Implementation of {IConfidentialFungibleToken}. - {ConfidentialFungibleTokenERC20Wrapper}: Extension of {ConfidentialFungibleToken} which wraps an `ERC20` into a confidential token. The wrapper allows for free conversion in both directions at a fixed rate. +- {ConfidentialFungibleTokenERC20Events}: Extension of {ConfidentialFungibleToken} that emits ERC20 events on transfers. - {ConfidentialFungibleTokenUtils}: A library that provides the on-transfer callback check used by {ConfidentialFungibleToken}. == Core @@ -14,6 +15,7 @@ This set of interfaces, contracts, and utilities are all related to the evolving == Extensions {{ConfidentialFungibleTokenERC20Wrapper}} +{{ConfidentialFungibleTokenERC20Events}} == Utilities {{ConfidentialFungibleTokenUtils}} \ No newline at end of file diff --git a/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol b/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol index a9635ef9..2ee85be3 100644 --- a/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol +++ b/contracts/token/extensions/ConfidentialFungibleTokenERC20Events.sol @@ -4,6 +4,13 @@ pragma solidity ^0.8.26; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {ConfidentialFungibleToken, euint64} from "../ConfidentialFungibleToken.sol"; +/** + * @dev Extension of `ConfidentialFungibleToken` that emits ERC20 events on transfers. This + * can be useful for surfacing confidential transfers on applications that support ERC20 events such as Etherscan. + * + * NOTE: The ERC20 events emitted only have meaningful data for the `to` and `from` fields. The `amount` field + * is fixed to 1. + */ abstract contract ConfidentialFungibleTokenERC20Events is ConfidentialFungibleToken { function _update(address from, address to, euint64 amount) internal virtual override returns (euint64) { emit IERC20.Transfer(from, to, 1);