-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Trust and TrustFactory Contracts for waas Recovery (#179)
* Initial commit simple trust * Full test coverage for trust contracts * Add nested wallet signatures test * Remove unused error definition * Update default config value for ETH_MNEMONIC * Add TrustFactory contract to deployment script * Add optional chainId on signatures * Add trust creation code to factory
- Loading branch information
Showing
8 changed files
with
1,145 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity 0.8.18; | ||
|
||
import "../interfaces/IERC1271Wallet.sol"; | ||
import "../utils/SignatureValidator.sol"; | ||
|
||
function absDiff(uint256 a, uint256 b) pure returns (bool, uint256) { | ||
if (a > b) { | ||
return (true, a - b); | ||
} | ||
|
||
return (false, b - a); | ||
} | ||
|
||
contract Trust is IERC1271Wallet { | ||
error UnlockInThePast(uint256 _unlocksAt, uint256 _elapsed); | ||
error UnlockTooEarly(uint256 _unlocksAt, uint256 _diff); | ||
|
||
error NotOwner(address _sender); | ||
error NotUnlocked(uint256 _unlocksAt); | ||
error FailedTransaction(address payable _to, uint256 _value, bytes _data, bytes _result); | ||
|
||
error EmptySignature(); | ||
error InvalidSignatureFlag(bytes _signature, bytes1 _flag); | ||
error InvalidSignature(bytes32 _hash, bytes32 _rehash, address _signer, bytes _signature); | ||
|
||
event SetUnlocksAt(uint256 _unlocksAt); | ||
event SentTransaction(address payable _to, uint256 _value, bytes _data, bytes _result); | ||
|
||
address immutable public owner; | ||
address immutable public beneficiary; | ||
uint256 immutable public duration; | ||
|
||
uint256 public unlocksAt = type(uint256).max; | ||
|
||
constructor ( | ||
address _owner, | ||
address _beneficiary, | ||
uint256 _duration | ||
) { | ||
owner = _owner; | ||
beneficiary = _beneficiary; | ||
duration = _duration; | ||
} | ||
|
||
modifier onlyAllowed() { | ||
if (msg.sender != owner) { | ||
if (msg.sender != beneficiary) { | ||
revert NotOwner(msg.sender); | ||
} | ||
|
||
if (isLocked()) { | ||
revert NotUnlocked(unlocksAt); | ||
} | ||
} | ||
|
||
_; | ||
} | ||
|
||
modifier onlyMember() { | ||
if (msg.sender != owner && msg.sender != beneficiary) { | ||
revert NotOwner(msg.sender); | ||
} | ||
|
||
_; | ||
} | ||
|
||
function isLocked() public view returns (bool) { | ||
return block.timestamp < unlocksAt; | ||
} | ||
|
||
function setUnlocksAt(uint256 _unlocksAt) external onlyMember { | ||
// Diff between the current time and the unlock time must be | ||
// greater than the duration of the trust | ||
(bool isPast, uint256 elapsed) = absDiff(block.timestamp, _unlocksAt); | ||
if (isPast) { | ||
revert UnlockInThePast(_unlocksAt, elapsed); | ||
} | ||
|
||
if (elapsed < duration) { | ||
revert UnlockTooEarly(_unlocksAt, elapsed); | ||
} | ||
|
||
emit SetUnlocksAt(_unlocksAt); | ||
unlocksAt = _unlocksAt; | ||
} | ||
|
||
function sendTransaction( | ||
address payable _to, | ||
uint256 _value, | ||
bytes calldata _data | ||
) external onlyAllowed returns (bytes memory) { | ||
(bool success, bytes memory result) = _to.call{value: _value}(_data); | ||
|
||
if (!success) { | ||
revert FailedTransaction(_to, _value, _data, result); | ||
} | ||
|
||
emit SentTransaction(_to, _value, _data, result); | ||
return result; | ||
} | ||
|
||
bytes4 internal constant SELECTOR_ERC1271_BYTES_BYTES = 0x20c13b0b; | ||
bytes4 internal constant SELECTOR_ERC1271_BYTES32_BYTES = 0x1626ba7e; | ||
|
||
function isValidSignature( | ||
bytes calldata _data, | ||
bytes calldata _signature | ||
) external view returns (bytes4) { | ||
bytes4 res = Trust(payable(address((this)))).isValidSignature( | ||
keccak256(_data), | ||
_signature | ||
); | ||
|
||
assert(res == SELECTOR_ERC1271_BYTES32_BYTES); | ||
return SELECTOR_ERC1271_BYTES_BYTES; | ||
} | ||
|
||
function isValidSignature( | ||
bytes32 _hash, | ||
bytes calldata _signature | ||
) external view returns (bytes4) { | ||
if (_signature.length == 0) { | ||
revert EmptySignature(); | ||
} | ||
|
||
// The last byte determines how the signature is going to be interpreted | ||
// 0x00 -> Signed by the owner | ||
// 0x01 -> Signed by the beneficiary | ||
// 0x02 -> Signed by the owner for any network | ||
// 0x03 -> Signed by the beneficiary for any network | ||
address signer; | ||
uint256 chainId; | ||
|
||
{ | ||
bytes1 flag = _signature[_signature.length - 1]; | ||
|
||
if (flag == 0x00) { | ||
signer = owner; | ||
chainId = block.chainid; | ||
} else if (flag == 0x01) { | ||
signer = beneficiary; | ||
chainId = block.chainid; | ||
} else if (flag == 0x02) { | ||
signer = owner; | ||
chainId = 0; | ||
} else if (flag == 0x03) { | ||
signer = beneficiary; | ||
chainId = 0; | ||
} else { | ||
revert InvalidSignatureFlag(_signature, flag); | ||
} | ||
} | ||
|
||
if (signer != owner && isLocked()) { | ||
revert NotUnlocked(unlocksAt); | ||
} | ||
|
||
// Re-hash the hash adding the address of the trust | ||
// otherwise the signature will be valid for any trust | ||
bytes32 rehash = keccak256(abi.encode(address(this), _hash, chainId)); | ||
|
||
// Validate the signature | ||
if (!SignatureValidator.isValidSignature(rehash, signer, _signature[0:_signature.length - 1])) { | ||
revert InvalidSignature(_hash, rehash, signer, _signature[0:_signature.length - 1]); | ||
} | ||
|
||
return SELECTOR_ERC1271_BYTES32_BYTES; | ||
} | ||
|
||
receive() external payable {} | ||
fallback() external payable {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity 0.8.18; | ||
|
||
import "./Trust.sol"; | ||
|
||
|
||
contract TrustFactory { | ||
function trustCreationCode() external pure returns (bytes memory) { | ||
return type(Trust).creationCode; | ||
} | ||
|
||
function addressOf( | ||
address _owner, | ||
address _beneficiary, | ||
uint256 _duration | ||
) external view returns (address) { | ||
return address(uint160(uint(keccak256(abi.encodePacked( | ||
bytes1(0xff), | ||
address(this), | ||
bytes32(0), | ||
keccak256(abi.encodePacked( | ||
type(Trust).creationCode, | ||
abi.encode(_owner, _beneficiary, _duration) | ||
)) | ||
))))); | ||
} | ||
|
||
function deploy( | ||
address _owner, | ||
address _beneficiary, | ||
uint256 _duration | ||
) external returns (Trust) { | ||
return new Trust{ salt: bytes32(0) }( _owner, _beneficiary, _duration); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.