-
Notifications
You must be signed in to change notification settings - Fork 81
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #221 from zama-ai/identity-update
feat() Implement feature explained in the article
- Loading branch information
Showing
22 changed files
with
1,294 additions
and
6,247 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 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); | ||
} | ||
|
||
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)); | ||
|
||
amount = rulesContract.transfer(from, to, amount); | ||
|
||
balances[to] = balances[to] + amount; | ||
balances[from] = balances[from] - amount; | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,53 +1,100 @@ | ||
// 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"; | ||
|
||
interface ICompliantERC20 { | ||
function getIdentifier(address wallet, string calldata identifier) external view returns (euint32); | ||
} | ||
|
||
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 transfer( | ||
Identity identityContract, | ||
address from, | ||
address to, | ||
euint32 _amount | ||
) public view returns (euint32) { | ||
require(address(this) != _this, "isTransferable must be called with delegatecall"); | ||
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(address from, address to, euint32 amount) public view returns (euint32) { | ||
ICompliantERC20 erc20 = ICompliantERC20(msg.sender); | ||
// Condition 1: 10k limit between two different countries | ||
euint8 fromCountry = identityContract.getCountry(from); | ||
euint8 toCountry = identityContract.getCountry(to); | ||
ebool transferLimitOK = checkLimitTransfer(erc20, from, to, amount); | ||
|
||
ebool condition = transferLimitOK; | ||
|
||
// Condition 2: Check that noone is blacklisted | ||
ebool blacklistOK = checkBlacklist(erc20, from, to); | ||
|
||
condition = TFHE.and(condition, blacklistOK); | ||
|
||
// Condition 3: Check country to country rules | ||
ebool c2cOK = checkCountryToCountry(erc20, from, to); | ||
|
||
condition = TFHE.and(condition, c2cOK); | ||
|
||
return TFHE.cmux(condition, amount, TFHE.asEuint32(0)); | ||
} | ||
|
||
function checkLimitTransfer( | ||
ICompliantERC20 erc20, | ||
address from, | ||
address to, | ||
euint32 amount | ||
) internal view returns (ebool) { | ||
euint8 fromCountry = TFHE.asEuint8(erc20.getIdentifier(from, "country")); | ||
euint8 toCountry = TFHE.asEuint8(erc20.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 | ||
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.or(sameCountry, amountBelow10k); | ||
} | ||
|
||
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)); | ||
} | ||
|
||
function checkCountryToCountry(ICompliantERC20 erc20, address from, address to) internal view returns (ebool) { | ||
// Disallow transfer from country 2 to country 1 | ||
uint16[] memory c2cRestrictions = getC2CRestrictions(); | ||
|
||
euint32 fromCountry = erc20.getIdentifier(from, "country"); | ||
euint32 toCountry = erc20.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; | ||
} | ||
} |
Oops, something went wrong.