Tokenized Asset Manager
1. Summary
This contract is responsible for managing tokenized asset balances. It makes sure that the balance of tokenised assets is always valid and provides a way to recover from an invalid tokenised balance in case of a stalking attack.
2. Contract details
TokenizedAssetManager.sol is written in Solidity version 0.8.15
Features
Keeping track of the tokenized asset balances
Provides a check for sufficient tokenised balance
Implements recovery functions to recover from the stalking attack
Functions
hasSufficientTokenizedBalance
Overview
A function to check the tokenised balance for the provided address is valid.
This function takes one argument supplied by the caller:
address
owner
- Address to check
Implementation
function hasSufficientTokenizedBalance(
address owner
) external view returns (bool) {
uint256[] memory atrs = tokenizedAssetsInSafe[owner].values();
for (uint256 i; i < atrs.length; ++i) {
MultiToken.Asset memory asset = assets[atrs[i]];
(, uint256 tokenizedBalance) = tokenizedBalances[owner][
asset.assetAddress
].tryGet(asset.id);
if (asset.balanceOf(owner) < tokenizedBalance) return false;
}
return true;
}
_increaseTokenizedBalance
Overview
An internal function called by the ATR Module which inherits this contract. It adds a new ATR Token to its owners' balance.
This function takes three arguments supplied by the ATR Module:
uint256
atrTokenId
- ID of the new ATR Tokenaddress
owner
- Address that is the owner of the new ATR TokenMultiToken.Asset memory
asset
- An asset struct (see MultiToken)
Implementation
function _increaseTokenizedBalance(
uint256 atrTokenId,
address owner,
MultiToken.Asset memory asset // Needs to be asset stored under given atrTokenId
) internal {
tokenizedAssetsInSafe[owner].add(atrTokenId);
EnumerableMap.UintToUintMap storage map = tokenizedBalances[owner][
asset.assetAddress
];
(, uint256 tokenizedBalance) = map.tryGet(asset.id);
map.set(asset.id, tokenizedBalance + asset.amount);
}
_decreaseTokenizedBalance
Overview
An internal function called by the ATR Module, which inherits this contract. It removes an ATR Token from its previous owners' balance.
This function takes three arguments supplied by the ATR Module:
uint256
atrTokenId
- ID of the ATR Token being removedaddress
owner
- Address of the previous ATR Token ownerMultiToken.Asset memory
asset
- An asset struct (see MultiToken)
Implementation
function _decreaseTokenizedBalance(
uint256 atrTokenId,
address owner,
MultiToken.Asset memory asset // Needs to be asset stored under given atrTokenId
) internal returns (bool) {
if (tokenizedAssetsInSafe[owner].remove(atrTokenId) == false)
return false;
EnumerableMap.UintToUintMap storage map = tokenizedBalances[owner][
asset.assetAddress
];
(, uint256 tokenizedBalance) = map.tryGet(asset.id);
if (tokenizedBalance == asset.amount) {
map.remove(asset.id);
} else {
map.set(asset.id, tokenizedBalance - asset.amount);
}
return true;
}
_storeTokenizedAsset
Overview
An internal function to add a new asset to the asset
mapping (ATR Token ID => Asset).
This function takes two arguments supplied by the ATR Module:
uint256
atrTokenId
- ID of the ATR TokenMultiToken.Asset memory
asset
- An asset struct (see MultiToken)
Implementation
function _storeTokenizedAsset(
uint256 atrTokenId,
MultiToken.Asset memory asset
) internal {
assets[atrTokenId] = asset;
}
_clearTokenizedAsset
Overview
An internal function called by the ATR Module when burning an ATR Token. It removes the asset corresponding to the supplied ATR Token ID from the asset
mapping (ATR Token ID => Asset).
This function takes one argument supplied by the ATR Module:
uint256
atrTokenId
- ID of the ATR Token to clear
Implementation
function _clearTokenizedAsset(uint256 atrTokenId) internal {
assets[atrTokenId] = MultiToken.Asset(
MultiToken.Category.ERC20,
address(0),
0,
0
);
}
Recovery functions to recover from stalking attack
This should not be necessary, thanks to the Recipient Permission Manager, but if you ever give permission to a malicious actor and you are a victim of this attack, please contact the PWN Team on discord.
Step 1:
reportInvalidTokenizedBalance
Overview
A function that checks the state is actually invalid and stores an on-chain report, that is used in the second step of the recovery process.
The reason to divide the recovery process into two transactions is to get rid of the otherwise possible reentrancy exploits. One could possibly transfer a tokenized asset from a PWN Safe and, before tokenized balance check can happen, call the recover function, which would recover the PWN Safe from that transitory invalid state and tokenized balance check would pass, effectively bypassing the transfer rights rules.
This function takes one argument supplied by the PWN Safe being recovered:
uint256
atrTokenId
- ID of the invalid ATR Tokenaddress
owner
- Address of the ATR tokens owner
Implementation
function reportInvalidTokenizedBalance(
uint256 atrTokenId,
address owner
) external {
// Check if atr token is in owners safe
// That would also check for non-existing ATR tokens
require(
tokenizedAssetsInSafe[owner].contains(atrTokenId),
"Asset is not in callers safe"
);
// Check if state is really invalid
MultiToken.Asset memory asset = assets[atrTokenId];
(, uint256 tokenizedBalance) = tokenizedBalances[owner][
asset.assetAddress
].tryGet(asset.id);
require(
asset.balanceOf(owner) < tokenizedBalance,
"Tokenized balance is not invalid"
);
// Store report
invalidTokenizedBalanceReports[owner] = InvalidTokenizedBalanceReport(
atrTokenId,
block.number
);
}
Struct: InvalidTokenizedBalanceReport
struct InvalidTokenizedBalanceReport {
uint256 atrTokenId;
uint256 block;
}
Step 2:
recoverInvalidTokenizedBalance
Overview
This function expects that the user called reportInvalidTokenizedBalance
function in some previous block. In the end, it will recover the PWN Safe from an invalid tokenized balance caused by the reported ATR Token and mark that token as invalid.
The primary reason for marking the ATR token as invalid rather than burning it is to prevent other DeFi protocols from unexpected behaviour in case the ATR token is used in them.
This function does not define any parameters and has to be called by the PWN Safe being recovered.
Implementation
function recoverInvalidTokenizedBalance() external {
address owner = msg.sender;
InvalidTokenizedBalanceReport
memory report = invalidTokenizedBalanceReports[owner];
uint256 atrTokenId = report.atrTokenId;
// Check that report exist
require(report.block > 0, "No reported invalid tokenized balance");
// Check that report was posted in different block than recover call
require(
report.block < block.number,
"Report block number has to be smaller then current block number"
);
// Decrease tokenized balance (would fail for invalid ATR token)
MultiToken.Asset memory asset = assets[atrTokenId];
require(
_decreaseTokenizedBalance(atrTokenId, owner, asset),
"Asset is not in callers safe"
);
delete invalidTokenizedBalanceReports[owner];
emit TransferViaATR(owner, address(0), atrTokenId, asset);
// Mark atr token as invalid (tokens asset holder is lost)
isInvalid[atrTokenId] = true;
}
View Functions
getAsset
Overview
Returns the asset struct (see MultiToken) corresponding to the supplied ATR Token ID.
This function takes one argument supplied by the caller:
uint256
atrTokenId
- ID of the ATR Token to check
Implementation
function getAsset(
uint256 atrTokenId
) external view returns (MultiToken.Asset memory) {
return assets[atrTokenId];
}
tokenizedAssetsInSafeOf
Overview
Returns IDs of all ATR Tokens that the supplied PWN Safe address holds.
This function takes one argument supplied by the caller:
address
owner
- Address of the PWN Safe to check
Implementation
function tokenizedAssetsInSafeOf(
address owner
) external view returns (uint256[] memory) {
return tokenizedAssetsInSafe[owner].values();
}
numberOfTokenizedAssetsFromCollection
Overview
Returns the total amount of tokenised assets the supplied owner holds from the supplied collection.
This function takes two arguments supplied by the caller:
address
owner
- Address of the PWN Safe to checkaddress
assetAddress
- Address of the asset to check
Implementation
function numberOfTokenizedAssetsFromCollection(
address owner,
address assetAddress
) external view returns (uint256) {
return tokenizedBalances[owner][assetAddress].length();
}
_canBeTokenized
Overview
An internal view function that checks the supplied owner address has some untokenised asset(s) from the supplied asset collection to tokenise.
This function takes two arguments supplied by the ATR Module:
address
owner
- Address of the PWN Safe to checkMultiToken.Asset memory
asset
- An asset struct (see MultiToken)
Implementation
function _canBeTokenized(
address owner,
MultiToken.Asset memory asset
) internal view returns (bool) {
uint256 balance = asset.balanceOf(owner);
(, uint256 tokenizedBalance) = tokenizedBalances[owner][
asset.assetAddress
].tryGet(asset.id);
return (balance - tokenizedBalance) >= asset.amount;
}
Events
The Tokenized Asset Manager contract defines one event and no custom errors.
event TransferViaATR(
address indexed from,
address indexed to,
uint256 indexed atrTokenId,
MultiToken.Asset asset
);
TransferViaATR
This event is emitted when a tokenised asset is transferred.
This event defines four parameters:
address indexed
from
- Address from which was the token transferredaddress indexed
to
- Address to which was the token transferreduint256 indexed
atrTokenId
- ID of the transferred tokenMultiToken.Asset
asset
- An asset struct (see MultiToken)
Last updated