-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #103 from 1inch/feature/bysig
Add universal gasless operation implementation BySig
- Loading branch information
Showing
6 changed files
with
610 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
library BySigTraits { | ||
error WrongNonceType(); | ||
|
||
// 2 bits for type | ||
// 6 bits reserved for future use | ||
// 40 bits for deadline | ||
// 80 bits for relayer address lower bits | ||
// 128 bits for nonce value | ||
type Value is uint256; | ||
|
||
enum NonceType { | ||
Account, | ||
Selector, | ||
Unique | ||
} | ||
|
||
uint256 constant public TYPE_BIT_SHIFT = 254; | ||
uint256 constant public DEADLINE_BIT_SHIFT = 208; | ||
uint256 constant public DEADLINE_BIT_MASK = (1 << 40) - 1; | ||
uint256 constant public RELAYER_BIT_SHIFT = 128; | ||
uint256 constant public RELAYER_BIT_MASK = (1 << 80) - 1; | ||
uint256 constant public NONCE_MASK = (1 << 128) - 1; | ||
|
||
function nonceType(Value traits) internal pure returns(NonceType) { | ||
uint256 _type = Value.unwrap(traits) >> TYPE_BIT_SHIFT; | ||
if (_type > uint256(NonceType.Unique)) revert WrongNonceType(); | ||
return NonceType(_type); | ||
} | ||
|
||
function deadline(Value traits) internal pure returns(uint256) { | ||
return (Value.unwrap(traits) >> DEADLINE_BIT_SHIFT) & DEADLINE_BIT_MASK; | ||
} | ||
|
||
function isRelayerAllowed(Value traits, address relayer) internal pure returns(bool) { | ||
uint256 relayerBits = (Value.unwrap(traits) >> RELAYER_BIT_SHIFT) & RELAYER_BIT_MASK; | ||
return relayerBits == 0 || (uint160(relayer) & RELAYER_BIT_MASK) == relayerBits; | ||
} | ||
|
||
function nonce(Value traits) internal pure returns(uint256) { | ||
return Value.unwrap(traits) & NONCE_MASK; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
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"; | ||
|
||
abstract contract BySig is Context, EIP712 { | ||
using Address for address; | ||
using BySigTraits for BySigTraits.Value; | ||
using AddressArray for AddressArray.Data; | ||
|
||
error WrongNonce(); | ||
error WrongRelayer(); | ||
error WrongSignature(); | ||
error DeadlineExceeded(); | ||
|
||
struct SignedCall { | ||
BySigTraits.Value traits; | ||
bytes data; | ||
} | ||
|
||
bytes32 constant public SIGNED_CALL_TYPEHASH = keccak256("SignedCall(uint256 traits,bytes data)"); | ||
|
||
AddressArray.Data /* transient */ private _msgSenders; | ||
mapping(address => uint256) private _bySigAccountNonces; | ||
mapping(address => mapping(bytes4 => uint256)) private _bySigSelectorNonces; | ||
mapping(address => mapping(uint256 => uint256)) private _bySigUniqueNonces; | ||
|
||
function bySigAccountNonces(address account) public view returns(uint256) { | ||
return _bySigAccountNonces[account]; | ||
} | ||
|
||
function bySigSelectorNonces(address account, bytes4 selector) public view returns(uint256) { | ||
return _bySigSelectorNonces[account][selector]; | ||
} | ||
|
||
function bySigUniqueNonces(address account, uint256 nonce) public view returns(bool) { | ||
return (_bySigUniqueNonces[account][nonce >> 8] & (1 << (nonce & 0xff))) != 0; | ||
} | ||
|
||
function bySigUniqueNoncesSlot(address account, uint256 nonce) public view returns(uint256) { | ||
return _bySigUniqueNonces[account][nonce >> 8]; | ||
} | ||
|
||
function hashBySig(SignedCall calldata sig) public view returns(bytes32) { | ||
return _hashTypedDataV4( | ||
keccak256(abi.encode( | ||
SIGNED_CALL_TYPEHASH, | ||
sig.traits, | ||
keccak256(sig.data) | ||
)) | ||
); | ||
} | ||
|
||
function bySig(address signer, SignedCall calldata sig, bytes calldata signature) public payable returns(bytes memory ret) { | ||
if (block.timestamp > sig.traits.deadline()) revert DeadlineExceeded(); // solhint-disable-line not-rely-on-time | ||
// Using _msgSender() in the next line allows private relay execution redelegation | ||
if (!sig.traits.isRelayerAllowed(_msgSender())) revert WrongRelayer(); | ||
if (!_useNonce(signer, sig.traits, sig.data)) revert WrongNonce(); | ||
if (!ECDSA.recoverOrIsValidSignature(signer, hashBySig(sig), signature)) revert WrongSignature(); | ||
|
||
_msgSenders.push(signer); | ||
ret = address(this).functionDelegateCall(sig.data); | ||
_msgSenders.pop(); | ||
} | ||
|
||
function sponsoredCall(address token, uint256 amount, bytes calldata data, bytes calldata extraData) public payable returns(bytes memory ret) { | ||
ret = address(this).functionDelegateCall(data); | ||
_chargeSigner(_msgSender(), msg.sender, token, amount, extraData); | ||
} | ||
|
||
// Override this method to implement sponsored call accounting | ||
// Example imeplementation: | ||
// | ||
// function _chargeSigner(address signer, address relayer, address token, uint256 amount, bytes calldata extraData) internal override { | ||
// balances[token][signer] -= amount; | ||
// balances[token][relayer] += amount; | ||
// } | ||
// | ||
function _chargeSigner(address signer, address relayer, address token, uint256 amount, bytes calldata extraData) internal virtual; | ||
|
||
function useBySigAccountNonce(uint32 advance) public { | ||
_bySigAccountNonces[_msgSender()] += advance; | ||
} | ||
|
||
function useBySigSelectorNonce(bytes4 selector, uint32 advance) public { | ||
_bySigSelectorNonces[_msgSender()][selector] += advance; | ||
} | ||
|
||
function useBySigUniqueNonce(uint256 nonce) public { | ||
_bySigUniqueNonces[_msgSender()][nonce >> 8] |= 1 << (nonce & 0xff); | ||
} | ||
|
||
function _msgSender() internal view override virtual returns (address) { | ||
uint256 length = _msgSenders.length(); | ||
if (length == 0) { | ||
return super._msgSender(); | ||
} | ||
return _msgSenders.at(length - 1); | ||
} | ||
|
||
function _useNonce(address signer, BySigTraits.Value traits, bytes calldata data) private returns(bool) { | ||
BySigTraits.NonceType nonceType = traits.nonceType(); | ||
uint256 nonce = traits.nonce(); | ||
if (nonceType == BySigTraits.NonceType.Account) { | ||
return nonce == _bySigAccountNonces[signer]++; | ||
} | ||
if (nonceType == BySigTraits.NonceType.Selector) { | ||
return nonce == _bySigSelectorNonces[signer][bytes4(data)]++; | ||
} | ||
if (nonceType == BySigTraits.NonceType.Unique) { | ||
mapping(uint256 => uint256) storage map = _bySigUniqueNonces[signer]; | ||
uint256 cache = map[nonce >> 8]; | ||
map[nonce >> 8] |= 1 << (nonce & 0xff); | ||
return cache != map[nonce >> 8]; | ||
} | ||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "../../libraries/BySigTraits.sol"; | ||
|
||
contract BySigTraitsMock { | ||
using BySigTraits for BySigTraits.Value; | ||
|
||
function nonceType(BySigTraits.Value traits) external pure returns(BySigTraits.NonceType) { | ||
return traits.nonceType(); | ||
} | ||
|
||
function deadline(BySigTraits.Value traits) external pure returns(uint256) { | ||
return traits.deadline(); | ||
} | ||
|
||
function isRelayerAllowed(BySigTraits.Value traits, address relayer) external pure returns(bool) { | ||
return traits.isRelayerAllowed(relayer); | ||
} | ||
|
||
function nonce(BySigTraits.Value traits) external pure returns(uint256) { | ||
return traits.nonce(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
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"; | ||
|
||
contract TokenWithBySig is TokenMock, BySig { | ||
error WrongToken(); | ||
|
||
event ChargedSigner(address signer, address relayer, address token, uint256 amount); | ||
|
||
// 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 getChainId() external view returns (uint256) { | ||
return block.chainid; | ||
} | ||
|
||
function _chargeSigner(address signer, address relayer, address token, uint256 amount, bytes calldata /* extraData */) internal override { | ||
if (token != address(this)) revert WrongToken(); | ||
_transfer(signer, relayer, amount); | ||
emit ChargedSigner(signer, relayer, token, amount); | ||
} | ||
} |
Oops, something went wrong.