Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat() Implement feature explained in the article #221

Merged
merged 14 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions examples/Identity/AbstractCompliantERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear

pragma solidity 0.8.19;

import "../../abstracts/EIP712WithModifier.sol";
import "./ERC20Rules.sol";
import "./IdentityRegistry.sol";

abstract contract AbstractCompliantERC20 is EIP712WithModifier {
IdentityRegistry identityContract;
ERC20Rules rulesContract;
mapping(address => euint32) internal balances;

constructor(address _identityAddr, address _rulesAddr) EIP712WithModifier("Authorization token", "1") {
identityContract = IdentityRegistry(_identityAddr);
rulesContract = ERC20Rules(_rulesAddr);
}

function identifiers() public view returns (string[] memory) {
return rulesContract.getIdentifiers();
}

function balanceOf(
address wallet,
bytes32 publicKey,
bytes calldata signature
) public view onlySignedPublicKey(publicKey, signature) returns (bytes memory) {
if (wallet == msg.sender) {
return TFHE.reencrypt(balances[msg.sender], publicKey, 0);
}

uint32 userCountry = rulesContract.countryWallets(msg.sender);
require(userCountry > 0, "You're not registered as a country wallet");

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

return TFHE.reencrypt(balance, publicKey, 0);
}

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

// Delegate call
(bool success, bytes memory returndata) = address(rulesContract).delegatecall(
abi.encodeWithSelector(ERC20Rules.transfer.selector, identityContract, rulesContract, from, to, amount)
);
immortal-tofu marked this conversation as resolved.
Show resolved Hide resolved
require(success == true);
amount = abi.decode(returndata, (euint32));

balances[to] = balances[to] + amount;
balances[from] = balances[from] - amount;
}
}
53 changes: 0 additions & 53 deletions examples/Identity/AbstractIdentifiedERC20.sol

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
pragma solidity 0.8.19;

import "../../lib/TFHE.sol";
import "./AbstractIdentifiedERC20.sol";
import "./AbstractCompliantERC20.sol";

contract IdentifiedERC20 is AbstractIdentifiedERC20 {
contract CompliantERC20 is AbstractCompliantERC20 {
euint32 private totalSupply;
string public constant name = "Naraggara"; // City of Zama's battle
string public constant symbol = "NARA";
Expand All @@ -17,7 +17,7 @@ contract IdentifiedERC20 is AbstractIdentifiedERC20 {
// The owner of the contract.
address public contractOwner;

constructor(address _identityAddr, address _rulesAddr) AbstractIdentifiedERC20(_identityAddr, _rulesAddr) {
constructor(address _identityAddr, address _rulesAddr) AbstractCompliantERC20(_identityAddr, _rulesAddr) {
contractOwner = msg.sender;
}

Expand Down Expand Up @@ -45,14 +45,6 @@ contract IdentifiedERC20 is AbstractIdentifiedERC20 {
return TFHE.reencrypt(totalSupply, publicKey, 0);
}

// Returns the balance of the caller encrypted under the provided public key.
function balanceOf(
bytes32 publicKey,
bytes calldata signature
) public view onlySignedPublicKey(publicKey, signature) returns (bytes memory) {
return TFHE.reencrypt(balances[msg.sender], publicKey, 0);
}

// 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;
Expand Down
108 changes: 83 additions & 25 deletions examples/Identity/ERC20Rules.sol
Original file line number Diff line number Diff line change
@@ -1,53 +1,111 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear

// Owner = ONU
// Issuer par pays
// Did associé à un issuer

pragma solidity 0.8.19;

import "./Identity.sol";

import "../../lib/TFHE.sol";
import "./IdentityRegistry.sol";

contract ERC20Rules {
address immutable _this;

string[] public identifiers;

mapping(address => uint32) public countryWallets;
mapping(string => uint8) public countries;
uint16[] public country2CountryRestrictions;

constructor() {
_this = address(this);
identifiers = ["country", "blacklist"];
countryWallets[address(0x133725C6461120439E85887C7639316CB27a2D9d)] = 1;
countryWallets[address(0x4CaCeF78615AFecEf7eF182CfbD243195Fc90a29)] = 2;

countries["fr"] = 1;
countries["us"] = 2;

country2CountryRestrictions = [createRestriction(countries["us"], countries["fr"])];
}

function createRestriction(uint16 from, uint16 to) internal pure returns (uint16) {
return (from << 8) + to;
}

function getIdentifiers() public view returns (string[] memory) {
return identifiers;
}

function getC2CRestrictions() public view returns (uint16[] memory) {
return country2CountryRestrictions;
}

function transfer(
Identity identityContract,
IdentityRegistry identityContract,
ERC20Rules rulesContract,
address from,
address to,
euint32 _amount
euint32 amount
) public view returns (euint32) {
require(address(this) != _this, "isTransferable must be called with delegatecall");
require(address(this) != _this, "transfer must be called with delegatecall");

// Condition 1: 10k limit between two different countries
euint8 fromCountry = identityContract.getCountry(from);
euint8 toCountry = identityContract.getCountry(to);
ebool transferLimitOK = checkLimitTransfer(identityContract, from, to, amount);

ebool condition = transferLimitOK;

// Condition 2: Check that noone is blacklisted
ebool blacklistOK = checkBlacklist(identityContract, from, to);

condition = TFHE.and(condition, blacklistOK);

// Condition 3: Check country to country rules
ebool c2cOK = checkCountryToCountry(identityContract, rulesContract, from, to);

condition = TFHE.and(condition, c2cOK);

return TFHE.cmux(condition, amount, TFHE.asEuint32(0));
}

function checkLimitTransfer(
IdentityRegistry identityContract,
address from,
address to,
euint32 amount
) internal view returns (ebool) {
euint8 fromCountry = TFHE.asEuint8(identityContract.getIdentifier(from, "country"));
euint8 toCountry = TFHE.asEuint8(identityContract.getIdentifier(to, "country"));
require(TFHE.isInitialized(fromCountry) && TFHE.isInitialized(toCountry), "You don't have access");
ebool sameCountry = TFHE.eq(fromCountry, toCountry);
ebool amountAbove10k = TFHE.gt(_amount, 10000);
ebool countryCondition = TFHE.cmux(
sameCountry,
TFHE.asEbool(true),
TFHE.cmux(amountAbove10k, TFHE.asEbool(false), TFHE.asEbool(true))
);
ebool amountBelow10k = TFHE.le(amount, 10000);

// Condition 2: Check that noone is blacklisted
return TFHE.or(sameCountry, amountBelow10k);
}

function checkBlacklist(IdentityRegistry identityContract, address from, address to) internal view returns (ebool) {
ebool fromBlacklisted = TFHE.asEbool(identityContract.getIdentifier(from, "blacklist"));
ebool toBlacklisted = TFHE.asEbool(identityContract.getIdentifier(to, "blacklist"));
ebool whitelisted = TFHE.not(TFHE.and(toBlacklisted, fromBlacklisted));
return TFHE.not(TFHE.and(toBlacklisted, fromBlacklisted));
}

function checkCountryToCountry(
IdentityRegistry identityContract,
ERC20Rules rulesContract,
address from,
address to
) internal view returns (ebool) {
// Disallow transfer from country 2 to country 1
uint16[] memory c2cRestrictions = rulesContract.getC2CRestrictions();

euint32 fromCountry = identityContract.getIdentifier(from, "country");
euint32 toCountry = identityContract.getIdentifier(to, "country");
require(TFHE.isInitialized(fromCountry) && TFHE.isInitialized(toCountry), "You don't have access");
euint16 countryToCountry = TFHE.shl(TFHE.asEuint16(fromCountry), 8) + TFHE.asEuint16(toCountry);
ebool condition = TFHE.asEbool(true);

euint32 amount = TFHE.cmux(
countryCondition,
TFHE.cmux(whitelisted, _amount, TFHE.asEuint32(0)),
TFHE.asEuint32(0)
);
// Check all countryToCountry restrictions
for (uint i = 0; i < c2cRestrictions.length; i++) {
condition = TFHE.and(condition, TFHE.ne(countryToCountry, c2cRestrictions[i]));
}

return amount;
return condition;
}
}
Loading