diff --git a/contracts/AuthCompatible.sol b/contracts/AccessTokenConsumer.sol similarity index 72% rename from contracts/AuthCompatible.sol rename to contracts/AccessTokenConsumer.sol index 9f14202..57bbe48 100644 --- a/contracts/AuthCompatible.sol +++ b/contracts/AccessTokenConsumer.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.4; +pragma solidity >=0.8.13; import "hardhat/console.sol"; -import "./IAuthVerifier.sol"; +import "./IAccessTokenVerifier.sol"; -contract AuthCompatible { - IAuthVerifier private _verifier; +contract AccessTokenConsumer { + IAccessTokenVerifier private _verifier; - constructor(address authVerifier) { - _verifier = IAuthVerifier(authVerifier); + constructor(address accessTokenVerifier) { + _verifier = IAccessTokenVerifier(accessTokenVerifier); } modifier requiresAuth( @@ -17,7 +17,7 @@ contract AuthCompatible { bytes32 s, uint256 expiry ) { - require(verify(v, r, s, expiry), "AuthToken: verification failure"); + require(verify(v, r, s, expiry), "AccessToken: verification failure"); _; } @@ -27,11 +27,11 @@ contract AuthCompatible { bytes32 s, uint256 expiry ) internal view returns (bool) { - AuthToken memory token = constructToken(expiry); + AccessToken memory token = constructToken(expiry); return _verifier.verify(token, v, r, s); } - function constructToken(uint256 expiry) internal view returns (AuthToken memory token) { + function constructToken(uint256 expiry) internal view returns (AccessToken memory token) { FunctionCall memory functionCall; functionCall.functionSignature = msg.sig; functionCall.target = address(this); @@ -55,9 +55,8 @@ contract AuthCompatible { let endOfSigExp := add(startPos, 0x80) let totalInputSize := sub(calldatasize(), endOfSigExp) - // disgusting dirty putrid abomination of a detestable drivelous hack because - // for some reason byte array pointers are being assigned the same address as another causing overwrite - inputs := add(inputs, mul(calldatasize(), 2)) + // Overwrite data to calldata pointer + inputs := ptr // Store expected length of total byte array as first value mstore(inputs, totalInputSize) diff --git a/contracts/AuthVerifier.sol b/contracts/AccessTokenVerifier.sol similarity index 81% rename from contracts/AuthVerifier.sol rename to contracts/AccessTokenVerifier.sol index 512b538..a986a5d 100644 --- a/contracts/AuthVerifier.sol +++ b/contracts/AccessTokenVerifier.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.4; +pragma solidity >=0.8.13; -import "./IAuthVerifier.sol"; +import "./IAccessTokenVerifier.sol"; import "./KeyInfrastructure.sol"; -contract AuthVerifier is IAuthVerifier, KeyInfrastructure { +contract AccessTokenVerifier is IAccessTokenVerifier, KeyInfrastructure { bytes32 private constant EIP712DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); @@ -15,7 +15,7 @@ contract AuthVerifier is IAuthVerifier, KeyInfrastructure { // solhint-disable max-line-length bytes32 private constant TOKEN_TYPEHASH = keccak256( - "AuthToken(uint256 expiry,FunctionCall functionCall)FunctionCall(bytes4 functionSignature,address target,address caller,bytes parameters)" + "AccessToken(uint256 expiry,FunctionCall functionCall)FunctionCall(bytes4 functionSignature,address target,address caller,bytes parameters)" ); // solhint-disable var-name-mixedcase @@ -58,19 +58,19 @@ contract AuthVerifier is IAuthVerifier, KeyInfrastructure { ); } - function hash(AuthToken memory token) internal pure returns (bytes32) { + function hash(AccessToken memory token) internal pure returns (bytes32) { return keccak256(abi.encode(TOKEN_TYPEHASH, token.expiry, hash(token.functionCall))); } function verify( - AuthToken memory token, + AccessToken memory token, uint8 v, bytes32 r, bytes32 s ) public view override returns (bool) { bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hash(token))); - require(token.expiry > block.timestamp, "AuthToken: has expired"); + require(token.expiry > block.timestamp, "AccessToken: has expired"); return ecrecover(digest, v, r, s) == _issuer; } } diff --git a/contracts/EtherMail.sol b/contracts/EtherMail.sol deleted file mode 100644 index 7f009f1..0000000 --- a/contracts/EtherMail.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.4; - -contract EtherMail { - struct EIP712Domain { - string name; - string version; - uint256 chainId; - address verifyingContract; - } - - struct Person { - string name; - address wallet; - } - - struct Mail { - Person from; - Person to; - string contents; - } - - bytes32 constant EIP712DOMAIN_TYPEHASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - bytes32 constant PERSON_TYPEHASH = keccak256("Person(string name,address wallet)"); - - bytes32 constant MAIL_TYPEHASH = - keccak256("Mail(Person from,Person to,string contents)Person(string name,address wallet)"); - - bytes32 DOMAIN_SEPARATOR; - - constructor() { - DOMAIN_SEPARATOR = hash( - EIP712Domain({ name: "Ether Mail", version: "1", chainId: block.chainid, verifyingContract: address(this) }) - ); - } - - function hash(EIP712Domain memory eip712Domain) internal pure returns (bytes32) { - return - keccak256( - abi.encode( - EIP712DOMAIN_TYPEHASH, - keccak256(bytes(eip712Domain.name)), - keccak256(bytes(eip712Domain.version)), - eip712Domain.chainId, - eip712Domain.verifyingContract - ) - ); - } - - function hash(Person memory person) internal pure returns (bytes32) { - return keccak256(abi.encode(PERSON_TYPEHASH, keccak256(bytes(person.name)), person.wallet)); - } - - function hash(Mail memory mail) internal pure returns (bytes32) { - return keccak256(abi.encode(MAIL_TYPEHASH, hash(mail.from), hash(mail.to), keccak256(bytes(mail.contents)))); - } - - function verify( - Mail memory mail, - uint8 v, - bytes32 r, - bytes32 s - ) public view returns (bool) { - bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hash(mail))); - return ecrecover(digest, v, r, s) == mail.from.wallet; - } -} diff --git a/contracts/IAuthVerifier.sol b/contracts/IAccessTokenVerifier.sol similarity index 86% rename from contracts/IAuthVerifier.sol rename to contracts/IAccessTokenVerifier.sol index 4a3b826..c67677d 100644 --- a/contracts/IAuthVerifier.sol +++ b/contracts/IAccessTokenVerifier.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.4; +pragma solidity >=0.8.13; struct EIP712Domain { string name; @@ -22,14 +22,14 @@ struct FunctionCall { // FunctionParam[] parameters; // array of input parameters to the function call } -struct AuthToken { +struct AccessToken { uint256 expiry; FunctionCall functionCall; } -interface IAuthVerifier { +interface IAccessTokenVerifier { function verify( - AuthToken memory token, + AccessToken memory token, uint8 v, bytes32 r, bytes32 s diff --git a/contracts/KeyInfrastructure.sol b/contracts/KeyInfrastructure.sol index 080464b..ae34c62 100644 --- a/contracts/KeyInfrastructure.sol +++ b/contracts/KeyInfrastructure.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity >=0.8.13; contract KeyInfrastructure { address internal _root; diff --git a/contracts/DummyDapp.sol b/contracts/examples/DummyDapp.sol similarity index 63% rename from contracts/DummyDapp.sol rename to contracts/examples/DummyDapp.sol index bd9531e..3fa8156 100644 --- a/contracts/DummyDapp.sol +++ b/contracts/examples/DummyDapp.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity >=0.8.4; +pragma solidity >=0.8.13; import "hardhat/console.sol"; -import "./AuthCompatible.sol"; +import "../AccessTokenConsumer.sol"; -contract DummyDapp is AuthCompatible { - constructor(address verifier) AuthCompatible(verifier) {} +contract DummyDapp is AccessTokenConsumer { + constructor(address verifier) AccessTokenConsumer(verifier) {} function lend( uint8 v, diff --git a/contracts/mocks/ConsumerMock.sol b/contracts/mocks/ConsumerMock.sol new file mode 100644 index 0000000..04aee1b --- /dev/null +++ b/contracts/mocks/ConsumerMock.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.13; + +import "hardhat/console.sol"; +import "../AccessTokenConsumer.sol"; + +contract ConsumerMock is AccessTokenConsumer { + constructor(address verifier) AccessTokenConsumer(verifier) {} + + function noParams( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } + + function singleAddress( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry, + address addr + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } + + function singleUint256( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry, + uint256 num + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } + + function singleStringCalldata( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry, + string calldata str + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } + + function singleStringMemory( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry, + string memory str + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } + + function singleByte( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry, + bytes1 b1 + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } + + function singleBytesCalldata( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry, + bytes calldata b1 + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } + + function singleBytesMemory( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry, + bytes memory b1 + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } + + function doubleAddressUint( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry, + address addr, + uint256 num + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } + + function doubleUint256String( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry, + uint256 num, + string memory str + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } + + function doubleStringBytesCalldata( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry, + string calldata str, + bytes calldata b + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } + + function doubleStringBytesMemory( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry, + string calldata str, + bytes calldata b + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } + + function multipleStringBytesAddress( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry, + string calldata str, + bytes calldata b, + address addr + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } + + function multipleStringBytesAddressUint256( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry, + string calldata str, + bytes calldata b, + address addr, + uint256 num + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } + + function multipleStringBytesAddressUint256Bytes( + uint8 v, + bytes32 r, + bytes32 s, + uint256 expiry, + string calldata str, + bytes calldata b, + address addr, + uint256 num, + bytes calldata b2 + ) public view requiresAuth(v, r, s, expiry) returns (bool) { + return true; + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 2a8f70d..568f077 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -11,14 +11,16 @@ import { resolve } from "path"; import { config as dotenvConfig } from "dotenv"; import { HardhatUserConfig } from "hardhat/config"; -import { NetworkUserConfig } from "hardhat/types"; +import { HDAccountsUserConfig, NetworkUserConfig } from "hardhat/types"; dotenvConfig({ path: resolve(__dirname, "./.env") }); // Ensure that we have all the environment variables we need. const mnemonic: string | undefined = process.env.MNEMONIC; -if (!mnemonic) { - throw new Error("Please set your MNEMONIC in a .env file"); +const privateKey: string | undefined = process.env.PRIVATE_KEY; + +if (!privateKey && !mnemonic) { + throw new Error("Please set your PRIVATE_KEY or MNEMONIC in a .env file"); } const infuraApiKey: string | undefined = process.env.INFURA_API_KEY; @@ -40,12 +42,19 @@ const chainIds = { function getChainConfig(network: keyof typeof chainIds): NetworkUserConfig { const url: string = "https://" + network + ".infura.io/v3/" + infuraApiKey; - return { - accounts: { + let accounts; + + // Prioritise private key if it is available + if (privateKey) accounts = [`0x${process.env.PRIVATE_KEY}`]; + else if (mnemonic) + accounts = { count: 20, mnemonic, path: "m/44'/60'/0'/0", - }, + }; + + return { + accounts, chainId: chainIds[network], url, }; @@ -59,6 +68,9 @@ const config: HardhatUserConfig = { excludeContracts: [], src: "./contracts", }, + etherscan: { + apiKey: process.env.ETHERSCAN_API_KEY, + }, networks: { hardhat: { accounts: { @@ -82,7 +94,7 @@ const config: HardhatUserConfig = { tests: "./test", }, solidity: { - version: "0.8.9", + version: "0.8.13", settings: { metadata: { // Not including the metadata hash diff --git a/package.json b/package.json index 40eb21a..29419a2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@violetprotocol/ethereum-access-token", "description": "Smart contracts for on-chain token-based access", - "version": "0.1.1", + "version": "0.2.0", "author": { "name": "papasmurf" }, diff --git a/src/README.md b/src/README.md index fde10b2..37d852a 100644 --- a/src/README.md +++ b/src/README.md @@ -2,7 +2,7 @@ Utilities for the Ethereum Access Token smart contract system. -Use these tools to help you generate and sign your EATs. First ensure that your smart contracts follow appropriate EAT interfaces by ensuring all functions that intend to be modified with `requiresAuth` to use the following parameters prepended before your usual function parameters: +Use these tools to help you generate and sign your Ethereum Access Tokens (EATs). First ensure that your smart contracts follow appropriate EAT interfaces by ensuring all functions that intend to be modified with `requiresAuth` to use the following parameters prepended before your usual function parameters: ```solidity function yourFunction(uint8 v, bytes32 r, bytes32 s, uint256 expiry, ...) {} @@ -23,7 +23,7 @@ Using yarn: ```typescript const { splitSignature } = require("@ethersproject/bytes"); const { - signAuthMessage, + signAccessToken, getSignerFromMnemonic, getSignerFromPrivateKey packParameters } = require("@violetprotocol/ethereum-access-token-helpers/utils"); @@ -33,25 +33,25 @@ const FUNCTION_SIGNATURE = "0xabcdefgh"; const CONTRACT: ethers.Contract = ...; // for example an ERC20 token contract const SIGNER: ethers.Signer = ...; const CALLER: ethers.Signer = ...; -const VERIFIER = "0x..."; // AuthVerifier contract address +const VERIFIER = "0x..."; // AccessTokenVerifier contract address const recipient = "0x123..."; const amount = 1; -// AuthToken domain for clear namespacing -const authDomain = { +// AccessToken domain for clear namespacing +const accessTokenDomain = { name: "Ethereum Access Token", version: "1", chainId: SIGNER.getChainId(), verifyingContract: VERIFIER, }; -// Construct AuthToken message with relevant data using ERC20 `transfer(address to, uint256 amount)` as the example tx -// In the Auth compatible case, the ERC20 transfer function actually looks like this: +// Construct AccessToken message with relevant data using ERC20 `transfer(address to, uint256 amount)` as the example tx +// In the AccessTokenConsumer case, the ERC20 transfer function actually looks like this: // `transfer(uint8 v, bytes32 r, bytes32 s, uint256 expiry, address to, uint256 amount)` // where we just augment the original function with the required parameters for auth // the `parameters` property takes a packed, abi-encoded set of original function parameters -const authMessage = { +const accessTokenMessage = { expiry: Math.floor(new Date().getTime() / 1000) + interval, functionCall: { functionSignature: FUNCTION_SIGNATURE, @@ -61,15 +61,15 @@ const authMessage = { }, }; -// Sign the AuthToken using the Signer -const signature = splitSignature(await signAuthMessage(SIGNER, authDomain, authMessage)); +// Sign the AccessToken using the Signer +const signature = splitSignature(await signAccessToken(SIGNER, accessTokenDomain, accessTokenMessage)); // Pass all signed data to a transaction function call await CONTRACT.functionName( signature.v, signature.r, signature.s, - authMessage.expiry, + accessTokenMessage.expiry, ...params ) ``` diff --git a/src/messages/auth.ts b/src/messages/accessToken.ts similarity index 75% rename from src/messages/auth.ts rename to src/messages/accessToken.ts index 0363db9..ddcd479 100644 --- a/src/messages/auth.ts +++ b/src/messages/accessToken.ts @@ -1,17 +1,15 @@ -import { BigNumberish } from "ethers"; - // The named list of all type definitions -const AuthMessageTypes = { +const AccessTokenTypes = { FunctionCall: [ { name: "functionSignature", type: "bytes4" }, { name: "target", type: "address" }, { name: "caller", type: "address" }, { name: "parameters", type: "bytes" }, ], - AuthToken: [ + AccessToken: [ { name: "expiry", type: "uint256" }, { name: "functionCall", type: "FunctionCall" }, ], }; -export { AuthMessageTypes }; +export { AccessTokenTypes }; diff --git a/src/messages/index.ts b/src/messages/index.ts index 24a12cf..dd78237 100644 --- a/src/messages/index.ts +++ b/src/messages/index.ts @@ -1,3 +1,2 @@ -export { AuthMessageTypes } from "./auth"; -export { MailMessageTypes } from "./mail"; +export { AccessTokenTypes } from "./accessToken"; export { Domain } from "./erc712"; diff --git a/src/messages/mail.ts b/src/messages/mail.ts deleted file mode 100644 index 6c99274..0000000 --- a/src/messages/mail.ts +++ /dev/null @@ -1,14 +0,0 @@ -// The named list of all type definitions -const MailMessageTypes = { - Person: [ - { name: "name", type: "string" }, - { name: "wallet", type: "address" }, - ], - Mail: [ - { name: "from", type: "Person" }, - { name: "to", type: "Person" }, - { name: "contents", type: "string" }, - ], -}; - -export { MailMessageTypes }; diff --git a/src/package.json b/src/package.json index 944d713..2c1044b 100644 --- a/src/package.json +++ b/src/package.json @@ -1,7 +1,7 @@ { "name": "@violetprotocol/ethereum-access-token-helpers", "description": "Typescript bindings and utilities for Ethereum Access Token", - "version": "0.1.1", + "version": "0.2.0", "main": "dist/index.js", "files": [ "dist/*" diff --git a/src/utils/index.ts b/src/utils/index.ts index 0b76cc8..e101d91 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,3 @@ -export { signAuthMessage } from "./signAuthMessage"; -export { signMailMessage } from "./signMailMessage"; +export { signAccessToken } from "./signAccessToken"; export { getSignerFromMnemonic, getSignerFromPrivateKey } from "./signer"; export { packParameters } from "./packParameters"; diff --git a/src/utils/packParameters.ts b/src/utils/packParameters.ts index 657e73f..1c4f775 100644 --- a/src/utils/packParameters.ts +++ b/src/utils/packParameters.ts @@ -1,5 +1,10 @@ import { hexlify } from "@ethersproject/bytes"; -import { ethers } from "ethers"; +import { BigNumber, ethers } from "ethers"; + +const placeholderV = 27; +const placeholderR = "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"; +const placeholderS = "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"; +const placeholderExpiry = 1652875695; const packParameters = ( contractInterface: ethers.utils.Interface, @@ -11,13 +16,27 @@ const packParameters = ( // check if selected function fragment complies with auth compatible function format: // functionName(uint8 v, bytes32 r, bytes32 s, uint256 expiry, ...) - if (!isAuthCompatible(functionFragment)) throw "packParameters: specified function is not AuthCompatible"; + if (!isAccessTokenCompatible(functionFragment)) + throw "packParameters: specified function is not compatible with AccessTokenConsumer"; // hexlify function encoding from index 4 onwards with parameters - return hexlify(contractInterface._encodeParams(functionFragment.inputs.slice(4), params)); + return `0x${hexlify( + contractInterface._encodeParams(functionFragment.inputs, [ + placeholderV, + placeholderR, + placeholderS, + placeholderExpiry, + ...params, + ]), + ) + .slice(2) // remove 0x + .slice(64) // remove v + .slice(64) // remove r + .slice(64) // remove s + .slice(64)}`; // remove expiry }; -const isAuthCompatible = (functionFragment: ethers.utils.FunctionFragment): boolean => { +const isAccessTokenCompatible = (functionFragment: ethers.utils.FunctionFragment): boolean => { if (functionFragment.inputs[0].name != "v" || functionFragment.inputs[0].type != "uint8") return false; if (functionFragment.inputs[1].name != "r" || functionFragment.inputs[1].type != "bytes32") return false; if (functionFragment.inputs[2].name != "s" || functionFragment.inputs[2].type != "bytes32") return false; diff --git a/src/utils/signAuthMessage.ts b/src/utils/signAccessToken.ts similarity index 60% rename from src/utils/signAuthMessage.ts rename to src/utils/signAccessToken.ts index 21c6ec2..14307c6 100644 --- a/src/utils/signAuthMessage.ts +++ b/src/utils/signAccessToken.ts @@ -1,19 +1,19 @@ import { JsonRpcSigner } from "@ethersproject/providers"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { Wallet } from "ethers"; -import { AuthMessageTypes } from "../messages/auth"; +import { AccessTokenTypes } from "../messages/accessToken"; import { Domain } from "../messages/erc712"; -import { AuthTokenStruct } from "../types/AuthVerifier"; +import { AccessTokenStruct } from "../types/IAccessTokenVerifier"; // Returns a 65-byte signature composed of v, r, s components concatenated: // https://docs.ethers.io/v5/api/utils/bytes/#signature-raw -const signAuthMessage = async ( +const signAccessToken = async ( signer: Wallet | JsonRpcSigner | SignerWithAddress, domain: Domain, - value: AuthTokenStruct, + value: AccessTokenStruct, ): Promise => { - const signature = await signer._signTypedData(domain, AuthMessageTypes, value); + const signature = await signer._signTypedData(domain, AccessTokenTypes, value); return signature; }; -export { signAuthMessage }; +export { signAccessToken }; diff --git a/src/utils/signMailMessage.ts b/src/utils/signMailMessage.ts deleted file mode 100644 index 6241533..0000000 --- a/src/utils/signMailMessage.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { JsonRpcSigner } from "@ethersproject/providers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { VoidSigner, Wallet, Signature } from "ethers"; -import { Domain } from "../messages/erc712"; -import { MailMessageTypes } from "../messages/mail"; -import { EtherMail } from "../types/EtherMail"; - -// Returns a 65-byte signature composed of v, r, s components concatenated: -// https://docs.ethers.io/v5/api/utils/bytes/#signature-raw -const signMailMessage = async ( - signer: Wallet | JsonRpcSigner | SignerWithAddress, - domain: Domain, - value: EtherMail.MailStruct, -): Promise => { - const signature = await signer._signTypedData(domain, MailMessageTypes, value); - return signature; -}; - -export { signMailMessage }; diff --git a/tasks/deploy/auth.ts b/tasks/deploy/auth.ts index b6711c9..7d7f08d 100644 --- a/tasks/deploy/auth.ts +++ b/tasks/deploy/auth.ts @@ -1,14 +1,18 @@ import { task } from "hardhat/config"; import { TaskArguments } from "hardhat/types"; -import { AuthVerifier } from "../../src/types/AuthVerifier"; -import { AuthVerifier__factory } from "../../src/types/factories/AuthVerifier__factory"; +import { AccessTokenVerifier } from "../../src/types/AccessTokenVerifier"; +import { AccessTokenVerifier__factory } from "../../src/types/factories/AccessTokenVerifier__factory"; -task("deploy:Auth") +task("deploy:AccessTokenVerifier") .addParam("root", "Root key") .setAction(async function (taskArguments: TaskArguments, { ethers }) { - const authFactory: AuthVerifier__factory = await ethers.getContractFactory("AuthVerifier"); - const auth: AuthVerifier = await authFactory.deploy(taskArguments.root); - await auth.deployed(); - console.log("Auth deployed to: ", auth.address); + const accessTokenVerifierFactory: AccessTokenVerifier__factory = ( + await ethers.getContractFactory("AccessTokenVerifier") + ); + const accessTokenVerifier: AccessTokenVerifier = ( + await accessTokenVerifierFactory.deploy(taskArguments.root) + ); + await accessTokenVerifier.deployed(); + console.log("AccessTokenVerifier deployed to: ", accessTokenVerifier.address); }); diff --git a/test/AccessTokenConsumer/AccessTokenConsumer.behaviour.ts b/test/AccessTokenConsumer/AccessTokenConsumer.behaviour.ts new file mode 100644 index 0000000..26108a8 --- /dev/null +++ b/test/AccessTokenConsumer/AccessTokenConsumer.behaviour.ts @@ -0,0 +1,1830 @@ +import { ethers, waffle } from "hardhat"; +import { signAccessToken } from "../../src/utils/signAccessToken"; +import { packParameters } from "../../src/utils/packParameters"; +import { splitSignature } from "@ethersproject/bytes"; + +const chai = require("chai"); +const { solidity } = waffle; +chai.use(solidity); +const { expect } = chai; +const { BigNumber } = ethers; + +const shouldBehaveLikeAccessTokenConsumer = function () { + describe("sign and verify", async () => { + context("when calling function", async function () { + context("with parameters", async function () { + describe("address", async function () { + before("construct token", async function () { + this.params = [this.signers.user0.address]; + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("singleAddress"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: packParameters(this.mock.interface, "singleAddress", this.params), + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.singleAddress( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .singleAddress(this.signature.v, this.signature.r, this.signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.singleAddress( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.sub(50), + this.params[0], + ), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.singleAddress( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.add(50), + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect values should revert", async function () { + await expect( + this.mock.singleAddress( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.signers.user1.address, + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect( + this.mock.singleAddress(signature.v, signature.r, signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect function signature should revert", async function () { + const signature = splitSignature( + await signAccessToken(this.signers.admin, this.domain, { + ...this.value, + functionCall: { + ...this.value.functionCall, + functionSignature: "0xdeadbeef", + }, + }), + ); + + await expect( + this.mock.singleAddress(signature.v, signature.r, signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.singleAddress( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + + describe("uint256", async function () { + before("construct token", async function () { + this.params = [BigNumber.from(42)]; + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("singleUint256"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: packParameters(this.mock.interface, "singleUint256", this.params), + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.singleUint256( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .singleUint256(this.signature.v, this.signature.r, this.signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.singleUint256( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.sub(50), + this.params[0], + ), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.singleUint256( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.add(50), + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect values should revert", async function () { + await expect( + this.mock.singleUint256(this.signature.v, this.signature.r, this.signature.s, this.value.expiry, 41), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect( + this.mock.singleUint256(signature.v, signature.r, signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect function signature should revert", async function () { + const signature = splitSignature( + await signAccessToken(this.signers.admin, this.domain, { + ...this.value, + functionCall: { + ...this.value.functionCall, + functionSignature: "0xdeadbeef", + }, + }), + ); + + await expect( + this.mock.singleUint256(signature.v, signature.r, signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.singleUint256( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + + describe("string calldata", async function () { + before("construct token", async function () { + this.params = ["random string"]; + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("singleStringCalldata"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: packParameters(this.mock.interface, "singleStringCalldata", this.params), + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.singleStringCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .singleStringCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.singleStringCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.sub(50), + this.params[0], + ), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.singleStringCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.add(50), + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect values should revert", async function () { + await expect( + this.mock.singleStringCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + "bad string", + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect( + this.mock.singleStringCalldata(signature.v, signature.r, signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect function signature should revert", async function () { + const signature = splitSignature( + await signAccessToken(this.signers.admin, this.domain, { + ...this.value, + functionCall: { + ...this.value.functionCall, + functionSignature: "0xdeadbeef", + }, + }), + ); + + await expect( + this.mock.singleStringCalldata(signature.v, signature.r, signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.singleStringCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + + describe("string memory", async function () { + before("construct token", async function () { + this.params = ["random string"]; + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("singleStringMemory"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: packParameters(this.mock.interface, "singleStringMemory", this.params), + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.singleStringMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .singleStringMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.singleStringMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.sub(50), + this.params[0], + ), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.singleStringMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.add(50), + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect values should revert", async function () { + await expect( + this.mock.singleStringMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + "bad string", + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect( + this.mock.singleStringMemory(signature.v, signature.r, signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect function signature should revert", async function () { + const signature = splitSignature( + await signAccessToken(this.signers.admin, this.domain, { + ...this.value, + functionCall: { + ...this.value.functionCall, + functionSignature: "0xdeadbeef", + }, + }), + ); + + await expect( + this.mock.singleStringMemory(signature.v, signature.r, signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.singleStringMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + + describe("byte", async function () { + before("construct token", async function () { + this.params = ["0x42"]; + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("singleByte"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: packParameters(this.mock.interface, "singleByte", this.params), + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.singleByte( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .singleByte(this.signature.v, this.signature.r, this.signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.singleByte( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.sub(50), + this.params[0], + ), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.singleByte( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.add(50), + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect values should revert", async function () { + await expect( + this.mock.singleByte(this.signature.v, this.signature.r, this.signature.s, this.value.expiry, "0x41"), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect( + this.mock.singleByte(signature.v, signature.r, signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect function signature should revert", async function () { + const signature = splitSignature( + await signAccessToken(this.signers.admin, this.domain, { + ...this.value, + functionCall: { + ...this.value.functionCall, + functionSignature: "0xdeadbeef", + }, + }), + ); + + await expect( + this.mock.singleByte(signature.v, signature.r, signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.singleByte( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + + describe("bytes calldata", async function () { + before("construct token", async function () { + this.params = ["0xaaaaaaaaaaaaaaaa"]; + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("singleBytesCalldata"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: packParameters(this.mock.interface, "singleBytesCalldata", this.params), + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.singleBytesCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .singleBytesCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.singleBytesCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.sub(50), + this.params[0], + ), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.singleBytesCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.add(50), + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect values should revert", async function () { + await expect( + this.mock.singleBytesCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + "0xbbbbbb", + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect( + this.mock.singleBytesCalldata(signature.v, signature.r, signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect function signature should revert", async function () { + const signature = splitSignature( + await signAccessToken(this.signers.admin, this.domain, { + ...this.value, + functionCall: { + ...this.value.functionCall, + functionSignature: "0xdeadbeef", + }, + }), + ); + + await expect( + this.mock.singleBytesCalldata(signature.v, signature.r, signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.singleBytesCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + + describe("bytes memory", async function () { + before("construct token", async function () { + this.params = ["0xaaaaaaaaaaaaaaaa"]; + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("singleBytesMemory"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: packParameters(this.mock.interface, "singleBytesMemory", this.params), + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.singleBytesMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .singleBytesMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.singleBytesMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.sub(50), + this.params[0], + ), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.singleBytesMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.add(50), + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect values should revert", async function () { + await expect( + this.mock.singleBytesMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + "0xbbbbbb", + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect( + this.mock.singleBytesMemory(signature.v, signature.r, signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect function signature should revert", async function () { + const signature = splitSignature( + await signAccessToken(this.signers.admin, this.domain, { + ...this.value, + functionCall: { + ...this.value.functionCall, + functionSignature: "0xdeadbeef", + }, + }), + ); + + await expect( + this.mock.singleBytesMemory(signature.v, signature.r, signature.s, this.value.expiry, this.params[0]), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.singleBytesMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + + describe("address, uint256", async function () { + before("construct token", async function () { + this.params = [this.signers.user0.address, 42]; + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("doubleAddressUint"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: packParameters(this.mock.interface, "doubleAddressUint", this.params), + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.doubleAddressUint( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .doubleAddressUint( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.doubleAddressUint( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.sub(50), + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.doubleAddressUint( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.add(50), + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect values should revert", async function () { + await expect( + this.mock.doubleAddressUint( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + 41, + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect( + this.mock.doubleAddressUint( + signature.v, + signature.r, + signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect function signature should revert", async function () { + const signature = splitSignature( + await signAccessToken(this.signers.admin, this.domain, { + ...this.value, + functionCall: { + ...this.value.functionCall, + functionSignature: "0xdeadbeef", + }, + }), + ); + + await expect( + this.mock.doubleAddressUint( + signature.v, + signature.r, + signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.doubleAddressUint( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + + describe("uint256, string", async function () { + before("construct token", async function () { + this.params = [42, "some string"]; + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("doubleUint256String"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: packParameters(this.mock.interface, "doubleUint256String", this.params), + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.doubleUint256String( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .doubleUint256String( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.doubleUint256String( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.sub(50), + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.doubleUint256String( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.add(50), + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect values should revert", async function () { + await expect( + this.mock.doubleUint256String( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + "wrong string", + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect( + this.mock.doubleUint256String( + signature.v, + signature.r, + signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect function signature should revert", async function () { + const signature = splitSignature( + await signAccessToken(this.signers.admin, this.domain, { + ...this.value, + functionCall: { + ...this.value.functionCall, + functionSignature: "0xdeadbeef", + }, + }), + ); + + await expect( + this.mock.doubleUint256String( + signature.v, + signature.r, + signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.doubleUint256String( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + + describe("string, bytes calldata", async function () { + before("construct token", async function () { + this.params = ["some string", "0xaaaaaaaaaaaaaa"]; + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("doubleStringBytesCalldata"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: packParameters(this.mock.interface, "doubleStringBytesCalldata", this.params), + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.doubleStringBytesCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .doubleStringBytesCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.doubleStringBytesCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.sub(50), + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.doubleStringBytesCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.add(50), + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect values should revert", async function () { + await expect( + this.mock.doubleStringBytesCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + "bad string", + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect( + this.mock.doubleStringBytesCalldata( + signature.v, + signature.r, + signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect function signature should revert", async function () { + const signature = splitSignature( + await signAccessToken(this.signers.admin, this.domain, { + ...this.value, + functionCall: { + ...this.value.functionCall, + functionSignature: "0xdeadbeef", + }, + }), + ); + + await expect( + this.mock.doubleStringBytesCalldata( + signature.v, + signature.r, + signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.doubleStringBytesCalldata( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + + describe("string, bytes memory", async function () { + before("construct token", async function () { + this.params = ["some string", "0xaaaaaaaaaaaaaa"]; + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("doubleStringBytesMemory"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: packParameters(this.mock.interface, "doubleStringBytesMemory", this.params), + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.doubleStringBytesMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .doubleStringBytesMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.doubleStringBytesMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.sub(50), + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.doubleStringBytesMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.add(50), + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect values should revert", async function () { + await expect( + this.mock.doubleStringBytesMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + "bad string", + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect( + this.mock.doubleStringBytesMemory( + signature.v, + signature.r, + signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect function signature should revert", async function () { + const signature = splitSignature( + await signAccessToken(this.signers.admin, this.domain, { + ...this.value, + functionCall: { + ...this.value.functionCall, + functionSignature: "0xdeadbeef", + }, + }), + ); + + await expect( + this.mock.doubleStringBytesMemory( + signature.v, + signature.r, + signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.doubleStringBytesMemory( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + + describe("string, bytes calldata, address", async function () { + before("construct token", async function () { + this.params = ["some string", "0xaaaaaaaaaaaaaa", this.signers.user0.address]; + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("multipleStringBytesAddress"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: packParameters(this.mock.interface, "multipleStringBytesAddress", this.params), + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.multipleStringBytesAddress( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + ), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .multipleStringBytesAddress( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.multipleStringBytesAddress( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.sub(50), + this.params[0], + this.params[1], + this.params[2], + ), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.multipleStringBytesAddress( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.add(50), + this.params[0], + this.params[1], + this.params[2], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect values should revert", async function () { + await expect( + this.mock.multipleStringBytesAddress( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + "bad string", + this.params[1], + this.params[2], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect( + this.mock.multipleStringBytesAddress( + signature.v, + signature.r, + signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect function signature should revert", async function () { + const signature = splitSignature( + await signAccessToken(this.signers.admin, this.domain, { + ...this.value, + functionCall: { + ...this.value.functionCall, + functionSignature: "0xdeadbeef", + }, + }), + ); + + await expect( + this.mock.multipleStringBytesAddress( + signature.v, + signature.r, + signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.multipleStringBytesAddress( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + + describe("string, bytes calldata, address, uint256", async function () { + before("construct token", async function () { + this.params = ["some string", "0xaaaaaaaaaaaaaa", this.signers.user0.address, 42]; + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("multipleStringBytesAddressUint256"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: packParameters(this.mock.interface, "multipleStringBytesAddressUint256", this.params), + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.multipleStringBytesAddressUint256( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + this.params[3], + ), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .multipleStringBytesAddressUint256( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + this.params[3], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.multipleStringBytesAddressUint256( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.sub(50), + this.params[0], + this.params[1], + this.params[2], + this.params[3], + ), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.multipleStringBytesAddressUint256( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.add(50), + this.params[0], + this.params[1], + this.params[2], + this.params[3], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect values should revert", async function () { + await expect( + this.mock.multipleStringBytesAddressUint256( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + "bad string", + this.params[1], + this.params[2], + this.params[3], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect( + this.mock.multipleStringBytesAddressUint256( + signature.v, + signature.r, + signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + this.params[3], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect function signature should revert", async function () { + const signature = splitSignature( + await signAccessToken(this.signers.admin, this.domain, { + ...this.value, + functionCall: { + ...this.value.functionCall, + functionSignature: "0xdeadbeef", + }, + }), + ); + + await expect( + this.mock.multipleStringBytesAddressUint256( + signature.v, + signature.r, + signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + this.params[3], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.multipleStringBytesAddressUint256( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + this.params[3], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + + describe("string, bytes calldata, address, uint256, bytes calldata", async function () { + before("construct token", async function () { + this.params = ["some string", "0xaaaaaaaaaaaaaa", this.signers.user0.address, 42, "0xbbbbbbbbbbbb"]; + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("multipleStringBytesAddressUint256Bytes"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: packParameters(this.mock.interface, "multipleStringBytesAddressUint256Bytes", this.params), + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.multipleStringBytesAddressUint256Bytes( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + this.params[3], + this.params[4], + ), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .multipleStringBytesAddressUint256Bytes( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + this.params[3], + this.params[4], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.multipleStringBytesAddressUint256Bytes( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.sub(50), + this.params[0], + this.params[1], + this.params[2], + this.params[3], + this.params[4], + ), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.multipleStringBytesAddressUint256Bytes( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry.add(50), + this.params[0], + this.params[1], + this.params[2], + this.params[3], + this.params[4], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect values should revert", async function () { + await expect( + this.mock.multipleStringBytesAddressUint256Bytes( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + "bad string", + this.params[1], + this.params[2], + this.params[3], + this.params[4], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect( + this.mock.multipleStringBytesAddressUint256Bytes( + signature.v, + signature.r, + signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + this.params[3], + this.params[4], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect function signature should revert", async function () { + const signature = splitSignature( + await signAccessToken(this.signers.admin, this.domain, { + ...this.value, + functionCall: { + ...this.value.functionCall, + functionSignature: "0xdeadbeef", + }, + }), + ); + + await expect( + this.mock.multipleStringBytesAddressUint256Bytes( + signature.v, + signature.r, + signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + this.params[3], + this.params[4], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.multipleStringBytesAddressUint256Bytes( + this.signature.v, + this.signature.r, + this.signature.s, + this.value.expiry, + this.params[0], + this.params[1], + this.params[2], + this.params[3], + this.params[4], + ), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + }); + + context("without parameters", async function () { + before("construct token", async function () { + this.params = BigNumber.from(42); + this.value = { + expiry: BigNumber.from(Math.floor(new Date().getTime() / 1000) + 10), + functionCall: { + functionSignature: this.mock.interface.getSighash("noParams"), + target: this.mock.address, + caller: this.signers.admin.address, + parameters: [], + }, + }; + this.signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + }); + + it("with correct values should succeed", async function () { + expect( + await this.mock.noParams(this.signature.v, this.signature.r, this.signature.s, this.value.expiry), + ).to.be.true; + }); + + it("with incorrect caller should revert", async function () { + await expect( + this.mock + .connect(this.signers.user1) + .noParams(this.signature.v, this.signature.r, this.signature.s, this.value.expiry), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with expired token should revert", async function () { + await expect( + this.mock.noParams(this.signature.v, this.signature.r, this.signature.s, this.value.expiry.sub(50)), + ).to.be.revertedWith("AccessToken: has expired"); + }); + + it("with incorrect expiry should revert", async function () { + await expect( + this.mock.noParams(this.signature.v, this.signature.r, this.signature.s, this.value.expiry.add(50)), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + + it("with incorrect signer should revert", async function () { + const signature = splitSignature(await signAccessToken(this.signers.user0, this.domain, this.value)); + + await expect(this.mock.noParams(signature.v, signature.r, signature.s, this.value.expiry)).to.be.revertedWith( + "AccessToken: verification failure", + ); + }); + + it("with incorrect target contract should revert", async function () { + await expect( + this.fakeMock.noParams(this.signature.v, this.signature.r, this.signature.s, this.value.expiry), + ).to.be.revertedWith("AccessToken: verification failure"); + }); + }); + }); + }); +}; + +export { shouldBehaveLikeAccessTokenConsumer }; diff --git a/test/AccessTokenConsumer/AccessTokenConsumer.test.ts b/test/AccessTokenConsumer/AccessTokenConsumer.test.ts new file mode 100644 index 0000000..83ee3d3 --- /dev/null +++ b/test/AccessTokenConsumer/AccessTokenConsumer.test.ts @@ -0,0 +1,53 @@ +import { artifacts, ethers, waffle } from "hardhat"; +import type { Artifact } from "hardhat/types"; +import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; + +import { DummyDapp } from "../../src/types/DummyDapp"; +import { packParameters } from "../../src/utils/packParameters"; +import { splitSignature } from "@ethersproject/bytes"; +import { shouldBehaveLikeAccessTokenConsumer } from "./AccessTokenConsumer.behaviour"; +import { AccessTokenVerifier, ConsumerMock } from "../../src/types"; +import { Signers } from "../types"; + +const chai = require("chai"); +const { solidity } = waffle; +chai.use(solidity); +const { expect } = chai; +const { BigNumber } = ethers; + +describe("AccessTokenConsumer", function () { + before("setup accounts", async function () { + this.signers = {} as Signers; + + const signers: SignerWithAddress[] = await ethers.getSigners(); + this.signers.admin = signers[0]; + this.signers.user0 = signers[1]; + this.signers.user1 = signers[2]; + this.signers.user2 = signers[3]; + }); + + before("deploy new", async function () { + const authArtifact: Artifact = await artifacts.readArtifact("AccessTokenVerifier"); + const dappArtifact: Artifact = await artifacts.readArtifact("DummyDapp"); + const mockArtifact: Artifact = await artifacts.readArtifact("ConsumerMock"); + this.auth = ( + await waffle.deployContract(this.signers.admin, authArtifact, [this.signers.admin.address]) + ); + this.dapp = await waffle.deployContract(this.signers.admin, dappArtifact, [this.auth.address]); + await this.auth.rotateIntermediate(this.signers.admin.address); + await this.auth.rotateIssuer(this.signers.admin.address); + this.mock = await waffle.deployContract(this.signers.admin, mockArtifact, [this.auth.address]); + this.fakeMock = await waffle.deployContract(this.signers.admin, mockArtifact, [this.auth.address]); + }); + + before("construct test values", async function () { + this.domain = { + name: "Ethereum Access Token", + version: "1", + chainId: await this.signers.admin.getChainId(), + verifyingContract: this.auth.address, + }; + }); + + shouldBehaveLikeAccessTokenConsumer(); +}); diff --git a/test/authVerifier.ts b/test/AccessTokenVerifier/AccessTokenVerifier.behaviour.ts similarity index 54% rename from test/authVerifier.ts rename to test/AccessTokenVerifier/AccessTokenVerifier.behaviour.ts index 1e61272..a4dea88 100644 --- a/test/authVerifier.ts +++ b/test/AccessTokenVerifier/AccessTokenVerifier.behaviour.ts @@ -1,10 +1,9 @@ import { artifacts, ethers, waffle } from "hardhat"; import type { Artifact } from "hardhat/types"; import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; -import { Signers } from "./types"; -import { AuthVerifier } from "../src/types/AuthVerifier"; -import { AuthTokenStruct } from "../src/types/IAuthVerifier"; -import { signAuthMessage } from "../src/utils/signAuthMessage"; +import { Signers } from "../types"; +import { AccessTokenStruct } from "../../src/types/IAccessTokenVerifier"; +import { signAccessToken } from "../../src/utils/signAccessToken"; import { splitSignature } from "@ethersproject/bytes"; const chai = require("chai"); @@ -13,49 +12,12 @@ chai.use(solidity); const { expect } = chai; const { BigNumber } = ethers; -describe("Auth", function () { - before(async function () { - this.signers = {} as Signers; - - const signers: SignerWithAddress[] = await ethers.getSigners(); - this.signers.admin = signers[0]; - this.signers.user0 = signers[1]; - this.signers.user1 = signers[2]; - this.signers.user2 = signers[3]; - }); - - before("deploy new", async function () { - const authArtifact: Artifact = await artifacts.readArtifact("AuthVerifier"); - this.auth = ( - await waffle.deployContract(this.signers.admin, authArtifact, [this.signers.admin.address]) - ); - await this.auth.rotateIntermediate(this.signers.admin.address); - await this.auth.rotateIssuer(this.signers.admin.address); - }); - - before("construct test values", async function () { - this.domain = { - name: "Ethereum Access Token", - version: "1", - chainId: await this.signers.admin.getChainId(), - verifyingContract: this.auth.address, - }; - - this.value = { - expiry: Math.floor(new Date().getTime() / 1000) + 10, - functionCall: { - functionSignature: "0x0f694584", - target: this.auth.address, - caller: this.signers.admin.address, - parameters: "0xff", - }, - }; - }); - +const shouldBehaveLikeAccessTokenVerifier = function () { describe("sign and verify", async () => { it("should succeed", async function () { - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, this.value)); + const signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); + await expect(this.auth.verify(this.value, signature.v, signature.r, signature.s)).to.not.be.reverted; expect(await this.auth.callStatic.verify(this.value, signature.v, signature.r, signature.s)).to.be.true; }); @@ -63,25 +25,25 @@ describe("Auth", function () { // The data to sign const value = { ...this.value, expiry: this.value.expiry - 10 }; - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, value)); + const signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, value)); await expect(this.auth.verify(value, signature.v, signature.r, signature.s)).to.be.revertedWith( - "AuthToken: has expired", + "AccessToken: has expired", ); }); it("should fail with incorrect expiry", async function () { - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, this.value)); + const signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); - const badToken: AuthTokenStruct = { ...this.value, expiry: BigNumber.from(this.value.expiry + 10) }; + const badToken: AccessTokenStruct = { ...this.value, expiry: BigNumber.from(this.value.expiry + 10) }; expect(await this.auth.callStatic.verify(badToken, signature.v, signature.r, signature.s)).to.equal(false); }); it("should fail with incorrect function signature", async function () { - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, this.value)); + const signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); - const badToken: AuthTokenStruct = { + const badToken: AccessTokenStruct = { ...this.value, functionCall: { ...this.value.functionCall, functionSignature: "0xdeadbeef" }, }; @@ -90,9 +52,9 @@ describe("Auth", function () { }); it("should fail with incorrect target address", async function () { - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, this.value)); + const signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); - const badToken: AuthTokenStruct = { + const badToken: AccessTokenStruct = { ...this.value, functionCall: { ...this.value.functionCall, target: "0x25AF0ccA791baEe922D9fa0744880ae6E0422021" }, }; @@ -101,9 +63,9 @@ describe("Auth", function () { }); it("should fail with incorrect caller address", async function () { - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, this.value)); + const signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); - const badToken: AuthTokenStruct = { + const badToken: AccessTokenStruct = { ...this.value, functionCall: { ...this.value.functionCall, caller: "0x25AF0ccA791baEe922D9fa0744880ae6E0422021" }, }; @@ -112,9 +74,9 @@ describe("Auth", function () { }); it("should fail with incorrect function parameters", async function () { - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, this.value)); + const signature = splitSignature(await signAccessToken(this.signers.admin, this.domain, this.value)); - const badToken: AuthTokenStruct = { + const badToken: AccessTokenStruct = { ...this.value, functionCall: { ...this.value.functionCall, parameters: "0xdd" }, }; @@ -122,4 +84,6 @@ describe("Auth", function () { expect(await this.auth.callStatic.verify(badToken, signature.v, signature.r, signature.s)).to.equal(false); }); }); -}); +}; + +export { shouldBehaveLikeAccessTokenVerifier }; diff --git a/test/AccessTokenVerifier/AccessTokenVerifier.test.ts b/test/AccessTokenVerifier/AccessTokenVerifier.test.ts new file mode 100644 index 0000000..a82bcc6 --- /dev/null +++ b/test/AccessTokenVerifier/AccessTokenVerifier.test.ts @@ -0,0 +1,50 @@ +import { artifacts, ethers, waffle } from "hardhat"; +import type { Artifact } from "hardhat/types"; +import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; +import { Signers } from "../types"; +import { shouldBehaveLikeAccessTokenVerifier } from "./AccessTokenVerifier.behaviour"; +import { AccessTokenVerifier } from "../../src/types"; + +const chai = require("chai"); +const { solidity } = waffle; +chai.use(solidity); +const { expect } = chai; +const { BigNumber } = ethers; + +describe("AccessTokenVerifier", function () { + before(async function () { + this.signers = {} as Signers; + + const signers: SignerWithAddress[] = await ethers.getSigners(); + this.signers.admin = signers[0]; + this.signers.user0 = signers[1]; + this.signers.user1 = signers[2]; + this.signers.user2 = signers[3]; + + const authArtifact: Artifact = await artifacts.readArtifact("AccessTokenVerifier"); + this.auth = ( + await waffle.deployContract(this.signers.admin, authArtifact, [this.signers.admin.address]) + ); + await this.auth.rotateIntermediate(this.signers.admin.address); + await this.auth.rotateIssuer(this.signers.admin.address); + + this.domain = { + name: "Ethereum Access Token", + version: "1", + chainId: await this.signers.admin.getChainId(), + verifyingContract: this.auth.address, + }; + + this.value = { + expiry: Math.floor(new Date().getTime() / 1000) + 10, + functionCall: { + functionSignature: "0x0f694584", + target: this.auth.address, + caller: this.signers.admin.address, + parameters: "0xff", + }, + }; + }); + + shouldBehaveLikeAccessTokenVerifier(); +}); diff --git a/test/keyInfrastructure.ts b/test/KeyInfrastructure/KeyInfrastructure.behaviour.ts similarity index 76% rename from test/keyInfrastructure.ts rename to test/KeyInfrastructure/KeyInfrastructure.behaviour.ts index 75dd6d1..48d9972 100644 --- a/test/keyInfrastructure.ts +++ b/test/KeyInfrastructure/KeyInfrastructure.behaviour.ts @@ -1,32 +1,15 @@ import { artifacts, ethers, waffle } from "hardhat"; import type { Artifact } from "hardhat/types"; import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; -import { Signers } from "./types"; -import { KeyInfrastructure } from "../src/types/KeyInfrastructure"; +import { Signers } from "../types"; +import { KeyInfrastructure } from "../../src/types/KeyInfrastructure"; const chai = require("chai"); const { solidity } = waffle; chai.use(solidity); const { expect } = chai; -describe("Key Infrastructure", function () { - before(async function () { - this.signers = {} as Signers; - - const signers: SignerWithAddress[] = await ethers.getSigners(); - this.signers.admin = signers[0]; - this.signers.user0 = signers[1]; - this.signers.user1 = signers[2]; - this.signers.user2 = signers[3]; - }); - - before("deploy new", async function () { - const keyInfraArtifact: Artifact = await artifacts.readArtifact("KeyInfrastructure"); - this.keyInfrastructure = ( - await waffle.deployContract(this.signers.admin, keyInfraArtifact, [this.signers.admin.address]) - ); - }); - +const shouldBehaveLikeKeyInfrastructure = function () { it("key infrastructure should have been initialised correctly", async function () { expect(await this.keyInfrastructure.callStatic.getRootKey()).to.equal(this.signers.admin.address); }); @@ -71,4 +54,6 @@ describe("Key Infrastructure", function () { expect(await this.keyInfrastructure.callStatic.getIssuerKey()).to.equal(this.signers.user0.address); }); }); -}); +}; + +export { shouldBehaveLikeKeyInfrastructure }; diff --git a/test/KeyInfrastructure/KeyInfrastructure.test.ts b/test/KeyInfrastructure/KeyInfrastructure.test.ts new file mode 100644 index 0000000..39678ac --- /dev/null +++ b/test/KeyInfrastructure/KeyInfrastructure.test.ts @@ -0,0 +1,30 @@ +import { artifacts, ethers, waffle } from "hardhat"; +import type { Artifact } from "hardhat/types"; +import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; +import { Signers } from "../types"; +import { KeyInfrastructure } from "../../src/types/KeyInfrastructure"; +import { shouldBehaveLikeKeyInfrastructure } from "./KeyInfrastructure.behaviour"; + +const chai = require("chai"); +const { solidity } = waffle; +chai.use(solidity); +const { expect } = chai; + +describe("Key Infrastructure", function () { + before(async function () { + this.signers = {} as Signers; + + const signers: SignerWithAddress[] = await ethers.getSigners(); + this.signers.admin = signers[0]; + this.signers.user0 = signers[1]; + this.signers.user1 = signers[2]; + this.signers.user2 = signers[3]; + + const keyInfraArtifact: Artifact = await artifacts.readArtifact("KeyInfrastructure"); + this.keyInfrastructure = ( + await waffle.deployContract(this.signers.admin, keyInfraArtifact, [this.signers.admin.address]) + ); + }); + + shouldBehaveLikeKeyInfrastructure(); +}); diff --git a/test/authCompatible.ts b/test/authCompatible.ts deleted file mode 100644 index 02599d5..0000000 --- a/test/authCompatible.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { artifacts, ethers, waffle } from "hardhat"; -import type { Artifact } from "hardhat/types"; -import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; -import { Signers } from "./types"; -import { AuthVerifier } from "../src/types/AuthVerifier"; -import { AuthTokenStruct } from "../src/types/IAuthVerifier"; -import { DummyDapp } from "../src/types/DummyDapp"; -import { signAuthMessage } from "../src/utils/signAuthMessage"; -import { packParameters } from "../src/utils/packParameters"; -import { splitSignature } from "@ethersproject/bytes"; - -const chai = require("chai"); -const { solidity } = waffle; -chai.use(solidity); -const { expect } = chai; -const { BigNumber } = ethers; - -describe("AuthCompatible", function () { - before(async function () { - this.signers = {} as Signers; - - const signers: SignerWithAddress[] = await ethers.getSigners(); - this.signers.admin = signers[0]; - this.signers.user0 = signers[1]; - this.signers.user1 = signers[2]; - this.signers.user2 = signers[3]; - }); - - before("deploy new", async function () { - const authArtifact: Artifact = await artifacts.readArtifact("AuthVerifier"); - const dappArtifact: Artifact = await artifacts.readArtifact("DummyDapp"); - this.auth = ( - await waffle.deployContract(this.signers.admin, authArtifact, [this.signers.admin.address]) - ); - this.dapp = await waffle.deployContract(this.signers.admin, dappArtifact, [this.auth.address]); - await this.auth.rotateIntermediate(this.signers.admin.address); - await this.auth.rotateIssuer(this.signers.admin.address); - }); - - before("construct test values", async function () { - this.domain = { - name: "Ethereum Access Token", - version: "1", - chainId: await this.signers.admin.getChainId(), - verifyingContract: this.auth.address, - }; - - this.amount = 5; - this.testTokenAddress = "0x25af0cca791baee922d9fa0744880ae6e0422021"; - - this.value = { - expiry: Math.floor(new Date().getTime() / 1000) + 50, - functionCall: { - functionSignature: "0xdbbfb456", - target: this.dapp.address.toLowerCase(), - caller: this.signers.admin.address.toLowerCase(), - // Parameters are hexadecimally represented, left-padded with 0 to multiples of 64-characters (32-bytes), and concatenated together - parameters: packParameters(this.dapp.interface, "lend", [this.testTokenAddress, this.amount]), - }, - }; - }); - - describe("sign and verify", async () => { - it("should succeed", async function () { - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, this.value)); - - expect( - await this.dapp.lend( - signature.v, - signature.r, - signature.s, - BigNumber.from(this.value.expiry), - this.testTokenAddress, - this.amount, - ), - ).to.be.true; - }); - - it("should fail with expired token", async function () { - // The data to sign - const value = { ...this.value, expiry: this.value.expiry - 50 }; - - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, this.value)); - - await expect( - this.dapp.lend( - signature.v, - signature.r, - signature.s, - BigNumber.from(value.expiry - 50), - this.testTokenAddress, - this.amount, - ), - ).to.be.revertedWith("AuthToken: has expired"); - }); - - it("should fail with wrong signer", async function () { - const signature = splitSignature(await signAuthMessage(this.signers.user0, this.domain, this.value)); - - await expect( - this.dapp.lend( - signature.v, - signature.r, - signature.s, - BigNumber.from(this.value.expiry), - this.testTokenAddress, - this.amount, - ), - ).to.be.revertedWith("AuthToken: verification failure"); - }); - - it("should fail with incorrect expiry", async function () { - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, this.value)); - - await expect( - this.dapp.lend( - signature.v, - signature.r, - signature.s, - BigNumber.from(this.value.expiry + 10), - this.testTokenAddress, - this.amount, - ), - ).to.be.revertedWith("AuthToken: verification failure"); - }); - - it("should fail with incorrect function signature", async function () { - // The data to sign - const value = { ...this.value, functionCall: { ...this.value.functionCall, functionSignature: "0xb50e969c" } }; - - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, value)); - - await expect( - this.dapp.lend( - signature.v, - signature.r, - signature.s, - BigNumber.from(value.expiry), - this.testTokenAddress, - this.amount, - ), - ).to.be.revertedWith("AuthToken: verification failure"); - }); - - it("should fail with incorrect target contract", async function () { - // The data to sign - const value = { ...this.value, functionCall: { ...this.value.functionCall, target: this.auth.address } }; - - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, value)); - - await expect( - this.dapp.lend( - signature.v, - signature.r, - signature.s, - BigNumber.from(value.expiry), - this.testTokenAddress, - this.amount, - ), - ).to.be.revertedWith("AuthToken: verification failure"); - }); - - it("should fail with incorrect caller", async function () { - // The data to sign - const value = { ...this.value, functionCall: { ...this.value.functionCall, caller: this.auth.address } }; - - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, value)); - - await expect( - this.dapp.lend( - signature.v, - signature.r, - signature.s, - BigNumber.from(value.expiry), - this.testTokenAddress, - this.amount, - ), - ).to.be.revertedWith("AuthToken: verification failure"); - }); - - it("should fail with incorrect token address", async function () { - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, this.value)); - - await expect( - this.dapp.lend( - signature.v, - signature.r, - signature.s, - BigNumber.from(this.value.expiry), - this.auth.address, - this.amount, - ), - ).to.be.revertedWith("AuthToken: verification failure"); - }); - - it("should fail with incorrect amount", async function () { - const signature = splitSignature(await signAuthMessage(this.signers.admin, this.domain, this.value)); - - await expect( - this.dapp.lend( - signature.v, - signature.r, - signature.s, - BigNumber.from(this.value.expiry), - this.testTokenAddress, - 6, - ), - ).to.be.revertedWith("AuthToken: verification failure"); - }); - }); -}); diff --git a/test/mail.ts b/test/mail.ts deleted file mode 100644 index b39c263..0000000 --- a/test/mail.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { artifacts, ethers, waffle } from "hardhat"; -import type { Artifact } from "hardhat/types"; -import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; -import { Signers } from "./types"; -import { EtherMail } from "../src/types/EtherMail"; -import { signMailMessage } from "../src/utils/signMailMessage"; - -const chai = require("chai"); -const web3 = require("web3"); -const { solidity } = waffle; -chai.use(solidity); -const { expect } = chai; -const { BigNumber } = ethers; - -describe("EtherMail", function () { - before(async function () { - this.signers = {} as Signers; - - const signers: SignerWithAddress[] = await ethers.getSigners(); - this.signers.admin = signers[0]; - this.signers.user0 = signers[1]; - this.signers.user1 = signers[2]; - this.signers.user2 = signers[3]; - }); - - before("deploy new", async function () { - const mailArtifact: Artifact = await artifacts.readArtifact("EtherMail"); - this.mail = await waffle.deployContract(this.signers.admin, mailArtifact); - }); - - it("sign and verify", async function () { - const domain = { - name: "Ether Mail", - version: "1", - chainId: await this.signers.admin.getChainId(), - verifyingContract: this.mail.address, - }; - - // The data to sign - const value = { - from: { - name: "Cow", - wallet: web3.utils.toChecksumAddress(this.signers.admin.address), - }, - to: { - name: "Bob", - wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", - }, - contents: "Hello, Bob!", - }; - - const signature = await signMailMessage(this.signers.admin, domain, value); - - expect( - await this.mail.callStatic.verify( - value, - BigNumber.from("0x".concat(signature.substring(130, 132))), // v uint8 - "0x".concat(signature.substring(2, 66)), // r bytes32 - "0x".concat(signature.substring(66, 130)), // s bytes32 - ), - ).to.be.true; - }); -}); diff --git a/test/types.ts b/test/types.ts index e545a01..d348e23 100644 --- a/test/types.ts +++ b/test/types.ts @@ -1,15 +1,16 @@ import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; import type { Fixture } from "ethereum-waffle"; -import { AuthVerifier } from "../src/types/AuthVerifier"; -import { DummyDapp } from "../src/types/DummyDapp"; +import { AccessTokenVerifier, ConsumerMock, DummyDapp } from "../src/types"; import type { KeyInfrastructure } from "../src/types/KeyInfrastructure"; declare module "mocha" { export interface Context { keyInfrastructure: KeyInfrastructure; - auth: AuthVerifier; + verifier: AccessTokenVerifier; dapp: DummyDapp; + mock: ConsumerMock; + fakeMock: ConsumerMock; loadFixture: (fixture: Fixture) => Promise; signers: Signers; }