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:

  • addressowner - 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:

  • uint256atrTokenId - ID of the new ATR Token

  • addressowner - Address that is the owner of the new ATR Token

  • MultiToken.Asset memoryasset - 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:

  • uint256atrTokenId - ID of the ATR Token being removed

  • addressowner - Address of the previous ATR Token owner

  • MultiToken.Asset memoryasset - 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:

  • uint256atrTokenId - ID of the ATR Token

  • MultiToken.Asset memoryasset - 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:

  • uint256atrTokenId - 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:

  • uint256atrTokenId - ID of the invalid ATR Token

  • addressowner - 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:

  • uint256atrTokenId - 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:

  • addressowner - 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:

  • addressowner - Address of the PWN Safe to check

  • addressassetAddress - 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:

  • addressowner - Address of the PWN Safe to check

  • MultiToken.Asset memoryasset - 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 indexedfrom - Address from which was the token transferred

  • address indexedto - Address to which was the token transferred

  • uint256 indexedatrTokenId - ID of the transferred token

  • MultiToken.Assetasset - An asset struct (see MultiToken)

Last updated