Skip to content

OZ ConfidentialFungibleToken + FHE mock 环境 fromExternal 校验失败(FHECounter 能跑通) #6

@yoona333

Description

@yoona333

问题描述:
FHECounter 合约和脚本能100%跑通
ConfidentialFungibleTokenMintableBurnable 合约和脚本(全流程用同一 signer)依然报 fromExternal 授权失败

依赖版本:
@fhevm/hardhat-plugin: 0.0.1-6
fhevm: 0.6.2
@openzeppelin/confidential-contracts: 0.2.0-rc.1

复现步骤:

  1. 启动 hardhat node
  2. 运行如下最小测试用例,我的合约代码以及测试脚本如下:

ConfidentialFungibleTokenMintableBurnable.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {FHE, externalEuint64, ebool, euint64} from "@fhevm/solidity/lib/FHE.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ConfidentialFungibleToken} from "@openzeppelin/confidential-contracts/token/ConfidentialFungibleToken.sol";
import {
IConfidentialFungibleToken
} from "@openzeppelin/confidential-contracts/interfaces/IConfidentialFungibleToken.sol";
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";

contract ConfidentialFungibleTokenMintableBurnable is ConfidentialFungibleToken, Ownable {
using FHE for *;
mapping(uint256 requestId => address) private _receivers;
IConfidentialFungibleToken private _fromToken;
IERC20 private _toToken;

constructor(
    address owner,
    string memory name,
    string memory symbol,
    string memory uri
) ConfidentialFungibleToken(name, symbol, uri) Ownable(owner) {}

function mint(address to, externalEuint64 amount, bytes memory inputProof) public onlyOwner {
    _mint(to, FHE.fromExternal(amount, inputProof));
}

function burn(address from, externalEuint64 amount, bytes memory inputProof) public onlyOwner {
    _burn(from, FHE.fromExternal(amount, inputProof));
}

function swapConfidentialForConfidential(
    IConfidentialFungibleToken fromToken,
    IConfidentialFungibleToken toToken,
    externalEuint64 amountInput,
    bytes calldata inputProof
) public virtual {
    require(fromToken.isOperator(msg.sender, address(this)));

    euint64 amount = FHE.fromExternal(amountInput, inputProof);

    FHE.allowTransient(amount, address(fromToken));
    euint64 amountTransferred = fromToken.confidentialTransferFrom(msg.sender, address(this), amount);

    FHE.allowTransient(amountTransferred, address(toToken));
    toToken.confidentialTransfer(msg.sender, amountTransferred);
}

}

ConfidentialFungibleTokenMintableBurnable.ts

import { ConfidentialFungibleTokenMintableBurnable, ConfidentialFungibleTokenMintableBurnable__factory } from "../types";
import { FhevmType } from "@fhevm/hardhat-plugin";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
import { expect } from "chai";
import { ethers, fhevm } from "hardhat";
import { ethers as ethersjs } from "ethers";

type Signers = {
deployer: HardhatEthersSigner;
alice: HardhatEthersSigner;
bob: HardhatEthersSigner;
};

async function deployFixture(deployerAddress: string) {
const factory = (await ethers.getContractFactory("ConfidentialFungibleTokenMintableBurnable")) as ConfidentialFungibleTokenMintableBurnable__factory;
const contract = (await factory.deploy(
deployerAddress, // owner address
"ConfidentialToken",
"CTK",
"https://example.com/metadata"
)) as ConfidentialFungibleTokenMintableBurnable;
const contractAddress = await contract.getAddress();

return { contract, contractAddress };
}

describe("ConfidentialFungibleTokenMintableBurnable", function () {
let signers: Signers;
let contract: ConfidentialFungibleTokenMintableBurnable;
let contractAddress: string;

before(async function () {
const ethSigners: HardhatEthersSigner[] = await ethers.getSigners();
signers = { deployer: ethSigners[0], alice: ethSigners[1], bob: ethSigners[2] };
});

beforeEach(async () => {
await fhevm.initializeCLIApi();
({ contract, contractAddress } = await deployFixture(await signers.deployer.getAddress()));
});

it("should deploy successfully and set correct owner", async function () {
const owner = await contract.owner();
expect(owner).to.equal(await signers.deployer.getAddress());
});

it("should have correct name and symbol", async function () {
const name = await contract.name();
expect(name).to.equal("ConfidentialToken");
const symbol = await contract.symbol();
expect(symbol).to.equal("CTK");
});

it("should have zero initial balance", async function () {
const initialBalance = await contract.confidentialBalanceOf(signers.deployer.address);
const initialBalanceHex = ethersjs.hexlify(initialBalance);
expect(initialBalanceHex).to.equal(ethers.ZeroHash);
});

it("should test FHE encryption and decryption", async function () {
// 全流程用deployer
const clearAmount = 100;
const user = signers.deployer;
console.log("[FHE] Using user:", await user.getAddress());
const encryptedAmount = await fhevm
.createEncryptedInput(contractAddress, await user.getAddress())
.add64(clearAmount)
.encrypt();
const handleHex = ethersjs.hexlify(encryptedAmount.handles[0]);
console.log("[FHE] handleHex:", handleHex);
console.log("[FHE] inputProof:", Buffer.from(encryptedAmount.inputProof).toString("hex"));
const decryptedAmount = await fhevm.userDecryptEuint(
FhevmType.euint64,
handleHex,
contractAddress,
user
);
console.log("[FHE] decryptedAmount:", decryptedAmount);
expect(decryptedAmount).to.equal(clearAmount);
});

it("should allow minting of confidential tokens by owner", async function () {
const clearAmount = 1000;
const user = signers.deployer;
console.log("[MINT] Using user:", await user.getAddress());
const encryptedAmount = await fhevm
.createEncryptedInput(contractAddress, await user.getAddress())
.add64(clearAmount)
.encrypt();
const handleHex = ethersjs.hexlify(encryptedAmount.handles[0]);
console.log("[MINT] handleHex:", handleHex);
console.log("[MINT] inputProof:", Buffer.from(encryptedAmount.inputProof).toString("hex"));
const tx = await contract
.connect(signers.deployer)
.mint(await user.getAddress(), encryptedAmount.handles[0], encryptedAmount.inputProof);
await tx.wait();
const encryptedBalance = await contract.confidentialBalanceOf(await user.getAddress());
const balanceHex = ethersjs.hexlify(encryptedBalance);
console.log("[MINT] balanceHex:", balanceHex);
const clearBalance = await fhevm.userDecryptEuint(
FhevmType.euint64,
balanceHex,
contractAddress,
user
);
console.log("[MINT] clearBalance:", clearBalance);
expect(clearBalance).to.equal(clearAmount);
});

it("should allow burning of confidential tokens by owner", async function () {
const mintAmount = 1000;
const burnAmount = 500;
const user = signers.deployer;
console.log("[BURN] Using user:", await user.getAddress());
const encryptedMintAmount = await fhevm
.createEncryptedInput(contractAddress, await user.getAddress())
.add64(mintAmount)
.encrypt();
const handleHexMint = ethersjs.hexlify(encryptedMintAmount.handles[0]);
console.log("[BURN] handleHexMint:", handleHexMint);
await contract
.connect(signers.deployer)
.mint(await user.getAddress(), encryptedMintAmount.handles[0], encryptedMintAmount.inputProof);
const encryptedBurnAmount = await fhevm
.createEncryptedInput(contractAddress, await user.getAddress())
.add64(burnAmount)
.encrypt();
const handleHexBurn = ethersjs.hexlify(encryptedBurnAmount.handles[0]);
console.log("[BURN] handleHexBurn:", handleHexBurn);
const tx = await contract
.connect(signers.deployer)
.burn(await user.getAddress(), encryptedBurnAmount.handles[0], encryptedBurnAmount.inputProof);
await tx.wait();
const encryptedBalance = await contract.confidentialBalanceOf(await user.getAddress());
const balanceHex = ethersjs.hexlify(encryptedBalance);
console.log("[BURN] balanceHex:", balanceHex);
const clearBalance = await fhevm.userDecryptEuint(
FhevmType.euint64,
balanceHex,
contractAddress,
user
);
console.log("[BURN] clearBalance:", clearBalance);
expect(clearBalance).to.equal(mintAmount - burnAmount);
});

it("should allow swapping of confidential tokens", async function () {
const mintAmount = 1000;
const swapAmount = 300;
const user = signers.deployer;
console.log("[SWAP] Using user:", await user.getAddress());
const encryptedMintAmount = await fhevm
.createEncryptedInput(contractAddress, await user.getAddress())
.add64(mintAmount)
.encrypt();
const handleHexMint = ethersjs.hexlify(encryptedMintAmount.handles[0]);
console.log("[SWAP] handleHexMint:", handleHexMint);
await contract
.connect(signers.deployer)
.mint(await user.getAddress(), encryptedMintAmount.handles[0], encryptedMintAmount.inputProof);
const tx1 = await contract
.connect(user)
.setOperator(contractAddress, Math.floor(Date.now() / 1000) + 3600);
await tx1.wait();
const encryptedSwapAmount = await fhevm
.createEncryptedInput(contractAddress, await user.getAddress())
.add64(swapAmount)
.encrypt();
const handleHexSwap = ethersjs.hexlify(encryptedSwapAmount.handles[0]);
console.log("[SWAP] handleHexSwap:", handleHexSwap);
const tx2 = await contract
.connect(user)
.swapConfidentialForConfidential(
contract,
contract,
encryptedSwapAmount.handles[0],
encryptedSwapAmount.inputProof
);
await tx2.wait();
const encryptedBalance = await contract.confidentialBalanceOf(await user.getAddress());
const balanceHex = ethersjs.hexlify(encryptedBalance);
console.log("[SWAP] balanceHex:", balanceHex);
const clearBalance = await fhevm.userDecryptEuint(
FhevmType.euint64,
balanceHex,
contractAddress,
user
);
console.log("[SWAP] clearBalance:", clearBalance);
expect(clearBalance).to.equal(mintAmount);
});
});

报错日志
yoona1020@localhost:~/Projects/zh-zama$ npx hardhat test

ConfidentialFungibleTokenMintableBurnable
✔ should deploy successfully and set correct owner
✔ should have correct name and symbol
✔ should have zero initial balance
[FHE] Using user: 0xc7b0D4dc5184b95Dda276b475dF59C3686d3E724
[FHE] handleHex: 0xcb61d40b70f7e2e88923cd08db153f3a3c474bfa66000000000000007a690500
[FHE] inputProof: 0101cb61d40b70f7e2e88923cd08db153f3a3c474bfa66000000000000007a6905001c06faf337e12eeda6c82127f1ff8085224cd13cda7fbdc6ddc49798631bf90d76fafdceec6296264f407b30e35d208a61876fe2e85b3ca3672eec6665ec03321b
1) should test FHE encryption and decryption
[MINT] Using user: 0xc7b0D4dc5184b95Dda276b475dF59C3686d3E724
[MINT] handleHex: 0xd3639cc273450387249eddcdaba06ebad76c7cdee6000000000000007a690500
[MINT] inputProof: 0101d3639cc273450387249eddcdaba06ebad76c7cdee6000000000000007a690500922c170eb530346dcdba411a383fe5d1d847d297df6fb3822344514905cee48627f9afd23df3563c18b00983a626a1e928d534fe82131acbfe4a4ee40f1d57771c
2) should allow minting of confidential tokens by owner
[BURN] Using user: 0xc7b0D4dc5184b95Dda276b475dF59C3686d3E724
[BURN] handleHexMint: 0xcff3f3161f37bd2608d21e408e7c4452d3397ed063000000000000007a690500
3) should allow burning of confidential tokens by owner
[SWAP] Using user: 0xc7b0D4dc5184b95Dda276b475dF59C3686d3E724
[SWAP] handleHexMint: 0x9f1cf54e18f91c401cc49519f550a4aae34a892992000000000000007a690500
4) should allow swapping of confidential tokens

3 passing (166ms)
4 failing

  1. ConfidentialFungibleTokenMintableBurnable
    should test FHE encryption and decryption:
    Error: User 0xc7b0D4dc5184b95Dda276b475dF59C3686d3E724 is not authorized to user decrypt handle 0xcb61d40b70f7e2e88923cd08db153f3a3c474bfa66000000000000007a690500!
    at /home/yoona1020/Projects/zh-zama/node_modules/@fhevm/mock-utils/fhevm/MockFhevmInstance.ts:451:15
    at async Promise.all (index 0)
    at async MockFhevmInstance.userDecrypt (node_modules/@fhevm/mock-utils/fhevm/MockFhevmInstance.ts:313:5)
    at async userDecryptHandleBytes32 (node_modules/@fhevm/mock-utils/fhevm/userDecrypt.ts:91:46)
    at async Proxy.userDecryptEuint (node_modules/@fhevm/hardhat-plugin/src/internal/FhevmExternalAPI.ts:277:48)
    at async Context. (test/ConfidentialFungibleTokenMintableBurnable.ts:72:29)

  2. ConfidentialFungibleTokenMintableBurnable
    should allow minting of confidential tokens by owner:
    Error: Transaction reverted: function returned an unexpected amount of data
    at ConfidentialFungibleTokenMintableBurnable.verify (@fhevm/solidity/lib/Impl.sol:637)
    at ConfidentialFungibleTokenMintableBurnable.fromExternal (@fhevm/solidity/lib/FHE.sol:8286)
    at ConfidentialFungibleTokenMintableBurnable.mint (contracts/ConfidentialFungibleTokenMintableBurnable.sol:26)
    at EdrProviderWrapper.request (node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:359:41)
    at async FhevmProviderExtender._handleEthSendTransaction (node_modules/@fhevm/hardhat-plugin/src/internal/provider/FhevmProviderExtender.ts:108:14)
    at async HardhatEthersSigner.sendTransaction (node_modules/@nomicfoundation/hardhat-ethers/src/signers.ts:181:18)
    at async send (node_modules/ethers/src.ts/contract/contract.ts:313:20)
    at async Proxy.mint (node_modules/ethers/src.ts/contract/contract.ts:352:16)
    at async Context. (test/ConfidentialFungibleTokenMintableBurnable.ts:93:16)

  3. ConfidentialFungibleTokenMintableBurnable
    should allow burning of confidential tokens by owner:
    Error: Transaction reverted: function returned an unexpected amount of data
    at ConfidentialFungibleTokenMintableBurnable.verify (@fhevm/solidity/lib/Impl.sol:637)
    at ConfidentialFungibleTokenMintableBurnable.fromExternal (@fhevm/solidity/lib/FHE.sol:8286)
    at ConfidentialFungibleTokenMintableBurnable.mint (contracts/ConfidentialFungibleTokenMintableBurnable.sol:26)
    at EdrProviderWrapper.request (node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:359:41)
    at async FhevmProviderExtender._handleEthSendTransaction (node_modules/@fhevm/hardhat-plugin/src/internal/provider/FhevmProviderExtender.ts:108:14)
    at async HardhatEthersSigner.sendTransaction (node_modules/@nomicfoundation/hardhat-ethers/src/signers.ts:181:18)
    at async send (node_modules/ethers/src.ts/contract/contract.ts:313:20)
    at async Proxy.mint (node_modules/ethers/src.ts/contract/contract.ts:352:16)
    at async Context. (test/ConfidentialFungibleTokenMintableBurnable.ts:121:5)

  4. ConfidentialFungibleTokenMintableBurnable
    should allow swapping of confidential tokens:
    Error: Transaction reverted: function returned an unexpected amount of data
    at ConfidentialFungibleTokenMintableBurnable.verify (@fhevm/solidity/lib/Impl.sol:637)
    at ConfidentialFungibleTokenMintableBurnable.fromExternal (@fhevm/solidity/lib/FHE.sol:8286)
    at ConfidentialFungibleTokenMintableBurnable.mint (contracts/ConfidentialFungibleTokenMintableBurnable.sol:26)
    at EdrProviderWrapper.request (node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:359:41)
    at async FhevmProviderExtender._handleEthSendTransaction (node_modules/@fhevm/hardhat-plugin/src/internal/provider/FhevmProviderExtender.ts:108:14)
    at async HardhatEthersSigner.sendTransaction (node_modules/@nomicfoundation/hardhat-ethers/src/signers.ts:181:18)
    at async send (node_modules/ethers/src.ts/contract/contract.ts:313:20)
    at async Proxy.mint (node_modules/ethers/src.ts/contract/contract.ts:352:16)
    at async Context. (test/ConfidentialFungibleTokenMintableBurnable.ts:158:5)

yoona1020@localhost:~/Projects/zh-zama$

备注:
全流程用同一个 signer,应该能通过 FHE handle 授权和 fromExternal 校验
FHECounter 合约和脚本能跑通,说明 mock 环境没坏,问题只在 OZ ConfidentialFungibleToken 体系和 mock 的 fromExternal 授权机制。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions