Skip to content

Commit

Permalink
feat: Separate nonce verifiers (#16)
Browse files Browse the repository at this point in the history
* feat: Separate nonce verifiers

* feat: Delete NonceVerifiable

* chore: Update README

* feat: Update events
  • Loading branch information
fzavalia authored Aug 4, 2022
1 parent 927b1b6 commit 93a3471
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 125 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,22 @@ import "@dcl/common-contracts/signatures/NonceVerifiable.sol";

Allows a contract to support meta transactions.

What are meta transactions?
What are meta transactions?

They provide a way for users to enjoy "gasless" transactions by just signing the data of the transaction they want to execute and letting a relayer, which is the one that ends up paying the fees, to execute it.

### NonceVerifiable
### ContractNonceVerifiable, SignerNonceVerifiable, AssetNonceVerifiable

Allows signatures to be invalidated on 3 different levels. Contract, Signer and Asset levels.

The contract should be queried for the current nonces on each level and use those nonces to create a signature.

When the signatures are recovered, they have to be verifies with these nonces.
When the signatures are recovered, they have to be verified with these nonces.

They can be updated in order to invalidate signatures that were created with previous values.

They come separated in 3 different contracts for each level but you might choose if you want all or just a couple depending on your requirements.

## Development

### Requirements
Expand Down
12 changes: 7 additions & 5 deletions contracts/mocks/DummyNonceVerifiableImplementor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@

pragma solidity ^0.8.7;

import "../signatures/NonceVerifiable.sol";
import "../signatures/ContractNonceVerifiable.sol";
import "../signatures/SignerNonceVerifiable.sol";
import "../signatures/AssetNonceVerifiable.sol";

contract DummyNonceVerifiableImplementor is NonceVerifiable {
contract DummyNonceVerifiableImplementor is ContractNonceVerifiable, SignerNonceVerifiable, AssetNonceVerifiable {
function initialize() external initializer {
__NonceVerifiable_init();
__ContractNonceVerifiable_init();
}

function test__NonceVerifiable_init() external {
__NonceVerifiable_init();
function test__ContractNonceVerifiable_init() external {
__ContractNonceVerifiable_init();
}

function verifyContractNonce(uint256 _nonce) external view {
Expand Down
57 changes: 57 additions & 0 deletions contracts/signatures/AssetNonceVerifiable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";

abstract contract AssetNonceVerifiable is ContextUpgradeable {
/// @notice Current nonce per asset per signer.
/// Updating it will invalidate all signatures created with the previous value on an asset level.
/// @custom:schema (contract address -> token id -> signer address -> nonce)
mapping(address => mapping(uint256 => mapping(address => uint256))) private assetNonce;

event AssetNonceUpdated(address indexed _signer, address indexed _contractAddress, uint256 indexed _tokenId, uint256 _newNonce, address _sender);

function __AssetNonceVerifiable_init() internal onlyInitializing {}

function __AssetNonceVerifiable_init_unchained() internal onlyInitializing {}

/// @notice Get the signer nonce for a given ERC721 token.
/// @param _contractAddress The address of the ERC721 contract.
/// @param _tokenId The id of the ERC721 token.
/// @param _signer The address of the signer.
/// @return The nonce of the given signer for the provided asset.
function getAssetNonce(
address _contractAddress,
uint256 _tokenId,
address _signer
) external view returns (uint256) {
return assetNonce[_contractAddress][_tokenId][_signer];
}

/// @notice Increase the asset nonce of the sender by 1.
/// @param _contractAddress The contract address of the asset.
/// @param _tokenId The token id of the asset.
function bumpAssetNonce(address _contractAddress, uint256 _tokenId) external {
_bumpAssetNonce(_contractAddress, _tokenId, _msgSender());
}

/// @dev Increase the asset nonce by 1
function _bumpAssetNonce(
address _contractAddress,
uint256 _tokenId,
address _signer
) internal {
emit AssetNonceUpdated(_signer, _contractAddress, _tokenId, ++assetNonce[_contractAddress][_tokenId][_signer], _msgSender());
}

/// @dev Reverts if the provided nonce does not match the asset nonce.
function _verifyAssetNonce(
address _contractAddress,
uint256 _tokenId,
address _signer,
uint256 _nonce
) internal view {
require(_nonce == assetNonce[_contractAddress][_tokenId][_signer], "AssetNonceVerifiable#_verifyAssetNonce: ASSET_NONCE_MISMATCH");
}
}
40 changes: 40 additions & 0 deletions contracts/signatures/ContractNonceVerifiable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

abstract contract ContractNonceVerifiable is OwnableUpgradeable {
/// @notice Current nonce at a contract level. Only updatable by the owner of the contract.
/// Updating it will invalidate all signatures created with the previous value on a contract level.
uint256 private contractNonce;

event ContractNonceUpdated(uint256 _newNonce, address _sender);

function __ContractNonceVerifiable_init() internal onlyInitializing {
__Ownable_init();
}

function __ContractNonceVerifiable_init_unchained() internal onlyInitializing {}

/// @notice Get the current contract nonce.
/// @return The current contract nonce.
function getContractNonce() external view returns (uint256) {
return contractNonce;
}

/// @notice As the owner of the contract, increase the contract nonce by 1.
function bumpContractNonce() external onlyOwner {
_bumpContractNonce();
}

/// @dev Increase the contract nonce by 1
function _bumpContractNonce() internal {
emit ContractNonceUpdated(++contractNonce, _msgSender());
}

/// @dev Reverts if the provided nonce does not match the contract nonce.
function _verifyContractNonce(uint256 _nonce) internal view {
require(_nonce == contractNonce, "ContractNonceVerifiable#_verifyContractNonce: CONTRACT_NONCE_MISMATCH");
}
}
113 changes: 0 additions & 113 deletions contracts/signatures/NonceVerifiable.sol

This file was deleted.

40 changes: 40 additions & 0 deletions contracts/signatures/SignerNonceVerifiable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

import "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";

abstract contract SignerNonceVerifiable is ContextUpgradeable {
/// @notice Current nonce per signer.
/// Updating it will invalidate all signatures created with the previous value on a signer level.
/// @custom:schema (signer address -> nonce)
mapping(address => uint256) private signerNonce;

event SignerNonceUpdated(address indexed _signer, uint256 _newNonce, address _sender);

function __SignerNonceVerifiable_init() internal onlyInitializing {}

function __SignerNonceVerifiable_init_unchained() internal onlyInitializing {}

/// @notice Get the current signer nonce.
/// @param _signer The address of the signer.
/// @return The nonce of the given signer.
function getSignerNonce(address _signer) external view returns (uint256) {
return signerNonce[_signer];
}

/// @notice Increase the signer nonce of the sender by 1.
function bumpSignerNonce() external {
_bumpSignerNonce(_msgSender());
}

/// @dev Increase the signer nonce by 1
function _bumpSignerNonce(address _signer) internal {
emit SignerNonceUpdated(_signer, ++signerNonce[_signer], _msgSender());
}

/// @dev Reverts if the provided nonce does not match the signer nonce.
function _verifySignerNonce(address _signer, uint256 _nonce) internal view {
require(_nonce == signerNonce[_signer], "SignerNonceVerifiable#_verifySignerNonce: SIGNER_NONCE_MISMATCH");
}
}
8 changes: 4 additions & 4 deletions test/NonceVerifiable.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('NonceVerifiable', () => {
})

it('should revert when called after initialization', async () => {
await expect(contract.connect(deployer).test__NonceVerifiable_init()).to.be.revertedWith('Initializable: contract is not initializing')
await expect(contract.connect(deployer).test__ContractNonceVerifiable_init()).to.be.revertedWith('Initializable: contract is not initializing')
})
})

Expand Down Expand Up @@ -92,7 +92,7 @@ describe('NonceVerifiable', () => {
})

describe('_verifyContractNonce', () => {
const err = 'NonceVerifiable#_verifyContractNonce: CONTRACT_NONCE_MISMATCH'
const err = 'ContractNonceVerifiable#_verifyContractNonce: CONTRACT_NONCE_MISMATCH'

it('should revert when the provided nonce does not match with the contract nonce', async () => {
await expect(contract.verifyContractNonce(1)).to.be.revertedWith(err)
Expand All @@ -104,7 +104,7 @@ describe('NonceVerifiable', () => {
})

describe('_verifySignerNonce', () => {
const err = 'NonceVerifiable#_verifySignerNonce: SIGNER_NONCE_MISMATCH'
const err = 'SignerNonceVerifiable#_verifySignerNonce: SIGNER_NONCE_MISMATCH'

it('should revert when the provided nonce does not match with the signer nonce', async () => {
await expect(contract.verifySignerNonce(signer.address, 1)).to.be.revertedWith(err)
Expand All @@ -116,7 +116,7 @@ describe('NonceVerifiable', () => {
})

describe('_verifyAssetNonce', () => {
const err = 'NonceVerifiable#_verifyAssetNonce: ASSET_NONCE_MISMATCH'
const err = 'AssetNonceVerifiable#_verifyAssetNonce: ASSET_NONCE_MISMATCH'

it('should revert when the provided nonce does not match with the asset nonce', async () => {
await expect(contract.verifyAssetNonce(extra.address, 0, signer.address, 1)).to.be.revertedWith(err)
Expand Down

0 comments on commit 93a3471

Please sign in to comment.