mirror of
https://github.com/Instadapp/dsa-periphery-contract.git
synced 2024-07-29 22:27:13 +00:00
128 lines
4.9 KiB
Solidity
128 lines
4.9 KiB
Solidity
// SPDX-License-Identifier: UNLICENSED
|
|
pragma solidity ^0.8.0;
|
|
|
|
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
|
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
|
|
|
|
import {IDSA} from "./interfaces/IDSA.sol";
|
|
|
|
/// @title InstadappAdapter
|
|
/// @author Connext
|
|
/// @notice This contract is inherited by InstadappTarget, it includes the logic to verify signatures
|
|
/// and execute the calls.
|
|
/// @dev This contract is not meant to be used directly, it is meant to be inherited by other contracts.
|
|
/// @custom:experimental This is an experimental contract.
|
|
contract InstadappAdapter is EIP712 {
|
|
/// Structs
|
|
/// @dev This struct is used to encode the data for InstadappTarget.cast function.
|
|
/// @param targetNames The names of the targets that will be called.
|
|
/// @param datas The data that will be sent to the targets.
|
|
/// @param origin The address that will be used as the origin of the call.
|
|
struct CastData {
|
|
string[] targetNames;
|
|
bytes[] datas;
|
|
address origin;
|
|
}
|
|
|
|
/// @dev This struct is used to encode the data that is signed by the auth address.
|
|
/// The signature is then verified by the verify function.
|
|
struct Sig {
|
|
CastData castData;
|
|
bytes32 salt;
|
|
uint256 deadline;
|
|
}
|
|
|
|
/// Storage
|
|
/// @dev This mapping is used to prevent replay attacks.
|
|
mapping(bytes32 => bool) private sigReplayProtection;
|
|
|
|
/// Constants
|
|
/// @dev This is the typehash for the CastData struct.
|
|
bytes32 public constant CASTDATA_TYPEHASH =
|
|
keccak256("CastData(string[] targetNames,bytes[] datas,address origin)");
|
|
|
|
/// @dev This is the typehash for the Sig struct.
|
|
bytes32 public constant SIG_TYPEHASH =
|
|
keccak256(
|
|
"Sig(CastData cast,bytes32 salt,uint256 deadline)CastData(string[] targetNames,bytes[] datas,address origin)"
|
|
);
|
|
|
|
/// Constructor
|
|
constructor() EIP712("InstaTargetAuth", "1") {}
|
|
|
|
/// Internal functions
|
|
/// @dev This function is used to forward the call to dsa.cast function.
|
|
/// Cast the call is forwarded, the signature is verified and the salt is stored in the sigReplayProtection mapping.
|
|
/// @param dsaAddress The address of the DSA.
|
|
/// @param _authcastCallData The data that will be sent to the targets and used for signature verification.
|
|
function authCast(
|
|
address dsaAddress,
|
|
bytes memory _authcastCallData
|
|
) internal {
|
|
/// Decode the _authcastCallData
|
|
// auth: address of Authority, which whitelisted at dsaContract.
|
|
// signature: signature is signed by the auth includes the castData with salt.
|
|
// castData: CastData required for execution at destination
|
|
// salt: salt for Signature Replay Protection, which is unique to each signature signed by auth.
|
|
// deadline: deadline for the cast to be valid
|
|
(
|
|
address auth,
|
|
bytes memory signature,
|
|
CastData memory castData,
|
|
bytes32 salt,
|
|
uint256 deadline
|
|
) = abi.decode(_authcastCallData, (address, bytes, CastData, bytes32, uint256));
|
|
|
|
IDSA dsa = IDSA(dsaAddress);
|
|
// check if Auth is valid, and included in the DSA
|
|
require(auth != address(0) && dsa.isAuth(auth), "Invalid Auth");
|
|
|
|
// check if signature is not replayed
|
|
require(!sigReplayProtection[salt], "Replay Attack");
|
|
|
|
// check if signature is not expired
|
|
require(block.timestamp < deadline, "Signature Expired");
|
|
|
|
// check if signature is valid, and not replayed
|
|
require(verify(auth, signature, castData, salt, deadline), "Invalid signature");
|
|
|
|
// Signature Replay Protection
|
|
sigReplayProtection[salt] = true;
|
|
|
|
// Cast the call
|
|
dsa.cast(castData.targetNames, castData.datas, castData.origin);
|
|
}
|
|
|
|
/// @dev This function is used to verify the signature.
|
|
/// @param auth The address of the auth.
|
|
/// @param signature The signature of the auth.
|
|
/// @param castData The data that will be sent to the targets.
|
|
/// @param salt The salt that will be used to prevent replay attacks.
|
|
/// @param deadline The deadline that will be used to prevent replay attacks.
|
|
/// @return boolean that indicates if the signature is valid.
|
|
function verify(
|
|
address auth,
|
|
bytes memory signature,
|
|
CastData memory castData,
|
|
bytes32 salt,
|
|
uint256 deadline
|
|
) internal view returns (bool) {
|
|
bytes32 digest = getDigest(castData, salt, deadline);
|
|
address signer = ECDSA.recover(digest, signature);
|
|
return signer == auth;
|
|
}
|
|
|
|
|
|
function getDigest(CastData memory castData, bytes32 salt, uint256 deadline) internal view returns (bytes32) {
|
|
return _hashTypedDataV4(keccak256(abi.encode(SIG_TYPEHASH, getHash(castData), salt, deadline)));
|
|
}
|
|
|
|
/// @dev This function is used to hash the CastData struct.
|
|
/// @param castData The data that will be sent to the targets.
|
|
/// @return bytes32 that is the hash of the CastData struct.
|
|
function getHash(CastData memory castData) internal pure returns (bytes32) {
|
|
return keccak256(abi.encode(CASTDATA_TYPEHASH, castData.targetNames, castData.datas, castData.origin));
|
|
}
|
|
|
|
}
|