Skip to content

Conversation

@shunkakinoki
Copy link
Collaborator

@shunkakinoki shunkakinoki commented Dec 16, 2025

  • Added TrailsValidator contract with functions to validate token balances, allowances, and ownership.
  • Implemented deployment script for TrailsValidator using SingletonDeployer.
  • Generated run-latest.json for deployment transaction details.

Ported RequireUtils to TrailsValidator with modifications to use msg.sender
From: https://github.com/0xsequence/wallet-contracts/blob/db6789c3f8ad774dc55253f0599e0f2f0833f76a/contracts/modules/utils/RequireUtils.sol

…r ERC20, ERC721, and ERC1155 tokens

- Added TrailsValidator contract with functions to validate token balances, allowances, and ownership.
- Implemented deployment script for TrailsValidator using SingletonDeployer.
- Generated run-latest.json for deployment transaction details.
@shunkakinoki shunkakinoki marked this pull request as ready for review December 16, 2025 14:34
@shunkakinoki shunkakinoki requested review from a team and Copilot December 16, 2025 14:34
@shunkakinoki shunkakinoki merged commit 2608a40 into master Dec 16, 2025
1 check passed
@shunkakinoki shunkakinoki deleted the trails-validator-patch branch December 16, 2025 14:34
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a TrailsValidator utility contract ported from Sequence's RequireUtils, adapted to use msg.sender for validation checks. The contract provides validation functions for token balances, allowances, ownership, and expiration checks that can be used in Trails intent transactions for counterfactual address derivation.

Key Changes

  • Added TrailsValidator contract with 8 validation functions for ERC20, ERC721, ERC1155 tokens and expiration checks
  • Implemented CREATE2 deployment script using SingletonDeployer for deterministic cross-chain addresses
  • Generated deployment artifacts for multiple chains (Base, Arbitrum, Polygon, Optimism)

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

File Description
src/TrailsValidator.sol New validator contract with view functions for checking token balances, allowances, ownership, and expirations using msg.sender
script/TrailsValidator.s.sol Deployment script using SingletonDeployer with zero salt for CREATE2 deployment
broadcast/TrailsValidator.s.sol//run-.json Deployment artifacts showing successful contract deployments across multiple chains with consistent addresses (0x1882898c585ad2577944373dff44ebc234b35eaa)
broadcast/TrailsIntentEntrypoint.s.sol/137/run-*.json Updated deployment artifacts for TrailsIntentEntrypoint contract on Polygon with new deployment address

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +19 to +102
contract TrailsValidator {
/**
* @notice Validates that a given expiration hasn't expired
* @dev Used as an optional transaction on a Sequence batch, to create expirable transactions.
* @param _expiration Expiration timestamp to check
*/
function requireNonExpired(uint256 _expiration) external view {
require(block.timestamp < _expiration, "TrailsValidator#requireNonExpired: EXPIRED");
}

/**
* @notice Validates that msg.sender has a minimum ERC20 token balance
* @param _token ERC20 token address
* @param _minBalance Minimum required balance
*/
function requireMinERC20Balance(address _token, uint256 _minBalance) external view {
uint256 balance = IERC20(_token).balanceOf(msg.sender);
require(balance >= _minBalance, "TrailsValidator#requireMinERC20Balance: BALANCE_TOO_LOW");
}

/**
* @notice Validates that msg.sender has a minimum native token balance
* @param _minBalance Minimum required balance
*/
function requireMinNativeBalance(uint256 _minBalance) external view {
require(msg.sender.balance >= _minBalance, "TrailsValidator#requireMinNativeBalance: BALANCE_TOO_LOW");
}

/**
* @notice Validates that msg.sender has a minimum ERC20 allowance for a spender
* @param _token ERC20 token address
* @param _spender Address allowed to spend the tokens
* @param _minAllowance Minimum required allowance
*/
function requireMinERC20Allowance(address _token, address _spender, uint256 _minAllowance) external view {
uint256 allowance = IERC20(_token).allowance(msg.sender, _spender);
require(allowance >= _minAllowance, "TrailsValidator#requireMinERC20Allowance: ALLOWANCE_TOO_LOW");
}

/**
* @notice Validates that msg.sender owns a specific ERC721 token
* @param _token ERC721 token address
* @param _tokenId Token ID to check for ownership
*/
function requireERC721Ownership(address _token, uint256 _tokenId) external view {
address owner = IERC721(_token).ownerOf(_tokenId);
require(owner == msg.sender, "TrailsValidator#requireERC721Ownership: NOT_OWNER");
}

/**
* @notice Validates that an ERC721 token owned by msg.sender is approved for a specific spender
* @param _token ERC721 token address
* @param _spender Address that should have approval
* @param _tokenId Token ID to check for approval
*/
function requireERC721Approval(address _token, address _spender, uint256 _tokenId) external view {
address approved = IERC721(_token).getApproved(_tokenId);
require(
approved == _spender || IERC721(_token).isApprovedForAll(msg.sender, _spender),
"TrailsValidator#requireERC721Approval: NOT_APPROVED"
);
}

/**
* @notice Validates that msg.sender has a minimum balance of an ERC1155 token
* @param _token ERC1155 token address
* @param _tokenId Token ID to check
* @param _minBalance Minimum required balance
*/
function requireMinERC1155Balance(address _token, uint256 _tokenId, uint256 _minBalance) external view {
uint256 balance = IERC1155(_token).balanceOf(msg.sender, _tokenId);
require(balance >= _minBalance, "TrailsValidator#requireMinERC1155Balance: BALANCE_TOO_LOW");
}

/**
* @notice Validates that an ERC1155 token is approved for a specific operator by msg.sender
* @param _token ERC1155 token address
* @param _operator Address that should have operator approval
*/
function requireERC1155Approval(address _token, address _operator) external view {
bool isApproved = IERC1155(_token).isApprovedForAll(msg.sender, _operator);
require(isApproved, "TrailsValidator#requireERC1155Approval: NOT_APPROVED");
}
}
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TrailsValidator contract lacks test coverage. The repository uses comprehensive automated testing (as seen in TrailsIntentEntrypoint.t.sol), but no tests exist for the TrailsValidator contract. Consider adding tests to verify all validation functions work correctly with various scenarios including edge cases, reverts with correct error messages, and interactions with different token standards.

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +80
function requireERC721Approval(address _token, address _spender, uint256 _tokenId) external view {
address approved = IERC721(_token).getApproved(_tokenId);
require(
approved == _spender || IERC721(_token).isApprovedForAll(msg.sender, _spender),
"TrailsValidator#requireERC721Approval: NOT_APPROVED"
);
}
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The requireERC721Approval function does not verify that msg.sender actually owns the token before checking approval. This could allow checking approval status for tokens not owned by the caller. Consider adding an ownership check similar to requireERC721Ownership to ensure msg.sender is the actual owner of the token before validating approval.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants