This contract is responsible for checking valid recipient permissions and tracking granted and revoked permissions. It is necessary to prevent the stalking attack.
- RecipientPermissionManager.sol is written in Solidity version 0.8.15
- Checks for valid recipient permissions
- Tracks granted and revoked permissions
- Provides a function to hash the
RecipientPermission
struct
grantRecipientPermission
A function to grant an on-chain recipient permission.
This function takes one argument supplied by the caller:
RecipientPermission calldata
permission
- The recipient permission struct
function grantRecipientPermission(RecipientPermission calldata permission) external {
// Check that caller is permission signer
require(msg.sender == permission.recipient, "Sender is not permission recipient");
bytes32 permissionHash = recipientPermissionHash(permission);
// Check that permission is not have been granted
require(grantedPermissions[permissionHash] == false, "Recipient permission is granted");
// Check that permission is not have been revoked
require(revokedPermissionNonces[msg.sender][permission.nonce] == false, "Recipient permission nonce is revoked");
// Grant permission
grantedPermissions[permissionHash] = true;
// Emit event
emit RecipientPermissionGranted(permissionHash);
}
revokeRecipientPermission
A function to revoke permissions.
This function takes one argument supplied by the caller:
bytes32
permissionNonce
- A nonce of the permission being revoked for the caller
function revokeRecipientPermission(bytes32 permissionNonce) external {
// Check that permission is has not been revoked
require(
revokedPermissionNonces[msg.sender][permissionNonce] == false,
"Recipient permission nonce is revoked"
);
// Revoke permission
revokedPermissionNonces[msg.sender][permissionNonce] = true;
// Emit event
emit RecipientPermissionNonceRevoked(msg.sender, permissionNonce);
}
recipientPermissionHash
A function to hash a permission struct using keccak256
.
This function takes one argument supplied by the caller:
RecipientPermission memory
permission
- The recipient permission struct
function recipientPermissionHash(RecipientPermission memory permission) public view returns (bytes32) {
return keccak256(abi.encodePacked(
"\x19\x01",
// Domain separator is composing to prevent replay attack in case of an Ethereum fork
keccak256(abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("AssetTransferRights")),
keccak256(bytes("0.1")),
block.chainid,
address(this)
)),
keccak256(abi.encode(
RECIPIENT_PERMISSION_TYPEHASH,
permission.assetCategory,
permission.assetAddress,
permission.assetId,
permission.assetAmount,
permission.ignoreAssetIdAndAmount,
permission.recipient,
permission.expiration,
permission.isPersistent,
permission.nonce
))
));
}
_checkValidPermission
An internal function that verifies the validity of supplied permission and marks it as used.
This function takes four arguments supplied by the ATR Module:
address
sender
- Address of the account that is sending the assetMultiToken.Asset memory
asset
- An asset struct (see MultiToken)RecipientPermission memory
permission
- The recipient permission structbytes calldata
permissionSignature
- EIP-712 raw signature of the recipient permission struct. This signature is not required if the permission was granted on-chain
function _useValidPermission(
address sender,
MultiToken.Asset memory asset,
RecipientPermission memory permission,
bytes calldata permissionSignature
) internal {
// Check that permission is not expired
uint40 expiration = permission.expiration;
require(expiration == 0 || block.timestamp < expiration, "Recipient permission is expired");
// Check permitted agent
address agent = permission.agent;
require(agent == address(0) || sender == agent, "Caller is not permitted agent");
// Check correct asset
require(permission.assetCategory == asset.category, "Invalid permitted asset");
require(permission.assetAddress == asset.assetAddress, "Invalid permitted asset");
// Check id and amount if ignore flag is false
if (permission.ignoreAssetIdAndAmount == false) {
require(permission.assetId == asset.id, "Invalid permitted asset");
require(permission.assetAmount == asset.amount, "Invalid permitted asset");
} // Skip id and amount check if ignore flag is true
// Check that permission nonce is not revoked
address recipient = permission.recipient;
bytes32 nonce = permission.nonce;
require(revokedPermissionNonces[recipient][nonce] == false, "Recipient permission nonce is revoked");
// Compute EIP-712 structured data hash
bytes32 permissionHash = recipientPermissionHash(permission);
// Check that permission is granted
// Via on-chain tx, EIP-1271 or off-chain signature
if (grantedPermissions[permissionHash] == true) {
// Permission is granted on-chain, no need to check signature
} else if (recipient.code.length > 0) {
// Check that permission is valid
require(IERC1271(recipient).isValidSignature(permissionHash, permissionSignature) == EIP1271_VALID_SIGNATURE, "Signature on behalf of contract is invalid");
} else {
// Check that permission signature is valid
require(ECDSA.recover(permissionHash, permissionSignature) == recipient, "Permission signer is not stated as recipient");
}
// Mark used permission nonce as revoked if not persistent
if (permission.isPersistent == false) {
revokedPermissionNonces[recipient][nonce] = true;
emit RecipientPermissionNonceRevoked(recipient, nonce);
}
}
Type | Name | Comment |
---|---|---|
MultiToken.Category | assetCategory | Category of the asset that is permitted to transfer (see MultiToken for more information) |
address | assetAddress | Contract address of the asset that is permitted to transfer |
uint256 | assetId | ID of the asset that is permitted to transfer |
uint256 | assetAmount | Amount of the asset that is permitted to transfer |
bool | ignoreAssetIdAndAmount | Flag stating if asset ID and amount are ignored when checking the permissioned asset |
address | recipient | Address of the recipient and permission signer |
address | agent | Optional address of a permitted agent that can process the permission. If this value is zero, any agent can process the permission |
uint40 | expiration | Optional permission expiration timestamp in seconds. If this value is zero, the permission doesn't have an expiration |
bool | isPersistent | Flag stating if a permission is valid for more than one use |
bytes32 | nonce | Additional value to distinguish otherwise identical permissions |
The Recipient Permission Manager contract defines two events and no custom errors.
event RecipientPermissionGranted(bytes32 indexed permissionHash);
event RecipientPermissionNonceRevoked(address indexed recipient, bytes32 indexed permissionNonce);
RecipientPermissionGranted
RecipientPermissionGranted event is emitted when on-chain recipient permission is granted.
This event has one parameter:
bytes32 indexed
permissionHash
- Hash of the recipient permission struct returned by the recipientPermissionHash function
RecipientPermissionNonceRevoked
RecipientPermissionNonceRevoked event is emitted when a recipient revokes previously granted permission.
This event has two parameters:
address indexed
recipient
- Address of the recipient who revoked the permissionbytes32 indexed
permissionNonce
- Nonce of the revoked permission (see RecipientPermission struct for more information)