ATR Guard
Last updated
Last updated
Asset Transfer Rights Guard is responsible for enforcing asset transfer rules. It checks the state before and after a transaction execution.
AssetTransferRightsGuard.sol is written in Solidity version 0.8.15
Checks every Safe transaction before and after its execution making sure that Asset Transfer Rights token rules are enforced
This contract inherits other contracts. Please see their reference for a complete overview of the ATR Guard.
checkTransaction
A hook called by the PWN Safe to check a transaction before its execution.
This function takes the transaction parameters as arguments (see 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
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.
function checkAfterExecution(bytes32 /*txHash*/, bool success) view external {
if (success)
require(atr.hasSufficientTokenizedBalance(msg.sender), "Insufficient tokenized balance");
}
hasOperatorFor
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:
address
safeAddress
- Address of the PWN Safe to check
address
assetAddress
- Address of the asset collection to check
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
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.
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
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:
address
safeAddress
- Address of the PWN Safe that initiated the transaction
address
target
- Target contract that is being called
bytes calldata
data
- Transaction calldata
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);
}
}
The Asset Transfer Rights Guard contract does not define any events or custom errors.