// SPDX-License-Identifier: MITpragmasolidity ^0.6.12;/**
* @title Block library
*
* @author Stanisław Głogowski <stan@pillarproject.io>
*/libraryBlockLib{
structBlockRelated {
bool added;
uint256 removedAtBlockNumber;
}
/**
* @notice Verifies self struct at current block
* @param self self struct
* @return true on correct self struct
*/functionverifyAtCurrentBlock(
BlockRelated memoryself)
internalviewreturns (bool)
{
return verifyAtBlock(self, block.number);
}
/**
* @notice Verifies self struct at any block
* @param self self struct
* @return true on correct self struct
*/functionverifyAtAnyBlock(
BlockRelated memoryself)
internalpurereturns (bool)
{
return verifyAtBlock(self, 0);
}
/**
* @notice Verifies self struct at specific block
* @param self self struct
* @param blockNumber block number to verify
* @return true on correct self struct
*/functionverifyAtBlock(
BlockRelated memoryself,
uint256 blockNumber
)
internalpurereturns (bool)
{
bool result =false;
if (self.added) {
if (self.removedAtBlockNumber ==0) {
result =true;
} elseif (blockNumber ==0) {
result =true;
} else {
result =self.removedAtBlockNumber > blockNumber;
}
}
return result;
}
}
Contract Source Code
File 6 of 19: BytesLib.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.6.12;/**
* @title Bytes library
*
* @author Stanisław Głogowski <stan@pillarproject.io>
*/libraryBytesLib{
/**
* @notice Converts bytes to address
* @param data data
* @return address
*/functiontoAddress(bytesmemory data
)
internalpurereturns (address)
{
address result;
require(
data.length==20,
"BytesLib: invalid data length"
);
// solhint-disable-next-line no-inline-assemblyassembly {
result :=div(mload(add(data, 0x20)), 0x1000000000000000000000000)
}
return result;
}
}
Contract Source Code
File 7 of 19: Controlled.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.6.12;/**
* @title Controlled
*
* @dev Contract module which provides an access control mechanism.
* It ensures there is only one controlling account of the smart contract
* and grants that account exclusive access to specific functions.
*
* The controller account will be the one that deploys the contract.
*
* @author Stanisław Głogowski <stan@pillarproject.io>
*/contractControlled{
/**
* @return controller account address
*/addresspublic controller;
// modifiers/**
* @dev Throws if msg.sender is not the controller
*/modifieronlyController() {
require(
msg.sender== controller,
"Controlled: msg.sender is not the controller"
);
_;
}
/**
* @dev Internal constructor
*/constructor()
internal{
controller =msg.sender;
}
}
// SPDX-License-Identifier: MITpragmasolidity ^0.6.12;/**
* @title ECDSA library
*
* @dev Based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/cryptography/ECDSA.sol#L26
*/libraryECDSALib{
functionrecoverAddress(bytes32 messageHash,
bytesmemory signature
)
internalpurereturns (address)
{
address result =address(0);
if (signature.length==65) {
bytes32 r;
bytes32 s;
uint8 v;
// solhint-disable-next-line no-inline-assemblyassembly {
r :=mload(add(signature, 0x20))
s :=mload(add(signature, 0x40))
v :=byte(0, mload(add(signature, 0x60)))
}
if (v <27) {
v +=27;
}
if (v ==27|| v ==28) {
result =ecrecover(messageHash, v, r, s);
}
}
return result;
}
functiontoEthereumSignedMessageHash(bytes32 messageHash
)
internalpurereturns (bytes32)
{
returnkeccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
messageHash
));
}
}
Contract Source Code
File 10 of 19: ERC20Token.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.6.12;import"../libs/SafeMathLib.sol";
/**
* @title ERC20 token
*
* @dev Based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/token/ERC20/ERC20.sol
*/contractERC20Token{
usingSafeMathLibforuint256;
stringpublic name;
stringpublic symbol;
uint8public decimals;
uint256public totalSupply;
mapping(address=>uint256) internal balances;
mapping(address=>mapping(address=>uint256)) internal allowances;
// eventseventTransfer(addressindexedfrom,
addressindexed to,
uint256 value
);
eventApproval(addressindexed owner,
addressindexed spender,
uint256 value
);
/**
* @dev internal constructor
*/constructor() internal{}
// external functionsfunctiontransfer(address to,
uint256 value
)
externalreturns (bool)
{
_transfer(_getSender(), to, value);
returntrue;
}
functiontransferFrom(addressfrom,
address to,
uint256 value
)
virtualexternalreturns (bool)
{
address sender = _getSender();
_transfer(from, to, value);
_approve(from, sender, allowances[from][sender].sub(value));
returntrue;
}
functionapprove(address spender,
uint256 value
)
virtualexternalreturns (bool)
{
_approve(_getSender(), spender, value);
returntrue;
}
// external functions (views)functionbalanceOf(address owner
)
virtualexternalviewreturns (uint256)
{
return balances[owner];
}
functionallowance(address owner,
address spender
)
virtualexternalviewreturns (uint256)
{
return allowances[owner][spender];
}
// internal functionsfunction_transfer(addressfrom,
address to,
uint256 value
)
virtualinternal{
require(
from!=address(0),
"ERC20Token: cannot transfer from 0x0 address"
);
require(
to !=address(0),
"ERC20Token: cannot transfer to 0x0 address"
);
balances[from] = balances[from].sub(value);
balances[to] = balances[to].add(value);
emit Transfer(from, to, value);
}
function_approve(address owner,
address spender,
uint256 value
)
virtualinternal{
require(
owner !=address(0),
"ERC20Token: cannot approve from 0x0 address"
);
require(
spender !=address(0),
"ERC20Token: cannot approve to 0x0 address"
);
allowances[owner][spender] = value;
emit Approval(owner, spender, value);
}
function_mint(address owner,
uint256 value
)
virtualinternal{
require(
owner !=address(0),
"ERC20Token: cannot mint to 0x0 address"
);
require(
value >0,
"ERC20Token: cannot mint 0 value"
);
balances[owner] = balances[owner].add(value);
totalSupply = totalSupply.add(value);
emit Transfer(address(0), owner, value);
}
function_burn(address owner,
uint256 value
)
virtualinternal{
require(
owner !=address(0),
"ERC20Token: cannot burn from 0x0 address"
);
balances[owner] = balances[owner].sub(
value,
"ERC20Token: burn value exceeds balance"
);
totalSupply = totalSupply.sub(value);
emit Transfer(owner, address(0), value);
}
// internal functions (views)function_getSender()
virtualinternalviewreturns (address)
{
returnmsg.sender;
}
}
Contract Source Code
File 11 of 19: ExternalAccountRegistry.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.6.12;import"../common/libs/BlockLib.sol";
/**
* @title External account registry
*
* @notice Global registry for keys and external (outside of the platform) contract based wallets
*
* @dev An account can call the registry to add (`addAccountOwner`) or remove (`removeAccountOwner`) its own owners.
* When the owner has been added, information about that fact will live in the registry forever.
* Removing an owner only affects the future blocks (until the owner is re-added).
*
* Given the fact, there is no way to sign the data using a contract based wallet,
* we created a registry to store signed by the key wallet proofs.
* ERC-1271 allows removing a signer after the signature was created. Thus store the signature for the later use
* doesn't guarantee the signer is still has access to that smart account.
* Because of that, the ERC1271's `isValidSignature()` cannot be used in e.g. `PaymentRegistry`.*
*
* An account can call the registry to add (`addAccountProof`) or remove (`removeAccountProof`) proof hash.
* When the proof has been added, information about that fact will live in the registry forever.
* Removing a proof only affects the future blocks (until the proof is re-added).
*
* @author Stanisław Głogowski <stan@pillarproject.io>
*/contractExternalAccountRegistry{
usingBlockLibforBlockLib.BlockRelated;
structAccount {
mapping(address=> BlockLib.BlockRelated) owners;
mapping(bytes32=> BlockLib.BlockRelated) proofs;
}
mapping(address=> Account) private accounts;
// events/**
* @dev Emitted when the new owner is added
* @param account account address
* @param owner owner address
*/eventAccountOwnerAdded(address account,
address owner
);
/**
* @dev Emitted when the existing owner is removed
* @param account account address
* @param owner owner address
*/eventAccountOwnerRemoved(address account,
address owner
);
/**
* @dev Emitted when the new proof is added
* @param account account address
* @param hash proof hash
*/eventAccountProofAdded(address account,
bytes32 hash
);
/**
* @dev Emitted when the existing proof is removed
* @param account account address
* @param hash proof hash
*/eventAccountProofRemoved(address account,
bytes32 hash
);
// external functions/**
* @notice Adds a new account owner
* @param owner owner address
*/functionaddAccountOwner(address owner
)
external{
require(
owner !=address(0),
"ExternalAccountRegistry: cannot add 0x0 owner"
);
require(
!accounts[msg.sender].owners[owner].verifyAtCurrentBlock(),
"ExternalAccountRegistry: owner already exists"
);
accounts[msg.sender].owners[owner].added =true;
accounts[msg.sender].owners[owner].removedAtBlockNumber =0;
emit AccountOwnerAdded(
msg.sender,
owner
);
}
/**
* @notice Removes existing account owner
* @param owner owner address
*/functionremoveAccountOwner(address owner
)
external{
require(
accounts[msg.sender].owners[owner].verifyAtCurrentBlock(),
"ExternalAccountRegistry: owner doesn't exist"
);
accounts[msg.sender].owners[owner].removedAtBlockNumber =block.number;
emit AccountOwnerRemoved(
msg.sender,
owner
);
}
/**
* @notice Adds a new account proof
* @param hash proof hash
*/functionaddAccountProof(bytes32 hash
)
external{
require(
!accounts[msg.sender].proofs[hash].verifyAtCurrentBlock(),
"ExternalAccountRegistry: proof already exists"
);
accounts[msg.sender].proofs[hash].added =true;
accounts[msg.sender].proofs[hash].removedAtBlockNumber =0;
emit AccountProofAdded(
msg.sender,
hash
);
}
/**
* @notice Removes existing account proof
* @param hash proof hash
*/functionremoveAccountProof(bytes32 hash
)
external{
require(
accounts[msg.sender].proofs[hash].verifyAtCurrentBlock(),
"ExternalAccountRegistry: proof doesn't exist"
);
accounts[msg.sender].proofs[hash].removedAtBlockNumber =block.number;
emit AccountProofRemoved(
msg.sender,
hash
);
}
// external functions (views)/**
* @notice Verifies the owner of the account at current block
* @param account account address
* @param owner owner address
* @return true on correct account owner
*/functionverifyAccountOwner(address account,
address owner
)
externalviewreturns (bool)
{
return accounts[account].owners[owner].verifyAtCurrentBlock();
}
/**
* @notice Verifies the owner of the account at specific block
* @param account account address
* @param owner owner address
* @param blockNumber block number to verify
* @return true on correct account owner
*/functionverifyAccountOwnerAtBlock(address account,
address owner,
uint256 blockNumber
)
externalviewreturns (bool)
{
return accounts[account].owners[owner].verifyAtBlock(blockNumber);
}
/**
* @notice Verifies the proof of the account at current block
* @param account account address
* @param hash proof hash
* @return true on correct account proof
*/functionverifyAccountProof(address account,
bytes32 hash
)
externalviewreturns (bool)
{
return accounts[account].proofs[hash].verifyAtCurrentBlock();
}
/**
* @notice Verifies the proof of the account at specific block
* @param account account address
* @param hash proof hash
* @param blockNumber block number to verify
* @return true on correct account proof
*/functionverifyAccountProofAtBlock(address account,
bytes32 hash,
uint256 blockNumber
)
externalviewreturns (bool)
{
return accounts[account].proofs[hash].verifyAtBlock(blockNumber);
}
}
Contract Source Code
File 12 of 19: Gateway.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.6.12;pragmaexperimentalABIEncoderV2;import"../common/libs/ECDSALib.sol";
import"../common/libs/SafeMathLib.sol";
import"../common/lifecycle/Initializable.sol";
import"../common/signature/SignatureValidator.sol";
import"../external/ExternalAccountRegistry.sol";
import"../personal/PersonalAccountRegistry.sol";
/**
* @title Gateway
*
* @notice GSN replacement
*
* @author Stanisław Głogowski <stan@pillarproject.io>
*/contractGatewayisInitializable, SignatureValidator{
usingECDSALibforbytes32;
usingSafeMathLibforuint256;
structDelegatedBatch {
address account;
uint256 nonce;
address[] to;
bytes[] data;
}
structDelegatedBatchWithGasPrice {
address account;
uint256 nonce;
address[] to;
bytes[] data;
uint256 gasPrice;
}
bytes32privateconstant HASH_PREFIX_DELEGATED_BATCH =keccak256(
"DelegatedBatch(address account,uint256 nonce,address[] to,bytes[] data)"
);
bytes32privateconstant HASH_PREFIX_DELEGATED_BATCH_WITH_GAS_PRICE =keccak256(
"DelegatedBatchWithGasPrice(address account,uint256 nonce,address[] to,bytes[] data,uint256 gasPrice)"
);
ExternalAccountRegistry public externalAccountRegistry;
PersonalAccountRegistry public personalAccountRegistry;
mapping(address=>uint256) private accountNonce;
// events/**
* @dev Emitted when the single batch is delegated
* @param sender sender address
* @param batch batch
* @param succeeded if succeeded
*/eventBatchDelegated(address sender,
bytes batch,
bool succeeded
);
/**
* @dev Public constructor
*/constructor() publicInitializable() SignatureValidator() {}
// external functions/**
* @notice Initializes `Gateway` contract
* @param externalAccountRegistry_ `ExternalAccountRegistry` contract address
* @param personalAccountRegistry_ `PersonalAccountRegistry` contract address
*/functioninitialize(
ExternalAccountRegistry externalAccountRegistry_,
PersonalAccountRegistry personalAccountRegistry_
)
externalonlyInitializer{
externalAccountRegistry = externalAccountRegistry_;
personalAccountRegistry = personalAccountRegistry_;
}
// public functions/**
* @notice Sends batch
* @dev `GatewayRecipient` context api:
* `_getContextAccount` will return `msg.sender`
* `_getContextSender` will return `msg.sender`
*
* @param to array of batch recipients contracts
* @param data array of batch data
*/functionsendBatch(address[] memory to,
bytes[] memory data
)
public{
_sendBatch(
msg.sender,
msg.sender,
to,
data
);
}
/**
* @notice Sends batch from the account
* @dev `GatewayRecipient` context api:
* `_getContextAccount` will return `account` arg
* `_getContextSender` will return `msg.sender`
*
* @param account account address
* @param to array of batch recipients contracts
* @param data array of batch data
*/functionsendBatchFromAccount(address account,
address[] memory to,
bytes[] memory data
)
public{
_sendBatch(
account,
msg.sender,
to,
data
);
}
/**
* @notice Delegates batch from the account
* @dev Use `hashDelegatedBatch` to create sender message payload.
*
* `GatewayRecipient` context api:
* `_getContextAccount` will return `account` arg
* `_getContextSender` will return recovered address from `senderSignature` arg
*
* @param account account address
* @param nonce next account nonce
* @param to array of batch recipients contracts
* @param data array of batch data
* @param senderSignature sender signature
*/functiondelegateBatch(address account,
uint256 nonce,
address[] memory to,
bytes[] memory data,
bytesmemory senderSignature
)
public{
require(
nonce > accountNonce[account],
"Gateway: nonce is lower than current account nonce"
);
address sender = _hashDelegatedBatch(
account,
nonce,
to,
data
).recoverAddress(senderSignature);
accountNonce[account] = nonce;
_sendBatch(
account,
sender,
to,
data
);
}
/**
* @notice Delegates batch from the account (with gas price)
*
* @dev Use `hashDelegatedBatchWithGasPrice` to create sender message payload (tx.gasprice as gasPrice)
*
* `GatewayRecipient` context api:
* `_getContextAccount` will return `account` arg
* `_getContextSender` will return recovered address from `senderSignature` arg
*
* @param account account address
* @param nonce next account nonce
* @param to array of batch recipients contracts
* @param data array of batch data
* @param senderSignature sender signature
*/functiondelegateBatchWithGasPrice(address account,
uint256 nonce,
address[] memory to,
bytes[] memory data,
bytesmemory senderSignature
)
public{
require(
nonce > accountNonce[account],
"Gateway: nonce is lower than current account nonce"
);
address sender = _hashDelegatedBatchWithGasPrice(
account,
nonce,
to,
data,
tx.gasprice
).recoverAddress(senderSignature);
accountNonce[account] = nonce;
_sendBatch(
account,
sender,
to,
data
);
}
/**
* @notice Delegates multiple batches
* @dev It will revert when all batches fail
* @param batches array of batches
* @param revertOnFailure reverts on any error
*/functiondelegateBatches(bytes[] memory batches,
bool revertOnFailure
)
public{
require(
batches.length>0,
"Gateway: cannot delegate empty batches"
);
bool anySucceeded;
for (uint256 i =0; i < batches.length; i++) {
// solhint-disable-next-line avoid-low-level-calls
(bool succeeded,) =address(this).call(batches[i]);
if (revertOnFailure) {
require(
succeeded,
"Gateway: batch reverted"
);
} elseif (succeeded &&!anySucceeded) {
anySucceeded =true;
}
emit BatchDelegated(
msg.sender,
batches[i],
succeeded
);
}
if (!anySucceeded) {
revert("Gateway: all batches reverted");
}
}
// public functions (views)/**
* @notice Hashes `DelegatedBatch` message payload
* @param delegatedBatch struct
* @return hash
*/functionhashDelegatedBatch(
DelegatedBatch memory delegatedBatch
)
publicviewreturns (bytes32)
{
return _hashDelegatedBatch(
delegatedBatch.account,
delegatedBatch.nonce,
delegatedBatch.to,
delegatedBatch.data
);
}
/**
* @notice Hashes `DelegatedBatchWithGasPrice` message payload
* @param delegatedBatch struct
* @return hash
*/functionhashDelegatedBatchWithGasPrice(
DelegatedBatchWithGasPrice memory delegatedBatch
)
publicviewreturns (bytes32)
{
return _hashDelegatedBatchWithGasPrice(
delegatedBatch.account,
delegatedBatch.nonce,
delegatedBatch.to,
delegatedBatch.data,
delegatedBatch.gasPrice
);
}
// external functions (views)/**
* @notice Gets next account nonce
* @param account account address
* @return next nonce
*/functiongetAccountNextNonce(address account
)
externalviewreturns (uint256)
{
return accountNonce[account].add(1);
}
// private functionsfunction_sendBatch(address account,
address sender,
address[] memory to,
bytes[] memory data
)
private{
require(
account !=address(0),
"Gateway: cannot send from 0x0 account"
);
require(
to.length>0,
"Gateway: cannot send empty batch"
);
require(
data.length== to.length,
"Gateway: invalid batch"
);
if (account != sender) {
require(
personalAccountRegistry.verifyAccountOwner(account, sender) ||
externalAccountRegistry.verifyAccountOwner(account, sender),
"Gateway: sender is not the account owner"
);
}
bool succeeded;
for (uint256 i =0; i < data.length; i++) {
require(
to[i] !=address(0),
"Gateway: cannot send to 0x0"
);
// solhint-disable-next-line avoid-low-level-calls
(succeeded,) = to[i].call(abi.encodePacked(data[i], account, sender));
require(
succeeded,
"Gateway: batch transaction reverted"
);
}
}
// private functions (views)function_hashDelegatedBatch(address account,
uint256 nonce,
address[] memory to,
bytes[] memory data
)
privateviewreturns (bytes32)
{
return _hashMessagePayload(HASH_PREFIX_DELEGATED_BATCH, abi.encodePacked(
account,
nonce,
to,
_concatBytes(data)
));
}
function_hashDelegatedBatchWithGasPrice(address account,
uint256 nonce,
address[] memory to,
bytes[] memory data,
uint256 gasPrice
)
privateviewreturns (bytes32)
{
return _hashMessagePayload(HASH_PREFIX_DELEGATED_BATCH_WITH_GAS_PRICE, abi.encodePacked(
account,
nonce,
to,
_concatBytes(data),
gasPrice
));
}
// private functions (pure)function_concatBytes(bytes[] memory data)
privatepurereturns (bytesmemory)
{
bytesmemory result;
uint dataLen = data.length;
for (uint i =0 ; i < dataLen ; i++) {
result =abi.encodePacked(result, data[i]);
}
return result;
}
}
// SPDX-License-Identifier: MITpragmasolidity ^0.6.12;import"../libs/ECDSALib.sol";
/**
* @title Guarded
*
* @dev Contract module which provides a guardian-type control mechanism.
* It allows key accounts to have guardians and restricts specific methods to be accessible by guardians only.
*
* Each guardian account can remove other guardians
*
* Use `_initializeGuarded` to initialize the contract
*
* @author Stanisław Głogowski <stan@pillarproject.io>
*/contractGuarded{
usingECDSALibforbytes32;
mapping(address=>bool) private guardians;
// events/**
* @dev Emitted when a new guardian is added
* @param sender sender address
* @param guardian guardian address
*/eventGuardianAdded(address sender,
address guardian
);
/**
* @dev Emitted when the existing guardian is removed
* @param sender sender address
* @param guardian guardian address
*/eventGuardianRemoved(address sender,
address guardian
);
// modifiers/**
* @dev Throws if tx.origin is not a guardian account
*/modifieronlyGuardian() {
require(
// solhint-disable-next-line avoid-tx-origin
guardians[tx.origin],
"Guarded: tx.origin is not the guardian"
);
_;
}
/**
* @dev Internal constructor
*/constructor() internal{}
// external functions/**
* @notice Adds a new guardian
* @param guardian guardian address
*/functionaddGuardian(address guardian
)
externalonlyGuardian{
_addGuardian(guardian);
}
/**
* @notice Removes the existing guardian
* @param guardian guardian address
*/functionremoveGuardian(address guardian
)
externalonlyGuardian{
require(
// solhint-disable-next-line avoid-tx-origintx.origin!= guardian,
"Guarded: cannot remove self"
);
require(
guardians[guardian],
"Guarded: guardian doesn't exist"
);
guardians[guardian] =false;
emit GuardianRemoved(
// solhint-disable-next-line avoid-tx-origintx.origin,
guardian
);
}
// external functions (views)/**
* @notice Check if guardian exists
* @param guardian guardian address
* @return true when guardian exists
*/functionisGuardian(address guardian
)
externalviewreturns (bool)
{
return guardians[guardian];
}
/**
* @notice Verifies guardian signature
* @param messageHash message hash
* @param signature signature
* @return true on correct guardian signature
*/functionverifyGuardianSignature(bytes32 messageHash,
bytescalldata signature
)
externalviewreturns (bool)
{
return _verifyGuardianSignature(
messageHash,
signature
);
}
// internal functions/**
* @notice Initializes `Guarded` contract
* @dev If `guardians_` array is empty `tx.origin` is added as guardian account
* @param guardians_ array of guardians addresses
*/function_initializeGuarded(address[] memory guardians_
)
internal{
if (guardians_.length==0) {
// solhint-disable-next-line avoid-tx-origin
_addGuardian(tx.origin);
} else {
uint guardiansLen = guardians_.length;
for (uint i =0; i < guardiansLen; i++) {
_addGuardian(guardians_[i]);
}
}
}
// internal functions (views)function_verifyGuardianSignature(bytes32 messageHash,
bytesmemory signature
)
internalviewreturns (bool)
{
address guardian = messageHash.recoverAddress(signature);
return guardians[guardian];
}
// private functionsfunction_addGuardian(address guardian
)
private{
require(
guardian !=address(0),
"Guarded: cannot add 0x0 guardian"
);
require(
!guardians[guardian],
"Guarded: guardian already exists"
);
guardians[guardian] =true;
emit GuardianAdded(
// solhint-disable-next-line avoid-tx-origintx.origin,
guardian
);
}
}
Contract Source Code
File 15 of 19: Initializable.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.6.12;/**
* @title Initializable
*
* @dev Contract module which provides access control mechanism, where
* there is the initializer account that can be granted exclusive access to
* specific functions.
*
* The initializer account will be tx.origin during contract deployment and will be removed on first use.
* Use `onlyInitializer` modifier on contract initialize process.
*
* @author Stanisław Głogowski <stan@pillarproject.io>
*/contractInitializable{
addressprivate initializer;
// events/**
* @dev Emitted after `onlyInitializer`
* @param initializer initializer address
*/eventInitialized(address initializer
);
// modifiers/**
* @dev Throws if tx.origin is not the initializer
*/modifieronlyInitializer() {
require(
// solhint-disable-next-line avoid-tx-origintx.origin== initializer,
"Initializable: tx.origin is not the initializer"
);
/// @dev removes initializer
initializer =address(0);
_;
emit Initialized(
// solhint-disable-next-line avoid-tx-origintx.origin
);
}
/**
* @dev Internal constructor
*/constructor()
internal{
// solhint-disable-next-line avoid-tx-origin
initializer =tx.origin;
}
// external functions (views)/**
* @notice Check if contract is initialized
* @return true when contract is initialized
*/functionisInitialized()
externalviewreturns (bool)
{
return initializer ==address(0);
}
}
Contract Source Code
File 16 of 19: PersonalAccountRegistry.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.6.12;import"../common/access/Guarded.sol";
import"../common/account/AccountController.sol";
import"../common/account/AccountRegistry.sol";
import"../common/libs/BlockLib.sol";
import"../common/libs/ECDSALib.sol";
import"../common/libs/ECDSAExtendedLib.sol";
import"../common/libs/SafeMathLib.sol";
import"../common/lifecycle/Initializable.sol";
import"../common/token/ERC20Token.sol";
import"../gateway/GatewayRecipient.sol";
/**
* @title Personal account registry
*
* @notice A registry for personal (controlled by owners) accounts
*
* @author Stanisław Głogowski <stan@pillarproject.io>
*/contractPersonalAccountRegistryisGuarded, AccountController, AccountRegistry, Initializable, GatewayRecipient{
usingBlockLibforBlockLib.BlockRelated;
usingSafeMathLibforuint256;
usingECDSALibforbytes32;
usingECDSAExtendedLibforbytes;
structAccount {
bool deployed;
bytes32 salt;
mapping(address=> BlockLib.BlockRelated) owners;
}
mapping(address=> Account) private accounts;
// events/**
* @dev Emitted when the new owner is added
* @param account account address
* @param owner owner address
*/eventAccountOwnerAdded(address account,
address owner
);
/**
* @dev Emitted when the existing owner is removed
* @param account account address
* @param owner owner address
*/eventAccountOwnerRemoved(address account,
address owner
);
/**
* @dev Emitted when the call is refunded
* @param account account address
* @param beneficiary beneficiary address
* @param token token address
* @param value value
*/eventAccountCallRefunded(address account,
address beneficiary,
address token,
uint256 value
);
/**
* @dev Public constructor
*/constructor() publicInitializable() {}
// external functions/**
* @notice Initializes `PersonalAccountRegistry` contract
* @param guardians_ array of guardians addresses
* @param accountImplementation_ account implementation address
* @param gateway_ `Gateway` contract address
*/functioninitialize(address[] calldata guardians_,
address accountImplementation_,
address gateway_
)
externalonlyInitializer{
// Guarded
_initializeGuarded(guardians_);
// AccountController
_initializeAccountController(address(this), accountImplementation_);
// GatewayRecipient
_initializeGatewayRecipient(gateway_);
}
/**
* @notice Upgrades `PersonalAccountRegistry` contract
* @param accountImplementation_ account implementation address
*/functionupgrade(address accountImplementation_
)
externalonlyGuardian{
_setAccountImplementation(accountImplementation_, true);
}
/**
* @notice Deploys account
* @param account account address
*/functiondeployAccount(address account
)
external{
_verifySender(account);
_deployAccount(account);
}
/**
* @notice Upgrades account
* @param account account address
*/functionupgradeAccount(address account
)
external{
_verifySender(account);
_upgradeAccount(account, true);
}
/**
* @notice Adds a new account owner
* @param account account address
* @param owner owner address
*/functionaddAccountOwner(address account,
address owner
)
external{
_verifySender(account);
require(
owner !=address(0),
"PersonalAccountRegistry: cannot add 0x0 owner"
);
require(
!accounts[account].owners[owner].verifyAtCurrentBlock(),
"PersonalAccountRegistry: owner already exists"
);
accounts[account].owners[owner].added =true;
accounts[account].owners[owner].removedAtBlockNumber =0;
emit AccountOwnerAdded(
account,
owner
);
}
/**
* @notice Removes the existing account owner
* @param account account address
* @param owner owner address
*/functionremoveAccountOwner(address account,
address owner
)
external{
address sender = _verifySender(account);
require(
owner != sender,
"PersonalAccountRegistry: cannot remove self"
);
require(
accounts[account].owners[owner].verifyAtCurrentBlock(),
"PersonalAccountRegistry: owner doesn't exist"
);
accounts[account].owners[owner].removedAtBlockNumber =block.number;
emit AccountOwnerRemoved(
account,
owner
);
}
/**
* @notice Executes account transaction
* @dev Deploys an account if not deployed yet
* @param account account address
* @param to to address
* @param value value
* @param data data
*/functionexecuteAccountTransaction(address account,
address to,
uint256 value,
bytescalldata data
)
external{
_verifySender(account);
_deployAccount(account);
_executeAccountTransaction(
account,
to,
value,
data,
true
);
}
/**
* @notice Refunds account call
* @dev Deploys an account if not deployed yet
* @param account account address
* @param token token address
* @param value value
*/functionrefundAccountCall(address account,
address token,
uint256 value
)
external{
_verifySender(account);
_deployAccount(account);
/* solhint-disable avoid-tx-origin */if (token ==address(0)) {
_executeAccountTransaction(
account,
tx.origin,
value,
newbytes(0),
false
);
} else {
bytesmemory response = _executeAccountTransaction(
account,
token,
0,
abi.encodeWithSelector(
ERC20Token(token).transfer.selector,
tx.origin,
value
),
false
);
if (response.length>0) {
require(
abi.decode(response, (bool)),
"PersonalAccountRegistry: ERC20Token transfer reverted"
);
}
}
emit AccountCallRefunded(
account,
tx.origin,
token,
value
);
/* solhint-enable avoid-tx-origin */
}
// external functions (views)/**
* @notice Computes account address
* @param saltOwner salt owner address
* @return account address
*/functioncomputeAccountAddress(address saltOwner
)
externalviewreturns (address)
{
return _computeAccountAddress(saltOwner);
}
/**
* @notice Checks if account is deployed
* @param account account address
* @return true when account is deployed
*/functionisAccountDeployed(address account
)
externalviewreturns (bool)
{
return accounts[account].deployed;
}
/**
* @notice Verifies the owner of the account at the current block
* @param account account address
* @param owner owner address
* @return true on correct account owner
*/functionverifyAccountOwner(address account,
address owner
)
externalviewreturns (bool)
{
return _verifyAccountOwner(account, owner);
}
/**
* @notice Verifies the owner of the account at a specific block
* @param account account address
* @param owner owner address
* @param blockNumber block number to verify
* @return true on correct account owner
*/functionverifyAccountOwnerAtBlock(address account,
address owner,
uint256 blockNumber
)
externalviewreturns (bool)
{
bool result =false;
if (_verifyAccountOwner(account, owner)) {
result =true;
} else {
result = accounts[account].owners[owner].verifyAtBlock(blockNumber);
}
return result;
}
/**
* @notice Verifies account signature
* @param account account address
* @param messageHash message hash
* @param signature signature
* @return magic hash if valid
*/functionisValidAccountSignature(address account,
bytes32 messageHash,
bytescalldata signature
)
overrideexternalviewreturns (bool)
{
return _verifyAccountOwner(
account,
messageHash.recoverAddress(signature)
);
}
/**
* @notice Verifies account signature
* @param account account address
* @param message message
* @param signature signature
* @return magic hash if valid
*/functionisValidAccountSignature(address account,
bytescalldata message,
bytescalldata signature
)
overrideexternalviewreturns (bool)
{
return _verifyAccountOwner(
account,
message.toEthereumSignedMessageHash().recoverAddress(signature)
);
}
// private functionsfunction_verifySender(address account
)
privatereturns (address)
{
address sender = _getContextSender();
if (accounts[account].owners[sender].added) {
require(
accounts[account].owners[sender].removedAtBlockNumber ==0,
"PersonalAccountRegistry: sender is not the account owner"
);
} else {
require(
accounts[account].salt ==0,
"PersonalAccountRegistry: sender is not the account owner"
);
bytes32 salt =keccak256(
abi.encodePacked(sender)
);
require(
account == _computeAccountAddress(salt),
"PersonalAccountRegistry: sender is not the account owner"
);
accounts[account].salt = salt;
accounts[account].owners[sender].added =true;
emit AccountOwnerAdded(
account,
sender
);
}
return sender;
}
function_deployAccount(address account
)
internal{
if (!accounts[account].deployed) {
_deployAccount(
accounts[account].salt,
true
);
accounts[account].deployed =true;
}
}
// private functions (views)function_computeAccountAddress(address saltOwner
)
privateviewreturns (address)
{
bytes32 salt =keccak256(
abi.encodePacked(saltOwner)
);
return _computeAccountAddress(salt);
}
function_verifyAccountOwner(address account,
address owner
)
privateviewreturns (bool)
{
bool result;
if (accounts[account].owners[owner].added) {
result = accounts[account].owners[owner].removedAtBlockNumber ==0;
} elseif (accounts[account].salt ==0) {
result = account == _computeAccountAddress(owner);
}
return result;
}
}
Contract Source Code
File 17 of 19: SafeMathLib.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.6.12;/**
* @title Safe math library
*
* @dev Based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/math/SafeMath.sol
*/librarySafeMathLib{
functionadd(uint256 a, uint256 b) internalpurereturns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMathLib: addition overflow");
return c;
}
functionsub(uint256 a, uint256 b) internalpurereturns (uint256) {
return sub(a, b, "SafeMathLib: subtraction overflow");
}
functionsub(uint256 a, uint256 b, stringmemory errorMessage) internalpurereturns (uint256) {
require(b <= a, errorMessage);
return a - b;
}
functionmul(uint256 a, uint256 b) internalpurereturns (uint256) {
if (a ==0) {
return0;
}
uint256 c = a * b;
require(c / a == b, "SafeMathLib: multiplication overflow");
return c;
}
functiondiv(uint256 a, uint256 b) internalpurereturns (uint256) {
return div(a, b, "SafeMathLib: division by zero");
}
functiondiv(uint256 a, uint256 b, stringmemory errorMessage) internalpurereturns (uint256) {
require(b >0, errorMessage);
return a / b;
}
functionmod(uint256 a, uint256 b) internalpurereturns (uint256) {
return mod(a, b, "SafeMathLib: modulo by zero");
}
functionmod(uint256 a, uint256 b, stringmemory errorMessage) internalpurereturns (uint256) {
require(b !=0, errorMessage);
return a % b;
}
}