diff --git a/contracts/libraries/MsgSender.sol b/contracts/libraries/MsgSender.sol new file mode 100644 index 00000000..9a2f087a --- /dev/null +++ b/contracts/libraries/MsgSender.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { Context } from "@openzeppelin/contracts/utils/Context.sol"; +import { TransientArray } from "./TransientArray.sol"; + +// Stack of senders for each function selector +contract MsgSender is Context { + using Math for uint256; + using TransientArray for TransientArray.Address; + + error MsgSenderPopExpectedMismatch(address expected, address popped); + + mapping(address caller => + mapping(bytes4 => TransientArray.Address)) private _senders; + + function _msgSender(address caller, bytes4 selector, uint256 index) internal view virtual returns(address) { + return _senders[caller][selector].at(index); + } + + function _msgSenderLength(address caller, bytes4 selector) internal view returns (uint256) { + return _senders[caller][selector].length(); + } + + function _msgSenderPush(address caller, bytes4 selector, address newSender) internal { + _senders[caller][selector].push(newSender); + } + + function _msgSenderPop(address caller, bytes4 selector, address expected) internal { + TransientArray.Address storage stack = _senders[caller][selector]; + address popped = stack.pop(); + if (expected != popped) revert MsgSenderPopExpectedMismatch(expected, popped); + } + + function _msgSender() internal view virtual override returns(address) { + TransientArray.Address storage stack = _senders[msg.sender][msg.sig]; + (bool success, uint256 lastIndex) = stack.length().trySub(1); + if (!success) { + return super._msgSender(); // Fallback to Context's _msgSender if stack is empty + } + return stack.unsafeAt(lastIndex); // Use the last pushed sender + } +} diff --git a/contracts/libraries/ReentrancyGuard.sol b/contracts/libraries/ReentrancyGuard.sol new file mode 100644 index 00000000..37473f0e --- /dev/null +++ b/contracts/libraries/ReentrancyGuard.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; // tload/tstore are available since 0.8.24 + +import { TransientLock, TransientLockLib } from "./TransientLock.sol"; + +/// @dev Base contract with reentrancy guard functionality using transient storage locks. +/// +/// Use private _lock defined in this contract: +/// ```solidity +/// function swap(...) external nonReentrant { +/// function doMagic(...) external onlyNonReentrantCall { +/// ``` +/// +/// Or use your own locks for more granular control: +/// ```solidity +/// TransientLock private _myLock; +/// function swap(...) external nonReentrantLock(_myLock) { +/// function doMagic(...) external onlyNonReentrantCallLock(_myLock) { +/// ``` +/// +abstract contract ReentrancyGuard { + using TransientLockLib for TransientLock; + + error MissingNonReentrantModifier(bytes4 selector); + + TransientLock private _lock; + + modifier nonReentrant { + _lock.lock(); + _; + _lock.unlock(); + } + + modifier onlyNonReentrantCall { + if (!_inNonReentrantCall()) revert MissingNonReentrantModifier(msg.sig); + _; + } + + modifier nonReentrantLock(TransientLock storage lock) { + lock.lock(); + _; + lock.unlock(); + } + + modifier onlyNonReentrantCallLock(TransientLock storage lock) { + if (!lock.isLocked()) revert MissingNonReentrantModifier(msg.sig); + _; + } + + function _inNonReentrantCall() internal view returns (bool) { + return _lock.isLocked(); + } +} diff --git a/contracts/libraries/Transient.sol b/contracts/libraries/Transient.sol new file mode 100644 index 00000000..a50d6b6a --- /dev/null +++ b/contracts/libraries/Transient.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; // tload/tstore are available since 0.8.24 + +struct tuint256 { // solhint-disable-line contract-name-camelcase + uint256 _raw; +} + +struct taddress { // solhint-disable-line contract-name-camelcase + address _raw; +} + +struct tbytes32 { // solhint-disable-line contract-name-camelcase + bytes32 _raw; +} + +/// @dev Library for drop-in replacement of uint256, address, and bytes32 with transient storage. +/// ```solidity +/// contract MagicProtocol { +/// using TransientLib for tuint256; +/// +/// error ReentrantCallDetected(); +/// +/// struct ReentrancyLock { +/// tuint256 counter; +/// } +/// +/// ReentrancyLock private _lock; +/// +/// modifier nonReentrable { +/// require(_lock.counter.inc() == 1, ReentrantCallDetected()); +/// _; +/// _lock.counter.dec(); +/// } +/// +/// function someMagicFunction(...) external nonReentrable { +/// ... +/// target.callSomeSuspiciousFunction(...); +/// ... +/// } +/// } +/// ``` +library TransientLib { + error MathOverflow(); + error MathUnderflow(); + + // Functions for tuint256 + + function tload(tuint256 storage self) internal view returns(uint256 ret) { + assembly ("memory-safe") { // solhint-disable-line no-inline-assembly + ret := tload(self.slot) + } + } + + function tstore(tuint256 storage self, uint256 value) internal { + assembly ("memory-safe") { // solhint-disable-line no-inline-assembly + tstore(self.slot, value) + } + } + + function inc(tuint256 storage self) internal returns (uint256 incremented) { + return inc(self, TransientLib.MathOverflow.selector); + } + + function inc(tuint256 storage self, bytes4 exception) internal returns (uint256 incremented) { + incremented = unsafeInc(self); + if (incremented == 0) { + assembly ("memory-safe") { // solhint-disable-line no-inline-assembly + mstore(0, exception) + revert(0, 4) + } + } + } + + function unsafeInc(tuint256 storage self) internal returns (uint256 incremented) { + assembly ("memory-safe") { // solhint-disable-line no-inline-assembly + incremented := add(tload(self.slot), 1) + tstore(self.slot, incremented) + } + } + + function dec(tuint256 storage self) internal returns (uint256 decremented) { + return dec(self, TransientLib.MathUnderflow.selector); + } + + function dec(tuint256 storage self, bytes4 exception) internal returns (uint256 decremented) { + decremented = unsafeDec(self); + if (decremented == type(uint256).max) { + assembly ("memory-safe") { // solhint-disable-line no-inline-assembly + mstore(0, exception) + revert(0, 4) + } + } + } + + function unsafeDec(tuint256 storage self) internal returns (uint256 decremented) { + assembly ("memory-safe") { // solhint-disable-line no-inline-assembly + decremented := sub(tload(self.slot), 1) + tstore(self.slot, decremented) + } + } + + // Functions for taddress + + function tload(taddress storage self) internal view returns(address ret) { + assembly ("memory-safe") { // solhint-disable-line no-inline-assembly + ret := tload(self.slot) + } + } + + function tstore(taddress storage self, address value) internal { + assembly ("memory-safe") { // solhint-disable-line no-inline-assembly + tstore(self.slot, value) + } + } + + // Functions for tbytes32 + + function tload(tbytes32 storage self) internal view returns(bytes32 ret) { + assembly ("memory-safe") { // solhint-disable-line no-inline-assembly + ret := tload(self.slot) + } + } + + function tstore(tbytes32 storage self, bytes32 value) internal { + assembly ("memory-safe") { // solhint-disable-line no-inline-assembly + tstore(self.slot, value) + } + } +} diff --git a/contracts/libraries/TransientArray.sol b/contracts/libraries/TransientArray.sol new file mode 100644 index 00000000..bcbfcf1b --- /dev/null +++ b/contracts/libraries/TransientArray.sol @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; // tload/tstore are available since 0.8.24 + +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { TransientLib, tuint256, taddress, tbytes32 } from "./Transient.sol"; + +/// @dev Library for managing transient dynamic arrays (TSTORE, TLOAD) of different types (uint256, address, bytes32). +/// ```solidity +/// contract MagicProtocol { +/// using TransientArray for TransientArray.Uint256; +/// +/// TransientArray.Uint256 private myArray; +/// +/// function cook(uint256 value) external { +/// myArray.push(value); +/// require(myArray._length() == 1, "Array should not be empty after push"); +/// require(myArray.at(0) == value, "Value at index 0 does not match pushed value"); +/// +/// uint256 poppedValue = myArray.pop(); +/// require(poppedValue == value, "Popped value does not match pushed value"); +/// require(myArray._length() == 0, "Array should be empty after pop"); +/// } +/// } +/// ``` +library TransientArray { + using Math for uint256; + using TransientLib for tuint256; + using TransientLib for taddress; + using TransientLib for tbytes32; + + error TransientArray_IndexOutOfBounds(); + error TransientArray_EmptyArrayPop(); + + struct Uint256 { + tuint256 _length; + mapping(uint256 => tuint256) _items; + } + + struct Address { + tuint256 _length; + mapping(uint256 => taddress) _items; + } + + struct Bytes32 { + tuint256 _length; + mapping(uint256 => tbytes32) _items; + } + + // Functions for Uint256 + + function length(Uint256 storage self) internal view returns (uint256) { + return self._length.tload(); + } + + function at(Uint256 storage self, uint256 index) internal view returns (uint256) { + if (index >= self._length.tload()) revert TransientArray_IndexOutOfBounds(); + return self._items[index].tload(); + } + + function unsafeAt(Uint256 storage self, uint256 index) internal view returns (uint256) { + return self._items[index].tload(); + } + + function get(Uint256 storage self) internal view returns (uint256[] memory array) { + uint256 len = self._length.tload(); + array = new uint256[](len); + for (uint256 i = 0; i < len; i++) { + array[i] = self._items[i].tload(); + } + } + + function set(Uint256 storage self, uint256 index, uint256 value) internal { + if (index >= self._length.tload()) revert TransientArray_IndexOutOfBounds(); + self._items[index].tstore(value); + } + + function push(Uint256 storage self, uint256 value) internal returns (uint256 newLength) { + uint256 nextElementIndex = self._length.tload(); + self._items[nextElementIndex].tstore(value); + unchecked { + newLength = nextElementIndex + 1; + self._length.tstore(newLength); + } + } + + function pop(Uint256 storage self) internal returns (uint256 ret) { + (bool success, uint256 newLength) = self._length.tload().trySub(1); + if (!success) revert TransientArray_EmptyArrayPop(); + + ret = self._items[newLength].tload(); + self._items[newLength].tstore(0); + self._length.tstore(newLength); + } + + // Functions for Address + + function length(Address storage self) internal view returns (uint256) { + return self._length.tload(); + } + + function at(Address storage self, uint256 index) internal view returns (address) { + if (index >= self._length.tload()) revert TransientArray_IndexOutOfBounds(); + return self._items[index].tload(); + } + + function unsafeAt(Address storage self, uint256 index) internal view returns (address) { + return self._items[index].tload(); + } + + function get(Address storage self) internal view returns (address[] memory array) { + uint256 len = self._length.tload(); + array = new address[](len); + for (uint256 i = 0; i < len; i++) { + array[i] = self._items[i].tload(); + } + } + + function set(Address storage self, uint256 index, address value) internal { + if (index >= self._length.tload()) revert TransientArray_IndexOutOfBounds(); + self._items[index].tstore(value); + } + + function push(Address storage self, address value) internal returns (uint256 newLength) { + uint256 nextElementIndex = self._length.tload(); + self._items[nextElementIndex].tstore(value); + unchecked { + newLength = nextElementIndex + 1; + self._length.tstore(newLength); + } + } + + function pop(Address storage self) internal returns (address ret) { + (bool success, uint256 newLength) = self._length.tload().trySub(1); + if (!success) revert TransientArray_EmptyArrayPop(); + + ret = self._items[newLength].tload(); + self._items[newLength].tstore(address(0)); + self._length.tstore(newLength); + } + + // Functions for Bytes32 + + function length(Bytes32 storage self) internal view returns (uint256) { + return self._length.tload(); + } + + function at(Bytes32 storage self, uint256 index) internal view returns (bytes32) { + if (index >= self._length.tload()) revert TransientArray_IndexOutOfBounds(); + return self._items[index].tload(); + } + + function unsafeAt(Bytes32 storage self, uint256 index) internal view returns (bytes32) { + return self._items[index].tload(); + } + + function get(Bytes32 storage self) internal view returns (bytes32[] memory array) { + uint256 len = self._length.tload(); + array = new bytes32[](len); + for (uint256 i = 0; i < len; i++) { + array[i] = self._items[i].tload(); + } + } + + function set(Bytes32 storage self, uint256 index, bytes32 value) internal { + if (index >= self._length.tload()) revert TransientArray_IndexOutOfBounds(); + self._items[index].tstore(value); + } + + function push(Bytes32 storage self, bytes32 value) internal returns (uint256 newLength) { + uint256 nextElementIndex = self._length.tload(); + self._items[nextElementIndex].tstore(value); + unchecked { + newLength = nextElementIndex + 1; + self._length.tstore(newLength); + } + } + + function pop(Bytes32 storage self) internal returns (bytes32 ret) { + (bool success, uint256 newLength) = self._length.tload().trySub(1); + if (!success) revert TransientArray_EmptyArrayPop(); + + ret = self._items[newLength].tload(); + self._items[newLength].tstore(bytes32(0)); + self._length.tstore(newLength); + } +} diff --git a/contracts/libraries/TransientLock.sol b/contracts/libraries/TransientLock.sol new file mode 100644 index 00000000..c31db6f3 --- /dev/null +++ b/contracts/libraries/TransientLock.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; // tload/tstore are available since 0.8.24 + +import { TransientLib, tuint256 } from "./Transient.sol"; + +struct TransientLock { + tuint256 _raw; +} + +library TransientLockLib { + using TransientLib for tuint256; + + uint256 constant private _UNLOCKED = 0; + uint256 constant private _LOCKED = 1; + + error UnexpectedLock(); + error UnexpectedUnlock(); + + function lock(TransientLock storage self) internal { + if (self._raw.inc() != _LOCKED) revert UnexpectedLock(); + } + + function isLocked(TransientLock storage self) internal view returns (bool) { + return self._raw.tload() == _LOCKED; + } + + function unlock(TransientLock storage self) internal { + self._raw.dec(UnexpectedUnlock.selector); + } + + function unlock(TransientLock storage self, bytes4 exception) internal { + self._raw.dec(exception); + } +} diff --git a/contracts/libraries/TransientSet.sol b/contracts/libraries/TransientSet.sol new file mode 100644 index 00000000..9d7b8c1d --- /dev/null +++ b/contracts/libraries/TransientSet.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; // tload/tstore are available since 0.8.24 + +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; + +import { TransientLib, tuint256, taddress, tbytes32 } from "./Transient.sol"; +import { TransientArray } from "./TransientArray.sol"; + +/// @dev Library for managing transient dynamic arrays (TSTORE, TLOAD) of different types (uint256, address, bytes32). +/// ```solidity +/// contract MagicProtocol { +/// using TransientSet for TransientSet.Uint256; +/// +/// TransientSet.Uint256 private mySet; +/// +/// function cook(uint256 value) external { +/// mySet.add(value); +/// require(mySet.length() == 1, "Set should not be empty after add"); +/// require(mySet.contains(value), "Set should contain the added value"); +/// +/// uint256 poppedValue = mySet.pop(); +/// require(poppedValue == value, "Popped value does not match pushed value"); +/// require(mySet.length() == 0, "Set should be empty after pop"); +/// } +/// } +/// ``` +library TransientSet { + using Math for uint256; + using TransientLib for tuint256; + using TransientLib for taddress; + using TransientLib for tbytes32; + using TransientArray for TransientArray.Uint256; + using TransientArray for TransientArray.Address; + using TransientArray for TransientArray.Bytes32; + + error TransientSet_IndexOutOfBounds(); + error TransientSet_EmptySetPop(); + error TransientSet_InsertExistingElement(); + + // Functions for Uint256 + + struct Uint256 { + TransientArray.Uint256 _items; + mapping(uint256 => tuint256) _lookup; + } + + function length(Uint256 storage set) internal view returns (uint256) { + return set._items.length(); + } + + function at(Uint256 storage set, uint256 index) internal view returns (uint256) { + return set._items.at(index); + } + + function unsafeAt(Uint256 storage set, uint256 index) internal view returns (uint256) { + return set._items.unsafeAt(index); + } + + function contains(Uint256 storage set, uint256 item) internal view returns (bool) { + return set._lookup[item].tload() != 0; + } + + function get(Uint256 storage set) internal view returns (uint256[] memory) { + return set._items.get(); + } + + function add(Uint256 storage set, uint256 item) internal returns (bool) { + uint256 index = set._lookup[item].tload(); + if (index != 0) { + return false; + } + set._lookup[item].tstore(set._items.push(item)); + return true; + } + + function remove(Uint256 storage set, uint256 item) internal returns (bool) { + uint256 index = set._lookup[item].tload(); + if (index == 0) { + return false; + } + + set._lookup[item].tstore(0); + uint256 lastItem = set._items.pop(); + if (lastItem != item) { + unchecked { + set._items.set(index - 1, lastItem); + set._lookup[lastItem].tstore(index); + } + } + return true; + } + + // Functions for Address + + struct Address { + TransientArray.Address _items; + mapping(address => tuint256) _lookup; + } + + function length(Address storage set) internal view returns (uint256) { + return set._items.length(); + } + + function at(Address storage set, uint256 index) internal view returns (address) { + return set._items.at(index); + } + + function unsafeAt(Address storage set, uint256 index) internal view returns (address) { + return set._items.unsafeAt(index); + } + + function contains(Address storage set, address item) internal view returns (bool) { + return set._lookup[item].tload() != 0; + } + + function get(Address storage set) internal view returns (address[] memory) { + return set._items.get(); + } + + function add(Address storage set, address item) internal returns (bool) { + uint256 index = set._lookup[item].tload(); + if (index != 0) { + return false; + } + set._lookup[item].tstore(set._items.push(item)); + return true; + } + + function remove(Address storage set, address item) internal returns (bool) { + uint256 index = set._lookup[item].tload(); + if (index == 0) { + return false; + } + + set._lookup[item].tstore(0); + address lastItem = set._items.pop(); + if (lastItem != item) { + unchecked { + set._items.set(index - 1, lastItem); + set._lookup[lastItem].tstore(index); + } + } + return true; + } + + // Functions for Bytes32 + + struct Bytes32 { + TransientArray.Bytes32 _items; + mapping(bytes32 => tuint256) _lookup; // stored as index, similar to +1 but unchecked math + } + + function length(Bytes32 storage set) internal view returns (uint256) { + return set._items.length(); + } + + function at(Bytes32 storage set, uint256 index) internal view returns (bytes32) { + return set._items.at(index); + } + + function unsafeAt(Bytes32 storage set, uint256 index) internal view returns (bytes32) { + return set._items.unsafeAt(index); + } + + function contains(Bytes32 storage set, bytes32 item) internal view returns (bool) { + return set._lookup[item].tload() != 0; + } + + function get(Bytes32 storage set) internal view returns (bytes32[] memory) { + return set._items.get(); + } + + function add(Bytes32 storage set, bytes32 item) internal returns (bool) { + uint256 index = set._lookup[item].tload(); + if (index != 0) { + return false; + } + set._lookup[item].tstore(set._items.push(item)); + return true; + } + + function remove(Bytes32 storage set, bytes32 item) internal returns (bool) { + uint256 index = set._lookup[item].tload(); + if (index == 0) { + return false; + } + + set._lookup[item].tstore(0); + bytes32 lastItem = set._items.pop(); + if (lastItem != item) { + unchecked { + set._items.set(index - 1, lastItem); + set._lookup[lastItem].tstore(index); + } + } + return true; + } +} diff --git a/contracts/mixins/BySig.sol b/contracts/mixins/BySig.sol index 72ddf6d7..27c75395 100644 --- a/contracts/mixins/BySig.sol +++ b/contracts/mixins/BySig.sol @@ -2,22 +2,22 @@ pragma solidity ^0.8.0; -import { Context } from "@openzeppelin/contracts/utils/Context.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import { ECDSA } from "../libraries/ECDSA.sol"; import { BySigTraits } from "../libraries/BySigTraits.sol"; -import { AddressArray } from "../libraries/AddressArray.sol"; +import { TransientArray } from "../libraries/TransientArray.sol"; +import { MsgSender } from "../libraries/MsgSender.sol"; /** * @title BySig * @notice Mixin that provides signature-based accessibility to every external method of the smart contract. * @dev Inherit your contract from this mixin and use `_msgSender()` instead of `msg.sender` everywhere. */ -abstract contract BySig is Context, EIP712 { +abstract contract BySig is MsgSender, EIP712 { using Address for address; using BySigTraits for BySigTraits.Value; - using AddressArray for AddressArray.Data; + using TransientArray for TransientArray.Address; /// @notice Emitted when the nonce used for a call is incorrect. error WrongNonce(); @@ -47,7 +47,6 @@ abstract contract BySig is Context, EIP712 { bytes32 constant public SIGNED_CALL_TYPEHASH = keccak256("SignedCall(uint256 traits,bytes data)"); // Various nonces used for signature verification and replay protection. - AddressArray.Data /* transient */ private _msgSenders; mapping(address => uint256) private _bySigAccountNonces; mapping(address => mapping(bytes4 => uint256)) private _bySigSelectorNonces; mapping(address => mapping(uint256 => uint256)) private _bySigUniqueNonces; @@ -124,9 +123,10 @@ abstract contract BySig is Context, EIP712 { if (!_useNonce(signer, sig.traits, sig.data)) revert WrongNonce(); if (!ECDSA.recoverOrIsValidSignature(signer, hashBySig(sig), signature)) revert WrongSignature(); - _msgSenders.push(signer); + bytes4 selector = bytes4(sig.data); + _msgSenderPush(msg.sender, selector, signer); ret = address(this).functionDelegateCall(sig.data); - _msgSenders.pop(); + _msgSenderPop(msg.sender, selector, signer); } /** @@ -141,8 +141,12 @@ abstract contract BySig is Context, EIP712 { * @return ret Result of the executed call. */ function sponsoredCall(address token, uint256 amount, bytes calldata data, bytes calldata extraData) public payable returns(bytes memory ret) { + address sender = _msgSender(); + _msgSenderPush(msg.sender, bytes4(data), sender); ret = address(this).functionDelegateCall(data); - _chargeSigner(_msgSender(), msg.sender, token, amount, extraData); + _msgSenderPop(msg.sender, bytes4(data), sender); + + _chargeSigner(sender, msg.sender, token, amount, extraData); } /** @@ -188,18 +192,6 @@ abstract contract BySig is Context, EIP712 { _bySigUniqueNonces[_msgSender()][nonce >> 8] |= 1 << (nonce & 0xff); } - /** - * @dev Returns the address of the message sender, replacing the traditional `msg.sender` with a potentially signed sender. - * @return The address of the message sender. - */ - function _msgSender() internal view override virtual returns (address) { - uint256 length = _msgSenders.length(); - if (length == 0) { - return super._msgSender(); - } - return _msgSenders.unsafeAt(length - 1); - } - function _useNonce(address signer, BySigTraits.Value traits, bytes calldata data) private returns(bool) { BySigTraits.NonceType nonceType = traits.nonceType(); uint256 nonce = traits.nonce(); diff --git a/contracts/tests/mocks/TokenWithBySig.sol b/contracts/tests/mocks/TokenWithBySig.sol index efa43477..3e61d927 100644 --- a/contracts/tests/mocks/TokenWithBySig.sol +++ b/contracts/tests/mocks/TokenWithBySig.sol @@ -6,6 +6,7 @@ import { Context } from "@openzeppelin/contracts/utils/Context.sol"; import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import { TokenMock } from "../../mocks/TokenMock.sol"; import { BySig } from "../../mixins/BySig.sol"; +import { MsgSender } from "../../libraries/MsgSender.sol"; contract TokenWithBySig is TokenMock, BySig { error WrongToken(); @@ -15,8 +16,8 @@ contract TokenWithBySig is TokenMock, BySig { // solhint-disable-next-line no-empty-blocks constructor(string memory name, string memory symbol, string memory version) TokenMock(name, symbol) EIP712(name, version) {} - function _msgSender() internal view override(Context, BySig) returns (address) { - return BySig._msgSender(); + function _msgSender() internal view override(Context, MsgSender) returns (address) { + return MsgSender._msgSender(); } function getChainId() external view returns (uint256) {