From ccb98622c944ab9ef296a835a1ae36c75c80848e Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Wed, 23 Nov 2022 13:57:35 +0100 Subject: [PATCH 1/6] Add support for ERC721 and ERC1155 --- contracts/ERC1155Pods.sol | 81 +++++++++++++ contracts/ERC20Pods.sol | 127 +++----------------- contracts/ERC721Pods.sol | 71 ++++++++++++ contracts/Pod.sol | 19 ++- contracts/TokenPodsLib.sol | 159 ++++++++++++++++++++++++++ contracts/interfaces/IERC1155Pods.sol | 17 +++ contracts/interfaces/IERC721Pods.sol | 17 +++ contracts/interfaces/IPod.sol | 3 +- contracts/mocks/PodMock.sol | 8 +- contracts/mocks/WrongPodMock.sol | 4 +- test/ERC20Pods.js | 4 +- test/behaviors/ERC20Pods.behavior.js | 14 +-- 12 files changed, 394 insertions(+), 130 deletions(-) create mode 100644 contracts/ERC1155Pods.sol create mode 100644 contracts/ERC721Pods.sol create mode 100644 contracts/TokenPodsLib.sol create mode 100644 contracts/interfaces/IERC1155Pods.sol create mode 100644 contracts/interfaces/IERC721Pods.sol diff --git a/contracts/ERC1155Pods.sol b/contracts/ERC1155Pods.sol new file mode 100644 index 0000000..9a3c7eb --- /dev/null +++ b/contracts/ERC1155Pods.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; + +import "./interfaces/IERC1155Pods.sol"; +import "./TokenPodsLib.sol"; +import "./libs/ReentrancyGuard.sol"; + +abstract contract ERC1155Pods is ERC1155, IERC1155Pods, ReentrancyGuardExt { + using TokenPodsLib for TokenPodsLib.Data; + using ReentrancyGuardLib for ReentrancyGuardLib.Data; + + error PodsLimitReachedForAccount(); + + uint256 public immutable podsLimit; + + ReentrancyGuardLib.Data private _guard; + mapping(uint256 => TokenPodsLib.Data) private _pods; + + constructor(uint256 podsLimit_) { + podsLimit = podsLimit_; + _guard.init(); + } + + function hasPod(address account, address pod, uint256 id) public view virtual returns(bool) { + return _pods[id].hasPod(account, pod); + } + + function podsCount(address account, uint256 id) public view virtual returns(uint256) { + return _pods[id].podsCount(account); + } + + function podAt(address account, uint256 index, uint256 id) public view virtual returns(address) { + return _pods[id].podAt(account, index); + } + + function pods(address account, uint256 id) public view virtual returns(address[] memory) { + return _pods[id].pods(account); + } + + function balanceOf(address account, uint256 id) public nonReentrantView(_guard) view override(IERC1155, ERC1155) virtual returns(uint256) { + return super.balanceOf(account, id); + } + + function podBalanceOf(address pod, address account, uint256 id) public nonReentrantView(_guard) view returns(uint256) { + return _pods[id].podBalanceOf(account, pod, balanceOf(msg.sender, id)); + } + + function addPod(address pod, uint256 id) public virtual { + if (_pods[id].addPod(msg.sender, pod, balanceOf(msg.sender, id)) > podsLimit) revert PodsLimitReachedForAccount(); + } + + function removePod(address pod, uint256 id) public virtual { + _pods[id].removePod(msg.sender, pod, balanceOf(msg.sender, id)); + } + + function removeAllPods(uint256 id) public virtual { + _pods[id].removeAllPods(msg.sender, balanceOf(msg.sender, id)); + } + + // ERC1155 Overrides + + function _afterTokenTransfer( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) internal nonReentrant(_guard) override virtual { + super._afterTokenTransfer(operator, from, to, ids, amounts, data); + + unchecked { + for (uint256 i = 0; i < ids.length; i++) { + _pods[ids[i]].updateBalancesWithTokenId(from, to, amounts[i], ids[i]); + } + } + } +} diff --git a/contracts/ERC20Pods.sol b/contracts/ERC20Pods.sol index 83ae1cd..743d8c9 100644 --- a/contracts/ERC20Pods.sol +++ b/contracts/ERC20Pods.sol @@ -6,26 +6,19 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@1inch/solidity-utils/contracts/libraries/AddressSet.sol"; import "./interfaces/IERC20Pods.sol"; -import "./interfaces/IPod.sol"; +import "./TokenPodsLib.sol"; import "./libs/ReentrancyGuard.sol"; abstract contract ERC20Pods is ERC20, IERC20Pods, ReentrancyGuardExt { - using AddressSet for AddressSet.Data; - using AddressArray for AddressArray.Data; + using TokenPodsLib for TokenPodsLib.Data; using ReentrancyGuardLib for ReentrancyGuardLib.Data; - error PodAlreadyAdded(); - error PodNotFound(); - error InvalidPodAddress(); error PodsLimitReachedForAccount(); - error InsufficientGas(); - - uint256 private constant _POD_CALL_GAS_LIMIT = 200_000; uint256 public immutable podsLimit; ReentrancyGuardLib.Data private _guard; - mapping(address => AddressSet.Data) private _pods; + TokenPodsLib.Data private _pods; constructor(uint256 podsLimit_) { podsLimit = podsLimit_; @@ -33,137 +26,45 @@ abstract contract ERC20Pods is ERC20, IERC20Pods, ReentrancyGuardExt { } function hasPod(address account, address pod) public view virtual returns(bool) { - return _pods[account].contains(pod); + return _pods.hasPod(account, pod); } function podsCount(address account) public view virtual returns(uint256) { - return _pods[account].length(); + return _pods.podsCount(account); } function podAt(address account, uint256 index) public view virtual returns(address) { - return _pods[account].at(index); + return _pods.podAt(account, index); } function pods(address account) public view virtual returns(address[] memory) { - return _pods[account].items.get(); + return _pods.pods(account); } - function balanceOf(address account) public nonReentrantView(_guard) view override(IERC20, ERC20) returns(uint256) { + function balanceOf(address account) public nonReentrantView(_guard) view override(IERC20, ERC20) virtual returns(uint256) { return super.balanceOf(account); } - function podBalanceOf(address pod, address account) public nonReentrantView(_guard) view returns(uint256) { - if (hasPod(account, pod)) { - return balanceOf(account); - } - return 0; + function podBalanceOf(address pod, address account) public nonReentrantView(_guard) view virtual returns(uint256) { + return _pods.podBalanceOf(account, pod, balanceOf(account)); } function addPod(address pod) public virtual { - _addPod(msg.sender, pod); + if (_pods.addPod(msg.sender, pod, balanceOf(msg.sender)) > podsLimit) revert PodsLimitReachedForAccount(); } function removePod(address pod) public virtual { - _removePod(msg.sender, pod); + _pods.removePod(msg.sender, pod, balanceOf(msg.sender)); } function removeAllPods() public virtual { - _removeAllPods(msg.sender); - } - - function _addPod(address account, address pod) internal virtual { - if (pod == address(0)) revert InvalidPodAddress(); - if (!_pods[account].add(pod)) revert PodAlreadyAdded(); - if (_pods[account].length() > podsLimit) revert PodsLimitReachedForAccount(); - - uint256 balance = balanceOf(account); - if (balance > 0) { - _updateBalances(pod, address(0), account, balance); - } - } - - function _removePod(address account, address pod) internal virtual { - if (!_pods[account].remove(pod)) revert PodNotFound(); - - uint256 balance = balanceOf(account); - if (balance > 0) { - _updateBalances(pod, account, address(0), balance); - } - } - - function _removeAllPods(address account) internal virtual { - address[] memory items = _pods[account].items.get(); - uint256 balance = balanceOf(account); - unchecked { - for (uint256 i = items.length; i > 0; i--) { - if (balance > 0) { - _updateBalances(items[i - 1], account, address(0), balance); - } - _pods[account].remove(items[i - 1]); - } - } - } - - /// @notice Assembly implementation of the gas limited call to avoid return gas bomb, - // moreover call to a destructed pod would also revert even inside try-catch block in Solidity 0.8.17 - /// @dev try IPod(pod).updateBalances{gas: _POD_CALL_GAS_LIMIT}(from, to, amount) {} catch {} - function _updateBalances(address pod, address from, address to, uint256 amount) private { - bytes4 selector = IPod.updateBalances.selector; - bytes4 exception = InsufficientGas.selector; - assembly { // solhint-disable-line no-inline-assembly - let ptr := mload(0x40) - mstore(ptr, selector) - mstore(add(ptr, 0x04), from) - mstore(add(ptr, 0x24), to) - mstore(add(ptr, 0x44), amount) - - if lt(div(mul(gas(), 63), 64), _POD_CALL_GAS_LIMIT) { - mstore(0, exception) - revert(0, 4) - } - pop(call(_POD_CALL_GAS_LIMIT, pod, 0, ptr, 0x64, 0, 0)) - } + _pods.removeAllPods(msg.sender, balanceOf(msg.sender)); } // ERC20 Overrides function _afterTokenTransfer(address from, address to, uint256 amount) internal nonReentrant(_guard) override virtual { super._afterTokenTransfer(from, to, amount); - - unchecked { - if (amount > 0 && from != to) { - address[] memory a = _pods[from].items.get(); - address[] memory b = _pods[to].items.get(); - uint256 aLength = a.length; - uint256 bLength = b.length; - - for (uint256 i = 0; i < aLength; i++) { - address pod = a[i]; - - uint256 j; - for (j = 0; j < bLength; j++) { - if (pod == b[j]) { - // Both parties are participating of the same Pod - _updateBalances(pod, from, to, amount); - b[j] = address(0); - break; - } - } - - if (j == bLength) { - // Sender is participating in a Pod, but receiver is not - _updateBalances(pod, from, address(0), amount); - } - } - - for (uint256 j = 0; j < bLength; j++) { - address pod = b[j]; - if (pod != address(0)) { - // Receiver is participating in a Pod, but sender is not - _updateBalances(pod, address(0), to, amount); - } - } - } - } + _pods.updateBalances(from, to, amount); } } diff --git a/contracts/ERC721Pods.sol b/contracts/ERC721Pods.sol new file mode 100644 index 0000000..9fc2e68 --- /dev/null +++ b/contracts/ERC721Pods.sol @@ -0,0 +1,71 @@ + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@1inch/solidity-utils/contracts/libraries/AddressSet.sol"; + +import "./interfaces/IERC721Pods.sol"; +import "./TokenPodsLib.sol"; +import "./libs/ReentrancyGuard.sol"; + +abstract contract ERC721Pods is ERC721, IERC721Pods, ReentrancyGuardExt { + using TokenPodsLib for TokenPodsLib.Data; + using ReentrancyGuardLib for ReentrancyGuardLib.Data; + + error PodsLimitReachedForAccount(); + + uint256 public immutable podsLimit; + + ReentrancyGuardLib.Data private _guard; + TokenPodsLib.Data private _pods; + + constructor(uint256 podsLimit_) { + podsLimit = podsLimit_; + _guard.init(); + } + + function hasPod(address account, address pod) public view virtual returns(bool) { + return _pods.hasPod(account, pod); + } + + function podsCount(address account) public view virtual returns(uint256) { + return _pods.podsCount(account); + } + + function podAt(address account, uint256 index) public view virtual returns(address) { + return _pods.podAt(account, index); + } + + function pods(address account) public view virtual returns(address[] memory) { + return _pods.pods(account); + } + + function balanceOf(address account) public nonReentrantView(_guard) view override(IERC721, ERC721) virtual returns(uint256) { + return super.balanceOf(account); + } + + function podBalanceOf(address pod, address account) public nonReentrantView(_guard) view virtual returns(uint256) { + return _pods.podBalanceOf(account, pod, balanceOf(account)); + } + + function addPod(address pod) public virtual { + if (_pods.addPod(msg.sender, pod, balanceOf(msg.sender)) > podsLimit) revert PodsLimitReachedForAccount(); + } + + function removePod(address pod) public virtual { + _pods.removePod(msg.sender, pod, balanceOf(msg.sender)); + } + + function removeAllPods() public virtual { + _pods.removeAllPods(msg.sender, balanceOf(msg.sender)); + } + + // ERC721 Overrides + + function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal nonReentrant(_guard) override virtual { + super._afterTokenTransfer(from, to, firstTokenId, batchSize); + _pods.updateBalances(from, to, batchSize); + } +} diff --git a/contracts/Pod.sol b/contracts/Pod.sol index 8a1154d..244cbfc 100644 --- a/contracts/Pod.sol +++ b/contracts/Pod.sol @@ -8,13 +8,30 @@ abstract contract Pod is IPod { error AccessDenied(); address public immutable token; + uint256 public immutable tokenId; modifier onlyToken { if (msg.sender != token) revert AccessDenied(); _; } - constructor(address token_) { + modifier onlyTokenId(uint256 id) { + if (id != tokenId) revert AccessDenied(); + _; + } + + constructor(address token_, uint256 tokenId_) { token = token_; + tokenId = tokenId_; + } + + function updateBalancesWithTokenId(address from, address to, uint256 amount, uint256 id) external onlyToken onlyTokenId(id) { + _updateBalances(from, to, amount); + } + + function updateBalances(address from, address to, uint256 amount) external onlyToken { + _updateBalances(from, to, amount); } + + function _updateBalances(address from, address to, uint256 amount) internal virtual; } diff --git a/contracts/TokenPodsLib.sol b/contracts/TokenPodsLib.sol new file mode 100644 index 0000000..98de0e4 --- /dev/null +++ b/contracts/TokenPodsLib.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "@1inch/solidity-utils/contracts/libraries/AddressSet.sol"; + +import "./interfaces/IPod.sol"; + +library TokenPodsLib { + using AddressSet for AddressSet.Data; + using AddressArray for AddressArray.Data; + + error PodAlreadyAdded(); + error PodNotFound(); + error InvalidPodAddress(); + error InsufficientGas(); + + struct Data { + mapping(address => AddressSet.Data) _pods; + } + + uint256 private constant _POD_CALL_GAS_LIMIT = 200_000; + + function hasPod(Data storage self, address account, address pod) internal view returns(bool) { + return self._pods[account].contains(pod); + } + + function podsCount(Data storage self, address account) internal view returns(uint256) { + return self._pods[account].length(); + } + + function podAt(Data storage self, address account, uint256 index) internal view returns(address) { + return self._pods[account].at(index); + } + + function pods(Data storage self, address account) internal view returns(address[] memory) { + return self._pods[account].items.get(); + } + + function podBalanceOf(Data storage self, address account, address pod, uint256 balance) internal view returns(uint256) { + if (self._pods[account].contains(pod)) { + return balance; + } + return 0; + } + + function addPod(Data storage self, address account, address pod, uint256 balance) internal returns(uint256) { + return _addPod(self, account, pod, balance); + } + + function removePod(Data storage self, address account, address pod, uint256 balance) internal { + _removePod(self, account, pod, balance); + } + + function removeAllPods(Data storage self, address account, uint256 balance) internal { + _removeAllPods(self, account, balance); + } + + function _addPod(Data storage self, address account, address pod, uint256 balance) private returns(uint256) { + if (pod == address(0)) revert InvalidPodAddress(); + if (!self._pods[account].add(pod)) revert PodAlreadyAdded(); + if (balance > 0) { + _notifyPod(pod, address(0), account, balance, 0, false); + } + return self._pods[account].length(); + } + + function _removePod(Data storage self, address account, address pod, uint256 balance) private { + if (!self._pods[account].remove(pod)) revert PodNotFound(); + if (balance > 0) { + _notifyPod(pod, account, address(0), balance, 0, false); + } + } + + function _removeAllPods(Data storage self, address account, uint256 balance) private { + address[] memory items = self._pods[account].items.get(); + unchecked { + for (uint256 i = items.length; i > 0; i--) { + if (balance > 0) { + _notifyPod(items[i - 1], account, address(0), balance, 0, false); + } + self._pods[account].remove(items[i - 1]); + } + } + } + + function updateBalances(Data storage self, address from, address to, uint256 amount) internal { + _updateBalances(self, from, to, amount, 0, false); + } + + function updateBalancesWithTokenId(Data storage self, address from, address to, uint256 amount, uint256 id) internal { + _updateBalances(self, from, to, amount, id, true); + } + + function _updateBalances(Data storage self, address from, address to, uint256 amount, uint256 id, bool hasId) private { + unchecked { + if (amount > 0 && from != to) { + address[] memory a = self._pods[from].items.get(); + address[] memory b = self._pods[to].items.get(); + uint256 aLength = a.length; + uint256 bLength = b.length; + + for (uint256 i = 0; i < aLength; i++) { + address pod = a[i]; + + uint256 j; + for (j = 0; j < bLength; j++) { + if (pod == b[j]) { + // Both parties are participating of the same Pod + _notifyPod(pod, from, to, amount, id, hasId); + b[j] = address(0); + break; + } + } + + if (j == bLength) { + // Sender is participating in a Pod, but receiver is not + _notifyPod(pod, from, address(0), amount, id, hasId); + } + } + + for (uint256 j = 0; j < bLength; j++) { + address pod = b[j]; + if (pod != address(0)) { + // Receiver is participating in a Pod, but sender is not + _notifyPod(pod, address(0), to, amount, id, hasId); + } + } + } + } + } + + /// @notice Assembly implementation of the gas limited call to avoid return gas bomb, + // moreover call to a destructed pod would also revert even inside try-catch block in Solidity 0.8.17 + /// @dev try IPod(pod).updateBalances{gas: _POD_CALL_GAS_LIMIT}(from, to, amount) {} catch {} + function _notifyPod(address pod, address from, address to, uint256 amount, uint256 id, bool hasId) private { + bytes4 selector = IPod.updateBalances.selector; + if (hasId) { + selector = IPod.updateBalancesWithTokenId.selector; + } + bytes4 exception = InsufficientGas.selector; + assembly { // solhint-disable-line no-inline-assembly + let ptr := mload(0x40) + mstore(ptr, selector) + mstore(add(ptr, 0x04), from) + mstore(add(ptr, 0x24), to) + mstore(add(ptr, 0x44), amount) + if hasId { + mstore(add(ptr, 0x64), id) + } + + if lt(div(mul(gas(), 63), 64), _POD_CALL_GAS_LIMIT) { + mstore(0, exception) + revert(0, 4) + } + pop(call(_POD_CALL_GAS_LIMIT, pod, 0, ptr, add(0x64, mul(hasId, 0x20)), 0, 0)) + } + } +} diff --git a/contracts/interfaces/IERC1155Pods.sol b/contracts/interfaces/IERC1155Pods.sol new file mode 100644 index 0000000..dc4aa02 --- /dev/null +++ b/contracts/interfaces/IERC1155Pods.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; + +interface IERC1155Pods is IERC1155 { + function hasPod(address account, address pod, uint256 id) external view returns(bool); + function podsCount(address account, uint256 id) external view returns(uint256); + function podAt(address account, uint256 index, uint256 id) external view returns(address); + function pods(address account, uint256 id) external view returns(address[] memory); + function podBalanceOf(address pod, address account, uint256 id) external view returns(uint256); + + function addPod(address pod, uint256 id) external; + function removePod(address pod, uint256 id) external; + function removeAllPods(uint256 id) external; +} diff --git a/contracts/interfaces/IERC721Pods.sol b/contracts/interfaces/IERC721Pods.sol new file mode 100644 index 0000000..f4d1f4e --- /dev/null +++ b/contracts/interfaces/IERC721Pods.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; + +interface IERC721Pods is IERC721 { + function hasPod(address account, address pod) external view returns(bool); + function podsCount(address account) external view returns(uint256); + function podAt(address account, uint256 index) external view returns(address); + function pods(address account) external view returns(address[] memory); + function podBalanceOf(address pod, address account) external view returns(uint256); + + function addPod(address pod) external; + function removePod(address pod) external; + function removeAllPods() external; +} diff --git a/contracts/interfaces/IPod.sol b/contracts/interfaces/IPod.sol index d5377e5..fc55bb4 100644 --- a/contracts/interfaces/IPod.sol +++ b/contracts/interfaces/IPod.sol @@ -3,5 +3,6 @@ pragma solidity ^0.8.0; interface IPod { - function updateBalances(address from, address to, uint256 amount) external; // onlyERC20Pods + function updateBalances(address from, address to, uint256 amount) external; + function updateBalancesWithTokenId(address from, address to, uint256 amount, uint256 id) external; } diff --git a/contracts/mocks/PodMock.sol b/contracts/mocks/PodMock.sol index b718c2e..accf41f 100644 --- a/contracts/mocks/PodMock.sol +++ b/contracts/mocks/PodMock.sol @@ -6,9 +6,13 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "../Pod.sol"; contract PodMock is ERC20, Pod { - constructor(string memory name, string memory symbol, address token_) ERC20(name, symbol) Pod(token_) {} // solhint-disable-line no-empty-blocks + constructor(string memory name, string memory symbol, address token_) + ERC20(name, symbol) + Pod(token_, 0) + {} // solhint-disable-line no-empty-blocks - function updateBalances(address from, address to, uint256 amount) external { + function _updateBalances(address from, address to, uint256 amount) internal override { + // Replicate balances if (from == address(0)) { _mint(to, amount); } else if (to == address(0)) { diff --git a/contracts/mocks/WrongPodMock.sol b/contracts/mocks/WrongPodMock.sol index 56cbb0b..58c0380 100644 --- a/contracts/mocks/WrongPodMock.sol +++ b/contracts/mocks/WrongPodMock.sol @@ -12,9 +12,9 @@ contract WrongPodMock is ERC20, Pod { bool public isOutOfGas; bool public isReturnGasBomb; - constructor(string memory name, string memory symbol, address token_) ERC20(name, symbol) Pod(token_) {} // solhint-disable-line no-empty-blocks + constructor(string memory name, string memory symbol, address token_) ERC20(name, symbol) Pod(token_, 0) {} // solhint-disable-line no-empty-blocks - function updateBalances(address /*from*/, address /*to*/, uint256 /*amount*/) external view { + function _updateBalances(address /* from */, address /* to */, uint256 /* amount */) internal view override { if (isRevert) revert PodsUpdateBalanceRevert(); if (isOutOfGas) assert(false); if (isReturnGasBomb) { assembly { return(0, 1000000) } } // solhint-disable-line no-inline-assembly diff --git a/test/ERC20Pods.js b/test/ERC20Pods.js index eafb5fe..442ffae 100644 --- a/test/ERC20Pods.js +++ b/test/ERC20Pods.js @@ -12,10 +12,8 @@ describe('ERC20Pods', function () { const pods = []; for (let i = 0; i < POD_LIMITS; i++) { - const token = await ERC20PodsMock.deploy(`TOKEN_${i}`, `TKN${i}`, POD_LIMITS); - await token.deployed(); const PodMock = await ethers.getContractFactory('PodMock'); - pods[i] = await PodMock.deploy(`POD_TOKEN_${i}`, `PT${i}`, token.address); + pods[i] = await PodMock.deploy(`POD_TOKEN_${i}`, `PT${i}`, erc20Pods.address); await pods[i].deployed(); } const amount = ether('1'); diff --git a/test/behaviors/ERC20Pods.behavior.js b/test/behaviors/ERC20Pods.behavior.js index adda559..5924dba 100644 --- a/test/behaviors/ERC20Pods.behavior.js +++ b/test/behaviors/ERC20Pods.behavior.js @@ -162,7 +162,7 @@ function shouldBehaveLikeERC20Pods (initContracts) { expect(await erc20Pods.hasPod(wallet1.address, pods[1].address)).to.be.equals(false); await erc20Pods.addPod(pods[0].address); await erc20Pods.addPod(pods[1].address); - expect(await erc20Pods.pods(wallet1.address)).to.have.deep.equals([pods[0].address, pods[1].address]); + expect(await erc20Pods.pods(wallet1.address)).to.be.deep.equals([pods[0].address, pods[1].address]); }); it('should updateBalance via pod only for wallets with non-zero balance', async function () { @@ -240,19 +240,17 @@ function shouldBehaveLikeERC20Pods (initContracts) { describe('_updateBalances', function () { it('should not fail when updateBalance in pod reverts', async function () { - const { erc20Pods, wrongPod, amount } = await loadFixture(initWrongPodAndMint); + const { erc20Pods, wrongPod } = await loadFixture(initWrongPodAndMint); await wrongPod.setIsRevert(true); - await expect(wrongPod.updateBalances(wallet1.address, wallet2.address, amount)) - .to.be.revertedWithCustomError(wrongPod, 'PodsUpdateBalanceRevert'); await erc20Pods.addPod(wrongPod.address); - expect(await erc20Pods.pods(wallet1.address)).to.have.deep.equals([wrongPod.address]); + expect(await erc20Pods.pods(wallet1.address)).to.be.deep.equals([wrongPod.address]); }); it('should not fail when updateBalance in pod has OutOfGas', async function () { const { erc20Pods, wrongPod } = await loadFixture(initWrongPodAndMint); await wrongPod.setOutOfGas(true); await erc20Pods.addPod(wrongPod.address); - expect(await erc20Pods.pods(wallet1.address)).to.have.deep.equals([wrongPod.address]); + expect(await erc20Pods.pods(wallet1.address)).to.be.deep.equals([wrongPod.address]); }); it('should not fail when updateBalance returns gas bomb @skip-on-coverage', async function () { @@ -260,8 +258,8 @@ function shouldBehaveLikeERC20Pods (initContracts) { await wrongPod.setReturnGasBomb(true); const tx = await erc20Pods.addPod(wrongPod.address); const receipt = await tx.wait(); - expect(receipt.gasUsed).to.be.lt(272123); // 272123 with solidity instead of assembly - expect(await erc20Pods.pods(wallet1.address)).to.have.deep.equals([wrongPod.address]); + expect(receipt.gasUsed).to.be.lt(274286); // 274286 with solidity instead of assembly + expect(await erc20Pods.pods(wallet1.address)).to.be.deep.equals([wrongPod.address]); }); }); From 7096866c24b29793fbbd357407b71e380f3e5155 Mon Sep 17 00:00:00 2001 From: Mikhail Melnik Date: Thu, 24 Nov 2022 13:21:10 +0400 Subject: [PATCH 2/6] fix --- test/ERC20Pods.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ERC20Pods.js b/test/ERC20Pods.js index 64454a2..09ba8a2 100644 --- a/test/ERC20Pods.js +++ b/test/ERC20Pods.js @@ -43,7 +43,7 @@ describe('ERC20Pods', function () { await wrongPod.setReturnGasBomb(true); const tx = await erc20Pods.addPod(wrongPod.address); const receipt = await tx.wait(); - expect(receipt.gasUsed).to.be.lt(274168); + expect(receipt.gasUsed).to.be.lt(274286); expect(await erc20Pods.pods(wallet1.address)).to.have.deep.equals([wrongPod.address]); }); }); From 9b07784bd24372ab37ad0d39d383d7ab3763fdfe Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Thu, 24 Nov 2022 10:33:20 +0100 Subject: [PATCH 3/6] Fix double reentrancy checks --- contracts/ERC1155Pods.sol | 2 +- contracts/ERC721Pods.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/ERC1155Pods.sol b/contracts/ERC1155Pods.sol index 9a3c7eb..9248c1c 100644 --- a/contracts/ERC1155Pods.sol +++ b/contracts/ERC1155Pods.sol @@ -45,7 +45,7 @@ abstract contract ERC1155Pods is ERC1155, IERC1155Pods, ReentrancyGuardExt { } function podBalanceOf(address pod, address account, uint256 id) public nonReentrantView(_guard) view returns(uint256) { - return _pods[id].podBalanceOf(account, pod, balanceOf(msg.sender, id)); + return _pods[id].podBalanceOf(account, pod, super.balanceOf(msg.sender, id)); } function addPod(address pod, uint256 id) public virtual { diff --git a/contracts/ERC721Pods.sol b/contracts/ERC721Pods.sol index 9fc2e68..cc9deca 100644 --- a/contracts/ERC721Pods.sol +++ b/contracts/ERC721Pods.sol @@ -47,7 +47,7 @@ abstract contract ERC721Pods is ERC721, IERC721Pods, ReentrancyGuardExt { } function podBalanceOf(address pod, address account) public nonReentrantView(_guard) view virtual returns(uint256) { - return _pods.podBalanceOf(account, pod, balanceOf(account)); + return _pods.podBalanceOf(account, pod, super.balanceOf(account)); } function addPod(address pod) public virtual { From 3af990fab42617649e7d394421f0e426d81a178d Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Tue, 20 Dec 2022 21:18:07 +0100 Subject: [PATCH 4/6] Refactoring, linter and bumping version+name --- contracts/Pod.sol | 4 ++-- contracts/TokenPodsLib.sol | 10 ++++------ contracts/interfaces/IPod.sol | 7 ++++++- contracts/interfaces/IPodWithId.sol | 15 +++++++++++++++ package.json | 4 ++-- test/ERC20Pods.js | 2 +- 6 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 contracts/interfaces/IPodWithId.sol diff --git a/contracts/Pod.sol b/contracts/Pod.sol index 244cbfc..4eabb94 100644 --- a/contracts/Pod.sol +++ b/contracts/Pod.sol @@ -25,11 +25,11 @@ abstract contract Pod is IPod { tokenId = tokenId_; } - function updateBalancesWithTokenId(address from, address to, uint256 amount, uint256 id) external onlyToken onlyTokenId(id) { + function updateBalances(address from, address to, uint256 amount) external onlyToken { _updateBalances(from, to, amount); } - function updateBalances(address from, address to, uint256 amount) external onlyToken { + function updateBalancesWithTokenId(address from, address to, uint256 amount, uint256 id) external onlyToken onlyTokenId(id) { _updateBalances(from, to, amount); } diff --git a/contracts/TokenPodsLib.sol b/contracts/TokenPodsLib.sol index f536f46..8e44bea 100644 --- a/contracts/TokenPodsLib.sol +++ b/contracts/TokenPodsLib.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.0; import "@1inch/solidity-utils/contracts/libraries/AddressSet.sol"; import "./interfaces/IPod.sol"; +import "./interfaces/IPodWithId.sol"; library TokenPodsLib { using AddressSet for AddressSet.Data; @@ -31,7 +32,7 @@ library TokenPodsLib { function makeInfo(Data storage data, uint256 podCallGasLimit_) internal pure returns(Info memory info) { DataPtr ptr; - assembly { + assembly { // solhint-disable-line no-inline-assembly ptr := data.slot } info.data = ptr; @@ -155,10 +156,7 @@ library TokenPodsLib { // moreover call to a destructed pod would also revert even inside try-catch block in Solidity 0.8.17 /// @dev try IPod(pod).updateBalances{gas: _POD_CALL_GAS_LIMIT}(from, to, amount) {} catch {} function _notifyPod(address pod, address from, address to, uint256 amount, uint256 id, bool hasId, uint256 gasLimit) private { - bytes4 selector = IPod.updateBalances.selector; - if (hasId) { - selector = IPod.updateBalancesWithTokenId.selector; - } + bytes4 selector = hasId ? IPodWithId.updateBalancesWithTokenId.selector : IPod.updateBalances.selector; bytes4 exception = InsufficientGas.selector; assembly { // solhint-disable-line no-inline-assembly let ptr := mload(0x40) @@ -180,7 +178,7 @@ library TokenPodsLib { function _getData(Info memory info) private pure returns(Data storage data) { DataPtr ptr = info.data; - assembly { + assembly { // solhint-disable-line no-inline-assembly data.slot := ptr } } diff --git a/contracts/interfaces/IPod.sol b/contracts/interfaces/IPod.sol index fc55bb4..4f150e5 100644 --- a/contracts/interfaces/IPod.sol +++ b/contracts/interfaces/IPod.sol @@ -2,7 +2,12 @@ pragma solidity ^0.8.0; +/// @title Pod interface interface IPod { + /// Pod receives notifications about balance changes of participants + /// @dev This function implementation should make sure `msg.sender` is designated token of this Pod + /// @param from The address of the sender or `address(0)` if the transfer is a mint or sender is not participating in this Pod + /// @param to The address of the recipient or `address(0)` if the transfer is a burn or recipient is not participating in this Pod + /// @param amount The amount of tokens being transferred function updateBalances(address from, address to, uint256 amount) external; - function updateBalancesWithTokenId(address from, address to, uint256 amount, uint256 id) external; } diff --git a/contracts/interfaces/IPodWithId.sol b/contracts/interfaces/IPodWithId.sol new file mode 100644 index 0000000..7952a88 --- /dev/null +++ b/contracts/interfaces/IPodWithId.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// @title Pod interface for tokens of EIP-1155 standard +interface IPodWithId { + /// Pod receives notifications about balance changes of participants + /// @dev This function implementation MUST make sure `msg.sender` is designated token of this Pod + /// @dev This function implementation MUST make sure `id` argument is designated token id of this Pod + /// @param from The address of the sender or `address(0)` if the transfer is a mint or sender is not participating in this Pod + /// @param to The address of the recipient or `address(0)` if the transfer is a burn or recipient is not participating in this Pod + /// @param id The EIP-1155 `token_id` of the token being transferred + /// @param amount The amount of tokens being transferred + function updateBalancesWithTokenId(address from, address to, uint256 amount, uint256 id) external; +} diff --git a/package.json b/package.json index bcc9980..69d4b0f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "@1inch/erc20-pods", - "version": "0.0.14", + "name": "@1inch/token-pods", + "version": "0.0.15", "description": "ERC20 extension enabling external smart contract based Pods to track balances of those users who opted-in to these Pods", "repository": { "type": "git", diff --git a/test/ERC20Pods.js b/test/ERC20Pods.js index ff9b127..31175a1 100644 --- a/test/ERC20Pods.js +++ b/test/ERC20Pods.js @@ -45,7 +45,7 @@ describe('ERC20Pods', function () { await wrongPod.setReturnGasBomb(true); const tx = await erc20Pods.addPod(wrongPod.address); const receipt = await tx.wait(); - expect(receipt.gasUsed).to.be.lt(275883); + expect(receipt.gasUsed).to.be.lt(275901); expect(await erc20Pods.pods(wallet1.address)).to.have.deep.equals([wrongPod.address]); }); }); From 8efff87e810c07e119a59176008981221953d864 Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Sun, 25 Dec 2022 12:14:45 +0400 Subject: [PATCH 5/6] Refactor to reduce code size and avoid memory structs --- contracts/ERC1155Pods.sol | 24 ++++----- contracts/ERC20Pods.sol | 24 ++++----- contracts/ERC721Pods.sol | 24 ++++----- contracts/Pod.sol | 3 +- contracts/TokenPodsLib.sol | 104 +++++++++++++++---------------------- package.json | 2 +- 6 files changed, 74 insertions(+), 107 deletions(-) diff --git a/contracts/ERC1155Pods.sol b/contracts/ERC1155Pods.sol index cf103b5..e031f19 100644 --- a/contracts/ERC1155Pods.sol +++ b/contracts/ERC1155Pods.sol @@ -9,7 +9,7 @@ import "./TokenPodsLib.sol"; import "./libs/ReentrancyGuard.sol"; abstract contract ERC1155Pods is ERC1155, IERC1155Pods, ReentrancyGuardExt { - using TokenPodsLib for TokenPodsLib.Info; + using TokenPodsLib for TokenPodsLib.Data; using ReentrancyGuardLib for ReentrancyGuardLib.Data; error ZeroPodsLimit(); @@ -29,19 +29,19 @@ abstract contract ERC1155Pods is ERC1155, IERC1155Pods, ReentrancyGuardExt { } function hasPod(address account, address pod, uint256 id) public view virtual returns(bool) { - return _info(id).hasPod(account, pod); + return _pods[id].hasPod(account, pod); } function podsCount(address account, uint256 id) public view virtual returns(uint256) { - return _info(id).podsCount(account); + return _pods[id].podsCount(account); } function podAt(address account, uint256 index, uint256 id) public view virtual returns(address) { - return _info(id).podAt(account, index); + return _pods[id].podAt(account, index); } function pods(address account, uint256 id) public view virtual returns(address[] memory) { - return _info(id).pods(account); + return _pods[id].pods(account); } function balanceOf(address account, uint256 id) public nonReentrantView(_guard) view override(IERC1155, ERC1155) virtual returns(uint256) { @@ -49,23 +49,19 @@ abstract contract ERC1155Pods is ERC1155, IERC1155Pods, ReentrancyGuardExt { } function podBalanceOf(address pod, address account, uint256 id) public nonReentrantView(_guard) view returns(uint256) { - return _info(id).podBalanceOf(account, pod, super.balanceOf(msg.sender, id)); + return _pods[id].podBalanceOf(account, pod, super.balanceOf(msg.sender, id)); } function addPod(address pod, uint256 id) public virtual { - if (_info(id).addPod(msg.sender, pod, balanceOf(msg.sender, id)) > podsLimit) revert PodsLimitReachedForAccount(); + if (_pods[id].addPod(msg.sender, pod, balanceOf(msg.sender, id), podCallGasLimit) > podsLimit) revert PodsLimitReachedForAccount(); } function removePod(address pod, uint256 id) public virtual { - _info(id).removePod(msg.sender, pod, balanceOf(msg.sender, id)); + _pods[id].removePod(msg.sender, pod, balanceOf(msg.sender, id), podCallGasLimit); } function removeAllPods(uint256 id) public virtual { - _info(id).removeAllPods(msg.sender, balanceOf(msg.sender, id)); - } - - function _info(uint256 id) private view returns(TokenPodsLib.Info memory) { - return TokenPodsLib.makeInfo(_pods[id], podCallGasLimit); + _pods[id].removeAllPods(msg.sender, balanceOf(msg.sender, id), podCallGasLimit); } // ERC1155 Overrides @@ -82,7 +78,7 @@ abstract contract ERC1155Pods is ERC1155, IERC1155Pods, ReentrancyGuardExt { unchecked { for (uint256 i = 0; i < ids.length; i++) { - _info(ids[i]).updateBalancesWithTokenId(from, to, amounts[i], ids[i]); + _pods[i].updateBalancesWithTokenId(from, to, amounts[i], ids[i], podCallGasLimit); } } } diff --git a/contracts/ERC20Pods.sol b/contracts/ERC20Pods.sol index 680b3ec..e6d61fb 100644 --- a/contracts/ERC20Pods.sol +++ b/contracts/ERC20Pods.sol @@ -10,7 +10,7 @@ import "./TokenPodsLib.sol"; import "./libs/ReentrancyGuard.sol"; abstract contract ERC20Pods is ERC20, IERC20Pods, ReentrancyGuardExt { - using TokenPodsLib for TokenPodsLib.Info; + using TokenPodsLib for TokenPodsLib.Data; using ReentrancyGuardLib for ReentrancyGuardLib.Data; error ZeroPodsLimit(); @@ -30,19 +30,19 @@ abstract contract ERC20Pods is ERC20, IERC20Pods, ReentrancyGuardExt { } function hasPod(address account, address pod) public view virtual returns(bool) { - return _info().hasPod(account, pod); + return _pods.hasPod(account, pod); } function podsCount(address account) public view virtual returns(uint256) { - return _info().podsCount(account); + return _pods.podsCount(account); } function podAt(address account, uint256 index) public view virtual returns(address) { - return _info().podAt(account, index); + return _pods.podAt(account, index); } function pods(address account) public view virtual returns(address[] memory) { - return _info().pods(account); + return _pods.pods(account); } function balanceOf(address account) public nonReentrantView(_guard) view override(IERC20, ERC20) virtual returns(uint256) { @@ -50,29 +50,25 @@ abstract contract ERC20Pods is ERC20, IERC20Pods, ReentrancyGuardExt { } function podBalanceOf(address pod, address account) public nonReentrantView(_guard) view virtual returns(uint256) { - return _info().podBalanceOf(account, pod, super.balanceOf(account)); + return _pods.podBalanceOf(account, pod, super.balanceOf(account)); } function addPod(address pod) public virtual { - if (_info().addPod(msg.sender, pod, balanceOf(msg.sender)) > podsLimit) revert PodsLimitReachedForAccount(); + if (_pods.addPod(msg.sender, pod, balanceOf(msg.sender), podCallGasLimit) > podsLimit) revert PodsLimitReachedForAccount(); } function removePod(address pod) public virtual { - _info().removePod(msg.sender, pod, balanceOf(msg.sender)); + _pods.removePod(msg.sender, pod, balanceOf(msg.sender), podCallGasLimit); } function removeAllPods() public virtual { - _info().removeAllPods(msg.sender, balanceOf(msg.sender)); - } - - function _info() private view returns(TokenPodsLib.Info memory) { - return TokenPodsLib.makeInfo(_pods, podCallGasLimit); + _pods.removeAllPods(msg.sender, balanceOf(msg.sender), podCallGasLimit); } // ERC20 Overrides function _afterTokenTransfer(address from, address to, uint256 amount) internal nonReentrant(_guard) override virtual { super._afterTokenTransfer(from, to, amount); - _info().updateBalances(from, to, amount); + _pods.updateBalances(from, to, amount, podCallGasLimit); } } diff --git a/contracts/ERC721Pods.sol b/contracts/ERC721Pods.sol index c4129b3..1ee301f 100644 --- a/contracts/ERC721Pods.sol +++ b/contracts/ERC721Pods.sol @@ -11,7 +11,7 @@ import "./TokenPodsLib.sol"; import "./libs/ReentrancyGuard.sol"; abstract contract ERC721Pods is ERC721, IERC721Pods, ReentrancyGuardExt { - using TokenPodsLib for TokenPodsLib.Info; + using TokenPodsLib for TokenPodsLib.Data; using ReentrancyGuardLib for ReentrancyGuardLib.Data; error ZeroPodsLimit(); @@ -31,19 +31,19 @@ abstract contract ERC721Pods is ERC721, IERC721Pods, ReentrancyGuardExt { } function hasPod(address account, address pod) public view virtual returns(bool) { - return _info().hasPod(account, pod); + return _pods.hasPod(account, pod); } function podsCount(address account) public view virtual returns(uint256) { - return _info().podsCount(account); + return _pods.podsCount(account); } function podAt(address account, uint256 index) public view virtual returns(address) { - return _info().podAt(account, index); + return _pods.podAt(account, index); } function pods(address account) public view virtual returns(address[] memory) { - return _info().pods(account); + return _pods.pods(account); } function balanceOf(address account) public nonReentrantView(_guard) view override(IERC721, ERC721) virtual returns(uint256) { @@ -51,29 +51,25 @@ abstract contract ERC721Pods is ERC721, IERC721Pods, ReentrancyGuardExt { } function podBalanceOf(address pod, address account) public nonReentrantView(_guard) view virtual returns(uint256) { - return _info().podBalanceOf(account, pod, super.balanceOf(account)); + return _pods.podBalanceOf(account, pod, super.balanceOf(account)); } function addPod(address pod) public virtual { - if (_info().addPod(msg.sender, pod, balanceOf(msg.sender)) > podsLimit) revert PodsLimitReachedForAccount(); + if (_pods.addPod(msg.sender, pod, balanceOf(msg.sender), podCallGasLimit) > podsLimit) revert PodsLimitReachedForAccount(); } function removePod(address pod) public virtual { - _info().removePod(msg.sender, pod, balanceOf(msg.sender)); + _pods.removePod(msg.sender, pod, balanceOf(msg.sender), podCallGasLimit); } function removeAllPods() public virtual { - _info().removeAllPods(msg.sender, balanceOf(msg.sender)); - } - - function _info() private view returns(TokenPodsLib.Info memory) { - return TokenPodsLib.makeInfo(_pods, podCallGasLimit); + _pods.removeAllPods(msg.sender, balanceOf(msg.sender), podCallGasLimit); } // ERC721 Overrides function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal nonReentrant(_guard) override virtual { super._afterTokenTransfer(from, to, firstTokenId, batchSize); - _info().updateBalances(from, to, batchSize); + _pods.updateBalances(from, to, batchSize, podCallGasLimit); } } diff --git a/contracts/Pod.sol b/contracts/Pod.sol index 11f28c3..06adc62 100644 --- a/contracts/Pod.sol +++ b/contracts/Pod.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.0; import "./interfaces/IPod.sol"; +import "./interfaces/IPodWithId.sol"; import "./interfaces/IERC20Pods.sol"; -abstract contract Pod is IPod { +abstract contract Pod is IPod, IPodWithId { error AccessDenied(); IERC20Pods public immutable token; diff --git a/contracts/TokenPodsLib.sol b/contracts/TokenPodsLib.sol index 8e44bea..e5883fc 100644 --- a/contracts/TokenPodsLib.sol +++ b/contracts/TokenPodsLib.sol @@ -22,103 +22,88 @@ library TokenPodsLib { type DataPtr is uint256; struct Data { - mapping(address => AddressSet.Data) pods; + mapping(address => AddressSet.Data) _pods; } - struct Info { - DataPtr data; - uint256 podCallGasLimit; + function hasPod(Data storage self, address account, address pod) internal view returns(bool) { + return self._pods[account].contains(pod); } - function makeInfo(Data storage data, uint256 podCallGasLimit_) internal pure returns(Info memory info) { - DataPtr ptr; - assembly { // solhint-disable-line no-inline-assembly - ptr := data.slot - } - info.data = ptr; - info.podCallGasLimit = podCallGasLimit_; - } - - function hasPod(Info memory self, address account, address pod) internal view returns(bool) { - return _getData(self).pods[account].contains(pod); - } - - function podsCount(Info memory self, address account) internal view returns(uint256) { - return _getData(self).pods[account].length(); + function podsCount(Data storage self, address account) internal view returns(uint256) { + return self._pods[account].length(); } - function podAt(Info memory self, address account, uint256 index) internal view returns(address) { - return _getData(self).pods[account].at(index); + function podAt(Data storage self, address account, uint256 index) internal view returns(address) { + return self._pods[account].at(index); } - function pods(Info memory self, address account) internal view returns(address[] memory) { - return _getData(self).pods[account].items.get(); + function pods(Data storage self, address account) internal view returns(address[] memory) { + return self._pods[account].items.get(); } - function podBalanceOf(Info memory self, address account, address pod, uint256 balance) internal view returns(uint256) { - if (_getData(self).pods[account].contains(pod)) { + function podBalanceOf(Data storage self, address account, address pod, uint256 balance) internal view returns(uint256) { + if (self._pods[account].contains(pod)) { return balance; } return 0; } - function addPod(Info memory self, address account, address pod, uint256 balance) internal returns(uint256) { - return _addPod(self, account, pod, balance); + function addPod(Data storage self, address account, address pod, uint256 balance, uint256 podCallGasLimit) internal returns(uint256) { + return _addPod(self, account, pod, balance, podCallGasLimit); } - function removePod(Info memory self, address account, address pod, uint256 balance) internal { - _removePod(self, account, pod, balance); + function removePod(Data storage self, address account, address pod, uint256 balance, uint256 podCallGasLimit) internal { + _removePod(self, account, pod, balance, podCallGasLimit); } - function removeAllPods(Info memory self, address account, uint256 balance) internal { - _removeAllPods(self, account, balance); + function removeAllPods(Data storage self, address account, uint256 balance, uint256 podCallGasLimit) internal { + _removeAllPods(self, account, balance, podCallGasLimit); } - function _addPod(Info memory self, address account, address pod, uint256 balance) private returns(uint256) { + function _addPod(Data storage self, address account, address pod, uint256 balance, uint256 podCallGasLimit) private returns(uint256) { if (pod == address(0)) revert InvalidPodAddress(); - if (!_getData(self).pods[account].add(pod)) revert PodAlreadyAdded(); + if (!self._pods[account].add(pod)) revert PodAlreadyAdded(); emit PodAdded(account, pod); if (balance > 0) { - _notifyPod(pod, address(0), account, balance, 0, false, self.podCallGasLimit); + _notifyPod(pod, address(0), account, balance, 0, false, podCallGasLimit); } - return _getData(self).pods[account].length(); + return self._pods[account].length(); } - function _removePod(Info memory self, address account, address pod, uint256 balance) private { - if (!_getData(self).pods[account].remove(pod)) revert PodNotFound(); + function _removePod(Data storage self, address account, address pod, uint256 balance, uint256 podCallGasLimit) private { + if (!self._pods[account].remove(pod)) revert PodNotFound(); if (balance > 0) { - _notifyPod(pod, account, address(0), balance, 0, false, self.podCallGasLimit); + _notifyPod(pod, account, address(0), balance, 0, false, podCallGasLimit); } } - function _removeAllPods(Info memory self, address account, uint256 balance) private { - address[] memory items = _getData(self).pods[account].items.get(); + function _removeAllPods(Data storage self, address account, uint256 balance, uint256 podCallGasLimit) private { + address[] memory items = self._pods[account].items.get(); unchecked { for (uint256 i = items.length; i > 0; i--) { - _getData(self).pods[account].remove(items[i - 1]); + self._pods[account].remove(items[i - 1]); emit PodRemoved(account, items[i - 1]); if (balance > 0) { - _notifyPod(items[i - 1], account, address(0), balance, 0, false, self.podCallGasLimit); + _notifyPod(items[i - 1], account, address(0), balance, 0, false, podCallGasLimit); } } } } - function updateBalances(Info memory self, address from, address to, uint256 amount) internal { - _updateBalances(self, from, to, amount, 0, false); + function updateBalances(Data storage self, address from, address to, uint256 amount, uint256 podCallGasLimit) internal { + _updateBalances(self, from, to, amount, 0, false, podCallGasLimit); } - function updateBalancesWithTokenId(Info memory self, address from, address to, uint256 amount, uint256 id) internal { - _updateBalances(self, from, to, amount, id, true); + function updateBalancesWithTokenId(Data storage self, address from, address to, uint256 amount, uint256 id, uint256 podCallGasLimit) internal { + _updateBalances(self, from, to, amount, id, true, podCallGasLimit); } - function _updateBalances(Info memory self, address from, address to, uint256 amount, uint256 id, bool hasId) private { + function _updateBalances(Data storage self, address from, address to, uint256 amount, uint256 id, bool hasId, uint256 podCallGasLimit) private { unchecked { if (amount > 0 && from != to) { - uint256 gasLimit = self.podCallGasLimit; - address[] memory a = _getData(self).pods[from].items.get(); - address[] memory b = _getData(self).pods[to].items.get(); + address[] memory a = self._pods[from].items.get(); + address[] memory b = self._pods[to].items.get(); uint256 aLength = a.length; uint256 bLength = b.length; @@ -129,7 +114,7 @@ library TokenPodsLib { for (j = 0; j < bLength; j++) { if (pod == b[j]) { // Both parties are participating of the same Pod - _notifyPod(pod, from, to, amount, id, hasId, gasLimit); + _notifyPod(pod, from, to, amount, id, hasId, podCallGasLimit); b[j] = address(0); break; } @@ -137,7 +122,7 @@ library TokenPodsLib { if (j == bLength) { // Sender is participating in a Pod, but receiver is not - _notifyPod(pod, from, address(0), amount, id, hasId, gasLimit); + _notifyPod(pod, from, address(0), amount, id, hasId, podCallGasLimit); } } @@ -145,7 +130,7 @@ library TokenPodsLib { address pod = b[j]; if (pod != address(0)) { // Receiver is participating in a Pod, but sender is not - _notifyPod(pod, address(0), to, amount, id, hasId, gasLimit); + _notifyPod(pod, address(0), to, amount, id, hasId, podCallGasLimit); } } } @@ -155,7 +140,7 @@ library TokenPodsLib { /// @notice Assembly implementation of the gas limited call to avoid return gas bomb, // moreover call to a destructed pod would also revert even inside try-catch block in Solidity 0.8.17 /// @dev try IPod(pod).updateBalances{gas: _POD_CALL_GAS_LIMIT}(from, to, amount) {} catch {} - function _notifyPod(address pod, address from, address to, uint256 amount, uint256 id, bool hasId, uint256 gasLimit) private { + function _notifyPod(address pod, address from, address to, uint256 amount, uint256 id, bool hasId, uint256 podCallGasLimit) private { bytes4 selector = hasId ? IPodWithId.updateBalancesWithTokenId.selector : IPod.updateBalances.selector; bytes4 exception = InsufficientGas.selector; assembly { // solhint-disable-line no-inline-assembly @@ -168,18 +153,11 @@ library TokenPodsLib { mstore(add(ptr, 0x64), id) } - if lt(div(mul(gas(), 63), 64), gasLimit) { + if lt(div(mul(gas(), 63), 64), podCallGasLimit) { mstore(0, exception) revert(0, 4) } - pop(call(gasLimit, pod, 0, ptr, add(0x64, mul(hasId, 0x20)), 0, 0)) - } - } - - function _getData(Info memory info) private pure returns(Data storage data) { - DataPtr ptr = info.data; - assembly { // solhint-disable-line no-inline-assembly - data.slot := ptr + pop(call(podCallGasLimit, pod, 0, ptr, add(0x64, mul(hasId, 0x20)), 0, 0)) } } } diff --git a/package.json b/package.json index 2423673..9f96d7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@1inch/token-pods", - "version": "0.0.17", + "version": "0.1.0", "description": "ERC20 extension enabling external smart contract based Pods to track balances of those users who opted-in to these Pods", "repository": { "type": "git", From ded86e3d9f8aafe429a1214135d43c1c14c27400 Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Sun, 25 Dec 2022 17:54:55 +0400 Subject: [PATCH 6/6] Change repository name --- README.md | 6 +++--- package.json | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index edf4299..d9bf97d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # ERC20Pods -[![Build Status](https://github.com/1inch/erc20-pods/workflows/CI/badge.svg)](https://github.com/1inch/erc20-pods/actions) -[![Coverage Status](https://codecov.io/gh/1inch/erc20-pods/branch/master/graph/badge.svg?token=Z3D5O3XUYV)](https://codecov.io/gh/1inch/erc20-pods) -[![NPM Package](https://img.shields.io/npm/v/@1inch/erc20-pods.svg)](https://www.npmjs.org/package/@1inch/erc20-pods) +[![Build Status](https://github.com/1inch/token-pods/workflows/CI/badge.svg)](https://github.com/1inch/token-pods/actions) +[![Coverage Status](https://codecov.io/gh/1inch/token-pods/branch/master/graph/badge.svg?token=Z3D5O3XUYV)](https://codecov.io/gh/1inch/token-pods) +[![NPM Package](https://img.shields.io/npm/v/@1inch/token-pods.svg)](https://www.npmjs.org/package/@1inch/token-pods) ERC20 extension enabling external smart contract based Pods to track balances of those users who opted-in to these Pods. diff --git a/package.json b/package.json index 9f96d7c..4aefa52 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,12 @@ "description": "ERC20 extension enabling external smart contract based Pods to track balances of those users who opted-in to these Pods", "repository": { "type": "git", - "url": "git@github.com:1inch/erc20-pods.git" + "url": "git@github.com:1inch/token-pods.git" }, "bugs": { - "url": "https://github.com/1inch/erc20-pods/issues" + "url": "https://github.com/1inch/token-pods/issues" }, - "homepage": "https://github.com/1inch/erc20-pods#readme", + "homepage": "https://github.com/1inch/token-pods#readme", "author": "1inch", "license": "MIT", "dependencies": {