Skip to content
15 changes: 12 additions & 3 deletions contracts/apx-protocol/ApxAssetsRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ contract ApxAssetsRegistry is IApxAssetsRegistry {
// EVENTS
//------------------------
event RegisterAsset(address caller, address original, address mirrored, bool state, uint256 timestamp);
event UpdatePrice(address priceManager, address asset, uint256 price, uint256 expiry, uint256 timestamp);
event UpdatePrice(
address priceManager,
address asset,
uint256 price,
uint8 priceDecimal,
uint256 expiry,
uint256 timestamp
);
event UpdateState(address assetManager, address asset, bool active, uint256 timestamp);
event TransferMasterOwnerRole(address oldMasterOwner, address newMasterOwner, uint256 timestamp);
event TransferAssetManagerRole(address oldAssetManager, address newAssetManager, uint256 timestamp);
Expand Down Expand Up @@ -110,7 +117,7 @@ contract ApxAssetsRegistry is IApxAssetsRegistry {
true,
state,
block.timestamp,
0, 0, 0, 0, address(0)
0, 0, 0, 0, 0, address(0)
);
assetsList.push(mirrored);
emit RegisterAsset(msg.sender, original, mirrored, state, block.timestamp);
Expand All @@ -128,6 +135,7 @@ contract ApxAssetsRegistry is IApxAssetsRegistry {
function updatePrice(
address asset,
uint256 price,
uint8 priceDecimals,
uint256 expiry,
uint256 capturedSupply
) external override onlyPriceManagerOrMasterOwner assetExists(asset) {
Expand All @@ -136,11 +144,12 @@ contract ApxAssetsRegistry is IApxAssetsRegistry {
require(expiry > 0, "ApxAssetsRegistry: expiry has to be > 0;");
require(capturedSupply == IToken(asset).totalSupply(), "ApxAssetsRegistry: inconsistent asset supply.");
assets[asset].price = price;
assets[asset].priceDecimals = priceDecimals;
assets[asset].priceUpdatedAt = block.timestamp;
assets[asset].priceValidUntil = block.timestamp + expiry;
assets[asset].capturedSupply = capturedSupply;
assets[asset].priceProvider = msg.sender;
emit UpdatePrice(msg.sender, asset, price, expiry, block.timestamp);
emit UpdatePrice(msg.sender, asset, price, priceDecimals, expiry, block.timestamp);
}

function migrate(address newAssetsRegistry, address originalAsset) external override onlyMasterOwner {
Expand Down
1 change: 1 addition & 0 deletions contracts/apx-protocol/IApxAssetsRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface IApxAssetsRegistry is IVersioned {
function updatePrice(
address asset,
uint256 price,
uint8 priceDecimals,
uint256 expiry,
uint256 capturedSupply
) external;
Expand Down
5 changes: 0 additions & 5 deletions contracts/asset-simple/AssetSimple.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ import "../shared/ICampaignCommon.sol";

contract AssetSimple is IAssetSimple, ERC20 {

//------------------------
// CONSTANTS
//------------------------
uint256 constant public override priceDecimalsPrecision = 10 ** 4;

//-----------------------
// STATE
//-----------------------
Expand Down
66 changes: 48 additions & 18 deletions contracts/asset-transferable/AssetTransferable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ import "../shared/Structs.sol";
contract AssetTransferable is IAssetTransferable, ERC20 {
using SafeERC20 for IERC20;

//------------------------
// CONSTANTS
//------------------------
uint256 constant public override priceDecimalsPrecision = 10 ** 4;

//----------------------
// STATE
//------------------------
Expand Down Expand Up @@ -72,7 +67,7 @@ contract AssetTransferable is IAssetTransferable, ERC20 {
params.info,
params.name,
params.symbol,
0, 0, 0,
0, 0, 0, 0,
false,
0, 0, 0
);
Expand Down Expand Up @@ -149,6 +144,7 @@ contract AssetTransferable is IAssetTransferable, ERC20 {
uint256 tokenValue = campaignState.fundsRaised;
uint256 tokenAmount = campaignState.tokensSold;
uint256 tokenPrice = campaignState.pricePerToken;
uint8 tokenPriceDecimals = campaignState.tokenPriceDecimals;
require(
tokenAmount > 0 && balanceOf(campaign) >= tokenAmount,
"AssetTransferable: Campaign has signalled the sale finalization but campaign tokens are not present"
Expand All @@ -164,7 +160,14 @@ contract AssetTransferable is IAssetTransferable, ERC20 {
);
sellHistory.push(tokenSaleInfo);
successfulTokenSalesMap[campaign] = tokenSaleInfo;
if (tokenPrice > state.highestTokenSellPrice) { state.highestTokenSellPrice = tokenPrice; }
if (
(state.highestTokenSellPrice == 0 && state.highestTokenSellPriceDecimals == 0) ||
_tokenValue(tokenAmount, tokenPrice, tokenPriceDecimals) >
_tokenValue(tokenAmount, state.highestTokenSellPrice, state.highestTokenSellPriceDecimals)
) {
state.highestTokenSellPrice = tokenPrice;
state.highestTokenSellPriceDecimals = tokenPriceDecimals;
}
emit FinalizeSale(
msg.sender,
tokenAmount,
Expand All @@ -179,17 +182,47 @@ contract AssetTransferable is IAssetTransferable, ERC20 {
require(assetRecord.exists, "AssetTransferable: Not registered in Apx Registry");
require(assetRecord.state, "AssetTransferable: Asset blocked in Apx Registry");
require(assetRecord.mirroredToken == address(this), "AssetTransferable: Invalid mirrored asset record");
require(block.timestamp <= assetRecord.priceValidUntil, "AssetTransferable: Price expired");
uint256 liquidationPrice =
(state.highestTokenSellPrice > assetRecord.price) ? state.highestTokenSellPrice : assetRecord.price;
require(block.timestamp <= assetRecord.priceValidUntil, "AssetTransferable: Price expired");
uint256 liquidationAmount;
uint256 liquidationPrice;
uint8 liquidationPriceDecimals;
uint256 liquidationAmountPerLargestSalePrice = _tokenValue(
totalSupply(),
state.highestTokenSellPrice,
state.highestTokenSellPriceDecimals
);
uint256 liquidationAmountPerMarketPrice = _tokenValue(
totalSupply(),
assetRecord.price,
assetRecord.priceDecimals
);
if (liquidationAmountPerLargestSalePrice > liquidationAmountPerMarketPrice) {
liquidationAmount = liquidationAmountPerLargestSalePrice;
liquidationPrice = state.highestTokenSellPrice;
liquidationPriceDecimals = state.highestTokenSellPriceDecimals;
} else {
liquidationAmount = liquidationAmountPerMarketPrice;
liquidationPrice = assetRecord.price;
liquidationPriceDecimals = assetRecord.priceDecimals;
}

uint256 liquidatorApprovedTokenAmount = this.allowance(msg.sender, address(this));
uint256 liquidatorApprovedTokenValue = _tokenValue(liquidatorApprovedTokenAmount, liquidationPrice);
uint256 liquidatorApprovedTokenValue = _tokenValue(
liquidatorApprovedTokenAmount,
liquidationPrice,
liquidationPriceDecimals
);

if (liquidatorApprovedTokenValue > 0) {
liquidationClaimsMap[msg.sender] += liquidatorApprovedTokenValue;
state.liquidationFundsClaimed += liquidatorApprovedTokenValue;
this.transferFrom(msg.sender, address(this), liquidatorApprovedTokenAmount);
}
uint256 liquidationFundsTotal = _tokenValue(totalSupply(), liquidationPrice);
uint256 liquidationFundsTotal = _tokenValue(
totalSupply(),
liquidationPrice,
liquidationPriceDecimals
);
uint256 liquidationFundsToPull = liquidationFundsTotal - liquidatorApprovedTokenValue;
if (liquidationFundsToPull > 0) {
_stablecoin().safeTransferFrom(msg.sender, address(this), liquidationFundsToPull);
Expand Down Expand Up @@ -308,19 +341,16 @@ contract AssetTransferable is IAssetTransferable, ERC20 {
return true;
}

function _tokenValue(uint256 amount, uint256 price) private view returns (uint256) {
function _tokenValue(uint256 amount, uint256 price, uint8 priceDecimals) private view returns (uint256) {
return amount
* price
* _stablecoin_decimals_precision()
/ (_asset_decimals_precision() * priceDecimalsPrecision);
/ (10 ** priceDecimals)
/ (10 ** decimals());
}

function _stablecoin_decimals_precision() private view returns (uint256) {
return 10 ** IToken(_stablecoin_address()).decimals();
}

function _asset_decimals_precision() private view returns (uint256) {
return 10 ** decimals();
}

}
59 changes: 46 additions & 13 deletions contracts/asset/Asset.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ import "../shared/Structs.sol";
contract Asset is IAsset, ERC20 {
using SafeERC20 for IERC20;

//------------------------
// CONSTANTS
//------------------------
uint256 constant public override priceDecimalsPrecision = 10 ** 4;

//-----------------------
// STATE
//-----------------------
Expand Down Expand Up @@ -69,7 +64,7 @@ contract Asset is IAsset, ERC20 {
params.info,
params.name,
params.symbol,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
false,
0, 0, 0
);
Expand Down Expand Up @@ -183,6 +178,7 @@ contract Asset is IAsset, ERC20 {
uint256 tokenValue = campaignState.fundsRaised;
uint256 tokenAmount = campaignState.tokensSold;
uint256 tokenPrice = campaignState.pricePerToken;
uint8 tokenPriceDecimals = campaignState.tokenPriceDecimals;
require(
tokenAmount > 0 && balanceOf(campaign) >= tokenAmount,
"Asset: Campaign has signalled the sale finalization but campaign tokens are not present"
Expand All @@ -198,7 +194,14 @@ contract Asset is IAsset, ERC20 {
);
sellHistory.push(tokenSaleInfo);
successfulTokenSalesMap[campaign] = tokenSaleInfo;
if (tokenPrice > state.highestTokenSellPrice) { state.highestTokenSellPrice = tokenPrice; }
if (
(state.highestTokenSellPrice == 0 && state.highestTokenSellPriceDecimals == 0) ||
_tokenValue(tokenAmount, tokenPrice, tokenPriceDecimals) >
_tokenValue(tokenAmount, state.highestTokenSellPrice, state.highestTokenSellPriceDecimals)
) {
state.highestTokenSellPrice = tokenPrice;
state.highestTokenSellPriceDecimals = tokenPriceDecimals;
}
emit FinalizeSale(
msg.sender,
tokenAmount,
Expand All @@ -209,29 +212,58 @@ contract Asset is IAsset, ERC20 {

function liquidate() external override ownerOnly {
require(!state.liquidated, "Asset: Action forbidden, asset liquidated.");
uint256 liquidationAmount;
uint256 liquidationPrice;
uint8 liquidationPriceDecimals;
uint256 liquidationAmountPerLargestSalePrice = _tokenValue(
totalSupply(),
state.highestTokenSellPrice,
state.highestTokenSellPriceDecimals
);
if (state.totalTokensLocked > 0) {
IApxAssetsRegistry apxRegistry = IApxAssetsRegistry(state.apxRegistry);
Structs.AssetRecord memory assetRecord = apxRegistry.getMirroredFromOriginal(address(this));
require(assetRecord.state, "Asset: Asset blocked in Apx Registry");
require(assetRecord.originalToken == address(this), "Asset: Invalid mirrored asset record");
require(block.timestamp <= assetRecord.priceValidUntil, "Asset: Price expired");
require(state.totalTokensLocked == assetRecord.capturedSupply, "Asset: MirroredToken supply inconsistent");
liquidationPrice =
(state.highestTokenSellPrice > assetRecord.price) ? state.highestTokenSellPrice : assetRecord.price;
uint256 liquidationAmountPerMarketPrice = _tokenValue(
totalSupply(),
assetRecord.price,
assetRecord.priceDecimals
);
if (liquidationAmountPerLargestSalePrice > liquidationAmountPerMarketPrice) {
liquidationAmount = liquidationAmountPerLargestSalePrice;
liquidationPrice = state.highestTokenSellPrice;
liquidationPriceDecimals = state.highestTokenSellPriceDecimals;
} else {
liquidationAmount = liquidationAmountPerMarketPrice;
liquidationPrice = assetRecord.price;
liquidationPriceDecimals = assetRecord.priceDecimals;
}
} else {
liquidationAmount = liquidationAmountPerLargestSalePrice;
liquidationPrice = state.highestTokenSellPrice;
liquidationPriceDecimals = state.highestTokenSellPriceDecimals;
}

uint256 liquidatorApprovedTokenAmount = this.allowance(msg.sender, address(this));
uint256 liquidatorApprovedTokenValue = _tokenValue(liquidatorApprovedTokenAmount, liquidationPrice);
uint256 liquidatorApprovedTokenValue = _tokenValue(
liquidatorApprovedTokenAmount,
liquidationPrice,
liquidationPriceDecimals
);
if (liquidatorApprovedTokenValue > 0) {
liquidationClaimsMap[msg.sender] += liquidatorApprovedTokenValue;
state.liquidationFundsClaimed += liquidatorApprovedTokenValue;
this.transferFrom(msg.sender, address(this), liquidatorApprovedTokenAmount);
}

uint256 liquidationFundsTotal = _tokenValue(totalSupply(), liquidationPrice);
uint256 liquidationFundsTotal = _tokenValue(
totalSupply(),
liquidationPrice,
liquidationPriceDecimals
);
uint256 liquidationFundsToPull = liquidationFundsTotal - liquidatorApprovedTokenValue;
if (liquidationFundsToPull > 0) {
_stablecoin().safeTransferFrom(msg.sender, address(this), liquidationFundsToPull);
Expand Down Expand Up @@ -348,11 +380,12 @@ contract Asset is IAsset, ERC20 {
return (campaignRecord.wallet == campaignAddress && campaignRecord.whitelisted);
}

function _tokenValue(uint256 amount, uint256 price) private view returns (uint256) {
function _tokenValue(uint256 amount, uint256 price, uint8 priceDecimals) private view returns (uint256) {
return amount
* price
* (10 ** IToken(_stablecoin_address()).decimals())
/ ((10 ** decimals()) * priceDecimalsPrecision);
/ (10 ** priceDecimals)
/ (10 ** decimals());
}

}
4 changes: 0 additions & 4 deletions contracts/issuer/IIssuer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ import "../shared/Structs.sol";

interface IIssuer is IIssuerCommon {

// Write

function changeOwnership(address newOwner) external;

// Read

function getState() external view returns (Structs.IssuerState memory);
Expand Down
20 changes: 6 additions & 14 deletions contracts/issuer/Issuer.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "../issuer/IIssuer.sol";
import "../shared/Structs.sol";

contract Issuer is IIssuer {
contract Issuer is IIssuer, Ownable {

//------------------------
// STATE
Expand All @@ -19,7 +20,6 @@ contract Issuer is IIssuer {
//------------------------
event WalletWhitelist(address indexed approver, address indexed wallet);
event WalletBlacklist(address indexed approver, address indexed wallet);
event ChangeOwnership(address caller, address newOwner, uint256 timestamp);
event ChangeWalletApprover(address caller, address oldWalletApprover, address newWalletApprover, uint256 timestamp);
event SetInfo(string info, address setter, uint256 timestamp);

Expand All @@ -35,7 +35,6 @@ contract Issuer is IIssuer {
string memory info
) {
require(owner != address(0), "Issuer: invalid owner address");
require(stablecoin != address(0), "Issuer: invalid stablecoin address");
require(walletApprover != address(0), "Issuer: invalid wallet approver address");

infoHistory.push(Structs.InfoEntry(
Expand All @@ -52,19 +51,12 @@ contract Issuer is IIssuer {
info
);
_setWalletState(owner, true);
_transferOwnership(owner);
}

//------------------------
// MODIFIERS
//------------------------
modifier ownerOnly {
require(
msg.sender == state.owner,
"Issuer: Only owner can make this action."
);
_;
}

modifier walletApproverOnly {
require(
msg.sender == state.walletApprover,
Expand All @@ -76,7 +68,7 @@ contract Issuer is IIssuer {
//------------------------
// IIssuer IMPL
//------------------------
function setInfo(string memory info) external override ownerOnly {
function setInfo(string memory info) external override onlyOwner {
infoHistory.push(Structs.InfoEntry(
info,
block.timestamp
Expand All @@ -95,9 +87,9 @@ contract Issuer is IIssuer {
emit WalletBlacklist(msg.sender, wallet);
}

function changeOwnership(address newOwner) external override ownerOnly {
function transferOwnership(address newOwner) public override onlyOwner {
super.transferOwnership(newOwner);
state.owner = newOwner;
emit ChangeOwnership(msg.sender, newOwner, block.timestamp);
}

function changeWalletApprover(address newWalletApprover) external override {
Expand Down
Loading