-
Notifications
You must be signed in to change notification settings - Fork 12
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
Burn Request Fix #18
base: main
Are you sure you want to change the base?
Burn Request Fix #18
Changes from all commits
326a4b8
a849188
90a91a3
49128a9
90e6b0f
6b7a70d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,11 @@ | ||
// SPDX-License-Identifier: BSD-3-Clause-Clear | ||
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol) | ||
|
||
|
||
pragma solidity ^0.8.24; | ||
|
||
import { IConfidentialERC20 } from "./Interfaces/IConfidentialERC20.sol"; | ||
import { IERC20Metadata } from "./Utils/IERC20Metadata.sol"; | ||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
|
||
import { IERC20Errors } from "./Utils/IERC6093.sol"; | ||
import "fhevm/lib/TFHE.sol"; | ||
import "fhevm/gateway/GatewayCaller.sol"; | ||
|
@@ -30,6 +30,10 @@ | |
abstract contract ConfidentialERC20 is Ownable, IConfidentialERC20, IERC20Metadata, IERC20Errors, GatewayCaller { | ||
mapping(address account => euint64) public _balances; | ||
|
||
// To safely handle burn requests we must ensure we lock up an amount of token that is in-flight so that the | ||
// we can guarantee there will be sufficient balance to be burnt at the point that _burnCallback runs. | ||
mapping(address account => uint64) public _lockedBalances; | ||
|
||
mapping(address account => mapping(address spender => euint64)) internal _allowances; | ||
|
||
uint64 public _totalSupply; | ||
|
@@ -43,14 +47,16 @@ | |
* All two of these values are immutable: they can only be set once during | ||
* construction. | ||
*/ | ||
constructor(string memory name_, string memory symbol_) Ownable(msg.sender) { | ||
constructor(string memory name_, string memory symbol_) { | ||
_name = name_; | ||
_symbol = symbol_; | ||
} | ||
struct BurnRq { | ||
address account; | ||
uint64 amount; | ||
bool exists; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not convinced this is useful ? |
||
} | ||
|
||
mapping(uint256 => BurnRq) public burnRqs; | ||
/** | ||
* @dev Returns the name of the token. | ||
|
@@ -108,9 +114,8 @@ | |
*/ | ||
function transfer(address to, euint64 value) public virtual returns (bool) { | ||
require(TFHE.isSenderAllowed(value)); | ||
address owner = _msgSender(); | ||
ebool isTransferable = TFHE.le(value, _balances[msg.sender]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why remove this ? |
||
_transfer(owner, to, value, isTransferable); | ||
address owner = msg.sender; | ||
_transfer(owner, to, value, TFHE.asEbool(true)); | ||
return true; | ||
} | ||
|
||
|
@@ -138,10 +143,11 @@ | |
*/ | ||
function approve(address spender, euint64 value) public virtual returns (bool) { | ||
require(TFHE.isSenderAllowed(value)); | ||
address owner = _msgSender(); | ||
address owner = msg.sender; | ||
_approve(owner, spender, value); | ||
return true; | ||
} | ||
|
||
function approve(address spender, einput encryptedAmount, bytes calldata inputProof) public virtual returns (bool) { | ||
approve(spender, TFHE.asEuint64(encryptedAmount, inputProof)); | ||
return true; | ||
|
@@ -162,7 +168,7 @@ | |
*/ | ||
function transferFrom(address from, address to, euint64 value) public virtual returns (bool) { | ||
require(TFHE.isSenderAllowed(value)); | ||
address spender = _msgSender(); | ||
address spender = msg.sender; | ||
ebool isTransferable = _decreaseAllowance(from, spender, value); | ||
_transfer(from, to, value, isTransferable); | ||
return true; | ||
|
@@ -178,6 +184,17 @@ | |
return true; | ||
} | ||
|
||
/** | ||
* @dev Checks the available balance of an account exceeds amount | ||
*/ | ||
function _hasSufficientBalance(address owner, euint64 amount) internal virtual returns (ebool) { | ||
return TFHE.le(TFHE.add(amount, _lockedBalances[owner]), _balances[owner]); | ||
} | ||
|
||
function _hasSufficientBalance(address owner, uint64 amount) internal virtual returns (ebool) { | ||
return _hasSufficientBalance(owner, TFHE.asEuint64(amount)); | ||
} | ||
|
||
/** | ||
* @dev Moves a `value` amount of tokens from `from` to `to`. | ||
* | ||
|
@@ -195,7 +212,12 @@ | |
if (to == address(0)) { | ||
revert ERC20InvalidReceiver(address(0)); | ||
} | ||
euint64 transferValue = TFHE.select(isTransferable, value, TFHE.asEuint64(0)); | ||
// Enforce sufficient balance constraint universally | ||
euint64 transferValue = TFHE.select( | ||
TFHE.and(_hasSufficientBalance(from, value), isTransferable), | ||
value, | ||
TFHE.asEuint64(0) | ||
); | ||
euint64 newBalanceTo = TFHE.add(_balances[to], transferValue); | ||
_balances[to] = newBalanceTo; | ||
TFHE.allow(newBalanceTo, address(this)); | ||
|
@@ -204,6 +226,7 @@ | |
_balances[from] = newBalanceFrom; | ||
TFHE.allow(newBalanceFrom, address(this)); | ||
TFHE.allow(newBalanceFrom, from); | ||
emit Transfer(from, to, transferValue); | ||
} | ||
|
||
/** | ||
|
@@ -232,6 +255,25 @@ | |
_totalSupply += value; | ||
} | ||
|
||
event BurnRequested(uint256 requestId, address account, uint64 amount); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. events should be at the top of the file |
||
|
||
event BurnRequestCancelled(uint256 requestID); | ||
|
||
error BurnRequestDoesNotExist(uint256 requestId); | ||
|
||
function _cancelBurn(uint256 requestId) internal virtual { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you must check the calling account is allowed to do this |
||
BurnRq memory burnRequest = burnRqs[requestId]; | ||
if (!burnRequest.exists) { | ||
revert BurnRequestDoesNotExist(requestId); | ||
} | ||
address account = burnRequest.account; | ||
uint64 amount = burnRequest.amount; | ||
// Unlock the burn amount unconditionally | ||
_lockedBalances[account] = _lockedBalances[account] - amount; | ||
delete burnRqs[requestId]; | ||
emit BurnRequestCancelled(requestId); | ||
} | ||
|
||
/** | ||
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply. | ||
* Relies on the `_update` mechanism. | ||
|
@@ -244,7 +286,9 @@ | |
if (account == address(0)) { | ||
revert ERC20InvalidReceiver(address(0)); | ||
} | ||
ebool enoughBalance = TFHE.le(amount, _balances[account]); | ||
ebool enoughBalance = _hasSufficientBalance(account, amount); | ||
// Unconditionally lock the burn amount after calculating sufficient balance | ||
_lockedBalances[account] = _lockedBalances[account] + amount; | ||
TFHE.allow(enoughBalance, address(this)); | ||
uint256[] memory cts = new uint256[](1); | ||
cts[0] = Gateway.toUint256(enoughBalance); | ||
|
@@ -256,22 +300,30 @@ | |
block.timestamp + 100, | ||
false | ||
); | ||
|
||
burnRqs[requestID] = BurnRq(account, amount); | ||
burnRqs[requestID] = BurnRq(account, amount, true); | ||
emit BurnRequested(requestID, account, amount); | ||
} | ||
|
||
function _burnCallback(uint256 requestID, bool decryptedInput) public virtual onlyGateway { | ||
event InsufficientBalanceToBurn(address account, uint64 burnAmount); | ||
|
||
function _burnCallback(uint256 requestID, bool hasEnoughBalance) public virtual onlyGateway { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the underlyning token transfer is missing |
||
BurnRq memory burnRequest = burnRqs[requestID]; | ||
if (!burnRequest.exists) { | ||
revert BurnRequestDoesNotExist(requestID); | ||
} | ||
address account = burnRequest.account; | ||
uint64 amount = burnRequest.amount; | ||
if (!decryptedInput) { | ||
revert("Decryption failed"); | ||
// Unlock the burn amount unconditionally | ||
_lockedBalances[account] = _lockedBalances[account] - amount; | ||
delete burnRqs[requestID]; | ||
if (!hasEnoughBalance) { | ||
emit InsufficientBalanceToBurn(account, amount); | ||
return; | ||
} | ||
_totalSupply = _totalSupply - amount; | ||
_balances[account] = TFHE.sub(_balances[account], amount); | ||
TFHE.allow(_balances[account], address(this)); | ||
TFHE.allow(_balances[account], account); | ||
delete burnRqs[requestID]; | ||
} | ||
/** | ||
* @dev Sets `value` as the allowance of `spender` over the `owner` s tokens. | ||
|
@@ -324,6 +376,7 @@ | |
TFHE.allow(value, address(this)); | ||
TFHE.allow(value, owner); | ||
TFHE.allow(value, spender); | ||
emit Approval(owner, spender, value); | ||
} | ||
|
||
/** | ||
|
@@ -334,14 +387,11 @@ | |
*/ | ||
function _decreaseAllowance(address owner, address spender, euint64 amount) internal virtual returns (ebool) { | ||
euint64 currentAllowance = _allowances[owner][spender]; | ||
|
||
ebool allowedTransfer = TFHE.le(amount, currentAllowance); | ||
|
||
ebool canTransfer = TFHE.le(amount, _balances[owner]); | ||
ebool isTransferable = TFHE.and(canTransfer, allowedTransfer); | ||
ebool isTransferable = TFHE.le(amount, currentAllowance); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't the eamount already containt this check and passed when this func is called ? |
||
_approve(owner, spender, TFHE.select(isTransferable, TFHE.sub(currentAllowance, amount), currentAllowance)); | ||
return isTransferable; | ||
} | ||
|
||
/** | ||
* @dev Increases `owner` s allowance for `spender` based on spent `value`. | ||
* | ||
|
@@ -350,7 +400,7 @@ | |
*/ | ||
function _increaseAllowance(address spender, euint64 addedValue) internal virtual returns (ebool) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why does this function even exist |
||
require(TFHE.isSenderAllowed(addedValue)); | ||
address owner = _msgSender(); | ||
address owner = msg.sender; | ||
ebool isTransferable = TFHE.le(addedValue, _balances[owner]); | ||
euint64 newAllowance = TFHE.add(_allowances[owner][spender], addedValue); | ||
TFHE.allow(newAllowance, address(this)); | ||
|
@@ -373,7 +423,7 @@ | |
|
||
function decreaseAllowance(address spender, euint64 subtractedValue) public virtual returns (ebool) { | ||
require(TFHE.isSenderAllowed(subtractedValue)); | ||
return _decreaseAllowance(_msgSender(), spender, subtractedValue); | ||
return _decreaseAllowance(msg.sender, spender, subtractedValue); | ||
} | ||
|
||
function decreaseAllowance( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,26 +3,26 @@ | |
|
||
import "./ConfidentialERC20.sol"; | ||
import "fhevm/lib/TFHE.sol"; | ||
|
||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
/** | ||
* @dev Example Implementation of the {ConfidentialERC20} contract, providing minting and additional functionality. | ||
*/ | ||
contract ConfidentialToken is ConfidentialERC20 { | ||
address private _owner; | ||
|
||
|
||
/** | ||
* @dev Sets the initial values for {name} and {symbol}, and assigns ownership to the deployer. | ||
*/ | ||
constructor(string memory name_, string memory symbol_) ConfidentialERC20(name_, symbol_) { | ||
_owner = msg.sender; | ||
constructor(string memory name_, string memory symbol_) ConfidentialERC20(name_, symbol_)Ownable(msg.sender) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why reintroduce the owner |
||
|
||
} | ||
|
||
/** | ||
* @dev Mint new tokens. | ||
* | ||
*/ | ||
function mint(address to, uint64 amount) public { | ||
require(msg.sender == _owner, "Only owner"); | ||
function mint(address to, uint64 amount) public onlyOwner() { | ||
|
||
_mint(to, amount); | ||
} | ||
|
||
|
@@ -31,23 +31,9 @@ | |
* | ||
*/ | ||
function burn(address from, uint64 amount) public { | ||
require(msg.sender == _owner, "Only owner"); | ||
burn(from, amount); | ||
_requestBurn(from, amount); | ||
} | ||
|
||
/** | ||
* @dev Change the owner | ||
* | ||
*/ | ||
function transferOwnership(address newOwner) public override { | ||
require(msg.sender == _owner, " Only owner"); | ||
_owner = newOwner; | ||
} | ||
|
||
/** | ||
* @dev Get the owner of the contract. | ||
*/ | ||
function owner() public view override returns (address) { | ||
return _owner; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// SPDX-License-Identifier: BSD-3-Clause-Clear | ||
pragma solidity ^0.8.24; | ||
|
||
import "fhevm/lib/TFHE.sol"; | ||
import { IConfidentialERC20 } from "./IConfidentialERC20.sol"; | ||
|
||
/** | ||
* @dev Interface of the Confidential ERC-20 wrapper. | ||
*/ | ||
|
||
interface IConfidentialERC20Wrapper is IConfidentialERC20 { | ||
/** | ||
* @dev Returns the base ERC-20 token address. | ||
*/ | ||
function baseERC20() external view returns (address); | ||
|
||
/** | ||
* @dev Returns the decimals of the base ERC-20 token. | ||
*/ | ||
function decimals() external view returns (uint8); | ||
|
||
/** | ||
* @dev Wraps a `amount` of base ERC-20 tokens into the wrapper. | ||
*/ | ||
function wrap(uint64 amount) external; | ||
|
||
/** | ||
* @dev Unwraps a `amount` of wrapped tokens into the base ERC-20 token. | ||
*/ | ||
function unwrap(uint256 amount) external; | ||
|
||
function setUnwrapStatus(address account, bool status) external; | ||
|
||
|
||
|
||
event Wrap(address indexed account, uint64 amount); | ||
event Unwrap(address indexed account, uint64 amount); | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is it for ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
transferLimit becomes unused