Skip to content

Commit

Permalink
Add functions to RequireUtils module (#188)
Browse files Browse the repository at this point in the history
* forge install: openzeppelin-contracts

v4.8.0

* add new functions to require utils

* all tests passed

* format fixing

* remove temp files

* fix other format

* change test funcion name

* remove unnecessary comments on tokens mocks
  • Loading branch information
nachopiris authored Sep 26, 2024
1 parent 21508fc commit db6789c
Show file tree
Hide file tree
Showing 10 changed files with 419 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
url = https://github.com/foundry-rs/forge-std
[submodule "lib/foundry-huff"]
path = lib/foundry-huff
url = https://github.com/huff-language/foundry-huff
url = https://github.com/huff-language/foundry-huff
22 changes: 22 additions & 0 deletions contracts/interfaces/tokens/IERC1155.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

interface IERC1155 {
function balanceOf(address account, uint256 id) external view returns (uint256);
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address account, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external;

event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
event URI(string value, uint256 indexed id);
}
14 changes: 14 additions & 0 deletions contracts/interfaces/tokens/IERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
18 changes: 18 additions & 0 deletions contracts/interfaces/tokens/IERC721.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

interface IERC721 {
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
}
63 changes: 63 additions & 0 deletions contracts/mocks/ERC1155Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract ERC1155Mock {
string public name = 'Mock ERC1155 Token';
string public symbol = 'MERC1155';
address public owner;

mapping(uint256 => mapping(address => uint256)) public balances;
mapping(address => mapping(address => bool)) public operatorApprovals;

constructor() {
owner = msg.sender;
}

modifier onlyOwner() {
require(msg.sender == owner, 'Only owner can mint');
_;
}

function balanceOf(address account, uint256 id) public view returns (uint256) {
return balances[id][account];
}

function balanceOfBatch(address[] memory accounts, uint256[] memory ids) public view returns (uint256[] memory) {
require(accounts.length == ids.length, 'Accounts and ids length mismatch');

uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; ++i) {
batchBalances[i] = balances[ids[i]][accounts[i]];
}
return batchBalances;
}

function mint(address to, uint256 id, uint256 amount) public onlyOwner {
require(to != address(0), 'Cannot mint to zero address');

balances[id][to] += amount;
emit TransferSingle(msg.sender, address(0), to, id, amount);
}

function safeTransferFrom(address from, address to, uint256 id, uint256 amount) public {
require(from == msg.sender || isApprovedForAll(from, msg.sender), 'Not approved to transfer');

require(balances[id][from] >= amount, 'Insufficient balance');
balances[id][from] -= amount;
balances[id][to] += amount;

emit TransferSingle(msg.sender, from, to, id, amount);
}

function setApprovalForAll(address operator, bool approved) public {
operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}

function isApprovedForAll(address account, address operator) public view returns (bool) {
return operatorApprovals[account][operator];
}

event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
}
51 changes: 51 additions & 0 deletions contracts/mocks/ERC20Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract ERC20Mock {
string public name = 'Mock ERC20 Token';
string public symbol = 'MERC20';
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balances;
mapping(address => mapping(address => uint256)) public allowances;

constructor(uint256 initialSupply) {
totalSupply = initialSupply;
balances[msg.sender] = initialSupply;
}

function balanceOf(address account) public view returns (uint256) {
return balances[account];
}

function transfer(address recipient, uint256 amount) public returns (bool) {
require(balances[msg.sender] >= amount, 'Insufficient balance');
balances[msg.sender] -= amount;
balances[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}

function allowance(address owner, address spender) public view returns (uint256) {
return allowances[owner][spender];
}

function approve(address spender, uint256 amount) public returns (bool) {
allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}

function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
require(balances[sender] >= amount, 'Insufficient balance');
require(allowances[sender][msg.sender] >= amount, 'Allowance exceeded');
balances[sender] -= amount;
balances[recipient] += amount;
allowances[sender][msg.sender] -= amount;
emit Transfer(sender, recipient, amount);
return true;
}

event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
86 changes: 86 additions & 0 deletions contracts/mocks/ERC721Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract ERC721Mock {
string public name = 'Mock ERC721 Token';
string public symbol = 'MERC721';
uint256 public totalSupply;
address public owner;

mapping(address => uint256) public balances;
mapping(uint256 => address) public owners;
mapping(address => mapping(address => bool)) public operatorApprovals;
mapping(uint256 => address) public tokenApprovals;

constructor() {
owner = msg.sender;
}

modifier onlyOwner() {
require(msg.sender == owner, 'Only owner can mint');
_;
}

function balanceOf(address _owner) public view returns (uint256) {
return balances[_owner];
}

function ownerOf(uint256 tokenId) public view returns (address) {
address tokenOwner = owners[tokenId];
require(tokenOwner != address(0), 'Token does not exist');
return tokenOwner;
}

function mint(address to, uint256 tokenId) public onlyOwner {
require(to != address(0), 'Cannot mint to zero address');
require(owners[tokenId] == address(0), 'Token already minted');

owners[tokenId] = to;
balances[to] += 1;
totalSupply += 1;

emit Transfer(address(0), to, tokenId);
}

function transferFrom(address from, address to, uint256 tokenId) public {
require(ownerOf(tokenId) == from, 'Not the owner of the token');
require(to != address(0), 'Cannot transfer to zero address');

require(
msg.sender == from || getApproved(tokenId) == msg.sender || isApprovedForAll(from, msg.sender),
'Not approved to transfer'
);

balances[from] -= 1;
balances[to] += 1;
owners[tokenId] = to;

emit Transfer(from, to, tokenId);
}

function approve(address to, uint256 tokenId) public {
address tokenOwner = ownerOf(tokenId);
require(to != tokenOwner, 'Cannot approve current owner');
require(msg.sender == tokenOwner || isApprovedForAll(tokenOwner, msg.sender), 'Not approved');

tokenApprovals[tokenId] = to;
emit Approval(tokenOwner, to, tokenId);
}

function getApproved(uint256 tokenId) public view returns (address) {
return tokenApprovals[tokenId];
}

function setApprovalForAll(address operator, bool approved) public {
operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}

function isApprovedForAll(address _owner, address operator) public view returns (bool) {
return operatorApprovals[_owner][operator];
}

event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
}
75 changes: 75 additions & 0 deletions contracts/modules/utils/RequireUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ pragma solidity 0.8.18;
import "../commons/ModuleNonce.sol";
import "../commons/submodules/nonce/SubModuleNonce.sol";

import "../../interfaces/tokens/IERC20.sol";
import "../../interfaces/tokens/IERC721.sol";
import "../../interfaces/tokens/IERC1155.sol";

contract RequireUtils {
/**
Expand All @@ -28,4 +31,76 @@ contract RequireUtils {
uint256 currentNonce = ModuleNonce(_wallet).readNonce(space);
require(currentNonce >= nonce, "RequireUtils#requireMinNonce: NONCE_BELOW_REQUIRED");
}

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

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

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

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

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

/**
* @notice Validates that an ERC1155 token is approved for a specific operator
* @param _token ERC1155 token address
* @param _owner Sequence wallet
* @param _operator Address that should have operator approval
*/
function requireERC1155Approval(address _token, address _owner, address _operator) external view {
bool isApproved = IERC1155(_token).isApprovedForAll(_owner, _operator);
require(isApproved, 'RequireUtils#requireERC1155Approval: NOT_APPROVED');
}
}
Loading

0 comments on commit db6789c

Please sign in to comment.