diff --git a/contracts/v2/accounts/default/ECDSA.sol b/contracts/v2/accounts/default/ECDSA.sol new file mode 100644 index 0000000..83c62b5 --- /dev/null +++ b/contracts/v2/accounts/default/ECDSA.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.7.0; + +/** + * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. + * + * These functions can be used to verify that a message was signed by the holder + * of the private keys of a given address. + */ +library ECDSA { + /** + * @dev Returns the address that signed a hashed message (`hash`) with + * `signature`. This address can then be used for verification purposes. + * + * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: + * this function rejects them by requiring the `s` value to be in the lower + * half order, and the `v` value to be either 27 or 28. + * + * IMPORTANT: `hash` _must_ be the result of a hash operation for the + * verification to be secure: it is possible to craft signatures that + * recover to arbitrary addresses for non-hashed data. A safe way to ensure + * this is by receiving a hash of the original message (which may otherwise + * be too long), and then calling {toEthSignedMessageHash} on it. + */ + function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { + // Check the signature length + if (signature.length != 65) { + revert("ECDSA: invalid signature length"); + } + + // Divide the signature in r, s and v variables + bytes32 r; + bytes32 s; + uint8 v; + + // ecrecover takes the signature parameters, and the only way to get them + // currently is to use assembly. + // solhint-disable-next-line no-inline-assembly + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + } + + return recover(hash, v, r, s); + } + + /** + * @dev Overload of {ECDSA-recover} that receives the `v`, + * `r` and `s` signature fields separately. + */ + function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { + // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature + // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines + // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most + // signatures from current libraries generate a unique signature with an s-value in the lower half order. + // + // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value + // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or + // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept + // these malleable signatures as well. + require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value"); + require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value"); + + // If the signature is valid (and not malleable), return the signer address + address signer = ecrecover(hash, v, r, s); + require(signer != address(0), "ECDSA: invalid signature"); + + return signer; + } + + /** + * @dev Returns an Ethereum Signed Message, created from a `hash`. This + * produces hash corresponding to the one signed with the + * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] + * JSON-RPC method as part of EIP-191. + * + * See {recover}. + */ + function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { + // 32 is the length in bytes of hash, + // enforced by the type signature above + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } + + /** + * @dev Returns an Ethereum Signed Typed Data, created from a + * `domainSeparator` and a `structHash`. This produces hash corresponding + * to the one signed with the + * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] + * JSON-RPC method as part of EIP-712. + * + * See {recover}. + */ + function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) { + return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); + } +} \ No newline at end of file diff --git a/contracts/v2/accounts/default/implementation_default.sol b/contracts/v2/accounts/default/implementation_default.sol index 6ad8803..019abe3 100644 --- a/contracts/v2/accounts/default/implementation_default.sol +++ b/contracts/v2/accounts/default/implementation_default.sol @@ -3,6 +3,8 @@ pragma experimental ABIEncoderV2; import { Variables } from "../variables.sol"; +import { ECDSA } from "./ECDSA.sol"; + interface IndexInterface { function list() external view returns (address); } @@ -20,8 +22,33 @@ contract Constants is Variables { // The Account Module Version. uint256 public constant version = 2; + // constants for EIP712 values + string public constant DOMAIN_SEPARATOR_NAME = "DeFi-Smart-Account"; + string public constant DOMAIN_SEPARATOR_VERSION = "2.0.0"; + + // hashed EIP712 values + bytes32 internal constant DOMAIN_SEPARATOR_NAME_HASHED = keccak256(bytes(DOMAIN_SEPARATOR_NAME)); + bytes32 internal constant DOMAIN_SEPARATOR_VERSION_HASHED = keccak256(bytes(DOMAIN_SEPARATOR_VERSION)); + + bytes32 internal constant TYPE_HASH = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + + // EIP712 typehash for signed hashes used for EIP1271 (`isValidSignature()`) + bytes32 public constant EIP1271_TYPE_HASH = keccak256("DSA(bytes32 hash)"); + + // "magic value" according to EIP1271 https://eips.ethereum.org/EIPS/eip-1271#specification + bytes4 internal constant EIP1271_MAGIC_VALUE = 0x1626ba7e; + + // chainId + uint256 public immutable CHAIN_ID; + constructor(address _instaIndex) { instaIndex = _instaIndex; + uint256 id; + assembly { + id := chainid() + } + CHAIN_ID = id; } } @@ -31,6 +58,8 @@ contract Record is Constants { event LogEnableUser(address indexed user); event LogDisableUser(address indexed user); event LogBetaMode(bool indexed beta); + event LogSignedMessage(bytes32 indexed message); + event LogRemoveSignedMessage(bytes32 indexed message); /** * @dev Check for Auth if enabled. @@ -47,6 +76,29 @@ contract Record is Constants { return _beta; } + /** + * @dev Returns the domain separator + */ + function domainSeparatorV4() public view returns (bytes32) { + return + keccak256( + abi.encode( + TYPE_HASH, + DOMAIN_SEPARATOR_NAME_HASHED, + DOMAIN_SEPARATOR_VERSION_HASHED, + CHAIN_ID, + address(this) + ) + ); + } + + /** + * @dev Check is `message` is signed or not + */ + function isSignedMessage(bytes32 message) public view returns (bool) { + return _signedMessages[message]; + } + /** * @dev Enable New User. * @param user Owner address @@ -119,6 +171,63 @@ contract Record is Constants { ) external returns (bytes4) { return 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")) } + + /** + * @dev Marks a bytes32 `message` (signature digest) as signed, making it verifiable by EIP-1271 `isValidSignature()`. + * @param message data hash to be allow-listed as signed. Input `message` is hashed with `domainSeparatorV4()` according to EIP712 typed data (`EIP1271_TYPE_HASH`) + */ + function signMessage(bytes32 message) external { + require(msg.sender == address(this), "not-self"); + + // hashing with domain separator mitigates any potential replaying on other networks or other Avocados of the same owner + message = ECDSA.toTypedDataHash( + domainSeparatorV4(), + keccak256(abi.encode(EIP1271_TYPE_HASH, message)) + ); + + _signedMessages[message] = true; + + emit LogSignedMessage(message); + } + + /** + * @dev Removes a previously `signMessage()` signed bytes32 `message_` (signature digest). + * @param message data hash to be removed from allow-listed signatures + */ + function removeSignedMessage(bytes32 message) external { + require(msg.sender == address(this), "not-self"); + + delete _signedMessages[message]; + + emit LogRemoveSignedMessage(message); + } + + /** + * @dev Should return whether the signature provided is valid for the provided data + * @param hash Hash of the data to be signed + * @param signature Signature byte array associated with data + */ + function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue) { + // hashing with domain separator mitigates any potential replaying on other networks or other DSA of the same owner + hash = ECDSA.toTypedDataHash( + domainSeparatorV4(), + keccak256(abi.encode(EIP1271_TYPE_HASH, hash)) + ); + + if (signature.length == 0) { + // must be pre-allow-listed via `signMessage()` method + require(_signedMessages[hash], "invalid-signed-message"); + } else { + address signer = ECDSA.recover(hash, signature); + + if (!_auth[signer]) { + require(_signedMessages[hash], "invalid-EIP-1271-signature"); + } + } + + return EIP1271_MAGIC_VALUE; + } + } contract InstaDefaultImplementation is Record { diff --git a/contracts/v2/accounts/variables.sol b/contracts/v2/accounts/variables.sol index a30e862..bb653b9 100644 --- a/contracts/v2/accounts/variables.sol +++ b/contracts/v2/accounts/variables.sol @@ -5,4 +5,6 @@ contract Variables { mapping (address => bool) internal _auth; // enable beta mode to access all the beta features. bool internal _beta; + // signed message for EIP-1271 + mapping (bytes32 => bool) internal _signedMessages; } \ No newline at end of file diff --git a/docs/addresses.json b/docs/addresses.json index d1e4102..6180bfd 100644 --- a/docs/addresses.json +++ b/docs/addresses.json @@ -14,7 +14,7 @@ "InstaConnectorsV2Proxy": "0x7D53E606308A2E0A1D396F30dc305cc7f8483436", "InstaConnectorsV2": "0x97b0B3A8bDeFE8cB9563a3c610019Ad10DB8aD11", "InstaImplementations": "0xCBA828153d3a85b30B5b912e1f2daCac5816aE9D", - "InstaDefaultImplementation": "0x28aDcDC02Ca7B3EDf11924102726066AA0fA7010", + "InstaDefaultImplementation": "0x8F4a8675Ea3a069D1D8280Bd19B802430f8F53be", "InstaImplementationM1": "0x8a3462A50e1a9Fe8c9e7d9023CAcbD9a98D90021", "InstaChiefTimelockContract": "0xb3e586BCE929312e8B0685E2c12c1d6dbbcdc370" } @@ -35,7 +35,7 @@ "InstaConnectorsV2Proxy": "0x01fEF4d2B513C9F69E34b2f93Ef707FA9Ff60109", "InstaConnectorsV2": "0x2A00684bFAb9717C21271E0751BCcb7d2D763c88", "InstaImplementations": "0x39d3d5e7c11D61E072511485878dd84711c19d4A", - "InstaDefaultImplementation": "0xFc8CcEFeB8bD4e637C787c70F7bf7c2E7Ba9aDdf", + "InstaDefaultImplementation": "0xb8e9ef2a085671858d923aa947cb93b88714d2f8", "InstaImplementationM1": "0x4aec8c5b1cf3498bef061e13d8e7f646feeb7029", "InstaImplementationM2": "0x2638c8950e04ef002d083f62aeaa10ee32f1ae60" } @@ -56,7 +56,7 @@ "InstaConnectorsV2Proxy": "0xFD48Bef7F198B561D5198bE19c14142a0574b859", "InstaConnectorsV2": "0x67fCE99Dd6d8d659eea2a1ac1b8881c57eb6592B", "InstaImplementations": "0xF3Bb2FbdCDa1B8B6d19f513D69462eA548d0eF12", - "InstaDefaultImplementation": "0x0C25490d97594D513Fd8a80C51e4900252fA18bF", + "InstaDefaultImplementation": "0x0a0a82D2F86b9E46AE60E22FCE4e8b916F858Ddc", "InstaImplementationM1": "0x3d464f9762493a7Cecb59119a8eCdd54e46b969F" } } @@ -76,7 +76,7 @@ "InstaConnectorsV2Proxy": "0x6C7256cf7C003dD85683339F75DdE9971f98f2FD", "InstaConnectorsV2": "0x127d8cD0E2b2E0366D522DeA53A787bfE9002C14", "InstaImplementations": "0x01fEF4d2B513C9F69E34b2f93Ef707FA9Ff60109", - "InstaDefaultImplementation": "0x39d3d5e7c11D61E072511485878dd84711c19d4A", + "InstaDefaultImplementation": "0x71b6691084681e4234Bda443a27C924012870Daf", "InstaImplementationM1": "0x28846f4051EB05594B3fF9dE76b7B5bf00431155" } } @@ -96,7 +96,7 @@ "InstaConnectorsV2Proxy": "0x6C7256cf7C003dD85683339F75DdE9971f98f2FD", "InstaConnectorsV2": "0x127d8cD0E2b2E0366D522DeA53A787bfE9002C14", "InstaImplementations": "0x01fEF4d2B513C9F69E34b2f93Ef707FA9Ff60109", - "InstaDefaultImplementation": "0x39d3d5e7c11D61E072511485878dd84711c19d4A", + "InstaDefaultImplementation": "0x497Bc53507DF17e60F731e9534cff74E8BC9DBb8", "InstaImplementationM1": "0x28846f4051EB05594B3fF9dE76b7B5bf00431155" } } @@ -136,7 +136,7 @@ "InstaConnectorsV2Proxy": "0x6C7256cf7C003dD85683339F75DdE9971f98f2FD", "InstaConnectorsV2": "0x127d8cD0E2b2E0366D522DeA53A787bfE9002C14", "InstaImplementations": "0x01fEF4d2B513C9F69E34b2f93Ef707FA9Ff60109", - "InstaDefaultImplementation": "0x39d3d5e7c11D61E072511485878dd84711c19d4A", + "InstaDefaultImplementation": "0x497Bc53507DF17e60F731e9534cff74E8BC9DBb8", "InstaImplementationM1": "0x28846f4051EB05594B3fF9dE76b7B5bf00431155" } } diff --git a/hardhat.config.ts b/hardhat.config.ts index 647dda5..df9598e 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -49,7 +49,7 @@ function createConfig(network: string) { function getNetworkUrl(networkType: string) { if (networkType === "avalanche") - return "https://api.avax.network/ext/bc/C/rpc"; + return "https://avalanche.drpc.org"; else if (networkType === "polygon") return `https://polygon-mainnet.g.alchemy.com/v2/${ALCHEMY_ID}`; else if (networkType === "arbitrum") @@ -85,9 +85,33 @@ const config = { }, kovan: createConfig("kovan"), mainnet: createConfig("mainnet"), - matic: createConfig("polygon"), - avax: createConfig("avalanche"), + polygon: createConfig("polygon"), + avalanche: { + url: "https://avalanche.drpc.org", + accounts: !!PRIVATE_KEY ? [`0x${PRIVATE_KEY}`] : { mnemonic }, + timeout: 150000, + }, arbitrum: createConfig("arbitrum"), + optimism: { + url: "https://rpc.ankr.com/optimism", + accounts: !!PRIVATE_KEY ? [`0x${PRIVATE_KEY}`] : { mnemonic }, + timeout: 150000, + }, + base: { + url: "https://rpc.ankr.com/base", + accounts: !!PRIVATE_KEY ? [`0x${PRIVATE_KEY}`] : { mnemonic }, + timeout: 150000, + }, + scroll: { + url: "https://rpc.ankr.com/scroll", + accounts: !!PRIVATE_KEY ? [`0x${PRIVATE_KEY}`] : { mnemonic }, + timeout: 150000, + }, + gnosis: { + url: "https://rpc.ankr.com/gnosis", + accounts: !!PRIVATE_KEY ? [`0x${PRIVATE_KEY}`] : { mnemonic }, + timeout: 150000, + } }, solidity: { compilers: [ @@ -118,7 +142,35 @@ const config = { tests: "./test", }, etherscan: { - apiKey: process.env.ETHERSCAN, + apiKey: { + mainnet: process.env.ETHERSCAN_API_KEY || "", + polygon: process.env.POLYGONSCAN_API_KEY || "", + arbitrumOne: process.env.ARBITRUM_API_KEY || "", + optimisticEthereum: process.env.OPTIMISIM_API_KEY || "", + base: process.env.BASE_API_KEY || "", + avalanche: process.env.SNOWTRACE_API_KEY || "", + scroll: process.env.SCROLL_API_KEY || "", + xdai: process.env.GNOSIS_API_KEY || "", + gnosis: process.env.GNOSIS_API_KEY || "", + }, + customChains: [ + { + network: "base", + chainId: 8453, + urls: { + apiURL: "https://api.basescan.org/api", + browserURL: "https://basescan.org/", + }, + }, + { + network: "scroll", + chainId: 534352, + urls: { + apiURL: "https://api.scrollscan.com/api", + browserURL: "https://scrollscan.com/", + }, + }, + ], }, typechain: { outDir: "typechain", diff --git a/scripts/constant/addresses.ts b/scripts/constant/addresses.ts index b59b2ee..34690c8 100644 --- a/scripts/constant/addresses.ts +++ b/scripts/constant/addresses.ts @@ -18,5 +18,7 @@ export default { polygon: "0xA9B99766E6C676Cf1975c0D3166F96C0848fF5ad", arbitrum: "0x1eE00C305C51Ff3bE60162456A9B533C07cD9288", avalanche: "0x6CE3e607C808b4f4C26B7F6aDAeB619e49CAbb25", + base: "0x6CE3e607C808b4f4C26B7F6aDAeB619e49CAbb25", + optimism: "0x6CE3e607C808b4f4C26B7F6aDAeB619e49CAbb25" }, }; diff --git a/scripts/deployDefaultImplmplementation.ts b/scripts/deployDefaultImplmplementation.ts new file mode 100644 index 0000000..57afda0 --- /dev/null +++ b/scripts/deployDefaultImplmplementation.ts @@ -0,0 +1,27 @@ +import hre from "hardhat"; +import addresses from "./constant/addresses"; +import instaDeployContract from "./deployContract"; + +async function main() { +const INSTA_INDEX = addresses.InstaIndex[hre.network.name]; + const instaAccountV2DefaultImpl = await instaDeployContract( + "InstaDefaultImplementation", + [INSTA_INDEX] + ); + + if (hre.network.name !== "hardhat") { + await hre.run("verify:verify", { + address: instaAccountV2DefaultImpl.address, + constructorArguments: [INSTA_INDEX], + }); + } else { + console.log(`Contracts deployed to ${hre.network.name}`); + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + });