175 lines
8.2 KiB
Solidity
175 lines
8.2 KiB
Solidity
// SPDX-License-Identifier: CC0-1.0
|
|
pragma solidity ^0.7.1;
|
|
import "./Salary.sol";
|
|
|
|
/// @author Victor Porton
|
|
/// @notice Not audited, not enough tested.
|
|
/// A base class for salary with receiver accounts that can be restored by an "attorney".
|
|
abstract contract BaseRestorableSalary is BaseSalary {
|
|
// INVARIANT: `_originalAddress(newToOldAccount[newAccount]) == _originalAddress(newAccount)`
|
|
// if `newToOldAccount[newAccount] != address(0)` for every `newAccount`
|
|
// INVARIANT: originalAddresses and originalToCurrentAddresses are mutually inverse.
|
|
// That is:
|
|
// - `originalAddresses[originalToCurrentAddresses[x]] == x` if `originalToCurrentAddresses[x] != address(0)`
|
|
// - `originalToCurrentAddresses[originalAddresses[x]] == x` if `originalAddresses[x] != address(0)`
|
|
|
|
/// Mapping (current address => very first address an account had).
|
|
mapping(address => address) public originalAddresses;
|
|
|
|
/// Mapping (very first address an account had => current address).
|
|
mapping(address => address) public originalToCurrentAddresses;
|
|
|
|
// Mapping from old to new account addresses (created after every change of an address).
|
|
mapping(address => address) public newToOldAccount;
|
|
|
|
/// Constructor.
|
|
/// @param _uri Our ERC-1155 tokens description URI.
|
|
constructor (string memory _uri) BaseSalary(_uri) { }
|
|
|
|
/// Below copied from https://github.com/vporton/restorable-funds/blob/f6192fd23cad529b84155d52ae202430cd97db23/contracts/RestorableERC1155.sol
|
|
|
|
/// Give the user the "permission" to move funds from `_oldAccount` to `_newAccount`.
|
|
///
|
|
/// This function is intended to be called by an attorney or the user to move to a new account.
|
|
/// @param _oldAccount is a current address.
|
|
/// @param _newAccount is a new address.
|
|
function permitRestoreAccount(address _oldAccount, address _newAccount) public {
|
|
if (msg.sender != _oldAccount) {
|
|
checkAllowedRestoreAccount(_oldAccount, _newAccount); // only authorized "attorneys" or attorney DAOs
|
|
}
|
|
_avoidZeroAddressManipulatins(_oldAccount, _newAccount);
|
|
address _orig = _originalAddress(_oldAccount);
|
|
|
|
// We don't disallow joining several accounts together to consolidate salaries for different projects.
|
|
// require(originalAddresses[_newAccount] == 0, "Account is taken.")
|
|
|
|
newToOldAccount[_newAccount] = _oldAccount;
|
|
originalAddresses[_newAccount] = _orig;
|
|
originalToCurrentAddresses[_orig] = _newAccount;
|
|
// Auditor: Check that the above invariant hold.
|
|
emit AccountRestored(_oldAccount, _newAccount);
|
|
}
|
|
|
|
/// Retire the user's "permission" to move funds from `_oldAccount` to `_newAccount`.
|
|
///
|
|
/// This function is intended to be called by an attorney.
|
|
/// @param _oldAccount is an old current address.
|
|
/// @param _newAccount is a new address.
|
|
/// (In general) it isn't allowed to be called by `msg.sender == _oldAccount`,
|
|
/// because it would allow to keep stealing the salary by hijacked old account.
|
|
function dispermitRestoreAccount(address _oldAccount, address _newAccount) public {
|
|
checkAllowedUnrestoreAccount(_oldAccount, _newAccount); // only authorized "attorneys" or attorney DAOs
|
|
_avoidZeroAddressManipulatins(_oldAccount, _newAccount);
|
|
newToOldAccount[_newAccount] = address(0);
|
|
originalToCurrentAddresses[_oldAccount] = address(0);
|
|
originalAddresses[_newAccount] = address(0);
|
|
// Auditor: Check that the above invariants hold.
|
|
emit AccountUnrestored(_oldAccount, _newAccount);
|
|
}
|
|
|
|
/// Move the entire balance of a token from an old account to a new account of the same user.
|
|
/// @param _oldAccount Old account.
|
|
/// @param _newAccount New account.
|
|
/// @param _token The ERC-1155 token ID.
|
|
/// This function can be called by the affected user.
|
|
///
|
|
/// Remark: We don't need to create new tokens like as on a regular transfer,
|
|
/// because it isn't a transfer to a trader.
|
|
function restoreFunds(address _oldAccount, address _newAccount, uint256 _token) public
|
|
checkMovedOwner(_oldAccount, _newAccount)
|
|
{
|
|
uint256 _amount = _balances[_token][_oldAccount];
|
|
|
|
_balances[_token][_newAccount] = _balances[_token][_oldAccount];
|
|
_balances[_token][_oldAccount] = 0;
|
|
|
|
emit TransferSingle(_msgSender(), _oldAccount, _newAccount, _token, _amount);
|
|
}
|
|
|
|
/// Move the entire balance of tokens from an old account to a new account of the same user.
|
|
/// @param _oldAccount Old account.
|
|
/// @param _newAccount New account.
|
|
/// @param _tokens The ERC-1155 token IDs.
|
|
/// This function can be called by the affected user.
|
|
///
|
|
/// Remark: We don't need to create new tokens like as on a regular transfer,
|
|
/// because it isn't a transfer to a trader.
|
|
function restoreFundsBatch(address _oldAccount, address _newAccount, uint256[] calldata _tokens) public
|
|
checkMovedOwner(_oldAccount, _newAccount)
|
|
{
|
|
uint256[] memory _amounts = new uint256[](_tokens.length);
|
|
for (uint _i = 0; _i < _tokens.length; ++_i) {
|
|
uint256 _token = _tokens[_i];
|
|
uint256 _amount = _balances[_token][_oldAccount];
|
|
_amounts[_i] = _amount;
|
|
|
|
_balances[_token][_newAccount] = _balances[_token][_oldAccount];
|
|
_balances[_token][_oldAccount] = 0;
|
|
}
|
|
|
|
emit TransferBatch(_msgSender(), _oldAccount, _newAccount, _tokens, _amounts);
|
|
}
|
|
|
|
// Internal functions //
|
|
|
|
function _avoidZeroAddressManipulatins(address _oldAccount, address _newAccount) internal view {
|
|
// To avoid make-rich-quick manipulations with lost funds:
|
|
require(_oldAccount != address(0) && _newAccount != address(0) &&
|
|
originalAddresses[_newAccount] != address(0) && newToOldAccount[_newAccount] != address(0),
|
|
"Trying to get nobody's funds.");
|
|
}
|
|
|
|
// Virtual functions //
|
|
|
|
/// Check if `msg.sender` is an attorney allowed to restore a user's account.
|
|
function checkAllowedRestoreAccount(address /*_oldAccount*/, address /*_newAccount*/) public virtual;
|
|
|
|
/// Check if `msg.sender` is an attorney allowed to unrestore a user's account.
|
|
function checkAllowedUnrestoreAccount(address /*_oldAccount*/, address /*_newAccount*/) public virtual;
|
|
|
|
/// Find the original address of a given account.
|
|
/// This function is internal, because it can be calculated off-chain.
|
|
/// @param _account The current address.
|
|
function _originalAddress(address _account) internal view virtual returns (address) {
|
|
address _newAddress = originalAddresses[_account];
|
|
return _newAddress != address(0) ? _newAddress : _account;
|
|
}
|
|
|
|
// Find the current address for an original address.
|
|
// @param _conditional The original address.
|
|
function originalToCurrentAddress(address _customer) internal virtual override returns (address) {
|
|
address current = originalToCurrentAddresses[_customer];
|
|
return current != address(0) ? current : _customer;
|
|
}
|
|
|
|
// TODO: Is the following function useful to save gas in other contracts?
|
|
// function getCurrent(address _account) public returns (uint256) {
|
|
// address _original = originalAddresses[_account];
|
|
// return _original == 0 ? 0 : originalToCurrentAddress(_original);
|
|
// }
|
|
|
|
// Modifiers //
|
|
|
|
/// Check that `_newAccount` is the user that has the right to restore funds from `_oldAccount`.
|
|
///
|
|
/// We also allow funds restoration by attorneys for convenience of users.
|
|
/// This is not an increased security risk, because a dishonest attorney can anyway transfer money to himself.
|
|
modifier checkMovedOwner(address _oldAccount, address _newAccount) virtual {
|
|
if (_msgSender() != _newAccount) {
|
|
checkAllowedRestoreAccount(_oldAccount, _newAccount); // only authorized "attorneys" or attorney DAOs
|
|
}
|
|
|
|
for (address _account = _oldAccount; _account != _newAccount; _account = newToOldAccount[_account]) {
|
|
require(_account != address(0), "Not a moved owner");
|
|
}
|
|
|
|
_;
|
|
}
|
|
|
|
// Events //
|
|
|
|
event AccountRestored(address indexed oldAccount, address indexed newAccount);
|
|
|
|
event AccountUnrestored(address indexed oldAccount, address indexed newAccount);
|
|
}
|