From 7c6b047c0fbeec83fc66def067c2bd861aa18581 Mon Sep 17 00:00:00 2001 From: Smart Contrart Date: Fri, 23 Feb 2024 07:06:14 -0500 Subject: [PATCH 1/4] Created Registry interface and imported in BatchCall --- source/registry/contracts/Registry.sol | 23 +----- .../contracts/interfaces/IRegistry.sol | 77 +++++++++++++++++++ 2 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 source/registry/contracts/interfaces/IRegistry.sol diff --git a/source/registry/contracts/Registry.sol b/source/registry/contracts/Registry.sol index d2bcd82f6..600addca4 100644 --- a/source/registry/contracts/Registry.sol +++ b/source/registry/contracts/Registry.sol @@ -5,12 +5,13 @@ pragma solidity 0.8.23; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import "./interfaces/IRegistry.sol"; /** * @title AirSwap: Server Registry * @notice https://www.airswap.io/ */ -contract Registry { +contract Registry is IRegistry { using SafeERC20 for IERC20; using EnumerableSet for EnumerableSet.AddressSet; using EnumerableSet for EnumerableSet.Bytes32Set; @@ -25,26 +26,6 @@ contract Registry { mapping(address => EnumerableSet.AddressSet) internal tokensByStaker; mapping(address => EnumerableSet.AddressSet) internal stakersByToken; - event SetServerURL(address indexed staker, string url); - event AddProtocols(address indexed staker, bytes4[] protocols); - event AddTokens(address indexed staker, address[] tokens); - event RemoveProtocols(address indexed staker, bytes4[] protocols); - event RemoveTokens(address indexed staker, address[] tokens); - event UnsetServer( - address indexed staker, - string url, - bytes4[] protocols, - address[] tokens - ); - - error ArgumentInvalid(); - error NoServerURLSet(); - error ProtocolDoesNotExist(bytes4); - error ProtocolExists(bytes4); - error TokenDoesNotExist(address); - error TokenExists(address); - error ServerURLInvalid(); - /** * @notice Registry constructor * @param _stakingToken IERC20 address of token used for staking diff --git a/source/registry/contracts/interfaces/IRegistry.sol b/source/registry/contracts/interfaces/IRegistry.sol new file mode 100644 index 000000000..8fd1e8226 --- /dev/null +++ b/source/registry/contracts/interfaces/IRegistry.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.23; + +interface IRegistry { + event SetServerURL(address indexed staker, string url); + event AddProtocols(address indexed staker, bytes4[] protocols); + event AddTokens(address indexed staker, address[] tokens); + event RemoveProtocols(address indexed staker, bytes4[] protocols); + event RemoveTokens(address indexed staker, address[] tokens); + event UnsetServer( + address indexed staker, + string url, + bytes4[] protocols, + address[] tokens + ); + + error ArgumentInvalid(); + error NoServerURLSet(); + error ProtocolDoesNotExist(bytes4); + error ProtocolExists(bytes4); + error TokenDoesNotExist(address); + error TokenExists(address); + error ServerURLInvalid(); + + function setServerURL(string calldata _url) external; + + function unsetServer() external; + + function addProtocols(bytes4[] calldata _protocols) external; + + function removeProtocols(bytes4[] calldata _protocols) external; + + function getServerURLsForProtocol( + bytes4 _protocol + ) external view returns (string[] memory _urls); + + function supportsProtocol( + address _staker, + bytes4 _protocol + ) external view returns (bool); + + function getProtocolsForStaker( + address _staker + ) external view returns (bytes4[] memory _protocolList); + + function getStakersForProtocol( + bytes4 _protocol + ) external view returns (address[] memory _stakers); + + function addTokens(address[] calldata _tokens) external; + + function removeTokens(address[] calldata _tokens) external; + + function getServerURLsForToken( + address _token + ) external view returns (string[] memory urls); + + function supportsToken( + address _staker, + address _token + ) external view returns (bool); + + function getTokensForStaker( + address _staker + ) external view returns (address[] memory tokenList); + + function getStakersForToken( + address _token + ) external view returns (address[] memory _stakers); + + function getServerURLsForStakers( + address[] calldata _stakers + ) external view returns (string[] memory _urls); + + function balanceOf(address _staker) external view returns (uint256); +} From e7a6ff1783f4cfaacc40a3c076bf79fc9f3889a3 Mon Sep 17 00:00:00 2001 From: Smart Contrart Date: Fri, 23 Feb 2024 11:13:40 -0500 Subject: [PATCH 2/4] Created getTokensForStakers --- source/batch-call/contracts/BatchCall.sol | 24 ++++++ source/batch-call/test/BatchCall.js | 95 ++++++++++++++++++++++- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/source/batch-call/contracts/BatchCall.sol b/source/batch-call/contracts/BatchCall.sol index 197d8c1c4..3964ba8cb 100644 --- a/source/batch-call/contracts/BatchCall.sol +++ b/source/batch-call/contracts/BatchCall.sol @@ -6,6 +6,8 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@airswap/swap/contracts/interfaces/ISwap.sol"; import "@airswap/swap-erc20/contracts/interfaces/ISwapERC20.sol"; +import "@airswap/registry/contracts/interfaces/IRegistry.sol"; +import "hardhat/console.sol"; /** * @title BatchCall: Batch balance, allowance, order validity checks, nonce usage check @@ -290,4 +292,26 @@ contract BatchCall { } return nonceUsed; } + + /** + * @notice provides the tokens supported by multiple Stakers + * @param stakers address[] list of stakers to be checked + * @param registryContract IRegistry Registry contract to call + * @return bool[] true indicates the nonce is used + */ + function getTokensForStakers( + address[] calldata stakers, + IRegistry registryContract + ) external view returns (address[][] memory) { + if (stakers.length == 0) revert ArgumentInvalid(); + address[][] memory tokensSupported = new address[][](stakers.length); + + for (uint256 i; i < stakers.length; ) { + tokensSupported[i] = registryContract.getTokensForStaker(stakers[i]); + unchecked { + ++i; + } + } + return tokensSupported; + } } diff --git a/source/batch-call/test/BatchCall.js b/source/batch-call/test/BatchCall.js index 721bb6665..7fe37bab7 100644 --- a/source/batch-call/test/BatchCall.js +++ b/source/batch-call/test/BatchCall.js @@ -3,6 +3,7 @@ const { ethers, waffle } = require('hardhat') const { deployMockContract } = waffle const IERC20 = require('@openzeppelin/contracts/build/contracts/IERC20.json') const IERC721 = require('@openzeppelin/contracts/build/contracts/ERC721Royalty.json') +const REGISTRY = require('@airswap/registry/build/contracts/Registry.sol/Registry.json') const SWAP = require('@airswap/swap/build/contracts/Swap.sol/Swap.json') const SWAP_ERC20 = require('@airswap/swap-erc20/build/contracts/SwapERC20.sol/SwapERC20.json') const ERC20_ADAPTER = require('@airswap/swap/build/contracts/adapters/ERC20Adapter.sol/ERC20Adapter.json') @@ -22,6 +23,8 @@ const DEFAULT_AMOUNT = '1000' const DEFAULT_BALANCE = '100000' const BONUS_SCALE = '10' const BONUS_MAX = '100' +const STAKING_COST = '1000000000' +const SUPPORT_COST = '1000000' let snapshotId let deployer @@ -31,6 +34,7 @@ let erc20token let erc20adapter let erc721token let erc721adapter +let registry let swap let swapERC20 let batchCall @@ -107,6 +111,15 @@ async function setUpAllowances(senderAmount, signerAmount) { async function setUpBalances(senderAmount, signerAmount) { await erc20token.mock.balanceOf.withArgs(sender.address).returns(senderAmount) await erc20token.mock.balanceOf.withArgs(signer.address).returns(signerAmount) + await erc20token.mock.balanceOf + .withArgs(staker1.address) + .returns(STAKING_COST) + await erc20token.mock.balanceOf + .withArgs(staker2.address) + .returns(STAKING_COST) + await erc20token.mock.balanceOf + .withArgs(staker3.address) + .returns(STAKING_COST) } describe('BatchCall Integration', () => { @@ -119,8 +132,17 @@ describe('BatchCall Integration', () => { }) before('deploy adapter and swap', async () => { - ;[deployer, sender, signer, affiliate, protocolFeeWallet, anyone] = - await ethers.getSigners() + ;[ + deployer, + sender, + signer, + affiliate, + protocolFeeWallet, + anyone, + staker1, + staker2, + staker3, + ] = await ethers.getSigners() erc20token = await deployMockContract(deployer, IERC20.abi) await erc20token.mock.allowance.returns(DEFAULT_AMOUNT) await erc20token.mock.balanceOf.returns(DEFAULT_AMOUNT) @@ -143,6 +165,12 @@ describe('BatchCall Integration', () => { ) ).deploy() await erc721adapter.deployed() + + registry = await ( + await ethers.getContractFactory(REGISTRY.abi, REGISTRY.bytecode) + ).deploy(erc20token.address, STAKING_COST, SUPPORT_COST) + await registry.deployed() + swap = await ( await ethers.getContractFactory(SWAP.abi, SWAP.bytecode) ).deploy( @@ -329,4 +357,67 @@ describe('BatchCall Integration', () => { ) }) }) + + describe('returns tokensSupported for mutiple stakers', () => { + it('returns all tokens supported by multiple stakers', async () => { + const tokensSupported = [] + for (let i = 0; i < 10; i++) { + tokensSupported[i] = await deployMockContract(deployer, IERC20.abi) + } + registry + .connect(staker1) + .addTokens([ + tokensSupported[0].address, + tokensSupported[1].address, + tokensSupported[2].address, + ]) + registry + .connect(staker2) + .addTokens([ + tokensSupported[3].address, + tokensSupported[4].address, + tokensSupported[5].address, + ]) + registry + .connect(staker3) + .addTokens([ + tokensSupported[6].address, + tokensSupported[7].address, + tokensSupported[8].address, + tokensSupported[9].address, + ]) + const expectedTokensSupported = await batchCall + .connect(deployer) + .getTokensForStakers( + [staker1.address, staker2.address, staker3.address], + registry.address + ) + expect(expectedTokensSupported.toString()).to.equal( + [ + [ + tokensSupported[0].address, + tokensSupported[1].address, + tokensSupported[2].address, + ], + [ + tokensSupported[3].address, + tokensSupported[4].address, + tokensSupported[5].address, + ], + [ + tokensSupported[6].address, + tokensSupported[7].address, + tokensSupported[8].address, + tokensSupported[9].address, + ], + ].toString() + ) + }) + + it('reverts if a wrong argument is passed', async () => { + await expect( + batchCall.getTokensForStakers([], registry.address) + ).to.be.revertedWith('ArgumentInvalid') + }) + }) }) From 01973530185dcd4d8ccb3da27afe3491e28ad7eb Mon Sep 17 00:00:00 2001 From: Smart Contrart Date: Fri, 23 Feb 2024 12:35:48 -0500 Subject: [PATCH 3/4] Ran Prettier --- source/batch-call/contracts/BatchCall.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/batch-call/contracts/BatchCall.sol b/source/batch-call/contracts/BatchCall.sol index 3964ba8cb..1ab886c29 100644 --- a/source/batch-call/contracts/BatchCall.sol +++ b/source/batch-call/contracts/BatchCall.sol @@ -293,7 +293,7 @@ contract BatchCall { return nonceUsed; } - /** + /** * @notice provides the tokens supported by multiple Stakers * @param stakers address[] list of stakers to be checked * @param registryContract IRegistry Registry contract to call From 04a1826a611c10742d797a9c94a092614dfc49f9 Mon Sep 17 00:00:00 2001 From: Smart Contrart Date: Mon, 18 Mar 2024 20:58:03 -0400 Subject: [PATCH 4/4] Switched to solady lib for ERC20 calls --- source/batch-call/contracts/BatchCall.sol | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/source/batch-call/contracts/BatchCall.sol b/source/batch-call/contracts/BatchCall.sol index 1ab886c29..7da409b68 100644 --- a/source/batch-call/contracts/BatchCall.sol +++ b/source/batch-call/contracts/BatchCall.sol @@ -1,19 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.23; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { ERC20 } from "solady/src/tokens/ERC20.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@airswap/swap/contracts/interfaces/ISwap.sol"; import "@airswap/swap-erc20/contracts/interfaces/ISwapERC20.sol"; import "@airswap/registry/contracts/interfaces/IRegistry.sol"; -import "hardhat/console.sol"; /** * @title BatchCall: Batch balance, allowance, order validity checks, nonce usage check */ contract BatchCall { - using SafeERC20 for IERC20; using Address for address; error ArgumentInvalid(); @@ -30,7 +27,7 @@ contract BatchCall { address tokenAddress ) public view returns (uint256) { if (tokenAddress.isContract()) { - IERC20 token = IERC20(tokenAddress); + ERC20 token = ERC20(tokenAddress); // Check if balanceOf succeeds. (bool success, ) = address(token).staticcall( abi.encodeWithSelector(token.balanceOf.selector, userAddress) @@ -121,7 +118,7 @@ contract BatchCall { address tokenAddress ) public view returns (uint256) { if (tokenAddress.isContract()) { - IERC20 token = IERC20(tokenAddress); + ERC20 token = ERC20(tokenAddress); // Check if allowance succeeds as a call else returns 0. (bool success, ) = address(token).staticcall( abi.encodeWithSelector(