Skip to content

Commit

Permalink
Merge pull request #245 from zama-ai/feature/improved-erc20
Browse files Browse the repository at this point in the history
Feature/improved erc20
  • Loading branch information
jatZama authored Jan 10, 2024
2 parents fe1e970 + db547ce commit 15992bd
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 221 deletions.
117 changes: 51 additions & 66 deletions examples/EncryptedERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,110 +3,98 @@
pragma solidity ^0.8.20;

import "../abstracts/Reencrypt.sol";

import "../lib/TFHE.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";

contract EncryptedERC20 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);

contract EncryptedERC20 is Reencrypt {
euint32 private totalSupply;
uint32 public totalSupply;
string public constant name = "Naraggara"; // City of Zama's battle
string public constant symbol = "NARA";
uint8 public constant decimals = 18;

// used for output authorization
bytes32 private DOMAIN_SEPARATOR;
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;

// The owner of the contract.
address public contractOwner;

mapping(address => euint8) internal lastError;

euint8 internal NO_ERROR;
euint8 internal NOT_ENOUGH_FUND;

constructor() {
contractOwner = msg.sender;
NO_ERROR = TFHE.asEuint8(0);
NOT_ENOUGH_FUND = TFHE.asEuint8(1);
}

function getLastError(
bytes32 publicKey,
bytes calldata signature
) public view onlySignedPublicKey(publicKey, signature) returns (bytes memory) {
return TFHE.reencrypt(lastError[msg.sender], publicKey, 0);
}
constructor() Ownable(msg.sender) {}

// Sets the balance of the owner to the given encrypted balance.
function mint(bytes calldata encryptedAmount) public onlyContractOwner {
euint32 amount = TFHE.asEuint32(encryptedAmount);
balances[contractOwner] = balances[contractOwner] + amount;
totalSupply = totalSupply + amount;
function mint(uint32 mintedAmount) public 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 {
function transfer(address to, bytes calldata encryptedAmount) public 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 {
_transfer(msg.sender, to, amount);
}

function getTotalSupply(
bytes32 publicKey,
bytes calldata signature
) public view onlySignedPublicKey(publicKey, signature) returns (bytes memory) {
return TFHE.reencrypt(totalSupply, publicKey, 0);
function transfer(address to, euint32 amount) public 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 onlySignedPublicKey(publicKey, signature) returns (bytes memory) {
) 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 {
function approve(address spender, bytes calldata encryptedAmount) public 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 returns (bool) {
address owner = msg.sender;
_approve(owner, spender, TFHE.asEuint32(encryptedAmount));
_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 onlySignedPublicKey(publicKey, signature) returns (bytes memory) {
address owner = msg.sender;

require(owner == msg.sender || spender == msg.sender);
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 {
function transferFrom(address from, address to, bytes calldata encryptedAmount) public 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 {
function transferFrom(address from, address to, euint32 amount) public returns (bool) {
address spender = msg.sender;
_updateAllowance(from, spender, amount);
_transfer(from, to, amount);
ebool isTransferable = _updateAllowance(from, spender, amount);
_transfer(from, to, amount, isTransferable);
return true;
}

function _approve(address owner, address spender, euint32 amount) internal {
Expand All @@ -121,25 +109,22 @@ contract EncryptedERC20 is Reencrypt {
}
}

function _updateAllowance(address owner, address spender, euint32 amount) internal {
function _updateAllowance(address owner, address spender, euint32 amount) internal returns (ebool) {
euint32 currentAllowance = _allowance(owner, spender);
ebool canTransfer = TFHE.le(amount, currentAllowance);
_approve(owner, spender, TFHE.cmux(canTransfer, currentAllowance - amount, TFHE.asEuint32(0)));
// 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) internal {
// Make sure the sender has enough tokens.
ebool canTransfer = TFHE.le(amount, balances[from]);
lastError[msg.sender] = TFHE.cmux(canTransfer, NO_ERROR, NOT_ENOUGH_FUND);

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(canTransfer, amount, TFHE.asEuint32(0));
balances[from] = balances[from] - TFHE.cmux(canTransfer, amount, TFHE.asEuint32(0));
}

modifier onlyContractOwner() {
require(msg.sender == contractOwner);
_;
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);
}
}
59 changes: 0 additions & 59 deletions examples/Identity/AbstractCompliantERC20.sol

This file was deleted.

112 changes: 36 additions & 76 deletions examples/Identity/CompliantERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,98 +3,58 @@
pragma solidity ^0.8.20;

import "../../lib/TFHE.sol";
import "./AbstractCompliantERC20.sol";
import "../../abstracts/Reencrypt.sol";
import "../EncryptedERC20.sol";
import "./ERC20Rules.sol";
import "./IdentityRegistry.sol";

contract CompliantERC20 is AbstractCompliantERC20 {
euint32 private totalSupply;
string public constant name = "Naraggara"; // City of Zama's battle
string public constant symbol = "NARA";
uint8 public constant decimals = 18;
contract CompliantERC20 is EncryptedERC20 {
IdentityRegistry identityContract;
ERC20Rules rulesContract;

// A mapping of the form mapping(owner => mapping(spender => allowance)).
mapping(address => mapping(address => euint32)) internal allowances;

// The owner of the contract.
address public contractOwner;

constructor(address _identityAddr, address _rulesAddr) AbstractCompliantERC20(_identityAddr, _rulesAddr) {
contractOwner = msg.sender;
constructor(address _identityAddr, address _rulesAddr) {
identityContract = IdentityRegistry(_identityAddr);
rulesContract = ERC20Rules(_rulesAddr);
}

// Sets the balance of the owner to the given encrypted balance.
function mint(bytes calldata encryptedAmount) public onlyContractOwner {
euint32 amount = TFHE.asEuint32(encryptedAmount);
balances[contractOwner] = balances[contractOwner] + amount;
totalSupply = totalSupply + amount;
}

// Transfers an encrypted amount from the message sender address to the `to` address.
function transfer(address to, bytes calldata encryptedAmount) public {
transfer(to, TFHE.asEuint32(encryptedAmount));
}

// Transfers an amount from the message sender address to the `to` address.
function transfer(address to, euint32 amount) public {
_transfer(msg.sender, to, amount);
}

function getTotalSupply(
bytes32 publicKey,
bytes calldata signature
) public view onlySignedPublicKey(publicKey, signature) returns (bytes memory) {
return TFHE.reencrypt(totalSupply, publicKey, 0);
function identifiers() public view returns (string[] memory) {
return rulesContract.getIdentifiers();
}

// Sets the `encryptedAmount` as the allowance of `spender` over the caller's tokens.
function approve(address spender, bytes calldata encryptedAmount) public {
address owner = msg.sender;
_approve(owner, spender, TFHE.asEuint32(encryptedAmount));
function getIdentifier(address wallet, string calldata identifier) external view returns (euint32) {
require(msg.sender == address(rulesContract), "Access restricted to the current ERC20Rules");
return identityContract.getIdentifier(wallet, identifier);
}

// 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 spender,
function balanceOf(
address wallet,
bytes32 publicKey,
bytes calldata signature
) public view onlySignedPublicKey(publicKey, signature) returns (bytes memory) {
address owner = msg.sender;
) public view override onlySignedPublicKey(publicKey, signature) returns (bytes memory) {
if (wallet == msg.sender) {
return TFHE.reencrypt(balances[msg.sender], publicKey, 0);
}

return TFHE.reencrypt(_allowance(owner, spender), publicKey);
}
uint32 userCountry = rulesContract.whitelistedWallets(msg.sender);
require(userCountry > 0, "You're not registered as a country wallet");

// Transfers `encryptedAmount` tokens using the caller's allowance.
function transferFrom(address from, address to, bytes calldata encryptedAmount) public {
transferFrom(from, to, TFHE.asEuint32(encryptedAmount));
}
euint32 walletCountry = identityContract.getIdentifier(wallet, "country");
ebool sameCountry = TFHE.eq(walletCountry, userCountry);
euint32 balance = TFHE.isInitialized(balances[wallet]) ? balances[wallet] : TFHE.asEuint32(0);
balance = TFHE.cmux(sameCountry, balance, TFHE.asEuint32(0));

// Transfers `amount` tokens using the caller's allowance.
function transferFrom(address from, address to, euint32 amount) public {
address spender = msg.sender;
_updateAllowance(from, spender, amount);
_transfer(from, to, amount);
return TFHE.reencrypt(balance, publicKey, 0);
}

function _approve(address owner, address spender, euint32 amount) internal {
allowances[owner][spender] = amount;
}
// Transfers an encrypted amount.
function _transfer(address from, address to, euint32 _amount, ebool isTransferable) internal override {
// Condition 1: hasEnoughFunds
ebool enoughFund = TFHE.le(_amount, balances[from]);
euint32 amount = TFHE.cmux(enoughFund, _amount, TFHE.asEuint32(0));

function _allowance(address owner, address spender) internal view 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 {
euint32 currentAllowance = _allowance(owner, spender);
ebool canTransfer = TFHE.le(amount, currentAllowance);
_approve(owner, spender, TFHE.cmux(canTransfer, currentAllowance - amount, TFHE.asEuint32(0)));
}
amount = rulesContract.transfer(from, to, amount);

modifier onlyContractOwner() {
require(msg.sender == contractOwner);
_;
balances[to] = balances[to] + amount;
balances[from] = balances[from] - amount;
}
}
2 changes: 1 addition & 1 deletion examples/Identity/ERC20Rules.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ contract ERC20Rules {
function checkBlacklist(ICompliantERC20 erc20, address from, address to) internal view returns (ebool) {
ebool fromBlacklisted = TFHE.asEbool(erc20.getIdentifier(from, "blacklist"));
ebool toBlacklisted = TFHE.asEbool(erc20.getIdentifier(to, "blacklist"));
return TFHE.not(TFHE.and(toBlacklisted, fromBlacklisted));
return TFHE.not(TFHE.or(toBlacklisted, fromBlacklisted));
}

function checkCountryToCountry(ICompliantERC20 erc20, address from, address to) internal view returns (ebool) {
Expand Down
Loading

0 comments on commit 15992bd

Please sign in to comment.