Skip to content

Commit

Permalink
Merge pull request #103 from 1inch/feature/bysig
Browse files Browse the repository at this point in the history
Add universal gasless operation implementation BySig
  • Loading branch information
ZumZoom authored Mar 19, 2024
2 parents 92bb41d + 33bcacc commit 6d1d795
Show file tree
Hide file tree
Showing 6 changed files with 610 additions and 0 deletions.
46 changes: 46 additions & 0 deletions contracts/libraries/BySigTraits.sol
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;
}
}
124 changes: 124 additions & 0 deletions contracts/mixins/BySig.sol
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;
}
}
25 changes: 25 additions & 0 deletions contracts/tests/mocks/BySigTraitsMock.sol
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();
}
}
31 changes: 31 additions & 0 deletions contracts/tests/mocks/TokenWithBySig.sol
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);
}
}
Loading

0 comments on commit 6d1d795

Please sign in to comment.