Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decrease Batch Transfer gas usage 7%, reduce contract deployment gas by 21% #144

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
63 changes: 22 additions & 41 deletions contracts/utils/ERC721BatchTransfer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
*/
contract ERC721BatchTransfer {
error InvalidArguments();
error NotOwnerOfToken();

event BatchTransferToSingle(
address indexed contractAddress,
Expand All @@ -37,19 +36,17 @@ contract ERC721BatchTransfer {
address to,
uint256[] calldata tokenIds
) external {
uint256 length = tokenIds.length;
for (uint256 i; i < length; ) {
uint256 tokenId = tokenIds[i];
address owner = erc721Contract.ownerOf(tokenId);
if (msg.sender != owner) {
revert NotOwnerOfToken();
}
erc721Contract.transferFrom(owner, to, tokenId);
for (uint256 i; i < tokenIds.length; ) {
erc721Contract.transferFrom(msg.sender, to, tokenIds[i]);
unchecked {
++i;
}
}
emit BatchTransferToSingle(address(erc721Contract), to, length);
emit BatchTransferToSingle(
address(erc721Contract),
to,
tokenIds.length
);
}

/**
Expand All @@ -63,19 +60,17 @@ contract ERC721BatchTransfer {
address to,
uint256[] calldata tokenIds
) external {
uint256 length = tokenIds.length;
for (uint256 i; i < length; ) {
uint256 tokenId = tokenIds[i];
address owner = erc721Contract.ownerOf(tokenId);
if (msg.sender != owner) {
revert NotOwnerOfToken();
}
erc721Contract.safeTransferFrom(owner, to, tokenId);
for (uint256 i; i < tokenIds.length; ) {
erc721Contract.safeTransferFrom(msg.sender, to, tokenIds[i]);
unchecked {
++i;
}
}
emit BatchTransferToSingle(address(erc721Contract), to, length);
emit BatchTransferToSingle(
address(erc721Contract),
to,
tokenIds.length
);
}

/**
Expand All @@ -95,23 +90,16 @@ contract ERC721BatchTransfer {
address[] calldata tos,
uint256[] calldata tokenIds
) external {
uint256 length = tokenIds.length;
if (tos.length != length) revert InvalidArguments();
if (tos.length != tokenIds.length) revert InvalidArguments();

for (uint256 i; i < length; ) {
uint256 tokenId = tokenIds[i];
address owner = erc721Contract.ownerOf(tokenId);
address to = tos[i];
if (msg.sender != owner) {
revert NotOwnerOfToken();
}
erc721Contract.transferFrom(owner, to, tokenId);
for (uint256 i; i < tokenIds.length; ) {
erc721Contract.transferFrom(msg.sender, tos[i], tokenIds[i]);
unchecked {
++i;
}
}

emit BatchTransferToMultiple(address(erc721Contract), length);
emit BatchTransferToMultiple(address(erc721Contract), tokenIds.length);
}

/**
Expand All @@ -130,22 +118,15 @@ contract ERC721BatchTransfer {
address[] calldata tos,
uint256[] calldata tokenIds
) external {
uint256 length = tokenIds.length;
if (tos.length != length) revert InvalidArguments();
if (tos.length != tokenIds.length) revert InvalidArguments();

for (uint256 i; i < length; ) {
uint256 tokenId = tokenIds[i];
address owner = erc721Contract.ownerOf(tokenId);
address to = tos[i];
if (msg.sender != owner) {
revert NotOwnerOfToken();
}
erc721Contract.safeTransferFrom(owner, to, tokenId);
for (uint256 i; i < tokenIds.length; ) {
erc721Contract.safeTransferFrom(msg.sender, tos[i], tokenIds[i]);
unchecked {
++i;
}
}

emit BatchTransferToMultiple(address(erc721Contract), length);
emit BatchTransferToMultiple(address(erc721Contract), tokenIds.length);
}
}
42 changes: 20 additions & 22 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'dotenv/config';

import "@nomicfoundation/hardhat-verify";
import '@nomicfoundation/hardhat-verify';
import '@nomiclabs/hardhat-waffle';
import '@typechain/hardhat';
import 'hardhat-contract-sizer';
Expand Down Expand Up @@ -56,7 +56,7 @@ const config: HardhatUserConfig = {
runs: 20,
details: {
yulDetails: {
optimizerSteps: "dhfoD[xarrscLMcCTU]uljmul",
optimizerSteps: 'dhfoD[xarrscLMcCTU]uljmul',
},
},
},
Expand All @@ -77,8 +77,8 @@ const config: HardhatUserConfig = {
networks: {
hardhat: {
accounts: {
accountsBalance: '1000000000000000000000'
}
accountsBalance: '1000000000000000000000',
},
},
base: {
url: process.env.BASE_URL || '',
Expand Down Expand Up @@ -130,8 +130,8 @@ const config: HardhatUserConfig = {
apiKey: process.env.ETHERSCAN_API_KEY,
},
sourcify: {
enabled: true
}
enabled: true,
},
};

task('setStages', 'Set stages for ERC721M')
Expand All @@ -146,7 +146,6 @@ task('setStages', 'Set stages for ERC721M')
)
.setAction(setStages);


task('set1155Stages', 'Set stages for ERC721M')
.addParam('contract', 'contract address')
.addParam('stages', 'stages json file')
Expand Down Expand Up @@ -181,10 +180,7 @@ task('deploy', 'Deploy ERC721M')
'ERC-20 contract address (if minting with ERC-20)',
'0x0000000000000000000000000000000000000000',
)
.addOptionalParam(
'fundreceiver',
'The treasury wallet to receive mint fund',
)
.addOptionalParam('fundreceiver', 'The treasury wallet to receive mint fund')
.addParam<boolean>(
'useoperatorfilterer',
'whether or not to use operator filterer, used with legacy 721M contract',
Expand Down Expand Up @@ -228,7 +224,6 @@ task('deploy', 'Deploy ERC721M')
await deploy(tasksArgs, hre);
});


task('deploy1155', 'Deploy ERC1155M')
.addParam('name', 'name')
.addParam('symbol', 'symbol')
Expand All @@ -240,10 +235,7 @@ task('deploy1155', 'Deploy ERC1155M')
'ERC-20 contract address (if minting with ERC-20)',
'0x0000000000000000000000000000000000000000',
)
.addOptionalParam(
'fundreceiver',
'The treasury wallet to receive mint fund',
)
.addOptionalParam('fundreceiver', 'The treasury wallet to receive mint fund')
.addParam<boolean>(
'openedition',
'whether or not a open edition mint (unlimited supply, 999,999,999)',
Expand All @@ -267,7 +259,7 @@ task('deploy1155', 'Deploy ERC1155M')
await hre.run('compile');
console.log('Deploying...');
await deploy1155(tasksArgs, hre);
});
});

task('setBaseURI', 'Set the base uri')
.addParam('uri', 'uri')
Expand Down Expand Up @@ -309,7 +301,6 @@ task('ownerMint1155', 'Mint token(s) as owner for ERC1155M')
.addOptionalParam('gaslimit', 'Set maximum gas units to spend on transaction')
.setAction(ownerMint1155);


task('setGlobalWalletLimit', 'Set the global wallet limit')
.addParam('contract', 'contract address')
.addParam('limit', 'global wallet limit (0 for no global limit)')
Expand Down Expand Up @@ -459,13 +450,16 @@ task('thawTrading', 'Thaw trading for 721Cv2')

task('cleanWhitelist', 'Clean up whitelist')
.addOptionalParam('whitelistpath', 'plain whitelist path')
.addOptionalParam('variablewalletlimitpath', 'variable wallet limit whitelist path')
.setAction(cleanWhitelist)
.addOptionalParam(
'variablewalletlimitpath',
'variable wallet limit whitelist path',
)
.setAction(cleanWhitelist);

task('deployCloneFactory', 'Deploy 721CMRoyalties clone factory')
.addOptionalParam('gaspricegwei', 'Set gas price in Gwei')
.addOptionalParam('gaslimit', 'Set maximum gas units to spend on transaction')
.setAction(deployCloneFactory)
.setAction(deployCloneFactory);

task('deployClone', 'Create 721CMRoyalties cline')
.addParam('name', 'name')
Expand All @@ -474,7 +468,11 @@ task('deployClone', 'Create 721CMRoyalties cline')
.addParam('tokenurisuffix', 'token uri suffix', '.json')
.addParam('globalwalletlimit', 'global wallet limit', '0')
.addParam('timestampexpiryseconds', 'timestamp expiry in seconds', '300')
.addParam('mintcurrency','ERC-20 contract address. 0x0 if using native token','0x0000000000000000000000000000000000000000')
.addParam(
'mintcurrency',
'ERC-20 contract address. 0x0 if using native token',
'0x0000000000000000000000000000000000000000',
)
.addParam('fundreceiver', 'The treasury wallet to receive mint fund')
.addParam('royaltyreceiver', 'erc2198 royalty receiver address')
.addParam('royaltyfeenumerator', 'erc2198 royalty fee numerator')
Expand Down
5 changes: 3 additions & 2 deletions scripts/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const ContractDetails = {
} as const;

export const ERC721BatchTransferContract =
'0x38F7ba911f7efc434D29D6E39c814E9d4De3FEef';
'0x2d9082592Db4A7C2536A1cEDc33Cc2a53a75Bf27';

export const ERC721CV2_VALIDATOR = '0x721C00182a990771244d7A71B9FA2ea789A3b433';
export const ERC721CV2_FREEZE_LEVEL = 4;
Expand All @@ -22,4 +22,5 @@ export const ERC721CV2_EMPTY_LIST = 4;
// Mainnet, Polygon, Base
export const ERC721CMRoyaltiesCloneFactoryContract = '0x7cEEd7215D71393d56966dA48C5727851326e101';

export const RESERVOIR_RELAYER_EOA = '0xf70da97812CB96acDF810712Aa562db8dfA3dbEF';
export const RESERVOIR_RELAYER_EOA = '0xf70da97812CB96acDF810712Aa562db8dfA3dbEF';

22 changes: 20 additions & 2 deletions test/ERC721BatchTransfer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('ERC721BatchTransfer', function () {

await nftContract.setApprovalForAll(transferContract.address, true);

await nftContract.mintBatch(owner.address, 5);
await nftContract.mintBatch(owner.address, 1000);

for (let i = 0; i < 5; i++) {
const tokenOwner = await nftContract.ownerOf(i);
Expand All @@ -56,6 +56,24 @@ describe('ERC721BatchTransfer', function () {
}
});

it.skip('batchTransferToSingleWallet max Batch', async () => {
// old cap was 777
const batchAmount = 834;
const tx = await transferContract.batchTransferToSingleWallet(
nftContract.address,
addresses.addr1,
Array.from({ length: batchAmount }, (_, i) => i),
);
const receipt = await tx.wait();

console.log(receipt.gasUsed);

for (let i = 0; i < batchAmount; i++) {
const tokenOwner = await nftContract.ownerOf(i);
expect(tokenOwner).to.equal(addresses.addr1);
}
});

it('safeBatchTransferToSingleWallet', async () => {
const tokenIds = [0, 1, 2, 3, 4];
await transferContract.safeBatchTransferToSingleWallet(
Expand Down Expand Up @@ -130,7 +148,7 @@ describe('ERC721BatchTransfer', function () {
tos,
tokenIds,
),
).to.be.revertedWith('NotOwnerOfToken');
).to.be.revertedWith('TransferFromIncorrectOwner()');
});

it('revert if invalid arguments', async () => {
Expand Down