Skip to main content

ATR Guard

1. Summary​

Asset Transfer Rights Guard is responsible for enforcing asset transfer rules. It checks the state before and after a transaction execution.

3. Contract details​

  • AssetTransferRightsGuard.sol is written in Solidity version 0.8.15

Features​

  • Checks every Safe transaction before and after its execution making sure that Asset Transfer Rights token rules are enforced

Inherited contracts​

This contract inherits other contracts. Please see their reference for a complete overview of the ATR Guard.

Functions​

checkTransaction

Overview​

A hook called by the PWN Safe to check a transaction before its execution.

This function takes the transaction parameters as arguments (see implementation).

Implementation​

function checkTransaction(
address to,
uint256 /*value*/,
bytes calldata data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 /*baseGas*/,
uint256 gasPrice,
address /*gasToken*/,
address payable /*refundReceiver*/,
bytes memory /*signatures*/,
address /*msgSender*/ // msgSender is caller on safe, msg.sender is safe
) external {
require(safeTxGas == 0, "Safe tx gas has to be 0 for tx to revert in case of failure");
require(gasPrice == 0, "Gas price has to be 0 for tx to revert in case of failure");

// Libraries has to be whitelisted
if (operation == Enum.Operation.DelegateCall)
require(whitelist.isWhitelistedLib(to), "Address is not whitelisted for delegatecalls");

// Self authorization calls
if (to == msg.sender)
_checkManagerUpdates(data);

// Trust ATR contract
if (to != address(atr))
_checkExecutionCalls(msg.sender, to, data);
}
checkAfterExecution

Overview​

A hook called after transaction execution to make sure there's a correct ATR token balance.

This function takes the transaction hash and a boolean determining transaction success as arguments.

Implementation​

function checkAfterExecution(bytes32 /*txHash*/, bool success) view external {
if (success)
require(atr.hasSufficientTokenizedBalance(msg.sender), "Insufficient tokenized balance");
}
hasOperatorFor

Overview​

Check function to determine if a PWN Safe has approved operators for a specific asset collection. This function returns a boolean.

This function takes two arguments supplied by the caller:

  • addresssafeAddress - Address of the PWN Safe to check
  • addressassetAddress - Address of the asset collection to check

Implementation​

function hasOperatorFor(address safeAddress, address assetAddress) override(OperatorsContext, IAssetTransferRightsGuard) public view returns (bool) {
// ERC777 defines `defaultOperators`
address implementer = IERC1820Registry(ERC1820_REGISTRY_ADDRESS).getInterfaceImplementer(assetAddress, keccak256("ERC777Token"));
if (implementer == assetAddress) {
address[] memory defaultOperators = IERC777(assetAddress).defaultOperators();

for (uint256 i; i < defaultOperators.length; ++i)
if (IERC777(assetAddress).isOperatorFor(defaultOperators[i], safeAddress))
return true;
}

return super.hasOperatorFor(safeAddress, assetAddress);
}
_checkManagerUpdates

Overview​

This check ensures that invalid manager functions aren't called.

This function takes the transaction calldata as an argument and can be called only the Guard.

Implementation​

function _checkManagerUpdates(bytes calldata data) pure private {
// Get function selector from data
bytes4 funcSelector = bytes4(data);

// GuardManager.setGuard(address)
if (funcSelector == 0xe19a9dd9) {
revert("Cannot change ATR guard");
}

// ModuleManager.enableModule(address)
else if (funcSelector == 0x610b5925) {
revert("Cannot enable ATR module");
}

// ModuleManager.disableModule(address,address)
else if (funcSelector == 0xe009cfde) {
revert("Cannot disable ATR module");
}

// FallbackManager.setFallbackHandler(address)
else if (funcSelector == 0xf08a0323) {
revert("Cannot change fallback handler");
}
}
_checkExecutionCalls

Overview​

This check ensures that invalid transfer and approve functions aren't called if there's an ATR token minted for a specific collection.

This function takes three arguments supplied by the ATR Guard:

  • addresssafeAddress - Address of the PWN Safe that initiated the transaction
  • addresstarget - Target contract that is being called
  • bytes calldatadata - Transaction calldata

Implementation​

function _checkExecutionCalls(address safeAddress, address target, bytes calldata data) private {
// Get function selector from data
bytes4 funcSelector = bytes4(data);

// ERC20/ERC721 - approve(address,uint256)
if (funcSelector == 0x095ea7b3) {
// Block any approve call if there is at least one tokenized asset from a collection
require(atr.numberOfTokenizedAssetsFromCollection(safeAddress, target) == 0, "Some asset from collection has transfer right token minted");

(address operator, uint256 amount) = abi.decode(data[4:], (address, uint256));

// Safe don't need to track approved ERC721 asset ids, because it's possible to get this information from ERC721 contract directly.
// ERC20 contract doesn't provide possibility to list all addresses that are approved to transfer asset on behalf of an owner.
// That's why a safe has to track operators.

_handleERC20Approval(safeAddress, target, operator, amount);
}

// ERC20 - increaseAllowance(address,uint256)
else if (funcSelector == 0x39509351) {
// Block any increaseAllowance call if there is at least one tokenized asset from a collection
require(atr.numberOfTokenizedAssetsFromCollection(safeAddress, target) == 0, "Some asset from collection has transfer right token minted");

(address operator, uint256 amount) = abi.decode(data[4:], (address, uint256));
if (amount > 0) {
_addOperator(safeAddress, target, operator);
}
}

// ERC20 - decreaseAllowance(address,uint256)
else if (funcSelector == 0xa457c2d7) {
(address operator, uint256 amount) = abi.decode(data[4:], (address, uint256));
try IERC20(target).allowance(safeAddress, operator) returns (uint256 allowance) {

if (allowance <= amount) {
_removeOperator(safeAddress, target, operator);
}

} catch {}
}

// ERC721/ERC1155 - setApprovalForAll(address,bool)
else if (funcSelector == 0xa22cb465) {
// Block any setApprovalForAll call if there is at least one tokenized asset from a collection
require(atr.numberOfTokenizedAssetsFromCollection(safeAddress, target) == 0, "Some asset from collection has transfer right token minted");

(address operator, bool approved) = abi.decode(data[4:], (address, bool));

// Not ERC721 nor ERC1155 does provider direct way how to get list of approved operators.
// That's why a wallet has to track them.

if (approved) {
_addOperator(safeAddress, target, operator);
} else {
_removeOperator(safeAddress, target, operator);
}
}

// ERC777 - authorizeOperator(address)
else if (funcSelector == 0x959b8c3f) {
// Block any authorizeOperator call if there is at least one tokenized asset from a collection
require(atr.numberOfTokenizedAssetsFromCollection(safeAddress, target) == 0, "Some asset from collection has transfer right token minted");

address operator = abi.decode(data[4:], (address));
_addOperator(safeAddress, target, operator);
}

// ERC777 - revokeOperator(address)
else if (funcSelector == 0xfad8b32a) {
address operator = abi.decode(data[4:], (address));
_removeOperator(safeAddress, target, operator);
}

// ERC1363 - approveAndCall(address,uint256)
else if (funcSelector == 0x3177029f) {
// Block any approveAndCall call if there is at least one tokenized asset from a collection
require(atr.numberOfTokenizedAssetsFromCollection(safeAddress, target) == 0, "Some asset from collection has transfer right token minted");

(address operator, uint256 amount) = abi.decode(data[4:], (address, uint256));
_handleERC20Approval(safeAddress, target, operator, amount);
}

// ERC1363 - approveAndCall(address,uint256,bytes)
else if (funcSelector == 0xcae9ca51) {
// Block any approveAndCall call if there is at least one tokenized asset from a collection
require(atr.numberOfTokenizedAssetsFromCollection(safeAddress, target) == 0, "Some asset from collection has transfer right token minted");

(address operator, uint256 amount,) = abi.decode(data[4:], (address, uint256, bytes));
_handleERC20Approval(safeAddress, target, operator, amount);
}
}

Events​

The Asset Transfer Rights Guard contract does not define any events or custom errors.