From 0c473d265278b9431f8e9e083e425db82dbc60ec Mon Sep 17 00:00:00 2001 From: Zach Petersen Date: Thu, 17 Oct 2024 15:28:52 -0500 Subject: [PATCH 1/4] Sync repos --- LICENSE | 2 +- Makefile | 41 + README.md | 57 +- contracts/archive/XYZImplementation.sol | 695 +++++++++++++ contracts/archive/XYZImplementationV1.sol | 1080 +++++++++++++++++++++ contracts/stablecoins/USDX.sol | 39 + scripts/deploy.js | 3 +- scripts/deployImplementation.js | 3 +- scripts/deploySupplyController.js | 22 +- scripts/deployUUPS.js | 11 +- scripts/getEncodeDataInitialize.js | 2 - scripts/utils.js | 12 +- test/StablecoinTest.js | 16 +- test/UpgradeToV2Test.js | 50 +- test/helpers/fixtures.js | 16 +- test/helpers/storageLayout.js | 2 +- 16 files changed, 1949 insertions(+), 102 deletions(-) create mode 100644 Makefile create mode 100644 contracts/archive/XYZImplementation.sol create mode 100644 contracts/archive/XYZImplementationV1.sol create mode 100644 contracts/stablecoins/USDX.sol diff --git a/LICENSE b/LICENSE index 4810994..e443e91 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Paxos Technology Solutions, LLC +Copyright (c) 2021 Paxos Technology Solutions, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..16d9f96 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +.PHONY:all +all: setup fmt compile generate-bin test-contracts-coverage + +.PHONY:clean +clean: + @rm -r build/ || true + +################## +# Code +################## + +.PHONY:setup +setup: + yarn install --ignore-optional + +.PHONY:fmt +fmt: + @npm run solhint + +.PHONY:ganache +ganache: + @npm run ganache + +.PHONY:compile +compile: + @npm run compile + +.PHONY:generate-bin +generate-bin: compile + @npm run generate-abi + @npm run generate-bin + +# compile is needed as a dependency here to ensure the zos-lib based tests work +.PHONY:test-contracts +test-contracts: compile + @npm test + +# TODO: get tests to pass in coverage env +.PHONY:test-contracts-coverage +test-contracts-coverage: + @npm run coverage diff --git a/README.md b/README.md index 9c4b4ce..1c8aabc 100644 --- a/README.md +++ b/README.md @@ -4,24 +4,27 @@ Paxos-issued USD-collateralized ERC20 stablecoin public smart contract repositor https://github.com/paxosglobal/paxos-token-contract -### Roles +### Roles and Addresses -| Role | -| ------------------------------ | -| DEFAULT_ADMIN_ROLE | -| PAUSE_ROLE | -| ASSET_PROTECTION_ROLE | -| SUPPLY_CONTROLLER_MANAGER_ROLE | -| SUPPLY_CONTROLLER_ROLE | +| Role | Address | +| ------------------------------ | ------------------------------------------ | +| DEFAULT_ADMIN_ROLE | TBD | +| PAUSE_ROLE | TBD | +| ASSET_PROTECTION_ROLE | TBD | +| SUPPLY_CONTROLLER_MANAGER_ROLE | TBD | +| SUPPLY_CONTROLLER_ROLE | TBD | To guard against centralized control, the addresses above utilize multisignature contracts ([source](https://github.com/paxosglobal/simple-multisig)). Any change requires the presence of a quorum of signers in the same physical location, ensuring that no individual signer can unilaterally influence a change. -### ABI and Addresses +### ABI, Address, and Verification -The contract abi is in `PaxosToken.abi`, which is the implementation contract abi. +The contract abi is in `PaxosToken.abi`, which is the imlementation contract abi. -Interaction with token is done at the address of the proxy. Deployed token addresses can be found in -the [Paxos docs](https://docs.paxos.com/stablecoin). +Interaction with token is done at the address of the proxy. Below is the table which list out all our contract addresses. + +| Token | Proxy-address | Implementation-Address | +| :-------------: |:-------------:| :-----:| +| USDP | 0x000000000000000000000000000000000000000 | 0x000000000000000000000000000000000000000 | ## Contract Specification @@ -88,6 +91,10 @@ While paused, the supply controller retains the ability to mint and burn tokens. ### Asset Protection Role +Paxos Trust Company is regulated by the New York Department of Financial Services (NYDFS). As required by the regulator, +Paxos must have a role for asset protection to freeze or seize the assets of a criminal party when required to do so by +law, including by court order or other legal process. + The `ASSET_PROTECTION_ROLE` can freeze and unfreeze the token balance of any address on chain. It can also wipe the balance of an address after it is frozen to allow the appropriate authorities to seize the backing assets. @@ -98,7 +105,7 @@ via `isFrozen(address who)`. ### Delegate Transfer -To facilitate gas-less transactions, we have implemented [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) and [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612). +To facilitate gas-less transactions, we have implemented [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) and (EIP-2612)[https://eips.ethereum.org/EIPS/eip-2612]. #### EIP-3009 The public functions, `transferWithAuthorization` and `transferWithAuthorizationBatch` (for multiple transfers request), allows a spender(delegate) to transfer tokens on behalf of the sender, with condition that a signature, conforming to [EIP-712](https://eips.ethereum.org/EIPS/eip-712), is provided by the respective sender. @@ -168,16 +175,7 @@ _in the context of the proxy storage_. This way the implementation pointer can be changed to a different implementation contract while still keeping the same data and contract address, which are really for the proxy contract. -USDP and PYUSD use `AdminUpgradeabilityProxy` from OpenZeppelin. - -USDG and SupplyControl use `UUPSUpgradeable` from OpenZeppelin. - -`UUPSUpgradeable` is a newer proxy pattern which -has some advantages over `AdminUpgradeabilityProxy`. One issue with `AdminUpgradeabilityProxy` is the proxy admin -cannot call any of the implementation functions which means the proxy admin must be a separate address -from the DEFAULT_ADMIN_ROLE. This is not an issue with `UUPSUpgradeable`. Another advantage is updating -the proxy admin in `UUPSUpgradeable` is a two step process due to using OpenZeppelin's AccessControlDefaultAdmin. -However, in `AdminUpgradeabilityProxy` it's one step which is more dangerous. +The proxy used here is AdminUpgradeabilityProxy from ZeppelinOS. ## Upgrade Process @@ -188,6 +186,19 @@ it can all be done in one transaction. You must first deploy a copy of the new i contract, which is automatically paused by its constructor to help avoid accidental calls directly to the proxy contract. +## Bytecode verification +[comment]: <> (TODO: ref: `@` -> Add implementation address for line 142) + +The proxy contract and implementation contracts are verified on etherscan at the following links: +https://etherscan.io/token/0x6c3ea9036406852006290770bedfcaba0e23a0e8 +https://etherscan.io/token/`@` + +Because the implementation address in the proxy is a private variable, +verifying that this is the proxy being used requires reading contract +storage directly. The contract storage slot address is `0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3`. + +`npx hardhat --network ${NETWORK} run scripts/getImplementationAddress.js` + ## Contract Tests Install dependencies: diff --git a/contracts/archive/XYZImplementation.sol b/contracts/archive/XYZImplementation.sol new file mode 100644 index 0000000..19e580f --- /dev/null +++ b/contracts/archive/XYZImplementation.sol @@ -0,0 +1,695 @@ +// File: contracts/zeppelin/SafeMath.sol + +pragma solidity ^0.4.24; + + +/** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ +library SafeMath { + /** + * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a); + uint256 c = a - b; + + return c; + } + + /** + * @dev Adds two numbers, reverts on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a); + + return c; + } +} + +// File: contracts/XYZImplementation.sol + +pragma solidity ^0.4.24; +pragma experimental "v0.5.0"; + + + +/** + * @title XYZImplementation + * @dev this contract is a Pausable ERC20 token with Burn and Mint + * controlled by a central SupplyController. By implementing XYZImplementation + * this contract also includes external methods for setting + * a new implementation contract for the Proxy. + * NOTE: The storage defined here will actually be held in the Proxy + * contract and all calls to this contract should be made through + * the proxy, including admin actions done as owner or supplyController. + * Any call to transfer against this contract should fail + * with insufficient funds since no tokens will be issued there. + */ +contract XYZImplementation { + + /** + * MATH + */ + + using SafeMath for uint256; + + /** + * DATA + */ + + // INITIALIZATION DATA + bool private initialized; + + // ERC20 BASIC DATA + mapping(address => uint256) internal balances; + uint256 internal totalSupply_; + string public constant name = "Hopper"; // solium-disable-line + string public constant symbol = "XYZ"; // solium-disable-line uppercase + uint8 public constant decimals = 6; // solium-disable-line uppercase + + // ERC20 DATA + mapping(address => mapping(address => uint256)) internal allowed; + + // OWNER DATA PART 1 + address public owner; + + // PAUSABILITY DATA + bool public paused; + + // ASSET PROTECTION DATA + address public assetProtectionRole; + mapping(address => bool) internal frozen; + + // SUPPLY CONTROL DATA + address public supplyController; + + // OWNER DATA PART 2 + address public proposedOwner; + + // DELEGATED TRANSFER DATA + address public betaDelegateWhitelister; + mapping(address => bool) internal betaDelegateWhitelist; + mapping(address => uint256) internal nextSeqs; + // EIP191 header for EIP712 prefix + string constant internal EIP191_HEADER = "\x19\x01"; + // Hash of the EIP712 Domain Separator Schema + bytes32 constant internal EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256( + "EIP712Domain(string name,address verifyingContract)" + ); + bytes32 constant internal EIP712_DELEGATED_TRANSFER_SCHEMA_HASH = keccak256( + "BetaDelegatedTransfer(address to,uint256 value,uint256 fee,uint256 seq,uint256 deadline)" + ); + // Hash of the EIP712 Domain Separator data + // solhint-disable-next-line var-name-mixedcase + bytes32 public EIP712_DOMAIN_HASH; + + /** + * EVENTS + */ + + // ERC20 BASIC EVENTS + event Transfer(address indexed from, address indexed to, uint256 value); + + // ERC20 EVENTS + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); + + // OWNABLE EVENTS + event OwnershipTransferProposed( + address indexed currentOwner, + address indexed proposedOwner + ); + event OwnershipTransferDisregarded( + address indexed oldProposedOwner + ); + event OwnershipTransferred( + address indexed oldOwner, + address indexed newOwner + ); + + // PAUSABLE EVENTS + event Pause(); + event Unpause(); + + // ASSET PROTECTION EVENTS + event AddressFrozen(address indexed addr); + event AddressUnfrozen(address indexed addr); + event FrozenAddressWiped(address indexed addr); + event AssetProtectionRoleSet ( + address indexed oldAssetProtectionRole, + address indexed newAssetProtectionRole + ); + + // SUPPLY CONTROL EVENTS + event SupplyIncreased(address indexed to, uint256 value); + event SupplyDecreased(address indexed from, uint256 value); + event SupplyControllerSet( + address indexed oldSupplyController, + address indexed newSupplyController + ); + + // DELEGATED TRANSFER EVENTS + event BetaDelegatedTransfer( + address indexed from, address indexed to, uint256 value, uint256 seq, uint256 fee + ); + event BetaDelegateWhitelisterSet( + address indexed oldWhitelister, + address indexed newWhitelister + ); + event BetaDelegateWhitelisted(address indexed newDelegate); + event BetaDelegateUnwhitelisted(address indexed oldDelegate); + + /** + * FUNCTIONALITY + */ + + // INITIALIZATION FUNCTIONALITY + + /** + * @dev sets 0 initials tokens, the owner, and the supplyController. + * this serves as the constructor for the proxy but compiles to the + * memory model of the Implementation contract. + */ + function initialize() public { + require(!initialized, "MANDATORY VERIFICATION REQUIRED: The proxy has already been initialized, verify the owner and supply controller addresses."); + owner = msg.sender; + assetProtectionRole = address(0); + totalSupply_ = 0; + supplyController = msg.sender; + initializeDomainSeparator(); + initialized = true; + } + + /** + * The constructor is used here to ensure that the implementation + * contract is initialized. An uncontrolled implementation + * contract might lead to misleading state + * for users who accidentally interact with it. + */ + constructor() public { + initialize(); + pause(); + } + + /** + * @dev To be called when upgrading the contract using upgradeAndCall to add delegated transfers + */ + function initializeDomainSeparator() private { + // hash the name context with the contract address + EIP712_DOMAIN_HASH = keccak256(abi.encodePacked(// solium-disable-line + EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH, + keccak256(bytes(name)), + bytes32(address(this)) + )); + } + + // ERC20 BASIC FUNCTIONALITY + + /** + * @dev Total number of tokens in existence + */ + function totalSupply() public view returns (uint256) { + return totalSupply_; + } + + /** + * @dev Transfer token to a specified address from msg.sender + * Note: the use of Safemath ensures that _value is nonnegative. + * @param _to The address to transfer to. + * @param _value The amount to be transferred. + */ + function transfer(address _to, uint256 _value) public whenNotPaused returns (bool) { + require(_to != address(0), "cannot transfer to address zero"); + require(!frozen[_to] && !frozen[msg.sender], "address frozen"); + require(_value <= balances[msg.sender], "insufficient funds"); + + balances[msg.sender] = balances[msg.sender].sub(_value); + balances[_to] = balances[_to].add(_value); + emit Transfer(msg.sender, _to, _value); + return true; + } + + /** + * @dev Gets the balance of the specified address. + * @param _addr The address to query the the balance of. + * @return An uint256 representing the amount owned by the passed address. + */ + function balanceOf(address _addr) public view returns (uint256) { + return balances[_addr]; + } + + // ERC20 FUNCTIONALITY + + /** + * @dev Transfer tokens from one address to another + * @param _from address The address which you want to send tokens from + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + */ + function transferFrom( + address _from, + address _to, + uint256 _value + ) + public + whenNotPaused + returns (bool) + { + require(_to != address(0), "cannot transfer to address zero"); + require(!frozen[_to] && !frozen[_from] && !frozen[msg.sender], "address frozen"); + require(_value <= balances[_from], "insufficient funds"); + require(_value <= allowed[_from][msg.sender], "insufficient allowance"); + + balances[_from] = balances[_from].sub(_value); + balances[_to] = balances[_to].add(_value); + allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); + emit Transfer(_from, _to, _value); + return true; + } + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. + * Beware that changing an allowance with this method brings the risk that someone may use both the old + * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this + * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * @param _spender The address which will spend the funds. + * @param _value The amount of tokens to be spent. + */ + function approve(address _spender, uint256 _value) public whenNotPaused returns (bool) { + require(!frozen[_spender] && !frozen[msg.sender], "address frozen"); + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + + /** + * @dev Increase the amount of tokens that an owner allowed to a spender. + * + * To increment allowed value is better to use this function to avoid 2 calls (and wait until the first transaction + * is mined) instead of approve. + * @param _spender The address which will spend the funds. + * @param _addedValue The amount of tokens to increase the allowance by. + */ + function increaseApproval(address _spender, uint _addedValue) public whenNotPaused returns (bool) { + require(!frozen[_spender] && !frozen[msg.sender], "address frozen"); + allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue); + emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); + return true; + } + + /** + * @dev Decrease the amount of tokens that an owner allowed to a spender. + * + * To decrement allowed value is better to use this function to avoid 2 calls (and wait until the first transaction + * is mined) instead of approve. + * @param _spender The address which will spend the funds. + * @param _subtractedValue The amount of tokens to decrease the allowance by. + */ + function decreaseApproval(address _spender, uint _subtractedValue) public whenNotPaused returns (bool) { + require(!frozen[_spender] && !frozen[msg.sender], "address frozen"); + uint oldValue = allowed[msg.sender][_spender]; + if (_subtractedValue > oldValue) { + allowed[msg.sender][_spender] = 0; + } else { + allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); + } + emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); + return true; + } + + /** + * @dev Function to check the amount of tokens that an owner allowed to a spender. + * @param _owner address The address which owns the funds. + * @param _spender address The address which will spend the funds. + * @return A uint256 specifying the amount of tokens still available for the spender. + */ + function allowance( + address _owner, + address _spender + ) + public + view + returns (uint256) + { + return allowed[_owner][_spender]; + } + + // OWNER FUNCTIONALITY + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(msg.sender == owner, "onlyOwner"); + _; + } + + /** + * @dev Allows the current owner to begin transferring control of the contract to a proposedOwner + * @param _proposedOwner The address to transfer ownership to. + */ + function proposeOwner(address _proposedOwner) public onlyOwner { + require(_proposedOwner != address(0), "cannot transfer ownership to address zero"); + require(msg.sender != _proposedOwner, "caller already is owner"); + proposedOwner = _proposedOwner; + emit OwnershipTransferProposed(owner, proposedOwner); + } + /** + * @dev Allows the current owner or proposed owner to cancel transferring control of the contract to a proposedOwner + */ + function disregardProposeOwner() public { + require(msg.sender == proposedOwner || msg.sender == owner, "only proposedOwner or owner"); + require(proposedOwner != address(0), "can only disregard a proposed owner that was previously set"); + address _oldProposedOwner = proposedOwner; + proposedOwner = address(0); + emit OwnershipTransferDisregarded(_oldProposedOwner); + } + /** + * @dev Allows the proposed owner to complete transferring control of the contract to the proposedOwner. + */ + function claimOwnership() public { + require(msg.sender == proposedOwner, "onlyProposedOwner"); + address _oldOwner = owner; + owner = proposedOwner; + proposedOwner = address(0); + emit OwnershipTransferred(_oldOwner, owner); + } + + /** + * @dev Reclaim all XYZ at the contract address. + * This sends the XYZ tokens that this contract add holding to the owner. + * Note: this is not affected by freeze constraints. + */ + function reclaimXYZ() external onlyOwner { + uint256 _balance = balances[this]; + balances[this] = 0; + balances[owner] = balances[owner].add(_balance); + emit Transfer(this, owner, _balance); + } + + // PAUSABILITY FUNCTIONALITY + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + */ + modifier whenNotPaused() { + require(!paused, "whenNotPaused"); + _; + } + + /** + * @dev called by the owner to pause, triggers stopped state + */ + function pause() public onlyOwner { + require(!paused, "already paused"); + paused = true; + emit Pause(); + } + + /** + * @dev called by the owner to unpause, returns to normal state + */ + function unpause() public onlyOwner { + require(paused, "already unpaused"); + paused = false; + emit Unpause(); + } + + // ASSET PROTECTION FUNCTIONALITY + + /** + * @dev Sets a new asset protection role address. + * @param _newAssetProtectionRole The new address allowed to freeze/unfreeze addresses and seize their tokens. + */ + function setAssetProtectionRole(address _newAssetProtectionRole) public { + require(msg.sender == assetProtectionRole || msg.sender == owner, "only assetProtectionRole or Owner"); + require(assetProtectionRole != _newAssetProtectionRole, "new address is same as a current one"); + emit AssetProtectionRoleSet(assetProtectionRole, _newAssetProtectionRole); + assetProtectionRole = _newAssetProtectionRole; + } + + modifier onlyAssetProtectionRole() { + require(msg.sender == assetProtectionRole, "onlyAssetProtectionRole"); + _; + } + + /** + * @dev Freezes an address balance from being transferred. + * @param _addr The new address to freeze. + */ + function freeze(address _addr) public onlyAssetProtectionRole { + require(!frozen[_addr], "address already frozen"); + frozen[_addr] = true; + emit AddressFrozen(_addr); + } + + /** + * @dev Unfreezes an address balance allowing transfer. + * @param _addr The new address to unfreeze. + */ + function unfreeze(address _addr) public onlyAssetProtectionRole { + require(frozen[_addr], "address already unfrozen"); + frozen[_addr] = false; + emit AddressUnfrozen(_addr); + } + + /** + * @dev Wipes the balance of a frozen address, and burns the tokens. + * @param _addr The new frozen address to wipe. + */ + function wipeFrozenAddress(address _addr) public onlyAssetProtectionRole { + require(frozen[_addr], "address is not frozen"); + uint256 _balance = balances[_addr]; + balances[_addr] = 0; + totalSupply_ = totalSupply_.sub(_balance); + emit FrozenAddressWiped(_addr); + emit SupplyDecreased(_addr, _balance); + emit Transfer(_addr, address(0), _balance); + } + + /** + * @dev Gets whether the address is currently frozen. + * @param _addr The address to check if frozen. + * @return A bool representing whether the given address is frozen. + */ + function isFrozen(address _addr) public view returns (bool) { + return frozen[_addr]; + } + + // SUPPLY CONTROL FUNCTIONALITY + + /** + * @dev Sets a new supply controller address. + * @param _newSupplyController The address allowed to burn/mint tokens to control supply. + */ + function setSupplyController(address _newSupplyController) public { + require(msg.sender == supplyController || msg.sender == owner, "only SupplyController or Owner"); + require(_newSupplyController != address(0), "cannot set supply controller to address zero"); + require(supplyController != _newSupplyController, "new address is same as a current one"); + emit SupplyControllerSet(supplyController, _newSupplyController); + supplyController = _newSupplyController; + } + + modifier onlySupplyController() { + require(msg.sender == supplyController, "onlySupplyController"); + _; + } + + /** + * @dev Increases the total supply by minting the specified number of tokens to the supply controller account. + * @param _value The number of tokens to add. + * @return A boolean that indicates if the operation was successful. + */ + function increaseSupply(uint256 _value) public onlySupplyController returns (bool success) { + totalSupply_ = totalSupply_.add(_value); + balances[supplyController] = balances[supplyController].add(_value); + emit SupplyIncreased(supplyController, _value); + emit Transfer(address(0), supplyController, _value); + return true; + } + + /** + * @dev Decreases the total supply by burning the specified number of tokens from the supply controller account. + * @param _value The number of tokens to remove. + * @return A boolean that indicates if the operation was successful. + */ + function decreaseSupply(uint256 _value) public onlySupplyController returns (bool success) { + require(_value <= balances[supplyController], "not enough supply"); + balances[supplyController] = balances[supplyController].sub(_value); + totalSupply_ = totalSupply_.sub(_value); + emit SupplyDecreased(supplyController, _value); + emit Transfer(supplyController, address(0), _value); + return true; + } + + // DELEGATED TRANSFER FUNCTIONALITY + + /** + * @dev returns the next seq for a target address. + * The transactor must submit nextSeqOf(transactor) in the next transaction for it to be valid. + * Note: that the seq context is specific to this smart contract. + * @param target The target address. + * @return the seq. + */ + // + function nextSeqOf(address target) public view returns (uint256) { + return nextSeqs[target]; + } + + /** + * @dev Performs a transfer on behalf of the from address, identified by its signature on the delegatedTransfer msg. + * Splits a signature byte array into r,s,v for convenience. + * @param sig the signature of the delgatedTransfer msg. + * @param to The address to transfer to. + * @param value The amount to be transferred. + * @param fee an optional ERC20 fee paid to the executor of betaDelegatedTransfer by the from address. + * @param seq a sequencing number included by the from address specific to this contract to protect from replays. + * @param deadline a block number after which the pre-signed transaction has expired. + * @return A boolean that indicates if the operation was successful. + */ + function betaDelegatedTransfer( + bytes sig, address to, uint256 value, uint256 fee, uint256 seq, uint256 deadline + ) public returns (bool) { + require(sig.length == 65, "signature should have length 65"); + bytes32 r; + bytes32 s; + uint8 v; + assembly { + r := mload(add(sig, 32)) + s := mload(add(sig, 64)) + v := byte(0, mload(add(sig, 96))) + } + _betaDelegatedTransfer(r, s, v, to, value, fee, seq, deadline); + return true; + } + + /** + * @dev Performs a transfer on behalf of the from address, identified by its signature on the betaDelegatedTransfer msg. + * Note: both the delegate and transactor sign in the fees. The transactor, however, + * has no control over the gas price, and therefore no control over the transaction time. + * Beta prefix chosen to avoid a name clash with an emerging standard in ERC865 or elsewhere. + * Internal to the contract - see betaDelegatedTransfer and betaDelegatedTransferBatch. + * @param r the r signature of the delgatedTransfer msg. + * @param s the s signature of the delgatedTransfer msg. + * @param v the v signature of the delgatedTransfer msg. + * @param to The address to transfer to. + * @param value The amount to be transferred. + * @param fee an optional ERC20 fee paid to the delegate of betaDelegatedTransfer by the from address. + * @param seq a sequencing number included by the from address specific to this contract to protect from replays. + * @param deadline a block number after which the pre-signed transaction has expired. + * @return A boolean that indicates if the operation was successful. + */ + function _betaDelegatedTransfer( + bytes32 r, bytes32 s, uint8 v, address to, uint256 value, uint256 fee, uint256 seq, uint256 deadline + ) internal whenNotPaused returns (bool) { + require(betaDelegateWhitelist[msg.sender], "Beta feature only accepts whitelisted delegates"); + require(value > 0 || fee > 0, "cannot transfer zero tokens with zero fee"); + require(block.number <= deadline, "transaction expired"); + // prevent sig malleability from ecrecover() + require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "signature incorrect"); + require(v == 27 || v == 28, "signature incorrect"); + + // EIP712 scheme: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md + bytes32 delegatedTransferHash = keccak256(abi.encodePacked(// solium-disable-line + EIP712_DELEGATED_TRANSFER_SCHEMA_HASH, bytes32(to), value, fee, seq, deadline + )); + bytes32 hash = keccak256(abi.encodePacked(EIP191_HEADER, EIP712_DOMAIN_HASH, delegatedTransferHash)); + address _from = ecrecover(hash, v, r, s); + + require(_from != address(0), "error determining from address from signature"); + require(to != address(0), "canno use address zero"); + require(!frozen[to] && !frozen[_from] && !frozen[msg.sender], "address frozen"); + require(value.add(fee) <= balances[_from], "insufficient fund"); + require(nextSeqs[_from] == seq, "incorrect seq"); + + nextSeqs[_from] = nextSeqs[_from].add(1); + balances[_from] = balances[_from].sub(value.add(fee)); + if (fee != 0) { + balances[msg.sender] = balances[msg.sender].add(fee); + emit Transfer(_from, msg.sender, fee); + } + balances[to] = balances[to].add(value); + emit Transfer(_from, to, value); + + emit BetaDelegatedTransfer(_from, to, value, seq, fee); + return true; + } + + /** + * @dev Performs an atomic batch of transfers on behalf of the from addresses, identified by their signatures. + * Lack of nested array support in arguments requires all arguments to be passed as equal size arrays where + * delegated transfer number i is the combination of all arguments at index i + * @param r the r signatures of the delgatedTransfer msg. + * @param s the s signatures of the delgatedTransfer msg. + * @param v the v signatures of the delgatedTransfer msg. + * @param to The addresses to transfer to. + * @param value The amounts to be transferred. + * @param fee optional ERC20 fees paid to the delegate of betaDelegatedTransfer by the from address. + * @param seq sequencing numbers included by the from address specific to this contract to protect from replays. + * @param deadline block numbers after which the pre-signed transactions have expired. + * @return A boolean that indicates if the operation was successful. + */ + function betaDelegatedTransferBatch( + bytes32[] r, bytes32[] s, uint8[] v, address[] to, uint256[] value, uint256[] fee, uint256[] seq, uint256[] deadline + ) public returns (bool) { + require(r.length == s.length && r.length == v.length && r.length == to.length && r.length == value.length, "length mismatch"); + require(r.length == fee.length && r.length == seq.length && r.length == deadline.length, "length mismatch"); + + for (uint i = 0; i < r.length; i++) { + _betaDelegatedTransfer(r[i], s[i], v[i], to[i], value[i], fee[i], seq[i], deadline[i]); + } + return true; + } + + /** + * @dev Gets whether the address is currently whitelisted for betaDelegateTransfer. + * @param _addr The address to check if whitelisted. + * @return A bool representing whether the given address is whitelisted. + */ + function isWhitelistedBetaDelegate(address _addr) public view returns (bool) { + return betaDelegateWhitelist[_addr]; + } + + /** + * @dev Sets a new betaDelegate whitelister. + * @param _newWhitelister The address allowed to whitelist betaDelegates. + */ + function setBetaDelegateWhitelister(address _newWhitelister) public { + require(msg.sender == betaDelegateWhitelister || msg.sender == owner, "only Whitelister or Owner"); + require(betaDelegateWhitelister != _newWhitelister, "new address is same as a current one"); + betaDelegateWhitelister = _newWhitelister; + emit BetaDelegateWhitelisterSet(betaDelegateWhitelister, _newWhitelister); + } + + modifier onlyBetaDelegateWhitelister() { + require(msg.sender == betaDelegateWhitelister, "onlyBetaDelegateWhitelister"); + _; + } + + /** + * @dev Whitelists an address to allow calling BetaDelegatedTransfer. + * @param _addr The new address to whitelist. + */ + function whitelistBetaDelegate(address _addr) public onlyBetaDelegateWhitelister { + require(!betaDelegateWhitelist[_addr], "delegate already whitelisted"); + betaDelegateWhitelist[_addr] = true; + emit BetaDelegateWhitelisted(_addr); + } + + /** + * @dev Unwhitelists an address to disallow calling BetaDelegatedTransfer. + * @param _addr The new address to whitelist. + */ + function unwhitelistBetaDelegate(address _addr) public onlyBetaDelegateWhitelister { + require(betaDelegateWhitelist[_addr], "delegate not whitelisted"); + betaDelegateWhitelist[_addr] = false; + emit BetaDelegateUnwhitelisted(_addr); + } +} diff --git a/contracts/archive/XYZImplementationV1.sol b/contracts/archive/XYZImplementationV1.sol new file mode 100644 index 0000000..80b09da --- /dev/null +++ b/contracts/archive/XYZImplementationV1.sol @@ -0,0 +1,1080 @@ +// File: contracts/lib/PaxosBaseAbstract.sol + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +/** + * @dev PaxosBaseAbstract + * An abstract contract for Paxos tokens with additional internal functions. + */ +abstract contract PaxosBaseAbstract { + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual; + + function _transfer( + address _from, + address _to, + uint256 _value + ) internal virtual; + + function isPaused() internal view virtual returns (bool); + + function isAddrFrozen(address _addr) internal view virtual returns (bool); + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + */ + modifier whenNotPaused() { + require(!isPaused(), "whenNotPaused"); + _; + } +} + +// File: contracts/lib/EIP712Domain.sol + +pragma solidity ^0.8.17; + +/** + * @dev An Abstract contract to store the domain separator for EIP712 signature. + * This contract is inherited by EIP3009 and EIP2612. + */ +abstract contract EIP712Domain { + /** + * @dev EIP712 Domain Separator + */ + bytes32 public DOMAIN_SEPARATOR; // solhint-disable-line var-name-mixedcase +} + +// File: contracts/lib/ECRecover.sol + +pragma solidity ^0.8.17; + +/** + * @title A library that provides a safe ECDSA recovery function + */ +library ECRecover { + /** + * @dev Recover signer's address from a signed message. + * Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.0/contracts/utils/cryptography/ECDSA.sol + * Modifications: Accept v, r, and s as separate arguments + * @param digest Keccak-256 hash digest of the signed message + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + * @return Signer address + */ + function recover( + bytes32 digest, + uint8 v, + bytes32 r, + bytes32 s + ) internal pure returns (address) { + // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature + // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines + // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most + // signatures from current libraries generate a unique signature with an s-value in the lower half order. + // + // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value + // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or + // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept + // these malleable signatures as well. + if ( + uint256(s) > + 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 + ) { + revert("ECRecover: invalid signature 's' value"); + } + + if (v != 27 && v != 28) { + revert("ECRecover: invalid signature 'v' value"); + } + + // If the signature is valid (and not malleable), return the signer address + address signer = ecrecover(digest, v, r, s); + require(signer != address(0), "ECRecover: invalid signature"); + + return signer; + } +} + +// File: contracts/lib/EIP712.sol + +pragma solidity ^0.8.17; + + +/** + * @title EIP712 + * @notice A library that provides EIP712 helper functions + */ +library EIP712 { + // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") + bytes32 public constant EIP712_DOMAIN_TYPEHASH = + 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; + + /** + * @notice Make EIP712 domain separator + * @param name Contract name + * @param version Contract version + * @return Domain separator + */ + function makeDomainSeparator( + string memory name, + string memory version + ) internal view returns (bytes32) { + uint256 chainId; + // solhint-disable-next-line no-inline-assembly + assembly { + chainId := chainid() + } + + return + keccak256( + abi.encode( + EIP712_DOMAIN_TYPEHASH, + keccak256(bytes(name)), + keccak256(bytes(version)), + bytes32(chainId), + address(this) + ) + ); + } + + /** + * @notice Recover signer's address from a EIP712 signature + * @param domainSeparator Domain separator + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + * @param typeHashAndData Type hash concatenated with data + * @return Signer's address + */ + function recover( + bytes32 domainSeparator, + uint8 v, + bytes32 r, + bytes32 s, + bytes memory typeHashAndData + ) internal pure returns (address) { + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + domainSeparator, + keccak256(typeHashAndData) + ) + ); + return ECRecover.recover(digest, v, r, s); + } +} + +// File: contracts/lib/EIP2612.sol + +pragma solidity ^0.8.17; + + + + +abstract contract EIP2612 is PaxosBaseAbstract, EIP712Domain { + // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") + bytes32 public constant PERMIT_TYPEHASH = + 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + + mapping(address => uint256) internal _nonces; + // Storage gap: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps + uint256[10] __gap_EIP2612; + + /** + * @notice Nonces for permit + * @param owner Token owner's address + * @return Next nonce + */ + function nonces(address owner) external view returns (uint256) { + return _nonces[owner]; + } + + /** + * @notice update allowance with a signed permit + * @param owner Token owner's address (Authorizer) + * @param spender Spender's address + * @param value Amount of allowance + * @param deadline The time at which this expires (unix time) + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external whenNotPaused { + require(deadline > block.timestamp, "EIP2612: permit is expired"); + require(!isAddrFrozen(msg.sender), "EIP2612: address frozen"); + + bytes memory data = abi.encode( + PERMIT_TYPEHASH, + owner, + spender, + value, + _nonces[owner]++, + deadline + ); + require( + EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == owner, + "EIP2612: invalid signature" + ); + + _approve(owner, spender, value); + } +} + +// File: contracts/lib/EIP3009.sol + +pragma solidity ^0.8.17; + + + + +abstract contract EIP3009 is PaxosBaseAbstract, EIP712Domain { + // keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") + bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = + 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267; + + // keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") + bytes32 public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = + 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8; + + // keccak256("CancelAuthorization(address authorizer,bytes32 nonce)") + bytes32 public constant CANCEL_AUTHORIZATION_TYPEHASH = + 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429; + + /** + * @dev authorizer address => nonce => state (true = used / false = unused) + */ + mapping(address => mapping(bytes32 => bool)) internal _authorizationStates; + // Storage gap: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps + uint256[10] __gap_EIP3009; + + event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce); + event AuthorizationCanceled( + address indexed authorizer, + bytes32 indexed nonce + ); + + string internal constant _INVALID_SIGNATURE_ERROR = + "EIP3009: invalid signature"; + string internal constant _AUTHORIZATION_USED_ERROR = + "EIP3009: authorization is used"; + + /** + * @notice Returns the state of an authorization + * @dev Nonces are randomly generated 32-byte data unique to the authorizer's + * address + * @param authorizer Authorizer's address + * @param nonce Nonce of the authorization + * @return True if the nonce is used + */ + function authorizationState( + address authorizer, + bytes32 nonce + ) external view returns (bool) { + return _authorizationStates[authorizer][nonce]; + } + + /** + * @notice Execute a transfer with a signed authorization + * @param from Payer's address (Authorizer) + * @param to Payee's address + * @param value Amount to be transferred + * @param validAfter The time after which this is valid (unix time) + * @param validBefore The time before which this is valid (unix time) + * @param nonce Unique nonce + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ + function transferWithAuthorization( + address from, + address to, + uint256 value, + uint256 validAfter, + uint256 validBefore, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s + ) external whenNotPaused { + _transferWithAuthorization( + TRANSFER_WITH_AUTHORIZATION_TYPEHASH, + from, + to, + value, + validAfter, + validBefore, + nonce, + v, + r, + s + ); + } + + function transferWithAuthorizationBatch( + address[] memory from, + address[] memory to, + uint256[] memory value, + uint256[] memory validAfter, + uint256[] memory validBefore, + bytes32[] memory nonce, + uint8[] memory v, + bytes32[] memory r, + bytes32[] memory s + ) external whenNotPaused { + // Validate length of each parameter with "from" argument to make sure lengths of all input arguments are the same. + require( + to.length == from.length && + value.length == from.length && + validAfter.length == from.length && + validBefore.length == from.length && + nonce.length == from.length && + v.length == from.length && + r.length == from.length && + s.length == from.length, + "argument's length mismatch" + ); + + for (uint16 i = 0; i < from.length; i++) { + _transferWithAuthorization( + TRANSFER_WITH_AUTHORIZATION_TYPEHASH, + from[i], + to[i], + value[i], + validAfter[i], + validBefore[i], + nonce[i], + v[i], + r[i], + s[i] + ); + } + } + + /** + * @notice Receive a transfer with a signed authorization from the payer + * @dev This has an additional check to ensure that the payee's address matches + * the caller of this function to prevent front-running attacks. (See security + * considerations) + * @param from Payer's address (Authorizer) + * @param to Payee's address + * @param value Amount to be transferred + * @param validAfter The time after which this is valid (unix time) + * @param validBefore The time before which this is valid (unix time) + * @param nonce Unique nonce + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ + function receiveWithAuthorization( + address from, + address to, + uint256 value, + uint256 validAfter, + uint256 validBefore, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s + ) external whenNotPaused { + require(to == msg.sender, "EIP3009: caller must be the payee"); + + _transferWithAuthorization( + RECEIVE_WITH_AUTHORIZATION_TYPEHASH, + from, + to, + value, + validAfter, + validBefore, + nonce, + v, + r, + s + ); + } + + /** + * @notice Attempt to cancel an authorization + * @param authorizer Authorizer's address + * @param nonce Nonce of the authorization + * @param v v of the signature + * @param r r of the signature + * @param s s of the signature + */ + function cancelAuthorization( + address authorizer, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s + ) external whenNotPaused { + require( + !isAddrFrozen(msg.sender) && !isAddrFrozen(authorizer), + "EIP3009: address frozen" + ); + require( + !_authorizationStates[authorizer][nonce], + _AUTHORIZATION_USED_ERROR + ); + + bytes memory data = abi.encode( + CANCEL_AUTHORIZATION_TYPEHASH, + authorizer, + nonce + ); + require( + EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == authorizer, + _INVALID_SIGNATURE_ERROR + ); + + _authorizationStates[authorizer][nonce] = true; + emit AuthorizationCanceled(authorizer, nonce); + } + + function _transferWithAuthorization( + bytes32 typeHash, + address from, + address to, + uint256 value, + uint256 validAfter, + uint256 validBefore, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s + ) internal { + require( + block.timestamp > validAfter, + "EIP3009: authorization is not yet valid" + ); + require( + block.timestamp < validBefore, + "EIP3009: authorization is expired" + ); + require(!isAddrFrozen(msg.sender), "EIP3009: address frozen"); + require(!_authorizationStates[from][nonce], _AUTHORIZATION_USED_ERROR); + + bytes memory data = abi.encode( + typeHash, + from, + to, + value, + validAfter, + validBefore, + nonce + ); + require( + EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from, + _INVALID_SIGNATURE_ERROR + ); + + _authorizationStates[from][nonce] = true; + emit AuthorizationUsed(from, nonce); + + _transfer(from, to, value); + } +} + +// File: contracts/XYZImplementation.sol + +pragma solidity ^0.8.17; + + +/** + * @title XYZImplementation + * @dev this contract is a Pausable ERC20 token with Burn and Mint + * controlled by a central SupplyController. By implementing XYZImplementation + * this contract also includes external methods for setting + * a new implementation contract for the Proxy. + * NOTE: The storage defined here will actually be held in the Proxy + * contract and all calls to this contract should be made through + * the proxy, including admin actions done as owner or supplyController. + * Any call to transfer against this contract should fail + * with insufficient funds since no tokens will be issued there. + */ +contract XYZImplementation is PaxosBaseAbstract{ + + /** + * DATA + */ + + // INITIALIZATION DATA + bool private initialized; + + // ERC20 BASIC DATA + mapping(address => uint256) internal balances; + uint256 internal totalSupply_; + string public constant name = "XYZ USD"; // solhint-disable-line const-name-snakecase + string public constant symbol = "XYZ"; // solhint-disable-line const-name-snakecase + uint8 public constant decimals = 6; // solhint-disable-line const-name-snakecase + + // ERC20 DATA + mapping(address => mapping(address => uint256)) internal allowed; + + // OWNER DATA PART 1 + address public owner; + + // PAUSABILITY DATA + bool public paused; + + // ASSET PROTECTION DATA + address public assetProtectionRole; + mapping(address => bool) internal frozen; + + // SUPPLY CONTROL DATA + address public supplyController; + + // OWNER DATA PART 2 + address public proposedOwner; + + // DELEGATED TRANSFER DATA - DEPRECATED + address public betaDelegateWhitelisterDeprecated; + mapping(address => bool) internal betaDelegateWhitelistDeprecated; + mapping(address => uint256) internal nextSeqsDeprecated; + // EIP191 header for EIP712 prefix + string constant internal EIP191_HEADER_DEPRECATED = "\x19\x01"; + // Hash of the EIP712 Domain Separator Schema + bytes32 constant internal EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH_DEPRECATED = keccak256( + "EIP712Domain(string name,address verifyingContract)" + ); + bytes32 constant internal EIP712_DELEGATED_TRANSFER_SCHEMA_HASH_DEPRECATED = keccak256( + "BetaDelegatedTransfer(address to,uint256 value,uint256 fee,uint256 seq,uint256 deadline)" + ); + // Hash of the EIP712 Domain Separator data + // solhint-disable-next-line var-name-mixedcase + bytes32 public EIP712_DOMAIN_HASH_DEPRECATED; + // Storage gap: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps + uint256[25] __gap_XYZImplementation; + + /** + * EVENTS + */ + + // ERC20 BASIC EVENTS + event Transfer(address indexed from, address indexed to, uint256 value); + + // ERC20 EVENTS + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); + + // OWNABLE EVENTS + event OwnershipTransferProposed( + address indexed currentOwner, + address indexed proposedOwner + ); + event OwnershipTransferDisregarded( + address indexed oldProposedOwner + ); + event OwnershipTransferred( + address indexed oldOwner, + address indexed newOwner + ); + + // PAUSABLE EVENTS + event Pause(); + event Unpause(); + + // ASSET PROTECTION EVENTS + event AddressFrozen(address indexed addr); + event AddressUnfrozen(address indexed addr); + event FrozenAddressWiped(address indexed addr); + event AssetProtectionRoleSet ( + address indexed oldAssetProtectionRole, + address indexed newAssetProtectionRole + ); + + // SUPPLY CONTROL EVENTS + event SupplyIncreased(address indexed to, uint256 value); + event SupplyDecreased(address indexed from, uint256 value); + event SupplyControllerSet( + address indexed oldSupplyController, + address indexed newSupplyController + ); + + /** + * FUNCTIONALITY + */ + + // INITIALIZATION FUNCTIONALITY + + /** + * @dev sets 0 initials tokens, the owner, and the supplyController. + * this serves as the constructor for the proxy but compiles to the + * memory model of the Implementation contract. + */ + function initialize() public { + require(!initialized, "MANDATORY VERIFICATION REQUIRED: The proxy has already been initialized, verify the owner and supply controller addresses."); + owner = msg.sender; + assetProtectionRole = address(0); + totalSupply_ = 0; + supplyController = msg.sender; + initialized = true; + } + + /** + * The constructor is used here to ensure that the implementation + * contract is initialized. An uncontrolled implementation + * contract might lead to misleading state + * for users who accidentally interact with it. + */ + constructor() { + initialize(); + pause(); + } + + // ERC20 BASIC FUNCTIONALITY + + /** + * @dev Total number of tokens in existence + */ + function totalSupply() public view returns (uint256) { + return totalSupply_; + } + + /** + * @dev Transfer token to a specified address from msg.sender + * @param _to The address to transfer to. + * @param _value The amount to be transferred. + * @return True if successful + */ + function transfer(address _to, uint256 _value) public whenNotPaused returns (bool) { + _transfer(msg.sender, _to, _value); + return true; + } + + /** + * @dev Gets the balance of the specified address. + * @param _addr The address to query the the balance of. + * @return An uint256 representing the amount owned by the passed address. + */ + function balanceOf(address _addr) public view returns (uint256) { + return balances[_addr]; + } + + // ERC20 FUNCTIONALITY + + /** + * @dev Transfer tokens from one address to another + * @param _from address The address which you want to send tokens from + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + * @return True if successful + */ + function transferFrom( + address _from, + address _to, + uint256 _value + ) + public + whenNotPaused + returns (bool) + { + require(!frozen[msg.sender], "sender address frozen"); + _transferFromAllowance(_from, _to, _value); + return true; + } + + /** + * @dev Transfer tokens from one set of address to another in a single transaction. + * @param _from addres[] The addresses which you want to send tokens from + * @param _to address[] The addresses which you want to transfer to + * @param _value uint256[] The amounts of tokens to be transferred + * @return True if successful + */ + function transferFromBatch( + address[] calldata _from, + address[] calldata _to, + uint256[] calldata _value + ) + public + whenNotPaused + returns (bool) + { + // Validate length of each parameter with "_from" argument to make sure lengths of all input arguments are the same. + require(_to.length == _from.length && _value.length == _from.length , "argument's length mismatch"); + require(!frozen[msg.sender], "sender address frozen"); + for (uint16 i = 0; i < _from.length; i++) { + _transferFromAllowance(_from[i], _to[i], _value[i]); + } + return true; + } + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. + * Beware that changing an allowance with this method brings the risk that someone may use both the old + * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this + * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * @param _spender The address which will spend the funds. + * @param _value The amount of tokens to be spent. + * @return True if successful + */ + function approve(address _spender, uint256 _value) public whenNotPaused returns (bool) { + require(!frozen[_spender] && !frozen[msg.sender], "address frozen"); + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + + /** + * @dev Increase the amount of tokens that an owner allowed to a spender. + * + * To increment allowed value is better to use this function to avoid 2 calls (and wait until the first transaction + * is mined) instead of approve. + * @param _spender The address which will spend the funds. + * @param _addedValue The amount of tokens to increase the allowance by. + * @return True if successful + */ + function increaseApproval(address _spender, uint256 _addedValue) public whenNotPaused returns (bool) { + require(!frozen[_spender] && !frozen[msg.sender], "address frozen"); + require(_addedValue != 0, "value cannot be zero"); + allowed[msg.sender][_spender] += _addedValue; + emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); + return true; + } + + /** + * @dev Decrease the amount of tokens that an owner allowed to a spender. + * + * To decrement allowed value is better to use this function to avoid 2 calls (and wait until the first transaction + * is mined) instead of approve. + * @param _spender The address which will spend the funds. + * @param _subtractedValue The amount of tokens to decrease the allowance by. + * @return True if successful + */ + function decreaseApproval(address _spender, uint256 _subtractedValue) public whenNotPaused returns (bool) { + require(!frozen[_spender] && !frozen[msg.sender], "address frozen"); + require(_subtractedValue != 0, "value cannot be zero"); + if (_subtractedValue > allowed[msg.sender][_spender]) { + allowed[msg.sender][_spender] = 0; + } else { + allowed[msg.sender][_spender] -= _subtractedValue; + } + emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); + return true; + } + + /** + * @dev Function to check the amount of tokens that an owner allowed to a spender. + * @param _owner address The address which owns the funds. + * @param _spender address The address which will spend the funds. + * @return A uint256 specifying the amount of tokens still available for the spender. + */ + function allowance( + address _owner, + address _spender + ) + public + view + returns (uint256) + { + return allowed[_owner][_spender]; + } + + // OWNER FUNCTIONALITY + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(msg.sender == owner, "onlyOwner"); + _; + } + + /** + * @dev Allows the current owner to begin transferring control of the contract to a proposedOwner + * @param _proposedOwner The address to transfer ownership to. + */ + function proposeOwner(address _proposedOwner) public onlyOwner { + require(_proposedOwner != address(0), "cannot transfer ownership to zero address"); + require(msg.sender != _proposedOwner, "caller already is owner"); + proposedOwner = _proposedOwner; + emit OwnershipTransferProposed(owner, proposedOwner); + } + /** + * @dev Allows the current owner or proposed owner to cancel transferring control of the contract to a proposedOwner + */ + function disregardProposeOwner() public { + require(msg.sender == proposedOwner || msg.sender == owner, "only proposedOwner or owner"); + require(proposedOwner != address(0), "can only disregard a proposed owner that was previously set"); + address _oldProposedOwner = proposedOwner; + proposedOwner = address(0); + emit OwnershipTransferDisregarded(_oldProposedOwner); + } + /** + * @dev Allows the proposed owner to complete transferring control of the contract to the proposedOwner. + */ + function claimOwnership() public { + require(msg.sender == proposedOwner, "onlyProposedOwner"); + address _oldOwner = owner; + owner = proposedOwner; + proposedOwner = address(0); + emit OwnershipTransferred(_oldOwner, owner); + } + + /** + * @dev Reclaim all XYZ at the contract address. + * This sends the XYZ tokens that this contract add holding to the owner. + * Note: this is not affected by freeze constraints. + */ + function reclaimXYZ() external onlyOwner { + uint256 _balance = balances[address(this)]; + balances[address(this)] = 0; + balances[owner] += _balance; + emit Transfer(address(this), owner, _balance); + } + + // PAUSABILITY FUNCTIONALITY + + /** + * @dev Check if contract is paused. + */ + function isPaused() internal view override returns (bool) { + return paused; + } + + /** + * @dev called by the owner to pause, triggers stopped state + */ + function pause() public onlyOwner { + require(!paused, "already paused"); + paused = true; + emit Pause(); + } + + /** + * @dev called by the owner to unpause, returns to normal state + */ + function unpause() public onlyOwner { + require(paused, "already unpaused"); + paused = false; + emit Unpause(); + } + + // ASSET PROTECTION FUNCTIONALITY + + /** + * @dev Sets a new asset protection role address. + * @param _newAssetProtectionRole The new address allowed to freeze/unfreeze addresses and seize their tokens. + */ + function setAssetProtectionRole(address _newAssetProtectionRole) public { + require(msg.sender == assetProtectionRole || msg.sender == owner, "only assetProtectionRole or Owner"); + require(_newAssetProtectionRole != address(0), "cannot use zero address"); + require(assetProtectionRole != _newAssetProtectionRole, "new address is same as a current one"); + emit AssetProtectionRoleSet(assetProtectionRole, _newAssetProtectionRole); + assetProtectionRole = _newAssetProtectionRole; + } + + modifier onlyAssetProtectionRole() { + require(msg.sender == assetProtectionRole, "onlyAssetProtectionRole"); + _; + } + + /** + * @dev Freezes an address balance from being transferred. + * @param _addr The new address to freeze. + */ + function freeze(address _addr) public onlyAssetProtectionRole { + require(!frozen[_addr], "address already frozen"); + frozen[_addr] = true; + emit AddressFrozen(_addr); + } + + /** + * @dev Unfreezes an address balance allowing transfer. + * @param _addr The new address to unfreeze. + */ + function unfreeze(address _addr) public onlyAssetProtectionRole { + require(frozen[_addr], "address already unfrozen"); + frozen[_addr] = false; + emit AddressUnfrozen(_addr); + } + + /** + * @dev Wipes the balance of a frozen address, and burns the tokens. + * @param _addr The new frozen address to wipe. + */ + function wipeFrozenAddress(address _addr) public onlyAssetProtectionRole { + require(frozen[_addr], "address is not frozen"); + uint256 _balance = balances[_addr]; + balances[_addr] = 0; + totalSupply_ -= _balance; + emit FrozenAddressWiped(_addr); + emit SupplyDecreased(_addr, _balance); + emit Transfer(_addr, address(0), _balance); + } + + /** + * @dev Internal function to check whether the address is currently frozen. + * @param _addr The address to check if frozen. + * @return A bool representing whether the given address is frozen. + */ + function isAddrFrozen(address _addr) internal view override returns (bool) { + return frozen[_addr]; + } + + /** + * @dev Gets whether the address is currently frozen. + * @param _addr The address to check if frozen. + * @return A bool representing whether the given address is frozen. + */ + function isFrozen(address _addr) public view returns (bool) { + return isAddrFrozen(_addr); + } + + // SUPPLY CONTROL FUNCTIONALITY + + /** + * @dev Sets a new supply controller address and transfer supplyController tokens to _newSupplyController. + * @param _newSupplyController The address allowed to burn/mint tokens to control supply. + */ + function setSupplyController(address _newSupplyController) public { + require(msg.sender == supplyController || msg.sender == owner, "only SupplyController or Owner"); + require(_newSupplyController != address(0), "cannot set supply controller to zero address"); + require(supplyController != _newSupplyController, "new address is same as a current one"); + emit Transfer(supplyController, _newSupplyController, balances[supplyController]); + balances[_newSupplyController] += balances[supplyController]; + balances[supplyController] = 0; + emit SupplyControllerSet(supplyController, _newSupplyController); + supplyController = _newSupplyController; + } + + modifier onlySupplyController() { + require(msg.sender == supplyController, "onlySupplyController"); + _; + } + + /** + * @dev Increases the total supply by minting the specified number of tokens to the supply controller account. + * @param _value The number of tokens to add. + * @return success A boolean that indicates if the operation was successful. + */ + function increaseSupply(uint256 _value) public onlySupplyController returns (bool success) { + totalSupply_ += _value; + balances[supplyController] += _value; + emit SupplyIncreased(supplyController, _value); + emit Transfer(address(0), supplyController, _value); + return true; + } + + /** + * @dev Decreases the total supply by burning the specified number of tokens from the supply controller account. + * @param _value The number of tokens to remove. + * @return success A boolean that indicates if the operation was successful. + */ + function decreaseSupply(uint256 _value) public onlySupplyController returns (bool success) { + require(_value <= balances[supplyController], "not enough supply"); + balances[supplyController] -= _value; + totalSupply_ -= _value; + emit SupplyDecreased(supplyController, _value); + emit Transfer(supplyController, address(0), _value); + return true; + } + + /** + * @dev Internal function to transfer balances _from => _to. + * Internal to the contract - see transferFrom and transferFromBatch. + * @param _from address The address which you want to send tokens from + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + */ + function _transferFromAllowance( + address _from, + address _to, + uint256 _value + ) + internal + { + require(_value <= allowed[_from][msg.sender], "insufficient allowance"); + _transfer(_from, _to, _value); + allowed[_from][msg.sender] -= _value; + } + + /** + * @dev Set allowance for a given spender, of a given owner. + * @param _owner address The address which owns the funds. + * @param _spender address The address which will spend the funds. + * @param _value uint256 The amount of tokens to increase the allowance by. + */ + function _approve( + address _owner, + address _spender, + uint256 _value + ) internal override { + require(_owner != address(0) && _spender != address(0), "ERC20: approve from is zero address"); + require(!frozen[_spender] && !frozen[_owner], "address frozen"); + + allowed[_owner][_spender] = _value; + emit Approval(_owner, _spender, _value); + } + + /** + * @dev Transfer `value` amount `_from` => `_to`. + * Internal to the contract - see transferFromAllowance and EIP3009.sol:_transferWithAuthorization. + * @param _from address The address which you want to send tokens from + * @param _to address The address which you want to send tokens to + * @param _value uint256 the amount of tokens to be transferred + */ + function _transfer( + address _from, + address _to, + uint256 _value + ) internal override { + require(_to != address(0) && _from != address(0), "cannot transfer to/from address zero"); + require(!frozen[_to] && !frozen[_from], "address frozen"); + require(_value <= balances[_from], "insufficient funds"); + + balances[_from] -= _value; + balances[_to] += _value; + emit Transfer(_from, _to, _value); + } + +} + +// File: contracts/XYZImplementationV1.sol + +pragma solidity ^0.8.17; + + + + + +/** + * @title XYZImplementationV1 + * @dev this contract is a Pausable ERC20 token with Burn and Mint + * controlled by a central SupplyController. By implementing XYZImplementationV1 + * this contract also includes external methods for setting + * a new implementation contract for the Proxy. + * NOTE: The storage defined here will actually be held in the Proxy + * contract and all calls to this contract should be made through + * the proxy, including admin actions done as owner or supplyController. + * Any call to transfer against this contract should fail + * with insufficient funds since no tokens will be issued there. + */ +contract XYZImplementationV1 is XYZImplementation, EIP2612, EIP3009 { + constructor() { + initializeEIP712DomainSeparator(); + } + + /** + * @dev To be called when upgrading the contract using upgradeAndCall and during initialization of contract. + */ + function initializeEIP712DomainSeparator() public { + DOMAIN_SEPARATOR = EIP712.makeDomainSeparator("XYZ", "1"); + } +} diff --git a/contracts/stablecoins/USDX.sol b/contracts/stablecoins/USDX.sol new file mode 100644 index 0000000..76c6f72 --- /dev/null +++ b/contracts/stablecoins/USDX.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { PaxosTokenV2 } from "./../PaxosTokenV2.sol"; + +/** + * @title USDX Smart contract + * @dev This contract is a {PaxosTokenV2-PaxosTokenV2} ERC20 token. + * @custom:security-contact smart-contract-security@paxos.com + */ +contract USDX is PaxosTokenV2, UUPSUpgradeable { + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return "USD Token"; + } + + /** + * @dev Returns the symbol of the token. + */ + function symbol() public view virtual override returns (string memory) { + return "USDX"; + } + + /** + * @dev Returns the decimal count of the token. + */ + function decimals() public view virtual override returns (uint8) { + return 6; + } + + /** + * @dev required by the OZ UUPS module to authorize an upgrade + * of the contract. Restricted to DEFAULT_ADMIN_ROLE. + */ + function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {} // solhint-disable-line no-empty-blocks +} \ No newline at end of file diff --git a/scripts/deploy.js b/scripts/deploy.js index 8c37626..f5f205b 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -1,5 +1,5 @@ const { ethers } = require("hardhat"); -const { PrintDeployerDetails, PrintContractDetails, ValidateEnvironmentVariables } = require('./utils'); +const { PrintDeployerDetails, PrintContractDetails } = require('./utils'); const { TOKEN_ADMIN_ADDRESS, TOKEN_OWNER_ADDRESS, PAUSER_ADDRESS, ASSET_PROTECTOR_ADDRESS, TOKEN_CONTRACT_NAME } = process.env; @@ -14,7 +14,6 @@ const initializerArgs = [ ] async function main() { - ValidateEnvironmentVariables([...initializerArgs, TOKEN_CONTRACT_NAME]) PrintDeployerDetails(); console.log("\nDeploying Implementation contract...") diff --git a/scripts/deployImplementation.js b/scripts/deployImplementation.js index 8d7182c..41e8058 100644 --- a/scripts/deployImplementation.js +++ b/scripts/deployImplementation.js @@ -1,10 +1,9 @@ const { ethers } = require("hardhat"); const { TOKEN_CONTRACT_NAME} = process.env; -const { PrintDeployerDetails, PrintContractDetails, ValidateEnvironmentVariables } = require('./utils'); +const { PrintDeployerDetails, PrintContractDetails } = require('./utils'); async function main() { - ValidateEnvironmentVariables([TOKEN_CONTRACT_NAME]) PrintDeployerDetails(); console.log("\nDeploying Implementation contract...") diff --git a/scripts/deploySupplyController.js b/scripts/deploySupplyController.js index 3620957..7f5bd1f 100644 --- a/scripts/deploySupplyController.js +++ b/scripts/deploySupplyController.js @@ -1,22 +1,23 @@ const { ethers, upgrades } = require("hardhat"); const { MaxUint256 } = require("hardhat").ethers; const { getImplementationAddress } = require('@openzeppelin/upgrades-core'); -const { PrintDeployerDetails, ValidateEnvironmentVariables } = require('./utils'); +const { PrintDeployerDetails, PrintContractDetails } = require('./utils'); const { SUPPLY_CONTROL_ADMIN_ADDRESS, SUPPLY_CONTROL_MANAGER_ADDRESS, TOKEN_PROXY_ADDRESS, - COLD_SUPPLY_CONTROLLER_ADDRESS, COLD_MINT_ADDRESS_LIST, WARM_SUPPLY_CONTROLLER_ADDRESS, - WARM_LIMIT_CAPACITY, WARM_REFILL_PER_SECOND, WARM_MINT_ADDRESS_LIST + COLD_SUPPLY_CONTROLLER_ADDRESS, COLD_MINT_BURN_ADDRESS_LIST, + WARM_SUPPLY_CONTROLLER_ADDRESS, WARM_LIMIT_AMOUNT_PER_TX, WARM_LIMIT_AMOUNT_PER_TIME_PERIOD, WARM_REFILL_PER_SECOND, WARM_MINT_BURN_ADDRESS_LIST } = process.env; -const COLD_LIMIT_CAPACITY = 0; +const COLD_LIMIT_AMOUNT_PER_TX = MaxUint256; +const COLD_LIMIT_AMOUNT_PER_TIME_PERIOD = MaxUint256; const COLD_REFILL_PER_SECOND = 0; //Special value to skip limit checking const SUPPLY_CONTROL_CONTRACT_NAME = "SupplyControl"; const scInitializationConfig = [ - [COLD_SUPPLY_CONTROLLER_ADDRESS, [COLD_LIMIT_CAPACITY, COLD_REFILL_PER_SECOND], COLD_MINT_ADDRESS_LIST.split(','), false], - [WARM_SUPPLY_CONTROLLER_ADDRESS, [WARM_LIMIT_CAPACITY, WARM_REFILL_PER_SECOND], WARM_MINT_ADDRESS_LIST.split(','), false] + [COLD_SUPPLY_CONTROLLER_ADDRESS, [COLD_LIMIT_AMOUNT_PER_TX, COLD_LIMIT_AMOUNT_PER_TIME_PERIOD, COLD_REFILL_PER_SECOND], COLD_MINT_BURN_ADDRESS_LIST.split(','), false], + [WARM_SUPPLY_CONTROLLER_ADDRESS, [WARM_LIMIT_AMOUNT_PER_TX, WARM_LIMIT_AMOUNT_PER_TIME_PERIOD, WARM_REFILL_PER_SECOND], WARM_MINT_BURN_ADDRESS_LIST.split(','), false] ] const initializerArgs = [ @@ -27,11 +28,6 @@ const initializerArgs = [ ] async function main() { - ValidateEnvironmentVariables([ - SUPPLY_CONTROL_ADMIN_ADDRESS, SUPPLY_CONTROL_MANAGER_ADDRESS, TOKEN_PROXY_ADDRESS, - COLD_SUPPLY_CONTROLLER_ADDRESS, COLD_MINT_ADDRESS_LIST, WARM_SUPPLY_CONTROLLER_ADDRESS, - WARM_LIMIT_CAPACITY, WARM_REFILL_PER_SECOND, WARM_MINT_ADDRESS_LIST] - ) PrintDeployerDetails(); const supplyControlFactory = await ethers.getContractFactory(SUPPLY_CONTROL_CONTRACT_NAME); @@ -50,8 +46,8 @@ async function main() { const coldScConfig = await supplyControl.getSupplyControllerConfig(COLD_SUPPLY_CONTROLLER_ADDRESS) const warmScConfig = await supplyControl.getSupplyControllerConfig(WARM_SUPPLY_CONTROLLER_ADDRESS) - console.log("Cold supply controller config: \n limit config: %s \n mintAddressWhitelist: %s\n allowAnyMintAndBurnAddress: %s\n", coldScConfig.limitConfig, coldScConfig.mintAddressWhitelist, coldScConfig.allowAnyMintAndBurnAddress) - console.log("Warm supply controller config: \n limit config: %s \n mintAddressWhitelist: %s\n allowAnyMintAndBurnAddress: %s\n", warmScConfig.limitConfig, warmScConfig.mintAddressWhitelist, warmScConfig.allowAnyMintAndBurnAddress) + console.log("Cold supply controller config: \n limit config: %s \n mintAndBurnAddressList: %s\n allowAnyMintAndBurnAddress: %s\n", coldScConfig.limitConfig, coldScConfig.mintAndBurnAddressSet, coldScConfig.allowAnyMintAndBurnAddress) + console.log("Warm supply controller config: \n limit config: %s \n mintAndBurnAddressList: %s\n allowAnyMintAndBurnAddress: %s\n", warmScConfig.limitConfig, warmScConfig.mintAndBurnAddressSet, warmScConfig.allowAnyMintAndBurnAddress) } main() diff --git a/scripts/deployUUPS.js b/scripts/deployUUPS.js index 025add9..eb70f2a 100644 --- a/scripts/deployUUPS.js +++ b/scripts/deployUUPS.js @@ -1,20 +1,19 @@ const { ethers, upgrades } = require("hardhat"); -const { PrintDeployerDetails, PrintProxyAndImplementation, ValidateEnvironmentVariables } = require('./utils'); +const { PrintDeployerDetails, PrintProxyAndImplementation } = require('./utils'); -const { TOKEN_OWNER_ADDRESS, PAUSER_ADDRESS, ASSET_PROTECTOR_ADDRESS } = process.env; +const { OWNER_ADDRESS, PAUSER_ADDRESS, ASSET_PROTECTOR_ADDRESS } = process.env; -const INITIAL_DELAY = 10800; -const CONTRACT_NAME = "USDG"; +const INITIAL_DELAY = 10; +const CONTRACT_NAME = "USDX"; const initializerArgs = [ INITIAL_DELAY, - TOKEN_OWNER_ADDRESS, + OWNER_ADDRESS, PAUSER_ADDRESS, ASSET_PROTECTOR_ADDRESS, ]; async function main() { - ValidateEnvironmentVariables(initializerArgs) await PrintDeployerDetails(); console.log("\nDeploying the contract...") diff --git a/scripts/getEncodeDataInitialize.js b/scripts/getEncodeDataInitialize.js index e06c5e3..2ade708 100644 --- a/scripts/getEncodeDataInitialize.js +++ b/scripts/getEncodeDataInitialize.js @@ -1,12 +1,10 @@ const { ethers } = require('ethers'); -const { ValidateEnvironmentVariables } = require('./utils'); const initializeAbi = [ "function initialize(uint48 initialDelay, address initialOwner, address pauser, address assetProtector)" ]; const { INITIAL_DELAY, TOKEN_OWNER_ADDRESS, PAUSER_ADDRESS, ASSET_PROTECTOR_ADDRESS } = process.env; -ValidateEnvironmentVariables(INITIAL_DELAY, TOKEN_OWNER_ADDRESS, PAUSER_ADDRESS, ASSET_PROTECTOR_ADDRESS ) const iface = new ethers.Interface(initializeAbi); diff --git a/scripts/utils.js b/scripts/utils.js index 08a9d05..76a1349 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -20,18 +20,8 @@ async function PrintProxyAndImplementation(contract, contractName) { console.log("%s contract deploy tx: %s", contractName, contract.deploymentTransaction().hash) } -// Throws an error if any of the arguments are falsy or undefined. -function ValidateEnvironmentVariables(args) { - for (const arg of args) { - if (!arg) { - throw new Error('Missing environment variable'); - } - } -} - module.exports = { PrintDeployerDetails, PrintContractDetails, - PrintProxyAndImplementation, - ValidateEnvironmentVariables, + PrintProxyAndImplementation } \ No newline at end of file diff --git a/test/StablecoinTest.js b/test/StablecoinTest.js index 5267ad1..be4ae18 100644 --- a/test/StablecoinTest.js +++ b/test/StablecoinTest.js @@ -1,4 +1,4 @@ -const { deployStableCoinFixturePYUSD, deployStableCoinFixtureUSDP, deployStableCoinFixtureUSDG } = require('./helpers/fixtures'); +const { deployStableCoinFixturePYUSD, deployStableCoinFixtureUSDP, deployStableCoinFixtureUSDX } = require('./helpers/fixtures'); const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers"); const { assert, expect } = require('chai'); const { ZeroAddress } = require("hardhat").ethers; @@ -31,27 +31,27 @@ describe('Stable coin testing', function () { }); }); - describe('USDG testing', async function () { + describe('USDX testing', async function () { it('has correct name, symbol, and decimals', async function () { - let { token } = await loadFixture(deployStableCoinFixtureUSDG); + let { token } = await loadFixture(deployStableCoinFixtureUSDX); const name = await token.name(); - assert.equal(name, "Global Dollar"); + assert.equal(name, "USD Token"); const symbol = await token.symbol(); - assert.equal(symbol, "USDG"); + assert.equal(symbol, "USDX"); const decimals = await token.decimals(); assert.equal(decimals, 6); }); describe("default admin role", function () { it("can upgrade with admin role", async () => { - const { token } = await loadFixture(deployStableCoinFixtureUSDG); - const newContract = await ethers.deployContract("USDG"); + const { token } = await loadFixture(deployStableCoinFixtureUSDX); + const newContract = await ethers.deployContract("USDX"); await expect(token.upgradeTo(newContract)).to.not.be.reverted; }); it("cannot upgrade without admin role", async () => { - const { token, acc } = await loadFixture(deployStableCoinFixtureUSDG); + const { token, acc } = await loadFixture(deployStableCoinFixtureUSDX); await expect( token.connect(acc).upgradeTo(ZeroAddress) diff --git a/test/UpgradeToV2Test.js b/test/UpgradeToV2Test.js index 94093a3..91e8e69 100644 --- a/test/UpgradeToV2Test.js +++ b/test/UpgradeToV2Test.js @@ -1,13 +1,13 @@ -const { deployPaxosTokenFixtureV1 } = require('./helpers/fixtures'); -const { abi: paxosTokenAbi }= require("../artifacts/contracts/PaxosTokenV2.sol/PaxosTokenV2.json") +const { deployXYZFixtureV1 } = require('./helpers/fixtures'); +const { abi: xyzAbi }= require("../artifacts/contracts/PaxosTokenV2.sol/PaxosTokenV2.json") const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers"); const { assert } = require('chai'); const { isStorageLayoutModified } = require('./helpers/storageLayout'); const { limits } = require('./helpers/constants'); const { MaxUint256 } = require("hardhat").ethers; -const PAXOS_TOKEN_V1 = "V1" -const PAXOS_TOKEN_V2 = "V2" +const XYZ_V1 = "V1" +const XYZ_V2 = "V2" // Testing an the upgrade is focused on testing the consistency of the memory model. The goal of this test is to // read every piece of mutable data (constants are not in storage anyway) that exists in the old version @@ -25,7 +25,7 @@ describe('UpgradeToV2', function () { // set all types of data - roles, balances, approvals, freezes this.setData = async function (version) { // set roles - if (version == PAXOS_TOKEN_V2) { + if (version == XYZ_V2) { await this.supplyControl.addSupplyController(supplyController, MaxUint256, limits.REFILL_PER_SECOND, [supplyController], false, {from: owner}) } else { await this.token.setSupplyController(supplyController); @@ -33,7 +33,7 @@ describe('UpgradeToV2', function () { // emulate some purchases and transfers - if (version == PAXOS_TOKEN_V2) { + if (version == XYZ_V2) { await this.token.connect(supplyControllerSigner).increaseSupplyToAddress(supply, supplyController); } else { await this.token.connect(supplyControllerSigner).increaseSupply(supply); @@ -44,7 +44,7 @@ describe('UpgradeToV2', function () { // emulate a redemption await this.token.connect(purchaserSigner).transfer(supplyController, subAmount); - if (version == PAXOS_TOKEN_V2) { + if (version == XYZ_V2) { await this.token.connect(supplyControllerSigner).decreaseSupplyFromAddress(subAmount, supplyController); } else { await this.token.connect(supplyControllerSigner).decreaseSupply(subAmount); @@ -75,12 +75,12 @@ describe('UpgradeToV2', function () { // deploy the contracts [ownerSigner, adminSigner, supplyControllerSigner, assetProtectionSigner, recipientSigner, purchaserSigner, holderSigner, bystanderSigner, frozenSigner] = await hre.ethers.getSigners(); [owner, admin, supplyController, assetProtection, recipient, purchaser, holder, bystander, frozen] = [ownerSigner.address, adminSigner.address, supplyControllerSigner.address, assetProtectionSigner.address, recipientSigner.address, purchaserSigner.address, holderSigner.address, bystanderSigner.address, frozenSigner.address] - Object.assign(this, await loadFixture(deployPaxosTokenFixtureV1)); + Object.assign(this, await loadFixture(deployXYZFixtureV1)); supply = BigInt(1000); amount = BigInt(100); subAmount = BigInt(10); - await this.setData(PAXOS_TOKEN_V1); + await this.setData(XYZ_V1); // read the data - note: the data here is always read before the upgrade @@ -108,8 +108,8 @@ describe('UpgradeToV2', function () { //Upgrade const SanctionListContract = require('./artifacts/IAddressList.json'); - const paxosTokenInterface = new ethers.Interface(paxosTokenAbi) - const data = paxosTokenInterface.encodeFunctionData("initialize", [ + const xyzInterface = new ethers.Interface(xyzAbi) + const data = xyzInterface.encodeFunctionData("initialize", [ 60*60*3, this.owner, this.owner, @@ -137,8 +137,8 @@ describe('UpgradeToV2', function () { //Upgrade const SanctionListContract = require('./artifacts/IAddressList.json'); - const paxosTokenInterface = new ethers.Interface(paxosTokenAbi) - const data = paxosTokenInterface.encodeFunctionData("initialize", [ + const xyzInterface = new ethers.Interface(xyzAbi) + const data = xyzInterface.encodeFunctionData("initialize", [ 60*60*3, this.owner, this.owner, @@ -157,28 +157,28 @@ describe('UpgradeToV2', function () { }); it('gives the same result as if we upgraded first', async function () { - const paxosToken = await hre.ethers.deployContract("PaxosTokenV1") + const xyz = await hre.ethers.deployContract("XYZImplementationV1") let ContractFactory = await hre.ethers.getContractFactory("AdminUpgradeabilityProxy"); - const proxy = await ContractFactory.connect(adminSigner).deploy(await paxosToken.getAddress()) + const proxy = await ContractFactory.connect(adminSigner).deploy(await xyz.getAddress()) await proxy.waitForDeployment() - const proxiedPaxosToken = await paxosToken.attach(await proxy.getAddress()) + const proxiedXYZ = await xyz.attach(await proxy.getAddress()) - await proxiedPaxosToken.initialize(); - await proxiedPaxosToken.setAssetProtectionRole(assetProtection); - await proxiedPaxosToken.setSupplyController(supplyController); + await proxiedXYZ.initialize(); + await proxiedXYZ.setAssetProtectionRole(assetProtection); + await proxiedXYZ.setSupplyController(supplyController); // make sure these are new contracts - assert.notStrictEqual(await this.token.getAddress(), await proxiedPaxosToken.getAddress()); + assert.notStrictEqual(await this.token.getAddress(), await proxiedXYZ.getAddress()); assert.notStrictEqual(await this.proxy.getAddress(), await proxy.getAddress()); - this.token = proxiedPaxosToken; + this.token = proxiedXYZ; const paxosTokenV2 = await hre.ethers.deployContract("PaxosTokenV2") - const paxosTokenInterface = new ethers.Interface(paxosTokenAbi) + const xyzInterface = new ethers.Interface(xyzAbi) const supplyControlFactory = await ethers.getContractFactory("SupplyControl"); this.supplyControl = await upgrades.deployProxy(supplyControlFactory, [this.owner, this.owner, await proxy.getAddress(), []], { initializer: "initialize", }); - const data = paxosTokenInterface.encodeFunctionData("initialize", [ + const data = xyzInterface.encodeFunctionData("initialize", [ 60*60*3, this.owner, this.owner, @@ -191,14 +191,14 @@ describe('UpgradeToV2', function () { const SanctionListContract = require('./artifacts/IAddressList.json'); // set the data on the new contracts after the upgrade this time - await this.setData(PAXOS_TOKEN_V2); + await this.setData(XYZ_V2); // check that the data on the contract is the same as what was read before the upgrade await this.checkData(); }); it('has the same storage layout', async function () { - const oldFullQualifiedName = "contracts/archive/PaxosTokenV1.sol:PaxosTokenV1"; + const oldFullQualifiedName = "contracts/archive/XYZImplementationV1.sol:XYZImplementationV1"; const newFullQualifiedName = "contracts/PaxosTokenV2.sol:PaxosTokenV2"; assert.isFalse(await isStorageLayoutModified(oldFullQualifiedName, newFullQualifiedName)) }); diff --git a/test/helpers/fixtures.js b/test/helpers/fixtures.js index 875ee95..9040c05 100644 --- a/test/helpers/fixtures.js +++ b/test/helpers/fixtures.js @@ -2,7 +2,7 @@ const { ethers, upgrades } = require("hardhat"); const { MaxUint256 } = require("hardhat").ethers; const { limits } = require('./constants'); -const PAXOS_TOKEN_V1 = "PaxosTokenV1" +const XYZ_V1 = "XYZImplementationV1" const PAXOS_TOKEN_V2 = "PaxosTokenV2" async function InitializePaxosTokenContract(admin, owner, assetProtectionRole, contractName) { @@ -13,7 +13,7 @@ async function InitializePaxosTokenContract(admin, owner, assetProtectionRole, c const proxiedPaxosToken = contract.attach(await proxy.getAddress()) let supplyControl; - if (contractName == PAXOS_TOKEN_V1) { //V1 uses old initialization pattern + if (contractName == XYZ_V1) { //V1 uses old initialization pattern await proxiedPaxosToken.initialize(); await proxiedPaxosToken.setAssetProtectionRole(assetProtectionRole.address); } else { @@ -57,8 +57,8 @@ async function deployUUPSContractFixture(contractName) { return { owner, admin, recipient, acc, acc2, acc3, assetProtectionRole, token, amount, supplyControl }; } -async function deployPaxosTokenFixtureV1() { - return deployContractFixture(PAXOS_TOKEN_V1) +async function deployXYZFixtureV1() { + return deployContractFixture(XYZ_V1) } async function deployPaxosTokenFixtureV2() { @@ -77,8 +77,8 @@ async function deployStableCoinFixtureUSDP() { return deployContractFixture("USDP"); } -async function deployStableCoinFixtureUSDG() { - return deployUUPSContractFixture("USDG"); +async function deployStableCoinFixtureUSDX() { + return deployUUPSContractFixture("USDX"); } async function deployRateLimitTest() { @@ -90,11 +90,11 @@ async function deployRateLimitTest() { module.exports = { InitializePaxosTokenContract, - deployPaxosTokenFixtureV1, + deployXYZFixtureV1, deployPaxosTokenFixtureV2, deployPaxosTokenFixtureLatest, deployStableCoinFixturePYUSD, deployStableCoinFixtureUSDP, - deployStableCoinFixtureUSDG, + deployStableCoinFixtureUSDX, deployRateLimitTest } diff --git a/test/helpers/storageLayout.js b/test/helpers/storageLayout.js index 10251cf..2805e08 100644 --- a/test/helpers/storageLayout.js +++ b/test/helpers/storageLayout.js @@ -125,7 +125,7 @@ module.exports = { /* Sample Usage async function exec() { - const oldFullQualifiedName = "contracts/archive/PaxosTokenV1.sol:PaxosTokenV1"; + const oldFullQualifiedName = "contracts/archive/XYZImplementationV1.sol:XYZImplementationV1"; const newFullQualifiedName = "contracts/PaxosTokenV2.sol:PaxosTokenV2"; console.table(await getComparison(oldFullQualifiedName, newFullQualifiedName)) } From fdfe2dd4fcdbb2dbfc5940eca1e172679c826423 Mon Sep 17 00:00:00 2001 From: Zach Petersen Date: Thu, 17 Oct 2024 15:31:02 -0500 Subject: [PATCH 2/4] Sync repos --- contracts/archive/PaxosToken.sol | 695 ------------------ contracts/archive/PaxosTokenV1.sol | 1080 ---------------------------- 2 files changed, 1775 deletions(-) delete mode 100644 contracts/archive/PaxosToken.sol delete mode 100644 contracts/archive/PaxosTokenV1.sol diff --git a/contracts/archive/PaxosToken.sol b/contracts/archive/PaxosToken.sol deleted file mode 100644 index 27da970..0000000 --- a/contracts/archive/PaxosToken.sol +++ /dev/null @@ -1,695 +0,0 @@ -// File: contracts/zeppelin/SafeMath.sol - -pragma solidity ^0.4.24; - - -/** - * @title SafeMath - * @dev Math operations with safety checks that throw on error - */ -library SafeMath { - /** - * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). - */ - function sub(uint256 a, uint256 b) internal pure returns (uint256) { - require(b <= a); - uint256 c = a - b; - - return c; - } - - /** - * @dev Adds two numbers, reverts on overflow. - */ - function add(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 c = a + b; - require(c >= a); - - return c; - } -} - -// File: contracts/PaxosToken.sol - -pragma solidity ^0.4.24; -pragma experimental "v0.5.0"; - - - -/** - * @title PaxosToken - * @dev this contract is a Pausable ERC20 token with Burn and Mint - * controlled by a central SupplyController. By implementing PaxosToken - * this contract also includes external methods for setting - * a new implementation contract for the Proxy. - * NOTE: The storage defined here will actually be held in the Proxy - * contract and all calls to this contract should be made through - * the proxy, including admin actions done as owner or supplyController. - * Any call to transfer against this contract should fail - * with insufficient funds since no tokens will be issued there. - */ -contract PaxosToken { - - /** - * MATH - */ - - using SafeMath for uint256; - - /** - * DATA - */ - - // INITIALIZATION DATA - bool private initialized; - - // ERC20 BASIC DATA - mapping(address => uint256) internal balances; - uint256 internal totalSupply_; - string public constant name = "PaxosToken USD"; // solium-disable-line - string public constant symbol = "PaxosToken"; // solium-disable-line uppercase - uint8 public constant decimals = 6; // solium-disable-line uppercase - - // ERC20 DATA - mapping(address => mapping(address => uint256)) internal allowed; - - // OWNER DATA PART 1 - address public owner; - - // PAUSABILITY DATA - bool public paused; - - // ASSET PROTECTION DATA - address public assetProtectionRole; - mapping(address => bool) internal frozen; - - // SUPPLY CONTROL DATA - address public supplyController; - - // OWNER DATA PART 2 - address public proposedOwner; - - // DELEGATED TRANSFER DATA - address public betaDelegateWhitelister; - mapping(address => bool) internal betaDelegateWhitelist; - mapping(address => uint256) internal nextSeqs; - // EIP191 header for EIP712 prefix - string constant internal EIP191_HEADER = "\x19\x01"; - // Hash of the EIP712 Domain Separator Schema - bytes32 constant internal EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256( - "EIP712Domain(string name,address verifyingContract)" - ); - bytes32 constant internal EIP712_DELEGATED_TRANSFER_SCHEMA_HASH = keccak256( - "BetaDelegatedTransfer(address to,uint256 value,uint256 fee,uint256 seq,uint256 deadline)" - ); - // Hash of the EIP712 Domain Separator data - // solhint-disable-next-line var-name-mixedcase - bytes32 public EIP712_DOMAIN_HASH; - - /** - * EVENTS - */ - - // ERC20 BASIC EVENTS - event Transfer(address indexed from, address indexed to, uint256 value); - - // ERC20 EVENTS - event Approval( - address indexed owner, - address indexed spender, - uint256 value - ); - - // OWNABLE EVENTS - event OwnershipTransferProposed( - address indexed currentOwner, - address indexed proposedOwner - ); - event OwnershipTransferDisregarded( - address indexed oldProposedOwner - ); - event OwnershipTransferred( - address indexed oldOwner, - address indexed newOwner - ); - - // PAUSABLE EVENTS - event Pause(); - event Unpause(); - - // ASSET PROTECTION EVENTS - event AddressFrozen(address indexed addr); - event AddressUnfrozen(address indexed addr); - event FrozenAddressWiped(address indexed addr); - event AssetProtectionRoleSet ( - address indexed oldAssetProtectionRole, - address indexed newAssetProtectionRole - ); - - // SUPPLY CONTROL EVENTS - event SupplyIncreased(address indexed to, uint256 value); - event SupplyDecreased(address indexed from, uint256 value); - event SupplyControllerSet( - address indexed oldSupplyController, - address indexed newSupplyController - ); - - // DELEGATED TRANSFER EVENTS - event BetaDelegatedTransfer( - address indexed from, address indexed to, uint256 value, uint256 seq, uint256 fee - ); - event BetaDelegateWhitelisterSet( - address indexed oldWhitelister, - address indexed newWhitelister - ); - event BetaDelegateWhitelisted(address indexed newDelegate); - event BetaDelegateUnwhitelisted(address indexed oldDelegate); - - /** - * FUNCTIONALITY - */ - - // INITIALIZATION FUNCTIONALITY - - /** - * @dev sets 0 initials tokens, the owner, and the supplyController. - * this serves as the constructor for the proxy but compiles to the - * memory model of the Implementation contract. - */ - function initialize() public { - require(!initialized, "MANDATORY VERIFICATION REQUIRED: The proxy has already been initialized, verify the owner and supply controller addresses."); - owner = msg.sender; - assetProtectionRole = address(0); - totalSupply_ = 0; - supplyController = msg.sender; - initializeDomainSeparator(); - initialized = true; - } - - /** - * The constructor is used here to ensure that the implementation - * contract is initialized. An uncontrolled implementation - * contract might lead to misleading state - * for users who accidentally interact with it. - */ - constructor() public { - initialize(); - pause(); - } - - /** - * @dev To be called when upgrading the contract using upgradeAndCall to add delegated transfers - */ - function initializeDomainSeparator() private { - // hash the name context with the contract address - EIP712_DOMAIN_HASH = keccak256(abi.encodePacked(// solium-disable-line - EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH, - keccak256(bytes(name)), - bytes32(address(this)) - )); - } - - // ERC20 BASIC FUNCTIONALITY - - /** - * @dev Total number of tokens in existence - */ - function totalSupply() public view returns (uint256) { - return totalSupply_; - } - - /** - * @dev Transfer token to a specified address from msg.sender - * Note: the use of Safemath ensures that _value is nonnegative. - * @param _to The address to transfer to. - * @param _value The amount to be transferred. - */ - function transfer(address _to, uint256 _value) public whenNotPaused returns (bool) { - require(_to != address(0), "cannot transfer to address zero"); - require(!frozen[_to] && !frozen[msg.sender], "address frozen"); - require(_value <= balances[msg.sender], "insufficient funds"); - - balances[msg.sender] = balances[msg.sender].sub(_value); - balances[_to] = balances[_to].add(_value); - emit Transfer(msg.sender, _to, _value); - return true; - } - - /** - * @dev Gets the balance of the specified address. - * @param _addr The address to query the the balance of. - * @return An uint256 representing the amount owned by the passed address. - */ - function balanceOf(address _addr) public view returns (uint256) { - return balances[_addr]; - } - - // ERC20 FUNCTIONALITY - - /** - * @dev Transfer tokens from one address to another - * @param _from address The address which you want to send tokens from - * @param _to address The address which you want to transfer to - * @param _value uint256 the amount of tokens to be transferred - */ - function transferFrom( - address _from, - address _to, - uint256 _value - ) - public - whenNotPaused - returns (bool) - { - require(_to != address(0), "cannot transfer to address zero"); - require(!frozen[_to] && !frozen[_from] && !frozen[msg.sender], "address frozen"); - require(_value <= balances[_from], "insufficient funds"); - require(_value <= allowed[_from][msg.sender], "insufficient allowance"); - - balances[_from] = balances[_from].sub(_value); - balances[_to] = balances[_to].add(_value); - allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); - emit Transfer(_from, _to, _value); - return true; - } - - /** - * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. - * Beware that changing an allowance with this method brings the risk that someone may use both the old - * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this - * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * @param _spender The address which will spend the funds. - * @param _value The amount of tokens to be spent. - */ - function approve(address _spender, uint256 _value) public whenNotPaused returns (bool) { - require(!frozen[_spender] && !frozen[msg.sender], "address frozen"); - allowed[msg.sender][_spender] = _value; - emit Approval(msg.sender, _spender, _value); - return true; - } - - /** - * @dev Increase the amount of tokens that an owner allowed to a spender. - * - * To increment allowed value is better to use this function to avoid 2 calls (and wait until the first transaction - * is mined) instead of approve. - * @param _spender The address which will spend the funds. - * @param _addedValue The amount of tokens to increase the allowance by. - */ - function increaseApproval(address _spender, uint _addedValue) public whenNotPaused returns (bool) { - require(!frozen[_spender] && !frozen[msg.sender], "address frozen"); - allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue); - emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); - return true; - } - - /** - * @dev Decrease the amount of tokens that an owner allowed to a spender. - * - * To decrement allowed value is better to use this function to avoid 2 calls (and wait until the first transaction - * is mined) instead of approve. - * @param _spender The address which will spend the funds. - * @param _subtractedValue The amount of tokens to decrease the allowance by. - */ - function decreaseApproval(address _spender, uint _subtractedValue) public whenNotPaused returns (bool) { - require(!frozen[_spender] && !frozen[msg.sender], "address frozen"); - uint oldValue = allowed[msg.sender][_spender]; - if (_subtractedValue > oldValue) { - allowed[msg.sender][_spender] = 0; - } else { - allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); - } - emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); - return true; - } - - /** - * @dev Function to check the amount of tokens that an owner allowed to a spender. - * @param _owner address The address which owns the funds. - * @param _spender address The address which will spend the funds. - * @return A uint256 specifying the amount of tokens still available for the spender. - */ - function allowance( - address _owner, - address _spender - ) - public - view - returns (uint256) - { - return allowed[_owner][_spender]; - } - - // OWNER FUNCTIONALITY - - /** - * @dev Throws if called by any account other than the owner. - */ - modifier onlyOwner() { - require(msg.sender == owner, "onlyOwner"); - _; - } - - /** - * @dev Allows the current owner to begin transferring control of the contract to a proposedOwner - * @param _proposedOwner The address to transfer ownership to. - */ - function proposeOwner(address _proposedOwner) public onlyOwner { - require(_proposedOwner != address(0), "cannot transfer ownership to address zero"); - require(msg.sender != _proposedOwner, "caller already is owner"); - proposedOwner = _proposedOwner; - emit OwnershipTransferProposed(owner, proposedOwner); - } - /** - * @dev Allows the current owner or proposed owner to cancel transferring control of the contract to a proposedOwner - */ - function disregardProposeOwner() public { - require(msg.sender == proposedOwner || msg.sender == owner, "only proposedOwner or owner"); - require(proposedOwner != address(0), "can only disregard a proposed owner that was previously set"); - address _oldProposedOwner = proposedOwner; - proposedOwner = address(0); - emit OwnershipTransferDisregarded(_oldProposedOwner); - } - /** - * @dev Allows the proposed owner to complete transferring control of the contract to the proposedOwner. - */ - function claimOwnership() public { - require(msg.sender == proposedOwner, "onlyProposedOwner"); - address _oldOwner = owner; - owner = proposedOwner; - proposedOwner = address(0); - emit OwnershipTransferred(_oldOwner, owner); - } - - /** - * @dev Reclaim all tokens at the contract address. - * This sends the tokens that this contract add holding to the owner. - * Note: this is not affected by freeze constraints. - */ - function reclaimToken() external onlyOwner { - uint256 _balance = balances[this]; - balances[this] = 0; - balances[owner] = balances[owner].add(_balance); - emit Transfer(this, owner, _balance); - } - - // PAUSABILITY FUNCTIONALITY - - /** - * @dev Modifier to make a function callable only when the contract is not paused. - */ - modifier whenNotPaused() { - require(!paused, "whenNotPaused"); - _; - } - - /** - * @dev called by the owner to pause, triggers stopped state - */ - function pause() public onlyOwner { - require(!paused, "already paused"); - paused = true; - emit Pause(); - } - - /** - * @dev called by the owner to unpause, returns to normal state - */ - function unpause() public onlyOwner { - require(paused, "already unpaused"); - paused = false; - emit Unpause(); - } - - // ASSET PROTECTION FUNCTIONALITY - - /** - * @dev Sets a new asset protection role address. - * @param _newAssetProtectionRole The new address allowed to freeze/unfreeze addresses and seize their tokens. - */ - function setAssetProtectionRole(address _newAssetProtectionRole) public { - require(msg.sender == assetProtectionRole || msg.sender == owner, "only assetProtectionRole or Owner"); - require(assetProtectionRole != _newAssetProtectionRole, "new address is same as a current one"); - emit AssetProtectionRoleSet(assetProtectionRole, _newAssetProtectionRole); - assetProtectionRole = _newAssetProtectionRole; - } - - modifier onlyAssetProtectionRole() { - require(msg.sender == assetProtectionRole, "onlyAssetProtectionRole"); - _; - } - - /** - * @dev Freezes an address balance from being transferred. - * @param _addr The new address to freeze. - */ - function freeze(address _addr) public onlyAssetProtectionRole { - require(!frozen[_addr], "address already frozen"); - frozen[_addr] = true; - emit AddressFrozen(_addr); - } - - /** - * @dev Unfreezes an address balance allowing transfer. - * @param _addr The new address to unfreeze. - */ - function unfreeze(address _addr) public onlyAssetProtectionRole { - require(frozen[_addr], "address already unfrozen"); - frozen[_addr] = false; - emit AddressUnfrozen(_addr); - } - - /** - * @dev Wipes the balance of a frozen address, and burns the tokens. - * @param _addr The new frozen address to wipe. - */ - function wipeFrozenAddress(address _addr) public onlyAssetProtectionRole { - require(frozen[_addr], "address is not frozen"); - uint256 _balance = balances[_addr]; - balances[_addr] = 0; - totalSupply_ = totalSupply_.sub(_balance); - emit FrozenAddressWiped(_addr); - emit SupplyDecreased(_addr, _balance); - emit Transfer(_addr, address(0), _balance); - } - - /** - * @dev Gets whether the address is currently frozen. - * @param _addr The address to check if frozen. - * @return A bool representing whether the given address is frozen. - */ - function isFrozen(address _addr) public view returns (bool) { - return frozen[_addr]; - } - - // SUPPLY CONTROL FUNCTIONALITY - - /** - * @dev Sets a new supply controller address. - * @param _newSupplyController The address allowed to burn/mint tokens to control supply. - */ - function setSupplyController(address _newSupplyController) public { - require(msg.sender == supplyController || msg.sender == owner, "only SupplyController or Owner"); - require(_newSupplyController != address(0), "cannot set supply controller to address zero"); - require(supplyController != _newSupplyController, "new address is same as a current one"); - emit SupplyControllerSet(supplyController, _newSupplyController); - supplyController = _newSupplyController; - } - - modifier onlySupplyController() { - require(msg.sender == supplyController, "onlySupplyController"); - _; - } - - /** - * @dev Increases the total supply by minting the specified number of tokens to the supply controller account. - * @param _value The number of tokens to add. - * @return A boolean that indicates if the operation was successful. - */ - function increaseSupply(uint256 _value) public onlySupplyController returns (bool success) { - totalSupply_ = totalSupply_.add(_value); - balances[supplyController] = balances[supplyController].add(_value); - emit SupplyIncreased(supplyController, _value); - emit Transfer(address(0), supplyController, _value); - return true; - } - - /** - * @dev Decreases the total supply by burning the specified number of tokens from the supply controller account. - * @param _value The number of tokens to remove. - * @return A boolean that indicates if the operation was successful. - */ - function decreaseSupply(uint256 _value) public onlySupplyController returns (bool success) { - require(_value <= balances[supplyController], "not enough supply"); - balances[supplyController] = balances[supplyController].sub(_value); - totalSupply_ = totalSupply_.sub(_value); - emit SupplyDecreased(supplyController, _value); - emit Transfer(supplyController, address(0), _value); - return true; - } - - // DELEGATED TRANSFER FUNCTIONALITY - - /** - * @dev returns the next seq for a target address. - * The transactor must submit nextSeqOf(transactor) in the next transaction for it to be valid. - * Note: that the seq context is specific to this smart contract. - * @param target The target address. - * @return the seq. - */ - // - function nextSeqOf(address target) public view returns (uint256) { - return nextSeqs[target]; - } - - /** - * @dev Performs a transfer on behalf of the from address, identified by its signature on the delegatedTransfer msg. - * Splits a signature byte array into r,s,v for convenience. - * @param sig the signature of the delgatedTransfer msg. - * @param to The address to transfer to. - * @param value The amount to be transferred. - * @param fee an optional ERC20 fee paid to the executor of betaDelegatedTransfer by the from address. - * @param seq a sequencing number included by the from address specific to this contract to protect from replays. - * @param deadline a block number after which the pre-signed transaction has expired. - * @return A boolean that indicates if the operation was successful. - */ - function betaDelegatedTransfer( - bytes sig, address to, uint256 value, uint256 fee, uint256 seq, uint256 deadline - ) public returns (bool) { - require(sig.length == 65, "signature should have length 65"); - bytes32 r; - bytes32 s; - uint8 v; - assembly { - r := mload(add(sig, 32)) - s := mload(add(sig, 64)) - v := byte(0, mload(add(sig, 96))) - } - _betaDelegatedTransfer(r, s, v, to, value, fee, seq, deadline); - return true; - } - - /** - * @dev Performs a transfer on behalf of the from address, identified by its signature on the betaDelegatedTransfer msg. - * Note: both the delegate and transactor sign in the fees. The transactor, however, - * has no control over the gas price, and therefore no control over the transaction time. - * Beta prefix chosen to avoid a name clash with an emerging standard in ERC865 or elsewhere. - * Internal to the contract - see betaDelegatedTransfer and betaDelegatedTransferBatch. - * @param r the r signature of the delgatedTransfer msg. - * @param s the s signature of the delgatedTransfer msg. - * @param v the v signature of the delgatedTransfer msg. - * @param to The address to transfer to. - * @param value The amount to be transferred. - * @param fee an optional ERC20 fee paid to the delegate of betaDelegatedTransfer by the from address. - * @param seq a sequencing number included by the from address specific to this contract to protect from replays. - * @param deadline a block number after which the pre-signed transaction has expired. - * @return A boolean that indicates if the operation was successful. - */ - function _betaDelegatedTransfer( - bytes32 r, bytes32 s, uint8 v, address to, uint256 value, uint256 fee, uint256 seq, uint256 deadline - ) internal whenNotPaused returns (bool) { - require(betaDelegateWhitelist[msg.sender], "Beta feature only accepts whitelisted delegates"); - require(value > 0 || fee > 0, "cannot transfer zero tokens with zero fee"); - require(block.number <= deadline, "transaction expired"); - // prevent sig malleability from ecrecover() - require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "signature incorrect"); - require(v == 27 || v == 28, "signature incorrect"); - - // EIP712 scheme: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md - bytes32 delegatedTransferHash = keccak256(abi.encodePacked(// solium-disable-line - EIP712_DELEGATED_TRANSFER_SCHEMA_HASH, bytes32(to), value, fee, seq, deadline - )); - bytes32 hash = keccak256(abi.encodePacked(EIP191_HEADER, EIP712_DOMAIN_HASH, delegatedTransferHash)); - address _from = ecrecover(hash, v, r, s); - - require(_from != address(0), "error determining from address from signature"); - require(to != address(0), "canno use address zero"); - require(!frozen[to] && !frozen[_from] && !frozen[msg.sender], "address frozen"); - require(value.add(fee) <= balances[_from], "insufficient fund"); - require(nextSeqs[_from] == seq, "incorrect seq"); - - nextSeqs[_from] = nextSeqs[_from].add(1); - balances[_from] = balances[_from].sub(value.add(fee)); - if (fee != 0) { - balances[msg.sender] = balances[msg.sender].add(fee); - emit Transfer(_from, msg.sender, fee); - } - balances[to] = balances[to].add(value); - emit Transfer(_from, to, value); - - emit BetaDelegatedTransfer(_from, to, value, seq, fee); - return true; - } - - /** - * @dev Performs an atomic batch of transfers on behalf of the from addresses, identified by their signatures. - * Lack of nested array support in arguments requires all arguments to be passed as equal size arrays where - * delegated transfer number i is the combination of all arguments at index i - * @param r the r signatures of the delgatedTransfer msg. - * @param s the s signatures of the delgatedTransfer msg. - * @param v the v signatures of the delgatedTransfer msg. - * @param to The addresses to transfer to. - * @param value The amounts to be transferred. - * @param fee optional ERC20 fees paid to the delegate of betaDelegatedTransfer by the from address. - * @param seq sequencing numbers included by the from address specific to this contract to protect from replays. - * @param deadline block numbers after which the pre-signed transactions have expired. - * @return A boolean that indicates if the operation was successful. - */ - function betaDelegatedTransferBatch( - bytes32[] r, bytes32[] s, uint8[] v, address[] to, uint256[] value, uint256[] fee, uint256[] seq, uint256[] deadline - ) public returns (bool) { - require(r.length == s.length && r.length == v.length && r.length == to.length && r.length == value.length, "length mismatch"); - require(r.length == fee.length && r.length == seq.length && r.length == deadline.length, "length mismatch"); - - for (uint i = 0; i < r.length; i++) { - _betaDelegatedTransfer(r[i], s[i], v[i], to[i], value[i], fee[i], seq[i], deadline[i]); - } - return true; - } - - /** - * @dev Gets whether the address is currently whitelisted for betaDelegateTransfer. - * @param _addr The address to check if whitelisted. - * @return A bool representing whether the given address is whitelisted. - */ - function isWhitelistedBetaDelegate(address _addr) public view returns (bool) { - return betaDelegateWhitelist[_addr]; - } - - /** - * @dev Sets a new betaDelegate whitelister. - * @param _newWhitelister The address allowed to whitelist betaDelegates. - */ - function setBetaDelegateWhitelister(address _newWhitelister) public { - require(msg.sender == betaDelegateWhitelister || msg.sender == owner, "only Whitelister or Owner"); - require(betaDelegateWhitelister != _newWhitelister, "new address is same as a current one"); - betaDelegateWhitelister = _newWhitelister; - emit BetaDelegateWhitelisterSet(betaDelegateWhitelister, _newWhitelister); - } - - modifier onlyBetaDelegateWhitelister() { - require(msg.sender == betaDelegateWhitelister, "onlyBetaDelegateWhitelister"); - _; - } - - /** - * @dev Whitelists an address to allow calling BetaDelegatedTransfer. - * @param _addr The new address to whitelist. - */ - function whitelistBetaDelegate(address _addr) public onlyBetaDelegateWhitelister { - require(!betaDelegateWhitelist[_addr], "delegate already whitelisted"); - betaDelegateWhitelist[_addr] = true; - emit BetaDelegateWhitelisted(_addr); - } - - /** - * @dev Unwhitelists an address to disallow calling BetaDelegatedTransfer. - * @param _addr The new address to whitelist. - */ - function unwhitelistBetaDelegate(address _addr) public onlyBetaDelegateWhitelister { - require(betaDelegateWhitelist[_addr], "delegate not whitelisted"); - betaDelegateWhitelist[_addr] = false; - emit BetaDelegateUnwhitelisted(_addr); - } -} diff --git a/contracts/archive/PaxosTokenV1.sol b/contracts/archive/PaxosTokenV1.sol deleted file mode 100644 index 6c021a4..0000000 --- a/contracts/archive/PaxosTokenV1.sol +++ /dev/null @@ -1,1080 +0,0 @@ -// File: contracts/lib/PaxosBaseAbstract.sol - -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -/** - * @dev PaxosBaseAbstract - * An abstract contract for Paxos tokens with additional internal functions. - */ -abstract contract PaxosBaseAbstract { - function _approve( - address owner, - address spender, - uint256 amount - ) internal virtual; - - function _transfer( - address _from, - address _to, - uint256 _value - ) internal virtual; - - function isPaused() internal view virtual returns (bool); - - function isAddrFrozen(address _addr) internal view virtual returns (bool); - - /** - * @dev Modifier to make a function callable only when the contract is not paused. - */ - modifier whenNotPaused() { - require(!isPaused(), "whenNotPaused"); - _; - } -} - -// File: contracts/lib/EIP712Domain.sol - -pragma solidity ^0.8.17; - -/** - * @dev An Abstract contract to store the domain separator for EIP712 signature. - * This contract is inherited by EIP3009 and EIP2612. - */ -abstract contract EIP712Domain { - /** - * @dev EIP712 Domain Separator - */ - bytes32 public DOMAIN_SEPARATOR; // solhint-disable-line var-name-mixedcase -} - -// File: contracts/lib/ECRecover.sol - -pragma solidity ^0.8.17; - -/** - * @title A library that provides a safe ECDSA recovery function - */ -library ECRecover { - /** - * @dev Recover signer's address from a signed message. - * Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.0/contracts/utils/cryptography/ECDSA.sol - * Modifications: Accept v, r, and s as separate arguments - * @param digest Keccak-256 hash digest of the signed message - * @param v v of the signature - * @param r r of the signature - * @param s s of the signature - * @return Signer address - */ - function recover( - bytes32 digest, - uint8 v, - bytes32 r, - bytes32 s - ) internal pure returns (address) { - // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature - // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines - // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most - // signatures from current libraries generate a unique signature with an s-value in the lower half order. - // - // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value - // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or - // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept - // these malleable signatures as well. - if ( - uint256(s) > - 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 - ) { - revert("ECRecover: invalid signature 's' value"); - } - - if (v != 27 && v != 28) { - revert("ECRecover: invalid signature 'v' value"); - } - - // If the signature is valid (and not malleable), return the signer address - address signer = ecrecover(digest, v, r, s); - require(signer != address(0), "ECRecover: invalid signature"); - - return signer; - } -} - -// File: contracts/lib/EIP712.sol - -pragma solidity ^0.8.17; - - -/** - * @title EIP712 - * @notice A library that provides EIP712 helper functions - */ -library EIP712 { - // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") - bytes32 public constant EIP712_DOMAIN_TYPEHASH = - 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; - - /** - * @notice Make EIP712 domain separator - * @param name Contract name - * @param version Contract version - * @return Domain separator - */ - function makeDomainSeparator( - string memory name, - string memory version - ) internal view returns (bytes32) { - uint256 chainId; - // solhint-disable-next-line no-inline-assembly - assembly { - chainId := chainid() - } - - return - keccak256( - abi.encode( - EIP712_DOMAIN_TYPEHASH, - keccak256(bytes(name)), - keccak256(bytes(version)), - bytes32(chainId), - address(this) - ) - ); - } - - /** - * @notice Recover signer's address from a EIP712 signature - * @param domainSeparator Domain separator - * @param v v of the signature - * @param r r of the signature - * @param s s of the signature - * @param typeHashAndData Type hash concatenated with data - * @return Signer's address - */ - function recover( - bytes32 domainSeparator, - uint8 v, - bytes32 r, - bytes32 s, - bytes memory typeHashAndData - ) internal pure returns (address) { - bytes32 digest = keccak256( - abi.encodePacked( - "\x19\x01", - domainSeparator, - keccak256(typeHashAndData) - ) - ); - return ECRecover.recover(digest, v, r, s); - } -} - -// File: contracts/lib/EIP2612.sol - -pragma solidity ^0.8.17; - - - - -abstract contract EIP2612 is PaxosBaseAbstract, EIP712Domain { - // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)") - bytes32 public constant PERMIT_TYPEHASH = - 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; - - mapping(address => uint256) internal _nonces; - // Storage gap: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps - uint256[10] __gap_EIP2612; - - /** - * @notice Nonces for permit - * @param owner Token owner's address - * @return Next nonce - */ - function nonces(address owner) external view returns (uint256) { - return _nonces[owner]; - } - - /** - * @notice update allowance with a signed permit - * @param owner Token owner's address (Authorizer) - * @param spender Spender's address - * @param value Amount of allowance - * @param deadline The time at which this expires (unix time) - * @param v v of the signature - * @param r r of the signature - * @param s s of the signature - */ - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external whenNotPaused { - require(deadline > block.timestamp, "EIP2612: permit is expired"); - require(!isAddrFrozen(msg.sender), "EIP2612: address frozen"); - - bytes memory data = abi.encode( - PERMIT_TYPEHASH, - owner, - spender, - value, - _nonces[owner]++, - deadline - ); - require( - EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == owner, - "EIP2612: invalid signature" - ); - - _approve(owner, spender, value); - } -} - -// File: contracts/lib/EIP3009.sol - -pragma solidity ^0.8.17; - - - - -abstract contract EIP3009 is PaxosBaseAbstract, EIP712Domain { - // keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") - bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = - 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267; - - // keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") - bytes32 public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = - 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8; - - // keccak256("CancelAuthorization(address authorizer,bytes32 nonce)") - bytes32 public constant CANCEL_AUTHORIZATION_TYPEHASH = - 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429; - - /** - * @dev authorizer address => nonce => state (true = used / false = unused) - */ - mapping(address => mapping(bytes32 => bool)) internal _authorizationStates; - // Storage gap: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps - uint256[10] __gap_EIP3009; - - event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce); - event AuthorizationCanceled( - address indexed authorizer, - bytes32 indexed nonce - ); - - string internal constant _INVALID_SIGNATURE_ERROR = - "EIP3009: invalid signature"; - string internal constant _AUTHORIZATION_USED_ERROR = - "EIP3009: authorization is used"; - - /** - * @notice Returns the state of an authorization - * @dev Nonces are randomly generated 32-byte data unique to the authorizer's - * address - * @param authorizer Authorizer's address - * @param nonce Nonce of the authorization - * @return True if the nonce is used - */ - function authorizationState( - address authorizer, - bytes32 nonce - ) external view returns (bool) { - return _authorizationStates[authorizer][nonce]; - } - - /** - * @notice Execute a transfer with a signed authorization - * @param from Payer's address (Authorizer) - * @param to Payee's address - * @param value Amount to be transferred - * @param validAfter The time after which this is valid (unix time) - * @param validBefore The time before which this is valid (unix time) - * @param nonce Unique nonce - * @param v v of the signature - * @param r r of the signature - * @param s s of the signature - */ - function transferWithAuthorization( - address from, - address to, - uint256 value, - uint256 validAfter, - uint256 validBefore, - bytes32 nonce, - uint8 v, - bytes32 r, - bytes32 s - ) external whenNotPaused { - _transferWithAuthorization( - TRANSFER_WITH_AUTHORIZATION_TYPEHASH, - from, - to, - value, - validAfter, - validBefore, - nonce, - v, - r, - s - ); - } - - function transferWithAuthorizationBatch( - address[] memory from, - address[] memory to, - uint256[] memory value, - uint256[] memory validAfter, - uint256[] memory validBefore, - bytes32[] memory nonce, - uint8[] memory v, - bytes32[] memory r, - bytes32[] memory s - ) external whenNotPaused { - // Validate length of each parameter with "from" argument to make sure lengths of all input arguments are the same. - require( - to.length == from.length && - value.length == from.length && - validAfter.length == from.length && - validBefore.length == from.length && - nonce.length == from.length && - v.length == from.length && - r.length == from.length && - s.length == from.length, - "argument's length mismatch" - ); - - for (uint16 i = 0; i < from.length; i++) { - _transferWithAuthorization( - TRANSFER_WITH_AUTHORIZATION_TYPEHASH, - from[i], - to[i], - value[i], - validAfter[i], - validBefore[i], - nonce[i], - v[i], - r[i], - s[i] - ); - } - } - - /** - * @notice Receive a transfer with a signed authorization from the payer - * @dev This has an additional check to ensure that the payee's address matches - * the caller of this function to prevent front-running attacks. (See security - * considerations) - * @param from Payer's address (Authorizer) - * @param to Payee's address - * @param value Amount to be transferred - * @param validAfter The time after which this is valid (unix time) - * @param validBefore The time before which this is valid (unix time) - * @param nonce Unique nonce - * @param v v of the signature - * @param r r of the signature - * @param s s of the signature - */ - function receiveWithAuthorization( - address from, - address to, - uint256 value, - uint256 validAfter, - uint256 validBefore, - bytes32 nonce, - uint8 v, - bytes32 r, - bytes32 s - ) external whenNotPaused { - require(to == msg.sender, "EIP3009: caller must be the payee"); - - _transferWithAuthorization( - RECEIVE_WITH_AUTHORIZATION_TYPEHASH, - from, - to, - value, - validAfter, - validBefore, - nonce, - v, - r, - s - ); - } - - /** - * @notice Attempt to cancel an authorization - * @param authorizer Authorizer's address - * @param nonce Nonce of the authorization - * @param v v of the signature - * @param r r of the signature - * @param s s of the signature - */ - function cancelAuthorization( - address authorizer, - bytes32 nonce, - uint8 v, - bytes32 r, - bytes32 s - ) external whenNotPaused { - require( - !isAddrFrozen(msg.sender) && !isAddrFrozen(authorizer), - "EIP3009: address frozen" - ); - require( - !_authorizationStates[authorizer][nonce], - _AUTHORIZATION_USED_ERROR - ); - - bytes memory data = abi.encode( - CANCEL_AUTHORIZATION_TYPEHASH, - authorizer, - nonce - ); - require( - EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == authorizer, - _INVALID_SIGNATURE_ERROR - ); - - _authorizationStates[authorizer][nonce] = true; - emit AuthorizationCanceled(authorizer, nonce); - } - - function _transferWithAuthorization( - bytes32 typeHash, - address from, - address to, - uint256 value, - uint256 validAfter, - uint256 validBefore, - bytes32 nonce, - uint8 v, - bytes32 r, - bytes32 s - ) internal { - require( - block.timestamp > validAfter, - "EIP3009: authorization is not yet valid" - ); - require( - block.timestamp < validBefore, - "EIP3009: authorization is expired" - ); - require(!isAddrFrozen(msg.sender), "EIP3009: address frozen"); - require(!_authorizationStates[from][nonce], _AUTHORIZATION_USED_ERROR); - - bytes memory data = abi.encode( - typeHash, - from, - to, - value, - validAfter, - validBefore, - nonce - ); - require( - EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from, - _INVALID_SIGNATURE_ERROR - ); - - _authorizationStates[from][nonce] = true; - emit AuthorizationUsed(from, nonce); - - _transfer(from, to, value); - } -} - -// File: contracts/PaxosToken.sol - -pragma solidity ^0.8.17; - - -/** - * @title PaxosToken - * @dev this contract is a Pausable ERC20 token with Burn and Mint - * controlled by a central SupplyController. By implementing PaxosToken - * this contract also includes external methods for setting - * a new implementation contract for the Proxy. - * NOTE: The storage defined here will actually be held in the Proxy - * contract and all calls to this contract should be made through - * the proxy, including admin actions done as owner or supplyController. - * Any call to transfer against this contract should fail - * with insufficient funds since no tokens will be issued there. - */ -contract PaxosToken is PaxosBaseAbstract{ - - /** - * DATA - */ - - // INITIALIZATION DATA - bool private initialized; - - // ERC20 BASIC DATA - mapping(address => uint256) internal balances; - uint256 internal totalSupply_; - string public constant name = "PaxosToken USD"; // solhint-disable-line const-name-snakecase - string public constant symbol = "PaxosToken"; // solhint-disable-line const-name-snakecase - uint8 public constant decimals = 6; // solhint-disable-line const-name-snakecase - - // ERC20 DATA - mapping(address => mapping(address => uint256)) internal allowed; - - // OWNER DATA PART 1 - address public owner; - - // PAUSABILITY DATA - bool public paused; - - // ASSET PROTECTION DATA - address public assetProtectionRole; - mapping(address => bool) internal frozen; - - // SUPPLY CONTROL DATA - address public supplyController; - - // OWNER DATA PART 2 - address public proposedOwner; - - // DELEGATED TRANSFER DATA - DEPRECATED - address public betaDelegateWhitelisterDeprecated; - mapping(address => bool) internal betaDelegateWhitelistDeprecated; - mapping(address => uint256) internal nextSeqsDeprecated; - // EIP191 header for EIP712 prefix - string constant internal EIP191_HEADER_DEPRECATED = "\x19\x01"; - // Hash of the EIP712 Domain Separator Schema - bytes32 constant internal EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH_DEPRECATED = keccak256( - "EIP712Domain(string name,address verifyingContract)" - ); - bytes32 constant internal EIP712_DELEGATED_TRANSFER_SCHEMA_HASH_DEPRECATED = keccak256( - "BetaDelegatedTransfer(address to,uint256 value,uint256 fee,uint256 seq,uint256 deadline)" - ); - // Hash of the EIP712 Domain Separator data - // solhint-disable-next-line var-name-mixedcase - bytes32 public EIP712_DOMAIN_HASH_DEPRECATED; - // Storage gap: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps - uint256[25] __gap_PaxosToken; - - /** - * EVENTS - */ - - // ERC20 BASIC EVENTS - event Transfer(address indexed from, address indexed to, uint256 value); - - // ERC20 EVENTS - event Approval( - address indexed owner, - address indexed spender, - uint256 value - ); - - // OWNABLE EVENTS - event OwnershipTransferProposed( - address indexed currentOwner, - address indexed proposedOwner - ); - event OwnershipTransferDisregarded( - address indexed oldProposedOwner - ); - event OwnershipTransferred( - address indexed oldOwner, - address indexed newOwner - ); - - // PAUSABLE EVENTS - event Pause(); - event Unpause(); - - // ASSET PROTECTION EVENTS - event AddressFrozen(address indexed addr); - event AddressUnfrozen(address indexed addr); - event FrozenAddressWiped(address indexed addr); - event AssetProtectionRoleSet ( - address indexed oldAssetProtectionRole, - address indexed newAssetProtectionRole - ); - - // SUPPLY CONTROL EVENTS - event SupplyIncreased(address indexed to, uint256 value); - event SupplyDecreased(address indexed from, uint256 value); - event SupplyControllerSet( - address indexed oldSupplyController, - address indexed newSupplyController - ); - - /** - * FUNCTIONALITY - */ - - // INITIALIZATION FUNCTIONALITY - - /** - * @dev sets 0 initials tokens, the owner, and the supplyController. - * this serves as the constructor for the proxy but compiles to the - * memory model of the Implementation contract. - */ - function initialize() public { - require(!initialized, "MANDATORY VERIFICATION REQUIRED: The proxy has already been initialized, verify the owner and supply controller addresses."); - owner = msg.sender; - assetProtectionRole = address(0); - totalSupply_ = 0; - supplyController = msg.sender; - initialized = true; - } - - /** - * The constructor is used here to ensure that the implementation - * contract is initialized. An uncontrolled implementation - * contract might lead to misleading state - * for users who accidentally interact with it. - */ - constructor() { - initialize(); - pause(); - } - - // ERC20 BASIC FUNCTIONALITY - - /** - * @dev Total number of tokens in existence - */ - function totalSupply() public view returns (uint256) { - return totalSupply_; - } - - /** - * @dev Transfer token to a specified address from msg.sender - * @param _to The address to transfer to. - * @param _value The amount to be transferred. - * @return True if successful - */ - function transfer(address _to, uint256 _value) public whenNotPaused returns (bool) { - _transfer(msg.sender, _to, _value); - return true; - } - - /** - * @dev Gets the balance of the specified address. - * @param _addr The address to query the the balance of. - * @return An uint256 representing the amount owned by the passed address. - */ - function balanceOf(address _addr) public view returns (uint256) { - return balances[_addr]; - } - - // ERC20 FUNCTIONALITY - - /** - * @dev Transfer tokens from one address to another - * @param _from address The address which you want to send tokens from - * @param _to address The address which you want to transfer to - * @param _value uint256 the amount of tokens to be transferred - * @return True if successful - */ - function transferFrom( - address _from, - address _to, - uint256 _value - ) - public - whenNotPaused - returns (bool) - { - require(!frozen[msg.sender], "sender address frozen"); - _transferFromAllowance(_from, _to, _value); - return true; - } - - /** - * @dev Transfer tokens from one set of address to another in a single transaction. - * @param _from addres[] The addresses which you want to send tokens from - * @param _to address[] The addresses which you want to transfer to - * @param _value uint256[] The amounts of tokens to be transferred - * @return True if successful - */ - function transferFromBatch( - address[] calldata _from, - address[] calldata _to, - uint256[] calldata _value - ) - public - whenNotPaused - returns (bool) - { - // Validate length of each parameter with "_from" argument to make sure lengths of all input arguments are the same. - require(_to.length == _from.length && _value.length == _from.length , "argument's length mismatch"); - require(!frozen[msg.sender], "sender address frozen"); - for (uint16 i = 0; i < _from.length; i++) { - _transferFromAllowance(_from[i], _to[i], _value[i]); - } - return true; - } - - /** - * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. - * Beware that changing an allowance with this method brings the risk that someone may use both the old - * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this - * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * @param _spender The address which will spend the funds. - * @param _value The amount of tokens to be spent. - * @return True if successful - */ - function approve(address _spender, uint256 _value) public whenNotPaused returns (bool) { - require(!frozen[_spender] && !frozen[msg.sender], "address frozen"); - allowed[msg.sender][_spender] = _value; - emit Approval(msg.sender, _spender, _value); - return true; - } - - /** - * @dev Increase the amount of tokens that an owner allowed to a spender. - * - * To increment allowed value is better to use this function to avoid 2 calls (and wait until the first transaction - * is mined) instead of approve. - * @param _spender The address which will spend the funds. - * @param _addedValue The amount of tokens to increase the allowance by. - * @return True if successful - */ - function increaseApproval(address _spender, uint256 _addedValue) public whenNotPaused returns (bool) { - require(!frozen[_spender] && !frozen[msg.sender], "address frozen"); - require(_addedValue != 0, "value cannot be zero"); - allowed[msg.sender][_spender] += _addedValue; - emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); - return true; - } - - /** - * @dev Decrease the amount of tokens that an owner allowed to a spender. - * - * To decrement allowed value is better to use this function to avoid 2 calls (and wait until the first transaction - * is mined) instead of approve. - * @param _spender The address which will spend the funds. - * @param _subtractedValue The amount of tokens to decrease the allowance by. - * @return True if successful - */ - function decreaseApproval(address _spender, uint256 _subtractedValue) public whenNotPaused returns (bool) { - require(!frozen[_spender] && !frozen[msg.sender], "address frozen"); - require(_subtractedValue != 0, "value cannot be zero"); - if (_subtractedValue > allowed[msg.sender][_spender]) { - allowed[msg.sender][_spender] = 0; - } else { - allowed[msg.sender][_spender] -= _subtractedValue; - } - emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); - return true; - } - - /** - * @dev Function to check the amount of tokens that an owner allowed to a spender. - * @param _owner address The address which owns the funds. - * @param _spender address The address which will spend the funds. - * @return A uint256 specifying the amount of tokens still available for the spender. - */ - function allowance( - address _owner, - address _spender - ) - public - view - returns (uint256) - { - return allowed[_owner][_spender]; - } - - // OWNER FUNCTIONALITY - - /** - * @dev Throws if called by any account other than the owner. - */ - modifier onlyOwner() { - require(msg.sender == owner, "onlyOwner"); - _; - } - - /** - * @dev Allows the current owner to begin transferring control of the contract to a proposedOwner - * @param _proposedOwner The address to transfer ownership to. - */ - function proposeOwner(address _proposedOwner) public onlyOwner { - require(_proposedOwner != address(0), "cannot transfer ownership to zero address"); - require(msg.sender != _proposedOwner, "caller already is owner"); - proposedOwner = _proposedOwner; - emit OwnershipTransferProposed(owner, proposedOwner); - } - /** - * @dev Allows the current owner or proposed owner to cancel transferring control of the contract to a proposedOwner - */ - function disregardProposeOwner() public { - require(msg.sender == proposedOwner || msg.sender == owner, "only proposedOwner or owner"); - require(proposedOwner != address(0), "can only disregard a proposed owner that was previously set"); - address _oldProposedOwner = proposedOwner; - proposedOwner = address(0); - emit OwnershipTransferDisregarded(_oldProposedOwner); - } - /** - * @dev Allows the proposed owner to complete transferring control of the contract to the proposedOwner. - */ - function claimOwnership() public { - require(msg.sender == proposedOwner, "onlyProposedOwner"); - address _oldOwner = owner; - owner = proposedOwner; - proposedOwner = address(0); - emit OwnershipTransferred(_oldOwner, owner); - } - - /** - * @dev Reclaim all tokens at the contract address. - * This sends the tokens that this contract add holding to the owner. - * Note: this is not affected by freeze constraints. - */ - function reclaimToken() external onlyOwner { - uint256 _balance = balances[address(this)]; - balances[address(this)] = 0; - balances[owner] += _balance; - emit Transfer(address(this), owner, _balance); - } - - // PAUSABILITY FUNCTIONALITY - - /** - * @dev Check if contract is paused. - */ - function isPaused() internal view override returns (bool) { - return paused; - } - - /** - * @dev called by the owner to pause, triggers stopped state - */ - function pause() public onlyOwner { - require(!paused, "already paused"); - paused = true; - emit Pause(); - } - - /** - * @dev called by the owner to unpause, returns to normal state - */ - function unpause() public onlyOwner { - require(paused, "already unpaused"); - paused = false; - emit Unpause(); - } - - // ASSET PROTECTION FUNCTIONALITY - - /** - * @dev Sets a new asset protection role address. - * @param _newAssetProtectionRole The new address allowed to freeze/unfreeze addresses and seize their tokens. - */ - function setAssetProtectionRole(address _newAssetProtectionRole) public { - require(msg.sender == assetProtectionRole || msg.sender == owner, "only assetProtectionRole or Owner"); - require(_newAssetProtectionRole != address(0), "cannot use zero address"); - require(assetProtectionRole != _newAssetProtectionRole, "new address is same as a current one"); - emit AssetProtectionRoleSet(assetProtectionRole, _newAssetProtectionRole); - assetProtectionRole = _newAssetProtectionRole; - } - - modifier onlyAssetProtectionRole() { - require(msg.sender == assetProtectionRole, "onlyAssetProtectionRole"); - _; - } - - /** - * @dev Freezes an address balance from being transferred. - * @param _addr The new address to freeze. - */ - function freeze(address _addr) public onlyAssetProtectionRole { - require(!frozen[_addr], "address already frozen"); - frozen[_addr] = true; - emit AddressFrozen(_addr); - } - - /** - * @dev Unfreezes an address balance allowing transfer. - * @param _addr The new address to unfreeze. - */ - function unfreeze(address _addr) public onlyAssetProtectionRole { - require(frozen[_addr], "address already unfrozen"); - frozen[_addr] = false; - emit AddressUnfrozen(_addr); - } - - /** - * @dev Wipes the balance of a frozen address, and burns the tokens. - * @param _addr The new frozen address to wipe. - */ - function wipeFrozenAddress(address _addr) public onlyAssetProtectionRole { - require(frozen[_addr], "address is not frozen"); - uint256 _balance = balances[_addr]; - balances[_addr] = 0; - totalSupply_ -= _balance; - emit FrozenAddressWiped(_addr); - emit SupplyDecreased(_addr, _balance); - emit Transfer(_addr, address(0), _balance); - } - - /** - * @dev Internal function to check whether the address is currently frozen. - * @param _addr The address to check if frozen. - * @return A bool representing whether the given address is frozen. - */ - function isAddrFrozen(address _addr) internal view override returns (bool) { - return frozen[_addr]; - } - - /** - * @dev Gets whether the address is currently frozen. - * @param _addr The address to check if frozen. - * @return A bool representing whether the given address is frozen. - */ - function isFrozen(address _addr) public view returns (bool) { - return isAddrFrozen(_addr); - } - - // SUPPLY CONTROL FUNCTIONALITY - - /** - * @dev Sets a new supply controller address and transfer supplyController tokens to _newSupplyController. - * @param _newSupplyController The address allowed to burn/mint tokens to control supply. - */ - function setSupplyController(address _newSupplyController) public { - require(msg.sender == supplyController || msg.sender == owner, "only SupplyController or Owner"); - require(_newSupplyController != address(0), "cannot set supply controller to zero address"); - require(supplyController != _newSupplyController, "new address is same as a current one"); - emit Transfer(supplyController, _newSupplyController, balances[supplyController]); - balances[_newSupplyController] += balances[supplyController]; - balances[supplyController] = 0; - emit SupplyControllerSet(supplyController, _newSupplyController); - supplyController = _newSupplyController; - } - - modifier onlySupplyController() { - require(msg.sender == supplyController, "onlySupplyController"); - _; - } - - /** - * @dev Increases the total supply by minting the specified number of tokens to the supply controller account. - * @param _value The number of tokens to add. - * @return success A boolean that indicates if the operation was successful. - */ - function increaseSupply(uint256 _value) public onlySupplyController returns (bool success) { - totalSupply_ += _value; - balances[supplyController] += _value; - emit SupplyIncreased(supplyController, _value); - emit Transfer(address(0), supplyController, _value); - return true; - } - - /** - * @dev Decreases the total supply by burning the specified number of tokens from the supply controller account. - * @param _value The number of tokens to remove. - * @return success A boolean that indicates if the operation was successful. - */ - function decreaseSupply(uint256 _value) public onlySupplyController returns (bool success) { - require(_value <= balances[supplyController], "not enough supply"); - balances[supplyController] -= _value; - totalSupply_ -= _value; - emit SupplyDecreased(supplyController, _value); - emit Transfer(supplyController, address(0), _value); - return true; - } - - /** - * @dev Internal function to transfer balances _from => _to. - * Internal to the contract - see transferFrom and transferFromBatch. - * @param _from address The address which you want to send tokens from - * @param _to address The address which you want to transfer to - * @param _value uint256 the amount of tokens to be transferred - */ - function _transferFromAllowance( - address _from, - address _to, - uint256 _value - ) - internal - { - require(_value <= allowed[_from][msg.sender], "insufficient allowance"); - _transfer(_from, _to, _value); - allowed[_from][msg.sender] -= _value; - } - - /** - * @dev Set allowance for a given spender, of a given owner. - * @param _owner address The address which owns the funds. - * @param _spender address The address which will spend the funds. - * @param _value uint256 The amount of tokens to increase the allowance by. - */ - function _approve( - address _owner, - address _spender, - uint256 _value - ) internal override { - require(_owner != address(0) && _spender != address(0), "ERC20: approve from is zero address"); - require(!frozen[_spender] && !frozen[_owner], "address frozen"); - - allowed[_owner][_spender] = _value; - emit Approval(_owner, _spender, _value); - } - - /** - * @dev Transfer `value` amount `_from` => `_to`. - * Internal to the contract - see transferFromAllowance and EIP3009.sol:_transferWithAuthorization. - * @param _from address The address which you want to send tokens from - * @param _to address The address which you want to send tokens to - * @param _value uint256 the amount of tokens to be transferred - */ - function _transfer( - address _from, - address _to, - uint256 _value - ) internal override { - require(_to != address(0) && _from != address(0), "cannot transfer to/from address zero"); - require(!frozen[_to] && !frozen[_from], "address frozen"); - require(_value <= balances[_from], "insufficient funds"); - - balances[_from] -= _value; - balances[_to] += _value; - emit Transfer(_from, _to, _value); - } - -} - -// File: contracts/PaxosTokenV1.sol - -pragma solidity ^0.8.17; - - - - - -/** - * @title PaxosTokenV1 - * @dev this contract is a Pausable ERC20 token with Burn and Mint - * controlled by a central SupplyController. By implementing PaxosTokenV1 - * this contract also includes external methods for setting - * a new implementation contract for the Proxy. - * NOTE: The storage defined here will actually be held in the Proxy - * contract and all calls to this contract should be made through - * the proxy, including admin actions done as owner or supplyController. - * Any call to transfer against this contract should fail - * with insufficient funds since no tokens will be issued there. - */ -contract PaxosTokenV1 is PaxosToken, EIP2612, EIP3009 { - constructor() { - initializeEIP712DomainSeparator(); - } - - /** - * @dev To be called when upgrading the contract using upgradeAndCall and during initialization of contract. - */ - function initializeEIP712DomainSeparator() public { - DOMAIN_SEPARATOR = EIP712.makeDomainSeparator("PaxosToken", "1"); - } -} From ea153de98a5177ad8680b74b22c002329cdeda1f Mon Sep 17 00:00:00 2001 From: Zach Petersen Date: Thu, 17 Oct 2024 15:32:01 -0500 Subject: [PATCH 3/4] Sync repos --- contracts/stablecoins/USDG.sol | 39 ---------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 contracts/stablecoins/USDG.sol diff --git a/contracts/stablecoins/USDG.sol b/contracts/stablecoins/USDG.sol deleted file mode 100644 index 8d046ca..0000000 --- a/contracts/stablecoins/USDG.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import { PaxosTokenV2 } from "./../PaxosTokenV2.sol"; - -/** - * @title USDG Smart contract - * @dev This contract is a {PaxosTokenV2-PaxosTokenV2} ERC20 token. - * @custom:security-contact smart-contract-security@paxos.com - */ -contract USDG is PaxosTokenV2, UUPSUpgradeable { - /** - * @dev Returns the name of the token. - */ - function name() public view virtual override returns (string memory) { - return "Global Dollar"; - } - - /** - * @dev Returns the symbol of the token. - */ - function symbol() public view virtual override returns (string memory) { - return "USDG"; - } - - /** - * @dev Returns the decimal count of the token. - */ - function decimals() public view virtual override returns (uint8) { - return 6; - } - - /** - * @dev required by the OZ UUPS module to authorize an upgrade - * of the contract. Restricted to DEFAULT_ADMIN_ROLE. - */ - function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {} // solhint-disable-line no-empty-blocks -} \ No newline at end of file From 7f1c18580900c4ad61bade03a03814416dfceea8 Mon Sep 17 00:00:00 2001 From: Zach Petersen Date: Thu, 17 Oct 2024 15:41:28 -0500 Subject: [PATCH 4/4] Sync repo --- test/{StablecoinTest.js => StableCoinTest.js} | 0 test/artifacts/IAddressList.json | 49 +++++++++++++++++++ 2 files changed, 49 insertions(+) rename test/{StablecoinTest.js => StableCoinTest.js} (100%) create mode 100644 test/artifacts/IAddressList.json diff --git a/test/StablecoinTest.js b/test/StableCoinTest.js similarity index 100% rename from test/StablecoinTest.js rename to test/StableCoinTest.js diff --git a/test/artifacts/IAddressList.json b/test/artifacts/IAddressList.json new file mode 100644 index 0000000..3649b37 --- /dev/null +++ b/test/artifacts/IAddressList.json @@ -0,0 +1,49 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "IAddressList", + "sourceName": "contracts/IAddressList.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "inAddrList", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "addresses", + "type": "address[]" + } + ], + "name": "isAnyAddrInList", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x", + "deployedBytecode": "0x", + "linkReferences": {}, + "deployedLinkReferences": {} +}