Skip to content

Commit

Permalink
Merge pull request #127 from CirclesUBI/20240402-test-wrap-erc20
Browse files Browse the repository at this point in the history
20240402 test wrap erc20
  • Loading branch information
jaensen authored Apr 11, 2024
2 parents fca18a8 + e7b4d3b commit 7b4852a
Show file tree
Hide file tree
Showing 21 changed files with 473 additions and 82 deletions.
88 changes: 66 additions & 22 deletions src/circles/Demurrage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ contract Demurrage is ICirclesERC1155Errors {

/**
* @dev Store a lookup table T(n) for computing issuance.
* T is only accessed for minting in Hub.sol, so it is initialized in
* storage of Hub.sol during the constructor, by copying these values.
* (It is not properly intialized in a ERC20 Proxy contract, but we never need
* to access it there, so it is not a problem - it is only initialized in the
* storage of the mastercopy during deployment.)
* See ../../specifications/TCIP009-demurrage.md for more details.
*/
int128[15] internal T = [
Expand All @@ -117,26 +122,35 @@ contract Demurrage is ICirclesERC1155Errors {
];

/**
* @dev Store a lookup table R(n) for computing issuance.
* @dev Store a lookup table R(n) for computing issuance and demurrage.
* This table is computed in the constructor of Hub.sol and mastercopy deployments,
* and lazily computed in the ERC20 Demurrage proxy contracts, then cached into their storage.
* The non-trivial situation for R(n) (vs T(n)) is that R is accessed
* from the ERC20 Demurrage proxy contracts, so their storage will not yet
* have been initialized with the constructor. (Counter to T which is only
* accessed for minting in Hub.sol, and as such initialized in the constructor
* of Hub.sol by Solidity by copying the python calculated values stored above.)
*
* Computing R in contract is done with .64bits precision, whereas the python computed
* table is slightly more accurate, but equal within dust (10^-18). See unit tests.
* However, we want to ensure that Hub.sol and the ERC20 Demurrage proxy contracts
* use the exact same R values (even if the difference would not matter).
* So for R we rely on the in-contract computed values.
* In the unit tests, the table of python computed values is stored in HIGHER_ACCURACY_R,
* and matched against the solidity computed values.
* See ../../specifications/TCIP009-demurrage.md for more details.
*/
int128[15] internal R = [
int128(18446744073709551616),
int128(18443079296116538654),
int128(18439415246597529027),
int128(18435751925007877736),
int128(18432089331202968517),
int128(18428427465038213837),
int128(18424766326369054888),
int128(18421105915050961582),
int128(18417446230939432544),
int128(18413787273889995104),
int128(18410129043758205300),
int128(18406471540399647861),
int128(18402814763669936209),
int128(18399158713424712450),
int128(18395503389519647372)
];
int128[15] internal R;

// Constructor

constructor() {
// we need to fill the R table upon construction so that
// in Hub.sol personalMint has the R table available
for (uint8 i = 0; i <= R_TABLE_LOOKUP; i++) {
R[i] = Math64x64.pow(GAMMA_64x64, i);
}
}

// Public functions

Expand Down Expand Up @@ -206,11 +220,41 @@ contract Demurrage is ICirclesERC1155Errors {
function _calculateDiscountedBalance(uint256 _balance, uint256 _daysDifference) internal view returns (uint256) {
if (_daysDifference == 0) {
return _balance;
} else if (_daysDifference <= R_TABLE_LOOKUP) {
return Math64x64.mulu(R[_daysDifference], _balance);
}
int128 r = _calculateDemurrageFactor(_daysDifference);
return Math64x64.mulu(r, _balance);
}

function _calculateDiscountedBalanceAndCache(uint256 _balance, uint256 _daysDifference)
internal
returns (uint256)
{
if (_daysDifference == 0) {
return _balance;
}
int128 r = _calculateDemurrageFactorAndCache(_daysDifference);
return Math64x64.mulu(r, _balance);
}

function _calculateDemurrageFactor(uint256 _dayDifference) internal view returns (int128) {
if (_dayDifference <= R_TABLE_LOOKUP && R[_dayDifference] != 0) {
return R[_dayDifference];
} else {
return Math64x64.pow(GAMMA_64x64, _dayDifference);
}
}

function _calculateDemurrageFactorAndCache(uint256 _dayDifference) internal returns (int128) {
if (_dayDifference <= R_TABLE_LOOKUP) {
if (R[_dayDifference] == 0) {
// for proxy ERC20 contracts, the storage does not contain the R table yet
// so compute it lazily and store it in the table
int128 r = Math64x64.pow(GAMMA_64x64, _dayDifference);
R[_dayDifference] = r;
}
return R[_dayDifference];
} else {
int128 r = Math64x64.pow(GAMMA_64x64, _daysDifference);
return Math64x64.mulu(r, _balance);
return Math64x64.pow(GAMMA_64x64, _dayDifference);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/circles/DiscountedBalances.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ contract DiscountedBalances is Demurrage {
* @dev stores the discounted balances of the accounts privately.
* Mapping from Circles identifiers to accounts to the discounted balance.
*/
mapping(uint256 => mapping(address => DiscountedBalance)) private discountedBalances;
mapping(uint256 => mapping(address => DiscountedBalance)) public discountedBalances;

// /**
// * @dev Store a lookup table T(n) for computing issuance.
Expand Down
14 changes: 8 additions & 6 deletions src/hub/Hub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,8 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors {
_registerGroup(msg.sender, _mint, standardTreasury, _name, _symbol);

// for groups register possible custom name and symbol
nameRegistry.registerName(msg.sender, _name);
nameRegistry.registerSymbol(msg.sender, _symbol);
nameRegistry.registerCustomName(msg.sender, _name);
nameRegistry.registerCustomSymbol(msg.sender, _symbol);

// store the IPFS CIDv0 digest for the group metadata
nameRegistry.updateCidV0Digest(msg.sender, _cidV0Digest);
Expand All @@ -304,8 +304,8 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors {
_registerGroup(msg.sender, _mint, _treasury, _name, _symbol);

// for groups register possible custom name and symbol
nameRegistry.registerName(msg.sender, _name);
nameRegistry.registerSymbol(msg.sender, _symbol);
nameRegistry.registerCustomName(msg.sender, _name);
nameRegistry.registerCustomSymbol(msg.sender, _symbol);

// store the IPFS CIDv0 digest for the group metadata
nameRegistry.updateCidV0Digest(msg.sender, _cidV0Digest);
Expand All @@ -322,7 +322,7 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors {
_insertAvatar(msg.sender);

// for organizations, only register possible custom name
nameRegistry.registerName(msg.sender, _name);
nameRegistry.registerCustomName(msg.sender, _name);

// store the IPFS CIDv0 digest for the organization metadata
nameRegistry.updateCidV0Digest(msg.sender, _cidV0Digest);
Expand Down Expand Up @@ -506,13 +506,15 @@ contract Hub is Circles, MetadataDefinitions, IHubErrors, ICirclesErrors {

// Public functions

function wrap(address _avatar, uint256 _amount, CirclesType _type) public {
function wrap(address _avatar, uint256 _amount, CirclesType _type) public returns (address) {
if (!isHuman(_avatar) && !isGroup(_avatar)) {
// Avatar must be human or group.
revert CirclesAvatarMustBeRegistered(_avatar, 2);
}
address erc20Wrapper = liftERC20.ensureERC20(_avatar, _type);
safeTransferFrom(msg.sender, erc20Wrapper, toTokenId(_avatar), _amount, "");

return erc20Wrapper;
}

// todo: if we have space, possibly have a wrapBatch function
Expand Down
8 changes: 5 additions & 3 deletions src/lift/DemurrageCircles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../hub/IHub.sol";
import "../names/INameRegistry.sol";
import "../proxy/MasterCopyNonUpgradable.sol";
import "./ERC20DiscountedBalances.sol";

contract DemurrageCircles is ERC20DiscountedBalances, ERC1155Holder {
contract DemurrageCircles is MasterCopyNonUpgradable, ERC20DiscountedBalances, ERC1155Holder {
// Constants

// State variables
Expand Down Expand Up @@ -42,7 +43,7 @@ contract DemurrageCircles is ERC20DiscountedBalances, ERC1155Holder {

// Setup function

function setup(IHubV2 _hub, INameRegistry _nameRegistry, address _avatar) external {
function setup(address _hub, address _nameRegistry, address _avatar) external {
if (address(hub) != address(0)) {
revert CirclesProxyAlreadyInitialized();
}
Expand All @@ -56,8 +57,9 @@ contract DemurrageCircles is ERC20DiscountedBalances, ERC1155Holder {
if (_avatar == address(0)) {
revert CirclesAddressCannotBeZero(2);
}
hub = _hub;
hub = IHubV2(_hub);
avatar = _avatar;
nameRegistry = INameRegistry(_nameRegistry);
// read inflation day zero from hub
inflationDayZero = hub.inflationDayZero();

Expand Down
5 changes: 3 additions & 2 deletions src/lift/ERC20DiscountedBalances.sol
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 {

function _discountAndAddToBalance(address _account, uint256 _value, uint64 _day) internal {
DiscountedBalance storage discountedBalance = discountedBalances[_account];
uint256 newBalance =
_calculateDiscountedBalance(discountedBalance.balance, _day - discountedBalance.lastUpdatedDay) + _value;
uint256 newBalance = _calculateDiscountedBalanceAndCache(
discountedBalance.balance, _day - discountedBalance.lastUpdatedDay
) + _value;
if (newBalance > MAX_VALUE) {
// Balance exceeds maximum value.
revert CirclesERC1155AmountExceedsMaxUint190(_account, 0, newBalance, 0);
Expand Down
7 changes: 4 additions & 3 deletions src/lift/ERC20Lift.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ contract ERC20Lift is ProxyFactory, IERC20Lift, ICirclesErrors {

// State variables

IHubV2 public immutable hub;
IHubV2 public hub;

INameRegistry public immutable nameRegistry;
INameRegistry public nameRegistry;

/**
* @dev The master copy of the ERC20 demurrage and inflation Circles contract.
Expand Down Expand Up @@ -94,7 +94,8 @@ contract ERC20Lift is ProxyFactory, IERC20Lift, ICirclesErrors {
// Internal functions

function _deployERC20(address _masterCopy, address _avatar) internal returns (address) {
bytes memory wrapperSetupData = abi.encodeWithSelector(ERC20_WRAPPER_SETUP_CALLPREFIX, hub, _avatar);
bytes memory wrapperSetupData =
abi.encodeWithSelector(ERC20_WRAPPER_SETUP_CALLPREFIX, hub, nameRegistry, _avatar);
address erc20wrapper = address(_createProxy(_masterCopy, wrapperSetupData));
return erc20wrapper;
}
Expand Down
12 changes: 9 additions & 3 deletions src/lift/InflationaryCircles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../hub/IHub.sol";
import "../names/INameRegistry.sol";
import "../proxy/MasterCopyNonUpgradable.sol";
import "./ERC20InflationaryBalances.sol";

contract InflationaryCircles is ERC20InflationaryBalances, ERC1155Holder {
contract InflationaryCircles is MasterCopyNonUpgradable, ERC20InflationaryBalances, ERC1155Holder {
// Constants

// State variables
Expand Down Expand Up @@ -42,7 +43,7 @@ contract InflationaryCircles is ERC20InflationaryBalances, ERC1155Holder {

// Setup function

function setup(IHubV2 _hub, INameRegistry _nameRegistry, address _avatar) external {
function setup(address _hub, address _nameRegistry, address _avatar) external {
if (address(hub) != address(0)) {
// Must not be initialized already.
revert CirclesProxyAlreadyInitialized();
Expand All @@ -59,8 +60,9 @@ contract InflationaryCircles is ERC20InflationaryBalances, ERC1155Holder {
// Must not be the zero address.
revert CirclesAddressCannotBeZero(2);
}
hub = _hub;
hub = IHubV2(_hub);
avatar = _avatar;
nameRegistry = INameRegistry(_nameRegistry);
// read inflation day zero from hub
inflationDayZero = hub.inflationDayZero();

Expand Down Expand Up @@ -120,4 +122,8 @@ contract InflationaryCircles is ERC20InflationaryBalances, ERC1155Holder {
{
revert CirclesERC1155CannotReceiveBatch(0);
}

function circlesIdentifier() public view returns (uint256) {
return toTokenId(avatar);
}
}
4 changes: 2 additions & 2 deletions src/names/INameRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ pragma solidity >=0.8.13;

interface INameRegistry {
function updateCidV0Digest(address avatar, bytes32 cidVoDigest) external;
function registerName(address avatar, string calldata name) external;
function registerSymbol(address avatar, string calldata symbol) external;
function registerCustomName(address avatar, string calldata name) external;
function registerCustomSymbol(address avatar, string calldata symbol) external;

function name(address avatar) external view returns (string memory);
function symbol(address avatar) external view returns (string memory);
Expand Down
7 changes: 4 additions & 3 deletions src/names/NameRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ pragma solidity >=0.8.13;
import "../errors/Errors.sol";
import "../hub/IHub.sol";
import "./Base58Converter.sol";
import "./INameRegistry.sol";

contract NameRegistry is Base58Converter, INameRegistryErrors, ICirclesErrors {
contract NameRegistry is Base58Converter, INameRegistry, INameRegistryErrors, ICirclesErrors {
// Constants

/**
Expand Down Expand Up @@ -166,10 +167,10 @@ contract NameRegistry is Base58Converter, INameRegistryErrors, ICirclesErrors {
uint72 shortName = shortNames[_avatar];
if (shortName == uint72(0)) {
string memory base58FullAddress = toBase58(uint256(uint160(_avatar)));
return string(abi.encodePacked("DEFAULT_CIRCLES_NAME_PREFIX", base58FullAddress));
return string(abi.encodePacked(DEFAULT_CIRCLES_NAME_PREFIX, base58FullAddress));
}
string memory base58ShortName = toBase58(uint256(shortName));
return string(abi.encodePacked("DEFAULT_CIRCLES_NAME_PREFIX", base58ShortName));
return string(abi.encodePacked(DEFAULT_CIRCLES_NAME_PREFIX, base58ShortName));
}

function symbol(address _avatar) external view mustBeRegistered(_avatar, 2) returns (string memory) {
Expand Down
22 changes: 18 additions & 4 deletions src/proxy/Proxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ interface IProxy {
/// applying the code of a master contract.
/// @author Stefan George - <[email protected]>
/// @author Richard Meissner - <[email protected]>
contract Proxy is ICirclesErrors {
contract Proxy {
// masterCopy always needs to be first declared variable,
// to ensure that it is at the same location in the contracts
// to which calls are delegated.
// To reduce deployment costs this variable is internal
// and needs to be retrieved via `getStorageAt`
address internal masterCopy;
address public masterCopy;

/// @dev Constructor function sets address of master copy contract.
/// @param _masterCopy Master copy address.
constructor(address _masterCopy) {
if (_masterCopy != address(0)) {
if (_masterCopy == address(0)) {
// Invalid master copy address provided
revert CirclesAddressCannotBeZero(0);
revert();
}
masterCopy = _masterCopy;
}
Expand All @@ -42,6 +42,20 @@ contract Proxy is ICirclesErrors {

// -- internal functions

// /// @dev Fallback function forwards all transactions and
// /// returns all received return data.
// function _fallback() internal {
// // solium-disable-next-line security/no-inline-assembly
// assembly {
// let mc := sload(0)
// calldatacopy(0, 0, calldatasize())
// let success := delegatecall(gas(), mc, 0, calldatasize(), 0, 0)
// returndatacopy(0, 0, returndatasize())
// if eq(success, 0) { revert(0, returndatasize()) }
// return(0, returndatasize())
// }
// }

/// @dev Fallback function forwards all transactions and
/// returns all received return data.
function _fallback() internal {
Expand Down
2 changes: 1 addition & 1 deletion src/treasury/standardTreasury.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ contract StandardTreasury is
/**
* @notice Address of the hub contract
*/
IHubV2 public immutable hub;
IHubV2 public hub;

/**
* @notice Address of the mastercopy standard vault contract
Expand Down
3 changes: 2 additions & 1 deletion src/treasury/standardVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ pragma solidity >=0.8.13;
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import "../errors/Errors.sol";
import "../hub/IHub.sol";
import "../proxy/MasterCopyNonUpgradable.sol";
import "./IStandardVault.sol";

contract StandardVault is ERC1155Holder, IStandardVault, ICirclesErrors {
contract StandardVault is MasterCopyNonUpgradable, ERC1155Holder, IStandardVault, ICirclesErrors {
// State variables

/**
Expand Down
Loading

0 comments on commit 7b4852a

Please sign in to comment.