Skip to content

Commit

Permalink
Add Trust and TrustFactory Contracts for waas Recovery (#179)
Browse files Browse the repository at this point in the history
* 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
Agusx1211 authored Mar 19, 2024
1 parent f1981eb commit 6617a20
Show file tree
Hide file tree
Showing 8 changed files with 1,145 additions and 5 deletions.
173 changes: 173 additions & 0 deletions contracts/trust/Trust.sol
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 {}
}
35 changes: 35 additions & 0 deletions contracts/trust/TrustFactory.sol
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);
}
}
2 changes: 2 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ src = 'contracts'
out = 'foundry_artifacts'
libs = ["node_modules", "lib"]
test = 'foundry_test'

ffi = true
max_test_rejects = 2048000
Loading

0 comments on commit 6617a20

Please sign in to comment.