Transaction Hash:
Block:
15084135 at Jul-05-2022 06:27:06 PM +UTC
Transaction Fee:
0.013500940473708107 ETH
$42.10
Gas Used:
237,287 Gas / 56.897092861 Gwei
Emitted Events:
| 96 |
FlexaCollateralManager.Withdrawal( supplier=[Sender] 0xabff616b2067c5ac3fbd7bf874dde3a6acaa5b30, partition=CCCCCCCC9E74DA6B6CF54FC8706D7F8B3445D8DFC790C524E3990EF014E7C578, amount=7141496000000000000000000, rootNonce=9619, authorizedAccountNonce=9581 )
|
| 97 |
FlexaCollateralManager.SupplyReceipt( supplier=[Sender] 0xabff616b2067c5ac3fbd7bf874dde3a6acaa5b30, partition=CCCCCCCC81DBDE757C384900706D7F8B3445D8DFC790C524E3990EF014E7C578, amount=7141496000000000000000000, nonce=31416 )
|
| 98 |
Amp.Transfer( from=FlexaCollateralManager, to=FlexaCollateralManager, value=7141496000000000000000000 )
|
| 99 |
Amp.TransferByPartition( fromPartition=CCCCCCCC9E74DA6B6CF54FC8706D7F8B3445D8DFC790C524E3990EF014E7C578, operator=[Sender] 0xabff616b2067c5ac3fbd7bf874dde3a6acaa5b30, from=FlexaCollateralManager, to=FlexaCollateralManager, value=7141496000000000000000000, data=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCCCCCCCC81DBDE757C384900706D7F8B3445D8DFC790C524E3990EF014E7C578, operatorData=0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA000000000000000000000000ABFF616B2067C5AC3FBD7BF874DDE3A6ACAA5B30000000000000000000000000000000000000000000000000000000000000256D00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000009055C9DB9B374980330CDAF4444F3B830AE73E846D3CAAB5FA18FE0E10B27709CA137F92AAE8E57DBFFE4CC3D6EB9AEE47F6C58DC676C212C2B13FF270E4774CB44532CC029D7F56DF4D5597002E445EC1FC7AE169C2D71779A5B7591ED671447E3CFD0510B46BDCDD7910C59EC660457F20F34D87C6193126E3D04A4B647E98166A4969DBD216AC23E845A1DFF594F7ADCE74A1A21E67087C74E4D86E5E92AAD237E6069C00EC8F48EE4DF1CB7D638F02EC89065BE0135D22DE193EB512A30B25D5B14D0277A5C9FA52A8892BECB303D5A63AA79EA138F5143ED8B43FAD5DE34279F57C30E513D13467AFAF853E400375A85B5C1029D6715653C6F003C8C18DC888FA313C91B516CFEB7A17620ACBC0AE4D209DDEF1ABBDB478C4958CC025D79 )
|
| 100 |
Amp.ChangedPartition( fromPartition=CCCCCCCC9E74DA6B6CF54FC8706D7F8B3445D8DFC790C524E3990EF014E7C578, toPartition=CCCCCCCC81DBDE757C384900706D7F8B3445D8DFC790C524E3990EF014E7C578, value=7141496000000000000000000 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
|
0x646dB8ff...60Df41087
Miner
| (Miner: 0x646...087) | 27.080902845007521178 Eth | 27.081258775507521178 Eth | 0.0003559305 | |
| 0x706D7F8B...014e7C578 | (Flexa: Staking) | ||||
| 0xabFf616B...6aCaa5B30 |
0.068795551157488966 Eth
Nonce: 84
|
0.055294610683780859 Eth
Nonce: 85
| 0.013500940473708107 | ||
| 0xfF208177...8D11095C2 |
Execution Trace
Amp.transferByPartition( _partition=CCCCCCCC9E74DA6B6CF54FC8706D7F8B3445D8DFC790C524E3990EF014E7C578, _from=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, _to=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, _value=7141496000000000000000000, _data=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCCCCCCCC81DBDE757C384900706D7F8B3445D8DFC790C524E3990EF014E7C578, _operatorData=0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA000000000000000000000000ABFF616B2067C5AC3FBD7BF874DDE3A6ACAA5B30000000000000000000000000000000000000000000000000000000000000256D00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000009055C9DB9B374980330CDAF4444F3B830AE73E846D3CAAB5FA18FE0E10B27709CA137F92AAE8E57DBFFE4CC3D6EB9AEE47F6C58DC676C212C2B13FF270E4774CB44532CC029D7F56DF4D5597002E445EC1FC7AE169C2D71779A5B7591ED671447E3CFD0510B46BDCDD7910C59EC660457F20F34D87C6193126E3D04A4B647E98166A4969DBD216AC23E845A1DFF594F7ADCE74A1A21E67087C74E4D86E5E92AAD237E6069C00EC8F48EE4DF1CB7D638F02EC89065BE0135D22DE193EB512A30B25D5B14D0277A5C9FA52A8892BECB303D5A63AA79EA138F5143ED8B43FAD5DE34279F57C30E513D13467AFAF853E400375A85B5C1029D6715653C6F003C8C18DC888FA313C91B516CFEB7A17620ACBC0AE4D209DDEF1ABBDB478C4958CC025D79 ) => ( CCCCCCCC81DBDE757C384900706D7F8B3445D8DFC790C524E3990EF014E7C578 )
-
ERC1820Registry.getInterfaceImplementer( _addr=0xfF20817765cB7f73d4bde2e66e067E58D11095C2, _interfaceHash=2C8F2151D082D980BC6C06D28B364B16371B6C228F9552BB0254F1658050629D ) => ( 0x455a3B78Cfe4B88268DBee2119eB06fB1d3F1f61 ) CollateralPoolPartitionValidator.isOperatorForPartitionScope( _partition=CCCCCCCC9E74DA6B6CF54FC8706D7F8B3445D8DFC790C524E3990EF014E7C578, 0xabFf616B2067C5ac3FBD7bF874DdE3A6aCaa5B30, _tokenHolder=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578 ) => ( True )-
Amp.isCollateralManager( _collateralManager=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578 ) => ( True )
-
-
ERC1820Registry.getInterfaceImplementer( _addr=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, _interfaceHash=60881B58A7AD1EBD3BC0E92B8277996363A67DED0F43BD95D11C320BAB72B5A4 ) => ( 0x706D7F8B3445D8Dfc790C524E3990ef014e7C578 ) -
FlexaCollateralManager.tokensToTransfer( System.Byte[], _partition=CCCCCCCC9E74DA6B6CF54FC8706D7F8B3445D8DFC790C524E3990EF014E7C578, _operator=0xabFf616B2067C5ac3FBD7bF874DdE3A6aCaa5B30, _from=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, _to=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, _value=7141496000000000000000000, _data=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCCCCCCCC81DBDE757C384900706D7F8B3445D8DFC790C524E3990EF014E7C578, _operatorData=0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA000000000000000000000000ABFF616B2067C5AC3FBD7BF874DDE3A6ACAA5B30000000000000000000000000000000000000000000000000000000000000256D00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000009055C9DB9B374980330CDAF4444F3B830AE73E846D3CAAB5FA18FE0E10B27709CA137F92AAE8E57DBFFE4CC3D6EB9AEE47F6C58DC676C212C2B13FF270E4774CB44532CC029D7F56DF4D5597002E445EC1FC7AE169C2D71779A5B7591ED671447E3CFD0510B46BDCDD7910C59EC660457F20F34D87C6193126E3D04A4B647E98166A4969DBD216AC23E845A1DFF594F7ADCE74A1A21E67087C74E4D86E5E92AAD237E6069C00EC8F48EE4DF1CB7D638F02EC89065BE0135D22DE193EB512A30B25D5B14D0277A5C9FA52A8892BECB303D5A63AA79EA138F5143ED8B43FAD5DE34279F57C30E513D13467AFAF853E400375A85B5C1029D6715653C6F003C8C18DC888FA313C91B516CFEB7A17620ACBC0AE4D209DDEF1ABBDB478C4958CC025D79 ) -
ERC1820Registry.getInterfaceImplementer( _addr=0xfF20817765cB7f73d4bde2e66e067E58D11095C2, _interfaceHash=2C8F2151D082D980BC6C06D28B364B16371B6C228F9552BB0254F1658050629D ) => ( 0x455a3B78Cfe4B88268DBee2119eB06fB1d3F1f61 ) -
CollateralPoolPartitionValidator.tokensFromPartitionToValidate( System.Byte[], CCCCCCCC9E74DA6B6CF54FC8706D7F8B3445D8DFC790C524E3990EF014E7C578, 0xabFf616B2067C5ac3FBD7bF874DdE3A6aCaa5B30, 0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, 0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, 7141496000000000000000000, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCCCCCCCC81DBDE757C384900706D7F8B3445D8DFC790C524E3990EF014E7C578, 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA000000000000000000000000ABFF616B2067C5AC3FBD7BF874DDE3A6ACAA5B30000000000000000000000000000000000000000000000000000000000000256D00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000009055C9DB9B374980330CDAF4444F3B830AE73E846D3CAAB5FA18FE0E10B27709CA137F92AAE8E57DBFFE4CC3D6EB9AEE47F6C58DC676C212C2B13FF270E4774CB44532CC029D7F56DF4D5597002E445EC1FC7AE169C2D71779A5B7591ED671447E3CFD0510B46BDCDD7910C59EC660457F20F34D87C6193126E3D04A4B647E98166A4969DBD216AC23E845A1DFF594F7ADCE74A1A21E67087C74E4D86E5E92AAD237E6069C00EC8F48EE4DF1CB7D638F02EC89065BE0135D22DE193EB512A30B25D5B14D0277A5C9FA52A8892BECB303D5A63AA79EA138F5143ED8B43FAD5DE34279F57C30E513D13467AFAF853E400375A85B5C1029D6715653C6F003C8C18DC888FA313C91B516CFEB7A17620ACBC0AE4D209DDEF1ABBDB478C4958CC025D79 ) -
ERC1820Registry.getInterfaceImplementer( _addr=0xfF20817765cB7f73d4bde2e66e067E58D11095C2, _interfaceHash=2C8F2151D082D980BC6C06D28B364B16371B6C228F9552BB0254F1658050629D ) => ( 0x455a3B78Cfe4B88268DBee2119eB06fB1d3F1f61 ) CollateralPoolPartitionValidator.tokensToPartitionToValidate( System.Byte[], _toPartition=CCCCCCCC81DBDE757C384900706D7F8B3445D8DFC790C524E3990EF014E7C578, 0xabFf616B2067C5ac3FBD7bF874DdE3A6aCaa5B30, 0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, _to=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, 7141496000000000000000000, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCCCCCCCC81DBDE757C384900706D7F8B3445D8DFC790C524E3990EF014E7C578, 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA000000000000000000000000ABFF616B2067C5AC3FBD7BF874DDE3A6ACAA5B30000000000000000000000000000000000000000000000000000000000000256D00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000009055C9DB9B374980330CDAF4444F3B830AE73E846D3CAAB5FA18FE0E10B27709CA137F92AAE8E57DBFFE4CC3D6EB9AEE47F6C58DC676C212C2B13FF270E4774CB44532CC029D7F56DF4D5597002E445EC1FC7AE169C2D71779A5B7591ED671447E3CFD0510B46BDCDD7910C59EC660457F20F34D87C6193126E3D04A4B647E98166A4969DBD216AC23E845A1DFF594F7ADCE74A1A21E67087C74E4D86E5E92AAD237E6069C00EC8F48EE4DF1CB7D638F02EC89065BE0135D22DE193EB512A30B25D5B14D0277A5C9FA52A8892BECB303D5A63AA79EA138F5143ED8B43FAD5DE34279F57C30E513D13467AFAF853E400375A85B5C1029D6715653C6F003C8C18DC888FA313C91B516CFEB7A17620ACBC0AE4D209DDEF1ABBDB478C4958CC025D79 )-
Amp.isCollateralManager( _collateralManager=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578 ) => ( True )
-
-
ERC1820Registry.getInterfaceImplementer( _addr=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, _interfaceHash=FA352D6368BBC643BCF9D528FFABA5DD3E826137BC42F935045C6C227BD4C72A ) => ( 0x706D7F8B3445D8Dfc790C524E3990ef014e7C578 ) -
FlexaCollateralManager.tokensReceived( System.Byte[], _partition=CCCCCCCC81DBDE757C384900706D7F8B3445D8DFC790C524E3990EF014E7C578, _operator=0xabFf616B2067C5ac3FBD7bF874DdE3A6aCaa5B30, 0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, _to=0x706D7F8B3445D8Dfc790C524E3990ef014e7C578, _value=7141496000000000000000000, _data=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCCCCCCCC81DBDE757C384900706D7F8B3445D8DFC790C524E3990EF014E7C578, 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA000000000000000000000000ABFF616B2067C5AC3FBD7BF874DDE3A6ACAA5B30000000000000000000000000000000000000000000000000000000000000256D00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000009055C9DB9B374980330CDAF4444F3B830AE73E846D3CAAB5FA18FE0E10B27709CA137F92AAE8E57DBFFE4CC3D6EB9AEE47F6C58DC676C212C2B13FF270E4774CB44532CC029D7F56DF4D5597002E445EC1FC7AE169C2D71779A5B7591ED671447E3CFD0510B46BDCDD7910C59EC660457F20F34D87C6193126E3D04A4B647E98166A4969DBD216AC23E845A1DFF594F7ADCE74A1A21E67087C74E4D86E5E92AAD237E6069C00EC8F48EE4DF1CB7D638F02EC89065BE0135D22DE193EB512A30B25D5B14D0277A5C9FA52A8892BECB303D5A63AA79EA138F5143ED8B43FAD5DE34279F57C30E513D13467AFAF853E400375A85B5C1029D6715653C6F003C8C18DC888FA313C91B516CFEB7A17620ACBC0AE4D209DDEF1ABBDB478C4958CC025D79 )
transferByPartition[Amp (ln:1266)]
_transferByPartition[Amp (ln:1275)]_isOperatorForPartition[Amp (ln:1650)]_isOperator[Amp (ln:2037)]_callPartitionStrategyOperatorHook[Amp (ln:2039)]_getPartitionPrefix[Amp (ln:2057)]interfaceAddr[Amp (ln:2064)]_getPartitionStrategyValidatorIName[Amp (ln:2066)]isOperatorForPartitionScope[Amp (ln:2070)]
sub[Amp (ln:1659)]_callPreTransferHooks[Amp (ln:1667)]interfaceAddr[Amp (ln:1873)]tokensToTransfer[Amp (ln:1875)]_getPartitionPrefix[Amp (ln:1889)]interfaceAddr[Amp (ln:1892)]_getPartitionStrategyValidatorIName[Amp (ln:1894)]tokensFromPartitionToValidate[Amp (ln:1897)]
_getDestinationPartition[Amp (ln:1682)]_removeTokenFromPartition[Amp (ln:1687)]sub[Amp (ln:1753)]sub[Amp (ln:1755)]sub[Amp (ln:1757)]_removePartitionFromTotalPartitions[Amp (ln:1764)]pop[Amp (ln:1781)]
_addTokenToPartition[Amp (ln:1688)]add[Amp (ln:1801)]push[Amp (ln:1804)]add[Amp (ln:1807)]_addPartitionToTotalPartitions[Amp (ln:1811)]push[Amp (ln:1823)]
add[Amp (ln:1813)]
_callPostTransferHooks[Amp (ln:1689)]_getPartitionPrefix[Amp (ln:1932)]interfaceAddr[Amp (ln:1935)]_getPartitionStrategyValidatorIName[Amp (ln:1937)]tokensToPartitionToValidate[Amp (ln:1940)]interfaceAddr[Amp (ln:1957)]tokensReceived[Amp (ln:1960)]
Transfer[Amp (ln:1699)]TransferByPartition[Amp (ln:1700)]ChangedPartition[Amp (ln:1711)]
File 1 of 4: Amp
File 2 of 4: FlexaCollateralManager
File 3 of 4: ERC1820Registry
File 4 of 4: CollateralPoolPartitionValidator
// SPDX-License-Identifier: MIT
pragma solidity 0.6.10;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
/**
* @title Ownable is a contract the provides contract ownership functionality, including a two-
* phase transfer.
*/
contract Ownable {
address private _owner;
address private _authorizedNewOwner;
/**
* @notice Emitted when the owner authorizes ownership transfer to a new address
* @param authorizedAddress New owner address
*/
event OwnershipTransferAuthorization(address indexed authorizedAddress);
/**
* @notice Emitted when the authorized address assumed ownership
* @param oldValue Old owner
* @param newValue New owner
*/
event OwnerUpdate(address indexed oldValue, address indexed newValue);
/**
* @notice Sets the owner to the sender / contract creator
*/
constructor() internal {
_owner = msg.sender;
}
/**
* @notice Retrieves the owner of the contract
* @return The contract owner
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @notice Retrieves the authorized new owner of the contract
* @return The authorized new contract owner
*/
function authorizedNewOwner() public view returns (address) {
return _authorizedNewOwner;
}
/**
* @notice Authorizes the transfer of ownership from owner to the provided address.
* NOTE: No transfer will occur unless authorizedAddress calls assumeOwnership().
* This authorization may be removed by another call to this function authorizing the zero
* address.
* @param _authorizedAddress The address authorized to become the new owner
*/
function authorizeOwnershipTransfer(address _authorizedAddress) external {
require(msg.sender == _owner, "Invalid sender");
_authorizedNewOwner = _authorizedAddress;
emit OwnershipTransferAuthorization(_authorizedNewOwner);
}
/**
* @notice Transfers ownership of this contract to the _authorizedNewOwner
* @dev Error invalid sender.
*/
function assumeOwnership() external {
require(msg.sender == _authorizedNewOwner, "Invalid sender");
address oldValue = _owner;
_owner = _authorizedNewOwner;
_authorizedNewOwner = address(0);
emit OwnerUpdate(oldValue, _owner);
}
}
abstract contract ERC1820Registry {
function setInterfaceImplementer(
address _addr,
bytes32 _interfaceHash,
address _implementer
) external virtual;
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash)
external
virtual
view
returns (address);
function setManager(address _addr, address _newManager) external virtual;
function getManager(address _addr) public virtual view returns (address);
}
/// Base client to interact with the registry.
contract ERC1820Client {
ERC1820Registry constant ERC1820REGISTRY = ERC1820Registry(
0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
);
function setInterfaceImplementation(
string memory _interfaceLabel,
address _implementation
) internal {
bytes32 interfaceHash = keccak256(abi.encodePacked(_interfaceLabel));
ERC1820REGISTRY.setInterfaceImplementer(
address(this),
interfaceHash,
_implementation
);
}
function interfaceAddr(address addr, string memory _interfaceLabel)
internal
view
returns (address)
{
bytes32 interfaceHash = keccak256(abi.encodePacked(_interfaceLabel));
return ERC1820REGISTRY.getInterfaceImplementer(addr, interfaceHash);
}
function delegateManagement(address _newManager) internal {
ERC1820REGISTRY.setManager(address(this), _newManager);
}
}
contract ERC1820Implementer {
/**
* @dev ERC1820 well defined magic value indicating the contract has
* registered with the ERC1820Registry that it can implement an interface.
*/
bytes32 constant ERC1820_ACCEPT_MAGIC = keccak256(
abi.encodePacked("ERC1820_ACCEPT_MAGIC")
);
/**
* @dev Mapping of interface name keccak256 hashes for which this contract
* implements the interface.
* @dev Only settable internally.
*/
mapping(bytes32 => bool) internal _interfaceHashes;
/**
* @notice Indicates whether the contract implements the interface `_interfaceHash`
* for the address `_addr`.
* @param _interfaceHash keccak256 hash of the name of the interface.
* @return ERC1820_ACCEPT_MAGIC only if the contract implements `ìnterfaceHash`
* for the address `_addr`.
* @dev In this implementation, the `_addr` (the address for which the
* contract will implement the interface) is always `address(this)`.
*/
function canImplementInterfaceForAddress(
bytes32 _interfaceHash,
address // Comments to avoid compilation warnings for unused variables. /*addr*/
) external view returns (bytes32) {
if (_interfaceHashes[_interfaceHash]) {
return ERC1820_ACCEPT_MAGIC;
} else {
return "";
}
}
/**
* @notice Internally set the fact this contract implements the interface
* identified by `_interfaceLabel`
* @param _interfaceLabel String representation of the interface.
*/
function _setInterface(string memory _interfaceLabel) internal {
_interfaceHashes[keccak256(abi.encodePacked(_interfaceLabel))] = true;
}
}
/**
* @title IAmpTokensSender
* @dev IAmpTokensSender token transfer hook interface
*/
interface IAmpTokensSender {
/**
* @dev Report if the transfer will succeed from the pespective of the
* token sender
*/
function canTransfer(
bytes4 functionSig,
bytes32 partition,
address operator,
address from,
address to,
uint256 value,
bytes calldata data,
bytes calldata operatorData
) external view returns (bool);
/**
* @dev Hook executed upon a transfer on behalf of the sender
*/
function tokensToTransfer(
bytes4 functionSig,
bytes32 partition,
address operator,
address from,
address to,
uint256 value,
bytes calldata data,
bytes calldata operatorData
) external;
}
/**
* @title IAmpTokensRecipient
* @dev IAmpTokensRecipient token transfer hook interface
*/
interface IAmpTokensRecipient {
/**
* @dev Report if the recipient will successfully receive the tokens
*/
function canReceive(
bytes4 functionSig,
bytes32 partition,
address operator,
address from,
address to,
uint256 value,
bytes calldata data,
bytes calldata operatorData
) external view returns (bool);
/**
* @dev Hook executed upon a transfer to the recipient
*/
function tokensReceived(
bytes4 functionSig,
bytes32 partition,
address operator,
address from,
address to,
uint256 value,
bytes calldata data,
bytes calldata operatorData
) external;
}
/**
* @notice Partition strategy validator hooks for Amp
*/
interface IAmpPartitionStrategyValidator {
function tokensFromPartitionToValidate(
bytes4 _functionSig,
bytes32 _partition,
address _operator,
address _from,
address _to,
uint256 _value,
bytes calldata _data,
bytes calldata _operatorData
) external;
function tokensToPartitionToValidate(
bytes4 _functionSig,
bytes32 _partition,
address _operator,
address _from,
address _to,
uint256 _value,
bytes calldata _data,
bytes calldata _operatorData
) external;
function isOperatorForPartitionScope(
bytes32 _partition,
address _operator,
address _tokenHolder
) external view returns (bool);
}
/**
* @title PartitionUtils
* @notice Partition related helper functions.
*/
library PartitionUtils {
bytes32 public constant CHANGE_PARTITION_FLAG = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
/**
* @notice Retrieve the destination partition from the 'data' field.
* A partition change is requested ONLY when 'data' starts with the flag:
*
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
*
* When the flag is detected, the destination partition is extracted from the
* 32 bytes following the flag.
* @param _data Information attached to the transfer. Will contain the
* destination partition if a change is requested.
* @param _fallbackPartition Partition value to return if a partition change
* is not requested in the `_data`.
* @return toPartition Destination partition. If the `_data` does not contain
* the prefix and bytes32 partition in the first 64 bytes, the method will
* return the provided `_fromPartition`.
*/
function _getDestinationPartition(bytes memory _data, bytes32 _fallbackPartition)
internal
pure
returns (bytes32)
{
if (_data.length < 64) {
return _fallbackPartition;
}
(bytes32 flag, bytes32 toPartition) = abi.decode(_data, (bytes32, bytes32));
if (flag == CHANGE_PARTITION_FLAG) {
return toPartition;
}
return _fallbackPartition;
}
/**
* @notice Helper to get the strategy identifying prefix from the `_partition`.
* @param _partition Partition to get the prefix for.
* @return 4 byte partition strategy prefix.
*/
function _getPartitionPrefix(bytes32 _partition) internal pure returns (bytes4) {
return bytes4(_partition);
}
/**
* @notice Helper method to split the partition into the prefix, sub partition
* and partition owner components.
* @param _partition The partition to split into parts.
* @return The 4 byte partition prefix, 8 byte sub partition, and final 20
* bytes representing an address.
*/
function _splitPartition(bytes32 _partition)
internal
pure
returns (
bytes4,
bytes8,
address
)
{
bytes4 prefix = bytes4(_partition);
bytes8 subPartition = bytes8(_partition << 32);
address addressPart = address(uint160(uint256(_partition)));
return (prefix, subPartition, addressPart);
}
/**
* @notice Helper method to get a partition strategy ERC1820 interface name
* based on partition prefix.
* @param _prefix 4 byte partition prefix.
* @dev Each 4 byte prefix has a unique interface name so that an individual
* hook implementation can be set for each prefix.
*/
function _getPartitionStrategyValidatorIName(bytes4 _prefix)
internal
pure
returns (string memory)
{
return string(abi.encodePacked("AmpPartitionStrategyValidator", _prefix));
}
}
/**
* @title ErrorCodes
* @notice Amp error codes.
*/
contract ErrorCodes {
string internal EC_50_TRANSFER_FAILURE = "50";
string internal EC_51_TRANSFER_SUCCESS = "51";
string internal EC_52_INSUFFICIENT_BALANCE = "52";
string internal EC_53_INSUFFICIENT_ALLOWANCE = "53";
string internal EC_56_INVALID_SENDER = "56";
string internal EC_57_INVALID_RECEIVER = "57";
string internal EC_58_INVALID_OPERATOR = "58";
string internal EC_59_INSUFFICIENT_RIGHTS = "59";
string internal EC_5A_INVALID_SWAP_TOKEN_ADDRESS = "5A";
string internal EC_5B_INVALID_VALUE_0 = "5B";
string internal EC_5C_ADDRESS_CONFLICT = "5C";
string internal EC_5D_PARTITION_RESERVED = "5D";
string internal EC_5E_PARTITION_PREFIX_CONFLICT = "5E";
string internal EC_5F_INVALID_PARTITION_PREFIX_0 = "5F";
string internal EC_60_SWAP_TRANSFER_FAILURE = "60";
}
interface ISwapToken {
function allowance(address owner, address spender)
external
view
returns (uint256 remaining);
function transferFrom(
address from,
address to,
uint256 value
) external returns (bool success);
}
/**
* @title Amp
* @notice Amp is an ERC20 compatible collateral token designed to support
* multiple classes of collateralization systems.
* @dev The Amp token contract includes the following features:
*
* Partitions
* Tokens can be segmented within a given address by "partition", which in
* pracice is a 32 byte identifier. These partitions can have unique
* permissions globally, through the using of partition strategies, and
* locally, on a per address basis. The ability to create the sub-segments
* of tokens and assign special behavior gives collateral managers
* flexibility in how they are implemented.
*
* Operators
* Inspired by ERC777, Amp allows token holders to assign "operators" on
* all (or any number of partitions) of their tokens. Operators are allowed
* to execute transfers on behalf of token owners without the need to use the
* ERC20 "allowance" semantics.
*
* Transfers with Data
* Inspired by ERC777, Amp transfers can include arbitrary data, as well as
* operator data. This data can be used to change the partition of tokens,
* be used by collateral manager hooks to validate a transfer, be propagated
* via event to an off chain system, etc.
*
* Token Transfer Hooks on Send and Receive
* Inspired by ERC777, Amp uses the ERC1820 Registry to allow collateral
* manager implementations to register hooks to be called upon sending to
* or transferring from the collateral manager's address or, using partition
* strategies, owned partition space. The hook implementations can be used
* to validate transfer properties, gate transfers, emit custom events,
* update local state, etc.
*
* Collateral Management Partition Strategies
* Amp is able to define certain sets of partitions, identified by a 4 byte
* prefix, that will allow special, custom logic to be executed when transfers
* are made to or from those partitions. This opens up the possibility of
* entire classes of collateral management systems that would not be possible
* without it.
*
* These features give collateral manager implementers flexibility while
* providing a consistent, "collateral-in-place", interface for interacting
* with collateral systems directly through the Amp contract.
*/
contract Amp is IERC20, ERC1820Client, ERC1820Implementer, ErrorCodes, Ownable {
using SafeMath for uint256;
/**************************************************************************/
/********************** ERC1820 Interface Constants ***********************/
/**
* @dev AmpToken interface label.
*/
string internal constant AMP_INTERFACE_NAME = "AmpToken";
/**
* @dev ERC20Token interface label.
*/
string internal constant ERC20_INTERFACE_NAME = "ERC20Token";
/**
* @dev AmpTokensSender interface label.
*/
string internal constant AMP_TOKENS_SENDER = "AmpTokensSender";
/**
* @dev AmpTokensRecipient interface label.
*/
string internal constant AMP_TOKENS_RECIPIENT = "AmpTokensRecipient";
/**
* @dev AmpTokensChecker interface label.
*/
string internal constant AMP_TOKENS_CHECKER = "AmpTokensChecker";
/**************************************************************************/
/*************************** Token properties *****************************/
/**
* @dev Token name (Amp).
*/
string internal _name;
/**
* @dev Token symbol (AMP).
*/
string internal _symbol;
/**
* @dev Total minted supply of token. This will increase comensurately with
* successful swaps of the swap token.
*/
uint256 internal _totalSupply;
/**
* @dev The granularity of the token. Hard coded to 1.
*/
uint256 internal constant _granularity = 1;
/**************************************************************************/
/***************************** Token mappings *****************************/
/**
* @dev Mapping from tokenHolder to balance. This reflects the balance
* across all partitions of an address.
*/
mapping(address => uint256) internal _balances;
/**************************************************************************/
/************************** Partition mappings ****************************/
/**
* @dev List of active partitions. This list reflects all partitions that
* have tokens assigned to them.
*/
bytes32[] internal _totalPartitions;
/**
* @dev Mapping from partition to their index.
*/
mapping(bytes32 => uint256) internal _indexOfTotalPartitions;
/**
* @dev Mapping from partition to global balance of corresponding partition.
*/
mapping(bytes32 => uint256) public totalSupplyByPartition;
/**
* @dev Mapping from tokenHolder to their partitions.
*/
mapping(address => bytes32[]) internal _partitionsOf;
/**
* @dev Mapping from (tokenHolder, partition) to their index.
*/
mapping(address => mapping(bytes32 => uint256)) internal _indexOfPartitionsOf;
/**
* @dev Mapping from (tokenHolder, partition) to balance of corresponding
* partition.
*/
mapping(address => mapping(bytes32 => uint256)) internal _balanceOfByPartition;
/**
* @notice Default partition of the token.
* @dev All ERC20 operations operate solely on this partition.
*/
bytes32
public constant defaultPartition = 0x0000000000000000000000000000000000000000000000000000000000000000;
/**
* @dev Zero partition prefix. Parititions with this prefix can not have
* a strategy assigned, and partitions with a different prefix must have one.
*/
bytes4 internal constant ZERO_PREFIX = 0x00000000;
/**************************************************************************/
/***************************** Operator mappings **************************/
/**
* @dev Mapping from (tokenHolder, operator) to authorized status. This is
* specific to the token holder.
*/
mapping(address => mapping(address => bool)) internal _authorizedOperator;
/**************************************************************************/
/********************** Partition operator mappings ***********************/
/**
* @dev Mapping from (partition, tokenHolder, spender) to allowed value.
* This is specific to the token holder.
*/
mapping(bytes32 => mapping(address => mapping(address => uint256)))
internal _allowedByPartition;
/**
* @dev Mapping from (tokenHolder, partition, operator) to 'approved for
* partition' status. This is specific to the token holder.
*/
mapping(address => mapping(bytes32 => mapping(address => bool)))
internal _authorizedOperatorByPartition;
/**************************************************************************/
/********************** Collateral Manager mappings ***********************/
/**
* @notice Collection of registered collateral managers.
*/
address[] public collateralManagers;
/**
* @dev Mapping of collateral manager addresses to registration status.
*/
mapping(address => bool) internal _isCollateralManager;
/**************************************************************************/
/********************* Partition Strategy mappings ************************/
/**
* @notice Collection of reserved partition strategies.
*/
bytes4[] public partitionStrategies;
/**
* @dev Mapping of partition strategy flag to registration status.
*/
mapping(bytes4 => bool) internal _isPartitionStrategy;
/**************************************************************************/
/***************************** Swap storage *******************************/
/**
* @notice Swap token address. Immutable.
*/
ISwapToken public swapToken;
/**
* @notice Swap token graveyard address.
* @dev This is the address that the incoming swapped tokens will be
* forwarded to upon successfully minting Amp.
*/
address
public constant swapTokenGraveyard = 0x000000000000000000000000000000000000dEaD;
/**************************************************************************/
/** EVENTS ****************************************************************/
/**************************************************************************/
/**************************************************************************/
/**************************** Transfer Events *****************************/
/**
* @notice Emitted when a transfer has been successfully completed.
* @param fromPartition The partition the tokens were transfered from.
* @param operator The address that initiated the transfer.
* @param from The address the tokens were transferred from.
* @param to The address the tokens were transferred to.
* @param value The amount of tokens transferred.
* @param data Additional metadata included with the transfer. Can include
* the partition the tokens were transferred to (if different than
* `fromPartition`).
* @param operatorData Additional metadata included with the transfer on
* behalf of the operator.
*/
event TransferByPartition(
bytes32 indexed fromPartition,
address operator,
address indexed from,
address indexed to,
uint256 value,
bytes data,
bytes operatorData
);
/**
* @notice Emitted when a transfer has been successfully completed and the
* tokens that were transferred have changed partitions.
* @param fromPartition The partition the tokens were transfered from.
* @param toPartition The partition the tokens were transfered to.
* @param value The amount of tokens transferred.
*/
event ChangedPartition(
bytes32 indexed fromPartition,
bytes32 indexed toPartition,
uint256 value
);
/**************************************************************************/
/**************************** Operator Events *****************************/
/**
* @notice Emitted when a token holder specifies an amount of tokens in a
* a partition that an operator can transfer.
* @param partition The partition of the tokens the holder has authorized the
* operator to transfer from.
* @param owner The token holder.
* @param spender The operator the `owner` has authorized the allowance for.
*/
event ApprovalByPartition(
bytes32 indexed partition,
address indexed owner,
address indexed spender,
uint256 value
);
/**
* @notice Emitted when a token holder has authorized an operator for their
* tokens.
* @dev This event applies to the token holder address across all partitions.
* @param operator The address that was authorized to transfer tokens on
* behalf of the `tokenHolder`.
* @param tokenHolder The address that authorized the `operator` to transfer
* their tokens.
*/
event AuthorizedOperator(address indexed operator, address indexed tokenHolder);
/**
* @notice Emitted when a token holder has de-authorized an operator from
* transferring their tokens.
* @dev This event applies to the token holder address across all partitions.
* @param operator The address that was de-authorized from transferring tokens
* on behalf of the `tokenHolder`.
* @param tokenHolder The address that revoked the `operator`'s permission
* to transfer their tokens.
*/
event RevokedOperator(address indexed operator, address indexed tokenHolder);
/**
* @notice Emitted when a token holder has authorized an operator to transfer
* their tokens of one partition.
* @param partition The partition the `operator` is allowed to transfer
* tokens from.
* @param operator The address that was authorized to transfer tokens on
* behalf of the `tokenHolder`.
* @param tokenHolder The address that authorized the `operator` to transfer
* their tokens in `partition`.
*/
event AuthorizedOperatorByPartition(
bytes32 indexed partition,
address indexed operator,
address indexed tokenHolder
);
/**
* @notice Emitted when a token holder has de-authorized an operator from
* transferring their tokens from a specific partition.
* @param partition The partition the `operator` is no longer allowed to
* transfer tokens from on behalf of the `tokenHolder`.
* @param operator The address that was de-authorized from transferring
* tokens on behalf of the `tokenHolder`.
* @param tokenHolder The address that revoked the `operator`'s permission
* to transfer their tokens from `partition`.
*/
event RevokedOperatorByPartition(
bytes32 indexed partition,
address indexed operator,
address indexed tokenHolder
);
/**************************************************************************/
/********************** Collateral Manager Events *************************/
/**
* @notice Emitted when a collateral manager has been registered.
* @param collateralManager The address of the collateral manager.
*/
event CollateralManagerRegistered(address collateralManager);
/**************************************************************************/
/*********************** Partition Strategy Events ************************/
/**
* @notice Emitted when a new partition strategy validator is set.
* @param flag The 4 byte prefix of the partitions that the stratgy affects.
* @param name The name of the partition strategy.
* @param implementation The address of the partition strategy hook
* implementation.
*/
event PartitionStrategySet(bytes4 flag, string name, address indexed implementation);
// ************** Mint & Swap **************
/**
* @notice Emitted when tokens are minted as a result of a token swap
* @param operator Address that executed the swap that resulted in tokens being minted
* @param to Address that received the newly minted tokens.
* @param value Amount of tokens minted
* @param data Empty bytes, required for interface compatibility
*/
event Minted(address indexed operator, address indexed to, uint256 value, bytes data);
/**
* @notice Indicates tokens swapped for Amp.
* @dev The tokens that are swapped for Amp will be transferred to a
* graveyard address that is for all practical purposes inaccessible.
* @param operator Address that executed the swap.
* @param from Address that the tokens were swapped from, and Amp minted for.
* @param value Amount of tokens swapped into Amp.
*/
event Swap(address indexed operator, address indexed from, uint256 value);
/**************************************************************************/
/** CONSTRUCTOR ***********************************************************/
/**************************************************************************/
/**
* @notice Initialize Amp, initialize the default partition, and register the
* contract implementation in the global ERC1820Registry.
* @param _swapTokenAddress_ The address of the ERC20 token that is set to be
* swappable for Amp.
* @param _name_ Name of the token.
* @param _symbol_ Symbol of the token.
*/
constructor(
address _swapTokenAddress_,
string memory _name_,
string memory _symbol_
) public {
// "Swap token cannot be 0 address"
require(_swapTokenAddress_ != address(0), EC_5A_INVALID_SWAP_TOKEN_ADDRESS);
swapToken = ISwapToken(_swapTokenAddress_);
_name = _name_;
_symbol = _symbol_;
_totalSupply = 0;
// Add the default partition to the total partitions on deploy
_addPartitionToTotalPartitions(defaultPartition);
// Register contract in ERC1820 registry
ERC1820Client.setInterfaceImplementation(AMP_INTERFACE_NAME, address(this));
ERC1820Client.setInterfaceImplementation(ERC20_INTERFACE_NAME, address(this));
// Indicate token verifies Amp and ERC20 interfaces
ERC1820Implementer._setInterface(AMP_INTERFACE_NAME);
ERC1820Implementer._setInterface(ERC20_INTERFACE_NAME);
}
/**************************************************************************/
/** EXTERNAL FUNCTIONS (ERC20) ********************************************/
/**************************************************************************/
/**
* @notice Get the total number of issued tokens.
* @return Total supply of tokens currently in circulation.
*/
function totalSupply() external override view returns (uint256) {
return _totalSupply;
}
/**
* @notice Get the balance of the account with address `_tokenHolder`.
* @dev This returns the balance of the holder across all partitions. Note
* that due to other functionality in Amp, this figure should not be used
* as the arbiter of the amount a token holder will successfully be able to
* send via the ERC20 compatible `transfer` method. In order to get that
* figure, use `balanceOfByParition` and to get the balance of the default
* partition.
* @param _tokenHolder Address for which the balance is returned.
* @return Amount of token held by `_tokenHolder` in the default partition.
*/
function balanceOf(address _tokenHolder) external override view returns (uint256) {
return _balances[_tokenHolder];
}
/**
* @notice Transfer token for a specified address.
* @dev This method is for ERC20 compatibility, and only affects the
* balance of the `msg.sender` address's default partition.
* @param _to The address to transfer to.
* @param _value The value to be transferred.
* @return A boolean that indicates if the operation was successful.
*/
function transfer(address _to, uint256 _value) external override returns (bool) {
_transferByDefaultPartition(msg.sender, msg.sender, _to, _value, "");
return true;
}
/**
* @notice Transfer tokens from one address to another.
* @dev This method is for ERC20 compatibility, and only affects the
* balance and allowance of the `_from` address's default partition.
* @param _from The address which you want to transfer tokens from.
* @param _to The address which you want to transfer to.
* @param _value The amount of tokens to be transferred.
* @return A boolean that indicates if the operation was successful.
*/
function transferFrom(
address _from,
address _to,
uint256 _value
) external override returns (bool) {
_transferByDefaultPartition(msg.sender, _from, _to, _value, "");
return true;
}
/**
* @notice Check the value of tokens that an owner allowed to a spender.
* @dev This method is for ERC20 compatibility, and only affects the
* allowance of the `msg.sender`'s default partition.
* @param _owner address The address which owns the funds.
* @param _spender address The address which will spend the funds.
* @return A uint256 specifying the value of tokens still available for the
* spender.
*/
function allowance(address _owner, address _spender)
external
override
view
returns (uint256)
{
return _allowedByPartition[defaultPartition][_owner][_spender];
}
/**
* @notice Approve the passed address to spend the specified amount of
* tokens from the default partition on behalf of 'msg.sender'.
* @dev This method is for ERC20 compatibility, and only affects the
* allowance of the `msg.sender`'s default partition.
* @param _spender The address which will spend the funds.
* @param _value The amount of tokens to be spent.
* @return A boolean that indicates if the operation was successful.
*/
function approve(address _spender, uint256 _value) external override returns (bool) {
_approveByPartition(defaultPartition, msg.sender, _spender, _value);
return true;
}
/**
* @notice Atomically increases the allowance granted to `_spender` by the
* for caller.
* @dev This is an alternative to {approve} that can be used as a mitigation
* problems described in {IERC20-approve}.
* Emits an {Approval} event indicating the updated allowance.
* Requirements:
* - `_spender` cannot be the zero address.
* @dev This method is for ERC20 compatibility, and only affects the
* allowance of the `msg.sender`'s default partition.
* @param _spender Operator allowed to transfer the tokens
* @param _addedValue Additional amount of the `msg.sender`s tokens `_spender`
* is allowed to transfer
* @return 'true' is successful, 'false' otherwise
*/
function increaseAllowance(address _spender, uint256 _addedValue)
external
returns (bool)
{
_approveByPartition(
defaultPartition,
msg.sender,
_spender,
_allowedByPartition[defaultPartition][msg.sender][_spender].add(_addedValue)
);
return true;
}
/**
* @notice Atomically decreases the allowance granted to `_spender` by the
* caller.
* @dev This is an alternative to {approve} that can be used as a mitigation
* for bugs caused by reentrancy.
* Emits an {Approval} event indicating the updated allowance.
* Requirements:
* - `_spender` cannot be the zero address.
* - `_spender` must have allowance for the caller of at least
* `_subtractedValue`.
* @dev This method is for ERC20 compatibility, and only affects the
* allowance of the `msg.sender`'s default partition.
* @param _spender Operator allowed to transfer the tokens
* @param _subtractedValue Amount of the `msg.sender`s tokens `_spender`
* is no longer allowed to transfer
* @return 'true' is successful, 'false' otherwise
*/
function decreaseAllowance(address _spender, uint256 _subtractedValue)
external
returns (bool)
{
_approveByPartition(
defaultPartition,
msg.sender,
_spender,
_allowedByPartition[defaultPartition][msg.sender][_spender].sub(
_subtractedValue
)
);
return true;
}
/**************************************************************************/
/** EXTERNAL FUNCTIONS (AMP) **********************************************/
/**************************************************************************/
/******************************** Swap ***********************************/
/**
* @notice Swap tokens to mint AMP.
* @dev Requires `_from` to have given allowance of swap token to contract.
* Otherwise will throw error code 53 (Insuffient Allowance).
* @param _from Token holder to execute the swap for.
*/
function swap(address _from) public {
uint256 amount = swapToken.allowance(_from, address(this));
require(amount > 0, EC_53_INSUFFICIENT_ALLOWANCE);
require(
swapToken.transferFrom(_from, swapTokenGraveyard, amount),
EC_60_SWAP_TRANSFER_FAILURE
);
_mint(msg.sender, _from, amount);
emit Swap(msg.sender, _from, amount);
}
/**************************************************************************/
/************************** Holder information ****************************/
/**
* @notice Get balance of a tokenholder for a specific partition.
* @param _partition Name of the partition.
* @param _tokenHolder Address for which the balance is returned.
* @return Amount of token of partition `_partition` held by `_tokenHolder` in the token contract.
*/
function balanceOfByPartition(bytes32 _partition, address _tokenHolder)
external
view
returns (uint256)
{
return _balanceOfByPartition[_tokenHolder][_partition];
}
/**
* @notice Get partitions index of a token holder.
* @param _tokenHolder Address for which the partitions index are returned.
* @return Array of partitions index of '_tokenHolder'.
*/
function partitionsOf(address _tokenHolder) external view returns (bytes32[] memory) {
return _partitionsOf[_tokenHolder];
}
/**************************************************************************/
/************************** Advanced Transfers ****************************/
/**
* @notice Transfer tokens from a specific partition on behalf of a token
* holder, optionally changing the parittion and optionally including
* arbitrary data with the transfer.
* @dev This can be used to transfer an address's own tokens, or transfer
* a different addresses tokens by specifying the `_from` param. If
* attempting to transfer from a different address than `msg.sender`, the
* `msg.sender` will need to be an operator or have enough allowance for the
* `_partition` of the `_from` address.
* @param _partition Name of the partition to transfer from.
* @param _from Token holder.
* @param _to Token recipient.
* @param _value Number of tokens to transfer.
* @param _data Information attached to the transfer. Will contain the
* destination partition (if changing partitions).
* @param _operatorData Information attached to the transfer, by the operator.
* @return Destination partition.
*/
function transferByPartition(
bytes32 _partition,
address _from,
address _to,
uint256 _value,
bytes calldata _data,
bytes calldata _operatorData
) external returns (bytes32) {
return
_transferByPartition(
_partition,
msg.sender,
_from,
_to,
_value,
_data,
_operatorData
);
}
/**************************************************************************/
/************************** Operator Management ***************************/
/**
* @notice Set a third party operator address as an operator of 'msg.sender'
* to transfer and redeem tokens on its behalf.
* @dev The msg.sender is always an operator for itself, and does not need to
* be explicitly added.
* @param _operator Address to set as an operator for 'msg.sender'.
*/
function authorizeOperator(address _operator) external {
require(_operator != msg.sender, EC_58_INVALID_OPERATOR);
_authorizedOperator[msg.sender][_operator] = true;
emit AuthorizedOperator(_operator, msg.sender);
}
/**
* @notice Remove the right of the operator address to be an operator for
* 'msg.sender' and to transfer and redeem tokens on its behalf.
* @dev The msg.sender is always an operator for itself, and cannot be
* removed.
* @param _operator Address to rescind as an operator for 'msg.sender'.
*/
function revokeOperator(address _operator) external {
require(_operator != msg.sender, EC_58_INVALID_OPERATOR);
_authorizedOperator[msg.sender][_operator] = false;
emit RevokedOperator(_operator, msg.sender);
}
/**
* @notice Set `_operator` as an operator for 'msg.sender' for a given partition.
* @dev The msg.sender is always an operator for itself, and does not need to
* be explicitly added to a partition.
* @param _partition Name of the partition.
* @param _operator Address to set as an operator for 'msg.sender'.
*/
function authorizeOperatorByPartition(bytes32 _partition, address _operator)
external
{
require(_operator != msg.sender, EC_58_INVALID_OPERATOR);
_authorizedOperatorByPartition[msg.sender][_partition][_operator] = true;
emit AuthorizedOperatorByPartition(_partition, _operator, msg.sender);
}
/**
* @notice Remove the right of the operator address to be an operator on a
* given partition for 'msg.sender' and to transfer and redeem tokens on its
* behalf.
* @dev The msg.sender is always an operator for itself, and cannot be
* removed from a partition.
* @param _partition Name of the partition.
* @param _operator Address to rescind as an operator on given partition for
* 'msg.sender'.
*/
function revokeOperatorByPartition(bytes32 _partition, address _operator) external {
require(_operator != msg.sender, EC_58_INVALID_OPERATOR);
_authorizedOperatorByPartition[msg.sender][_partition][_operator] = false;
emit RevokedOperatorByPartition(_partition, _operator, msg.sender);
}
/**************************************************************************/
/************************** Operator Information **************************/
/**
* @notice Indicate whether the `_operator` address is an operator of the
* `_tokenHolder` address.
* @dev An operator in this case is an operator across all of the partitions
* of the `msg.sender` address.
* @param _operator Address which may be an operator of `_tokenHolder`.
* @param _tokenHolder Address of a token holder which may have the
* `_operator` address as an operator.
* @return 'true' if operator is an operator of 'tokenHolder' and 'false'
* otherwise.
*/
function isOperator(address _operator, address _tokenHolder)
external
view
returns (bool)
{
return _isOperator(_operator, _tokenHolder);
}
/**
* @notice Indicate whether the operator address is an operator of the
* `_tokenHolder` address for the given partition.
* @param _partition Name of the partition.
* @param _operator Address which may be an operator of tokenHolder for the
* given partition.
* @param _tokenHolder Address of a token holder which may have the
* `_operator` address as an operator for the given partition.
* @return 'true' if 'operator' is an operator of `_tokenHolder` for
* partition '_partition' and 'false' otherwise.
*/
function isOperatorForPartition(
bytes32 _partition,
address _operator,
address _tokenHolder
) external view returns (bool) {
return _isOperatorForPartition(_partition, _operator, _tokenHolder);
}
/**
* @notice Indicate when the `_operator` address is an operator of the
* `_collateralManager` address for the given partition.
* @dev This method is the same as `isOperatorForPartition`, except that it
* also requires the address that `_operator` is being checked for MUST be
* a registered collateral manager, and this method will not execute
* partition strategy operator check hooks.
* @param _partition Name of the partition.
* @param _operator Address which may be an operator of `_collateralManager`
* for the given partition.
* @param _collateralManager Address of a collateral manager which may have
* the `_operator` address as an operator for the given partition.
*/
function isOperatorForCollateralManager(
bytes32 _partition,
address _operator,
address _collateralManager
) external view returns (bool) {
return
_isCollateralManager[_collateralManager] &&
(_isOperator(_operator, _collateralManager) ||
_authorizedOperatorByPartition[_collateralManager][_partition][_operator]);
}
/**************************************************************************/
/***************************** Token metadata *****************************/
/**
* @notice Get the name of the token (Amp).
* @return Name of the token.
*/
function name() external view returns (string memory) {
return _name;
}
/**
* @notice Get the symbol of the token (AMP).
* @return Symbol of the token.
*/
function symbol() external view returns (string memory) {
return _symbol;
}
/**
* @notice Get the number of decimals of the token.
* @dev Hard coded to 18.
* @return The number of decimals of the token (18).
*/
function decimals() external pure returns (uint8) {
return uint8(18);
}
/**
* @notice Get the smallest part of the token that’s not divisible.
* @dev Hard coded to 1.
* @return The smallest non-divisible part of the token.
*/
function granularity() external pure returns (uint256) {
return _granularity;
}
/**
* @notice Get list of existing partitions.
* @return Array of all exisiting partitions.
*/
function totalPartitions() external view returns (bytes32[] memory) {
return _totalPartitions;
}
/************************************************************************************************/
/******************************** Partition Token Allowances ************************************/
/**
* @notice Check the value of tokens that an owner allowed to a spender.
* @param _partition Name of the partition.
* @param _owner The address which owns the tokens.
* @param _spender The address which will spend the tokens.
* @return The value of tokens still for the spender to transfer.
*/
function allowanceByPartition(
bytes32 _partition,
address _owner,
address _spender
) external view returns (uint256) {
return _allowedByPartition[_partition][_owner][_spender];
}
/**
* @notice Approve the `_spender` address to spend the specified amount of
* tokens in `_partition` on behalf of 'msg.sender'.
* @param _partition Name of the partition.
* @param _spender The address which will spend the tokens.
* @param _value The amount of tokens to be tokens.
* @return A boolean that indicates if the operation was successful.
*/
function approveByPartition(
bytes32 _partition,
address _spender,
uint256 _value
) external returns (bool) {
_approveByPartition(_partition, msg.sender, _spender, _value);
return true;
}
/**
* @notice Atomically increases the allowance granted to `_spender` by the
* caller.
* @dev This is an alternative to {approveByPartition} that can be used as
* a mitigation for bugs caused by reentrancy.
* Emits an {ApprovalByPartition} event indicating the updated allowance.
* Requirements:
* - `_spender` cannot be the zero address.
* @param _partition Name of the partition.
* @param _spender Operator allowed to transfer the tokens
* @param _addedValue Additional amount of the `msg.sender`s tokens `_spender`
* is allowed to transfer
* @return 'true' is successful, 'false' otherwise
*/
function increaseAllowanceByPartition(
bytes32 _partition,
address _spender,
uint256 _addedValue
) external returns (bool) {
_approveByPartition(
_partition,
msg.sender,
_spender,
_allowedByPartition[_partition][msg.sender][_spender].add(_addedValue)
);
return true;
}
/**
* @notice Atomically decreases the allowance granted to `_spender` by the
* caller.
* @dev This is an alternative to {approveByPartition} that can be used as
* a mitigation for bugs caused by reentrancy.
* Emits an {ApprovalByPartition} event indicating the updated allowance.
* Requirements:
* - `_spender` cannot be the zero address.
* - `_spender` must have allowance for the caller of at least
* `_subtractedValue`.
* @param _spender Operator allowed to transfer the tokens
* @param _subtractedValue Amount of the `msg.sender`s tokens `_spender` is
* no longer allowed to transfer
* @return 'true' is successful, 'false' otherwise
*/
function decreaseAllowanceByPartition(
bytes32 _partition,
address _spender,
uint256 _subtractedValue
) external returns (bool) {
// TOOD: Figure out if safe math will panic below 0
_approveByPartition(
_partition,
msg.sender,
_spender,
_allowedByPartition[_partition][msg.sender][_spender].sub(_subtractedValue)
);
return true;
}
/**************************************************************************/
/************************ Collateral Manager Admin ************************/
/**
* @notice Allow a collateral manager to self-register.
* @dev Error 0x5c.
*/
function registerCollateralManager() external {
// Short circuit a double registry
require(!_isCollateralManager[msg.sender], EC_5C_ADDRESS_CONFLICT);
collateralManagers.push(msg.sender);
_isCollateralManager[msg.sender] = true;
emit CollateralManagerRegistered(msg.sender);
}
/**
* @notice Get the status of a collateral manager.
* @param _collateralManager The address of the collateral mananger in question.
* @return 'true' if `_collateralManager` has self registered, 'false'
* otherwise.
*/
function isCollateralManager(address _collateralManager)
external
view
returns (bool)
{
return _isCollateralManager[_collateralManager];
}
/**************************************************************************/
/************************ Partition Strategy Admin ************************/
/**
* @notice Sets an implementation for a partition strategy identified by prefix.
* @dev This is an administration method, callable only by the owner of the
* Amp contract.
* @param _prefix The 4 byte partition prefix the strategy applies to.
* @param _implementation The address of the implementation of the strategy hooks.
*/
function setPartitionStrategy(bytes4 _prefix, address _implementation) external {
require(msg.sender == owner(), EC_56_INVALID_SENDER);
require(!_isPartitionStrategy[_prefix], EC_5E_PARTITION_PREFIX_CONFLICT);
require(_prefix != ZERO_PREFIX, EC_5F_INVALID_PARTITION_PREFIX_0);
string memory iname = PartitionUtils._getPartitionStrategyValidatorIName(_prefix);
ERC1820Client.setInterfaceImplementation(iname, _implementation);
partitionStrategies.push(_prefix);
_isPartitionStrategy[_prefix] = true;
emit PartitionStrategySet(_prefix, iname, _implementation);
}
/**
* @notice Return if a partition strategy has been reserved and has an
* implementation registered.
* @param _prefix The partition strategy identifier.
* @return 'true' if the strategy has been registered, 'false' if not.
*/
function isPartitionStrategy(bytes4 _prefix) external view returns (bool) {
return _isPartitionStrategy[_prefix];
}
/**************************************************************************/
/*************************** INTERNAL FUNCTIONS ***************************/
/**************************************************************************/
/**************************************************************************/
/**************************** Token Transfers *****************************/
/**
* @dev Transfer tokens from a specific partition.
* @param _fromPartition Partition of the tokens to transfer.
* @param _operator The address performing the transfer.
* @param _from Token holder.
* @param _to Token recipient.
* @param _value Number of tokens to transfer.
* @param _data Information attached to the transfer. Contains the destination
* partition if a partition change is requested.
* @param _operatorData Information attached to the transfer, by the operator
* (if any).
* @return Destination partition.
*/
function _transferByPartition(
bytes32 _fromPartition,
address _operator,
address _from,
address _to,
uint256 _value,
bytes memory _data,
bytes memory _operatorData
) internal returns (bytes32) {
require(_to != address(0), EC_57_INVALID_RECEIVER);
// If the `_operator` is attempting to transfer from a different `_from`
// address, first check that they have the requisite operator or
// allowance permissions.
if (_from != _operator) {
require(
_isOperatorForPartition(_fromPartition, _operator, _from) ||
(_value <= _allowedByPartition[_fromPartition][_from][_operator]),
EC_53_INSUFFICIENT_ALLOWANCE
);
// If the sender has an allowance for the partition, that should
// be decremented
if (_allowedByPartition[_fromPartition][_from][_operator] >= _value) {
_allowedByPartition[_fromPartition][_from][msg
.sender] = _allowedByPartition[_fromPartition][_from][_operator].sub(
_value
);
} else {
_allowedByPartition[_fromPartition][_from][_operator] = 0;
}
}
_callPreTransferHooks(
_fromPartition,
_operator,
_from,
_to,
_value,
_data,
_operatorData
);
require(
_balanceOfByPartition[_from][_fromPartition] >= _value,
EC_52_INSUFFICIENT_BALANCE
);
bytes32 toPartition = PartitionUtils._getDestinationPartition(
_data,
_fromPartition
);
_removeTokenFromPartition(_from, _fromPartition, _value);
_addTokenToPartition(_to, toPartition, _value);
_callPostTransferHooks(
toPartition,
_operator,
_from,
_to,
_value,
_data,
_operatorData
);
emit Transfer(_from, _to, _value);
emit TransferByPartition(
_fromPartition,
_operator,
_from,
_to,
_value,
_data,
_operatorData
);
if (toPartition != _fromPartition) {
emit ChangedPartition(_fromPartition, toPartition, _value);
}
return toPartition;
}
/**
* @notice Transfer tokens from default partitions.
* @dev Used as a helper method for ERC20 compatibility.
* @param _operator The address performing the transfer.
* @param _from Token holder.
* @param _to Token recipient.
* @param _value Number of tokens to transfer.
* @param _data Information attached to the transfer, and intended for the
* token holder (`_from`). Should contain the destination partition if
* changing partitions.
*/
function _transferByDefaultPartition(
address _operator,
address _from,
address _to,
uint256 _value,
bytes memory _data
) internal {
_transferByPartition(defaultPartition, _operator, _from, _to, _value, _data, "");
}
/**
* @dev Remove a token from a specific partition.
* @param _from Token holder.
* @param _partition Name of the partition.
* @param _value Number of tokens to transfer.
*/
function _removeTokenFromPartition(
address _from,
bytes32 _partition,
uint256 _value
) internal {
if (_value == 0) {
return;
}
_balances[_from] = _balances[_from].sub(_value);
_balanceOfByPartition[_from][_partition] = _balanceOfByPartition[_from][_partition]
.sub(_value);
totalSupplyByPartition[_partition] = totalSupplyByPartition[_partition].sub(
_value
);
// If the total supply is zero, finds and deletes the partition.
// Do not delete the _defaultPartition from totalPartitions.
if (totalSupplyByPartition[_partition] == 0 && _partition != defaultPartition) {
_removePartitionFromTotalPartitions(_partition);
}
// If the balance of the TokenHolder's partition is zero, finds and
// deletes the partition.
if (_balanceOfByPartition[_from][_partition] == 0) {
uint256 index = _indexOfPartitionsOf[_from][_partition];
if (index == 0) {
return;
}
// move the last item into the index being vacated
bytes32 lastValue = _partitionsOf[_from][_partitionsOf[_from].length - 1];
_partitionsOf[_from][index - 1] = lastValue; // adjust for 1-based indexing
_indexOfPartitionsOf[_from][lastValue] = index;
_partitionsOf[_from].pop();
_indexOfPartitionsOf[_from][_partition] = 0;
}
}
/**
* @dev Add a token to a specific partition.
* @param _to Token recipient.
* @param _partition Name of the partition.
* @param _value Number of tokens to transfer.
*/
function _addTokenToPartition(
address _to,
bytes32 _partition,
uint256 _value
) internal {
if (_value == 0) {
return;
}
_balances[_to] = _balances[_to].add(_value);
if (_indexOfPartitionsOf[_to][_partition] == 0) {
_partitionsOf[_to].push(_partition);
_indexOfPartitionsOf[_to][_partition] = _partitionsOf[_to].length;
}
_balanceOfByPartition[_to][_partition] = _balanceOfByPartition[_to][_partition]
.add(_value);
if (_indexOfTotalPartitions[_partition] == 0) {
_addPartitionToTotalPartitions(_partition);
}
totalSupplyByPartition[_partition] = totalSupplyByPartition[_partition].add(
_value
);
}
/**
* @dev Add a partition to the total partitions collection.
* @param _partition Name of the partition.
*/
function _addPartitionToTotalPartitions(bytes32 _partition) internal {
_totalPartitions.push(_partition);
_indexOfTotalPartitions[_partition] = _totalPartitions.length;
}
/**
* @dev Remove a partition to the total partitions collection.
* @param _partition Name of the partition.
*/
function _removePartitionFromTotalPartitions(bytes32 _partition) internal {
uint256 index = _indexOfTotalPartitions[_partition];
if (index == 0) {
return;
}
// move the last item into the index being vacated
bytes32 lastValue = _totalPartitions[_totalPartitions.length - 1];
_totalPartitions[index - 1] = lastValue; // adjust for 1-based indexing
_indexOfTotalPartitions[lastValue] = index;
_totalPartitions.pop();
_indexOfTotalPartitions[_partition] = 0;
}
/**************************************************************************/
/********************************* Hooks **********************************/
/**
* @notice Check for and call the 'AmpTokensSender' hook on the sender address
* (`_from`), and, if `_fromPartition` is within the scope of a strategy,
* check for and call the 'AmpPartitionStrategy.tokensFromPartitionToTransfer'
* hook for the strategy.
* @param _fromPartition Name of the partition to transfer tokens from.
* @param _operator Address which triggered the balance decrease (through
* transfer).
* @param _from Token holder.
* @param _to Token recipient for a transfer.
* @param _value Number of tokens the token holder balance is decreased by.
* @param _data Extra information, pertaining to the `_from` address.
* @param _operatorData Extra information, attached by the operator (if any).
*/
function _callPreTransferHooks(
bytes32 _fromPartition,
address _operator,
address _from,
address _to,
uint256 _value,
bytes memory _data,
bytes memory _operatorData
) internal {
address senderImplementation;
senderImplementation = interfaceAddr(_from, AMP_TOKENS_SENDER);
if (senderImplementation != address(0)) {
IAmpTokensSender(senderImplementation).tokensToTransfer(
msg.sig,
_fromPartition,
_operator,
_from,
_to,
_value,
_data,
_operatorData
);
}
// Used to ensure that hooks implemented by a collateral manager to validate
// transfers from it's owned partitions are called
bytes4 fromPartitionPrefix = PartitionUtils._getPartitionPrefix(_fromPartition);
if (_isPartitionStrategy[fromPartitionPrefix]) {
address fromPartitionValidatorImplementation;
fromPartitionValidatorImplementation = interfaceAddr(
address(this),
PartitionUtils._getPartitionStrategyValidatorIName(fromPartitionPrefix)
);
if (fromPartitionValidatorImplementation != address(0)) {
IAmpPartitionStrategyValidator(fromPartitionValidatorImplementation)
.tokensFromPartitionToValidate(
msg.sig,
_fromPartition,
_operator,
_from,
_to,
_value,
_data,
_operatorData
);
}
}
}
/**
* @dev Check for 'AmpTokensRecipient' hook on the recipient and call it.
* @param _toPartition Name of the partition the tokens were transferred to.
* @param _operator Address which triggered the balance increase (through
* transfer or mint).
* @param _from Token holder for a transfer (0x when mint).
* @param _to Token recipient.
* @param _value Number of tokens the recipient balance is increased by.
* @param _data Extra information related to the token holder (`_from`).
* @param _operatorData Extra information attached by the operator (if any).
*/
function _callPostTransferHooks(
bytes32 _toPartition,
address _operator,
address _from,
address _to,
uint256 _value,
bytes memory _data,
bytes memory _operatorData
) internal {
bytes4 toPartitionPrefix = PartitionUtils._getPartitionPrefix(_toPartition);
if (_isPartitionStrategy[toPartitionPrefix]) {
address partitionManagerImplementation;
partitionManagerImplementation = interfaceAddr(
address(this),
PartitionUtils._getPartitionStrategyValidatorIName(toPartitionPrefix)
);
if (partitionManagerImplementation != address(0)) {
IAmpPartitionStrategyValidator(partitionManagerImplementation)
.tokensToPartitionToValidate(
msg.sig,
_toPartition,
_operator,
_from,
_to,
_value,
_data,
_operatorData
);
}
} else {
require(toPartitionPrefix == ZERO_PREFIX, EC_5D_PARTITION_RESERVED);
}
address recipientImplementation;
recipientImplementation = interfaceAddr(_to, AMP_TOKENS_RECIPIENT);
if (recipientImplementation != address(0)) {
IAmpTokensRecipient(recipientImplementation).tokensReceived(
msg.sig,
_toPartition,
_operator,
_from,
_to,
_value,
_data,
_operatorData
);
}
}
/**************************************************************************/
/******************************* Allowance ********************************/
/**
* @notice Approve the `_spender` address to spend the specified amount of
* tokens in `_partition` on behalf of 'msg.sender'.
* @param _partition Name of the partition.
* @param _tokenHolder Owner of the tokens.
* @param _spender The address which will spend the tokens.
* @param _amount The amount of tokens to be tokens.
*/
function _approveByPartition(
bytes32 _partition,
address _tokenHolder,
address _spender,
uint256 _amount
) internal {
require(_tokenHolder != address(0), EC_56_INVALID_SENDER);
require(_spender != address(0), EC_58_INVALID_OPERATOR);
_allowedByPartition[_partition][_tokenHolder][_spender] = _amount;
emit ApprovalByPartition(_partition, _tokenHolder, _spender, _amount);
if (_partition == defaultPartition) {
emit Approval(_tokenHolder, _spender, _amount);
}
}
/**************************************************************************/
/************************** Operator Information **************************/
/**
* @dev Indicate whether the operator address is an operator of the
* tokenHolder address. An operator in this case is an operator across all
* partitions of the `msg.sender` address.
* @param _operator Address which may be an operator of '_tokenHolder'.
* @param _tokenHolder Address of a token holder which may have the '_operator'
* address as an operator.
* @return 'true' if `_operator` is an operator of `_tokenHolder` and 'false'
* otherwise.
*/
function _isOperator(address _operator, address _tokenHolder)
internal
view
returns (bool)
{
return (_operator == _tokenHolder ||
_authorizedOperator[_tokenHolder][_operator]);
}
/**
* @dev Indicate whether the operator address is an operator of the
* tokenHolder address for the given partition.
* @param _partition Name of the partition.
* @param _operator Address which may be an operator of tokenHolder for the
* given partition.
* @param _tokenHolder Address of a token holder which may have the operator
* address as an operator for the given partition.
* @return 'true' if 'operator' is an operator of 'tokenHolder' for partition
* `_partition` and 'false' otherwise.
*/
function _isOperatorForPartition(
bytes32 _partition,
address _operator,
address _tokenHolder
) internal view returns (bool) {
return (_isOperator(_operator, _tokenHolder) ||
_authorizedOperatorByPartition[_tokenHolder][_partition][_operator] ||
_callPartitionStrategyOperatorHook(_partition, _operator, _tokenHolder));
}
/**
* @notice Check if the `_partition` is within the scope of a strategy, and
* call it's isOperatorForPartitionScope hook if so.
* @dev This allows implicit granting of operatorByPartition permissions
* based on the partition being used being of a strategy.
* @param _partition The partition to check.
* @param _operator The address to check if is an operator for `_tokenHolder`.
* @param _tokenHolder The address to validate that `_operator` is an
* operator for.
*/
function _callPartitionStrategyOperatorHook(
bytes32 _partition,
address _operator,
address _tokenHolder
) internal view returns (bool) {
bytes4 prefix = PartitionUtils._getPartitionPrefix(_partition);
if (!_isPartitionStrategy[prefix]) {
return false;
}
address strategyValidatorImplementation;
strategyValidatorImplementation = interfaceAddr(
address(this),
PartitionUtils._getPartitionStrategyValidatorIName(prefix)
);
if (strategyValidatorImplementation != address(0)) {
return
IAmpPartitionStrategyValidator(strategyValidatorImplementation)
.isOperatorForPartitionScope(_partition, _operator, _tokenHolder);
}
// Not a partition format that imbues special operator rules
return false;
}
/**************************************************************************/
/******************************** Minting *********************************/
/**
* @notice Perform the minting of tokens.
* @dev The tokens will be minted on behalf of the `_to` address, and will be
* minted to the address's default partition.
* @param _operator Address which triggered the issuance.
* @param _to Token recipient.
* @param _value Number of tokens issued.
*/
function _mint(
address _operator,
address _to,
uint256 _value
) internal {
require(_to != address(0), EC_57_INVALID_RECEIVER);
_totalSupply = _totalSupply.add(_value);
_addTokenToPartition(_to, defaultPartition, _value);
_callPostTransferHooks(
defaultPartition,
_operator,
address(0),
_to,
_value,
"",
""
);
emit Minted(_operator, _to, _value, "");
emit Transfer(address(0), _to, _value);
emit TransferByPartition(bytes32(0), _operator, address(0), _to, _value, "", "");
}
}File 2 of 4: FlexaCollateralManager
// SPDX-License-Identifier: MIT
pragma solidity 0.6.10;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
interface IAmp {
function registerCollateralManager() external;
}
/**
* @title Ownable is a contract the provides contract ownership functionality, including a two-
* phase transfer.
*/
contract Ownable {
address private _owner;
address private _authorizedNewOwner;
/**
* @notice Emitted when the owner authorizes ownership transfer to a new address
* @param authorizedAddress New owner address
*/
event OwnershipTransferAuthorization(address indexed authorizedAddress);
/**
* @notice Emitted when the authorized address assumed ownership
* @param oldValue Old owner
* @param newValue New owner
*/
event OwnerUpdate(address indexed oldValue, address indexed newValue);
/**
* @notice Sets the owner to the sender / contract creator
*/
constructor() internal {
_owner = msg.sender;
}
/**
* @notice Retrieves the owner of the contract
* @return The contract owner
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @notice Retrieves the authorized new owner of the contract
* @return The authorized new contract owner
*/
function authorizedNewOwner() public view returns (address) {
return _authorizedNewOwner;
}
/**
* @notice Authorizes the transfer of ownership from owner to the provided address.
* NOTE: No transfer will occur unless authorizedAddress calls assumeOwnership().
* This authorization may be removed by another call to this function authorizing the zero
* address.
* @param _authorizedAddress The address authorized to become the new owner
*/
function authorizeOwnershipTransfer(address _authorizedAddress) external {
require(msg.sender == _owner, "Invalid sender");
_authorizedNewOwner = _authorizedAddress;
emit OwnershipTransferAuthorization(_authorizedNewOwner);
}
/**
* @notice Transfers ownership of this contract to the _authorizedNewOwner
* @dev Error invalid sender.
*/
function assumeOwnership() external {
require(msg.sender == _authorizedNewOwner, "Invalid sender");
address oldValue = _owner;
_owner = _authorizedNewOwner;
_authorizedNewOwner = address(0);
emit OwnerUpdate(oldValue, _owner);
}
}
abstract contract ERC1820Registry {
function setInterfaceImplementer(
address _addr,
bytes32 _interfaceHash,
address _implementer
) external virtual;
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash)
external
virtual
view
returns (address);
function setManager(address _addr, address _newManager) external virtual;
function getManager(address _addr) public virtual view returns (address);
}
/// Base client to interact with the registry.
contract ERC1820Client {
ERC1820Registry constant ERC1820REGISTRY = ERC1820Registry(
0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
);
function setInterfaceImplementation(
string memory _interfaceLabel,
address _implementation
) internal {
bytes32 interfaceHash = keccak256(abi.encodePacked(_interfaceLabel));
ERC1820REGISTRY.setInterfaceImplementer(
address(this),
interfaceHash,
_implementation
);
}
function interfaceAddr(address addr, string memory _interfaceLabel)
internal
view
returns (address)
{
bytes32 interfaceHash = keccak256(abi.encodePacked(_interfaceLabel));
return ERC1820REGISTRY.getInterfaceImplementer(addr, interfaceHash);
}
function delegateManagement(address _newManager) internal {
ERC1820REGISTRY.setManager(address(this), _newManager);
}
}
/**
* @title IAmpTokensRecipient
* @dev IAmpTokensRecipient token transfer hook interface
*/
interface IAmpTokensRecipient {
/**
* @dev Report if the recipient will successfully receive the tokens
*/
function canReceive(
bytes4 functionSig,
bytes32 partition,
address operator,
address from,
address to,
uint256 value,
bytes calldata data,
bytes calldata operatorData
) external view returns (bool);
/**
* @dev Hook executed upon a transfer to the recipient
*/
function tokensReceived(
bytes4 functionSig,
bytes32 partition,
address operator,
address from,
address to,
uint256 value,
bytes calldata data,
bytes calldata operatorData
) external;
}
/**
* @title IAmpTokensSender
* @dev IAmpTokensSender token transfer hook interface
*/
interface IAmpTokensSender {
/**
* @dev Report if the transfer will succeed from the pespective of the
* token sender
*/
function canTransfer(
bytes4 functionSig,
bytes32 partition,
address operator,
address from,
address to,
uint256 value,
bytes calldata data,
bytes calldata operatorData
) external view returns (bool);
/**
* @dev Hook executed upon a transfer on behalf of the sender
*/
function tokensToTransfer(
bytes4 functionSig,
bytes32 partition,
address operator,
address from,
address to,
uint256 value,
bytes calldata data,
bytes calldata operatorData
) external;
}
/**
* @title PartitionUtils
* @notice Partition related helper functions.
*/
library PartitionUtils {
bytes32 public constant CHANGE_PARTITION_FLAG = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
/**
* @notice Retrieve the destination partition from the 'data' field.
* A partition change is requested ONLY when 'data' starts with the flag:
*
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
*
* When the flag is detected, the destination partition is extracted from the
* 32 bytes following the flag.
* @param _data Information attached to the transfer. Will contain the
* destination partition if a change is requested.
* @param _fallbackPartition Partition value to return if a partition change
* is not requested in the `_data`.
* @return toPartition Destination partition. If the `_data` does not contain
* the prefix and bytes32 partition in the first 64 bytes, the method will
* return the provided `_fromPartition`.
*/
function _getDestinationPartition(bytes memory _data, bytes32 _fallbackPartition)
internal
pure
returns (bytes32)
{
if (_data.length < 64) {
return _fallbackPartition;
}
(bytes32 flag, bytes32 toPartition) = abi.decode(_data, (bytes32, bytes32));
if (flag == CHANGE_PARTITION_FLAG) {
return toPartition;
}
return _fallbackPartition;
}
/**
* @notice Helper to get the strategy identifying prefix from the `_partition`.
* @param _partition Partition to get the prefix for.
* @return 4 byte partition strategy prefix.
*/
function _getPartitionPrefix(bytes32 _partition) internal pure returns (bytes4) {
return bytes4(_partition);
}
/**
* @notice Helper method to split the partition into the prefix, sub partition
* and partition owner components.
* @param _partition The partition to split into parts.
* @return The 4 byte partition prefix, 8 byte sub partition, and final 20
* bytes representing an address.
*/
function _splitPartition(bytes32 _partition)
internal
pure
returns (
bytes4,
bytes8,
address
)
{
bytes4 prefix = bytes4(_partition);
bytes8 subPartition = bytes8(_partition << 32);
address addressPart = address(uint160(uint256(_partition)));
return (prefix, subPartition, addressPart);
}
/**
* @notice Helper method to get a partition strategy ERC1820 interface name
* based on partition prefix.
* @param _prefix 4 byte partition prefix.
* @dev Each 4 byte prefix has a unique interface name so that an individual
* hook implementation can be set for each prefix.
*/
function _getPartitionStrategyValidatorIName(bytes4 _prefix)
internal
pure
returns (string memory)
{
return string(abi.encodePacked("AmpPartitionStrategyValidator", _prefix));
}
}
/**
* @title FlexaCollateralManager is an implementation of IAmpTokensSender and IAmpTokensRecipient
* which serves as the Amp collateral manager for the Flexa Network.
*/
contract FlexaCollateralManager is Ownable, IAmpTokensSender, IAmpTokensRecipient, ERC1820Client {
/**
* @dev AmpTokensSender interface label.
*/
string internal constant AMP_TOKENS_SENDER = "AmpTokensSender";
/**
* @dev AmpTokensRecipient interface label.
*/
string internal constant AMP_TOKENS_RECIPIENT = "AmpTokensRecipient";
/**
* @dev Change Partition Flag used in transfer data parameters to signal which partition
* will receive the tokens.
*/
bytes32
internal constant CHANGE_PARTITION_FLAG = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
/**
* @dev Required prefix for all registered partitions. Used to ensure the Collateral Pool
* Partition Validator is used within Amp.
*/
bytes4 internal constant PARTITION_PREFIX = 0xCCCCCCCC;
/**********************************************************************************************
* Operator Data Flags
*********************************************************************************************/
/**
* @dev Flag used in operator data parameters to indicate the transfer is a withdrawal
*/
bytes32
internal constant WITHDRAWAL_FLAG = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;
/**
* @dev Flag used in operator data parameters to indicate the transfer is a fallback
* withdrawal
*/
bytes32
internal constant FALLBACK_WITHDRAWAL_FLAG = 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb;
/**
* @dev Flag used in operator data parameters to indicate the transfer is a supply refund
*/
bytes32
internal constant REFUND_FLAG = 0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc;
/**
* @dev Flag used in operator data parameters to indicate the transfer is a direct transfer
*/
bytes32
internal constant DIRECT_TRANSFER_FLAG = 0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd;
/**********************************************************************************************
* Configuration
*********************************************************************************************/
/**
* @notice Address of the Amp contract. Immutable.
*/
address public amp;
/**
* @notice Permitted partitions
*/
mapping(bytes32 => bool) public partitions;
/**********************************************************************************************
* Roles
*********************************************************************************************/
/**
* @notice Address authorized to publish withdrawal roots
*/
address public withdrawalPublisher;
/**
* @notice Address authorized to publish fallback withdrawal roots
*/
address public fallbackPublisher;
/**
* @notice Address authorized to adjust the withdrawal limit
*/
address public withdrawalLimitPublisher;
/**
* @notice Address authorized to directly transfer tokens
*/
address public directTransferer;
/**
* @notice Address authorized to manage permitted partition
*/
address public partitionManager;
/**
* @notice Struct used to record received tokens that can be recovered during the fallback
* withdrawal period
* @param supplier Token supplier
* @param partition Partition which received the tokens
* @param amount Number of tokens received
*/
struct Supply {
address supplier;
bytes32 partition;
uint256 amount;
}
/**********************************************************************************************
* Supply State
*********************************************************************************************/
/**
* @notice Supply nonce used to track incoming token transfers
*/
uint256 public supplyNonce = 0;
/**
* @notice Mapping of all incoming token transfers
*/
mapping(uint256 => Supply) public nonceToSupply;
/**********************************************************************************************
* Withdrawal State
*********************************************************************************************/
/**
* @notice Remaining withdrawal limit. Initially set to 100,000 Amp.
*/
uint256 public withdrawalLimit = 100 * 1000 * (10**18);
/**
* @notice Withdrawal maximum root nonce
*/
uint256 public maxWithdrawalRootNonce = 0;
/**
* @notice Active set of withdrawal roots
*/
mapping(bytes32 => uint256) public withdrawalRootToNonce;
/**
* @notice Last invoked withdrawal root for each account, per partition
*/
mapping(bytes32 => mapping(address => uint256)) public addressToWithdrawalNonce;
/**
* @notice Total amount withdrawn for each account, per partition
*/
mapping(bytes32 => mapping(address => uint256)) public addressToCumulativeAmountWithdrawn;
/**********************************************************************************************
* Fallback Withdrawal State
*********************************************************************************************/
/**
* @notice Withdrawal fallback delay. Initially set to one week.
*/
uint256 public fallbackWithdrawalDelaySeconds = 1 weeks;
/**
* @notice Current fallback withdrawal root
*/
bytes32 public fallbackRoot;
/**
* @notice Timestamp of when the last fallback root was published
*/
uint256 public fallbackSetDate = 2**200; // very far in the future
/**
* @notice Latest supply reflected in the fallback withdrawal authorization tree
*/
uint256 public fallbackMaxIncludedSupplyNonce = 0;
/**********************************************************************************************
* Supplier Events
*********************************************************************************************/
/**
* @notice Indicates a token supply has been received
* @param supplier Token supplier
* @param amount Number of tokens transferred
* @param nonce Nonce of the supply
*/
event SupplyReceipt(
address indexed supplier,
bytes32 indexed partition,
uint256 amount,
uint256 indexed nonce
);
/**
* @notice Indicates that a withdrawal was executed
* @param supplier Address whose withdrawal authorization was executed
* @param partition Partition from which the tokens were transferred
* @param amount Amount of tokens transferred
* @param rootNonce Nonce of the withdrawal root used for authorization
* @param authorizedAccountNonce Maximum previous nonce used by the account
*/
event Withdrawal(
address indexed supplier,
bytes32 indexed partition,
uint256 amount,
uint256 indexed rootNonce,
uint256 authorizedAccountNonce
);
/**
* @notice Indicates a fallback withdrawal was executed
* @param supplier Address whose fallback withdrawal authorization was executed
* @param partition Partition from which the tokens were transferred
* @param amount Amount of tokens transferred
*/
event FallbackWithdrawal(
address indexed supplier,
bytes32 indexed partition,
uint256 indexed amount
);
/**
* @notice Indicates a release of supply is requested
* @param supplier Token supplier
* @param partition Parition from which the tokens should be released
* @param amount Number of tokens requested to be released
* @param data Metadata provided by the requestor
*/
event ReleaseRequest(
address indexed supplier,
bytes32 indexed partition,
uint256 indexed amount,
bytes data
);
/**
* @notice Indicates a supply refund was executed
* @param supplier Address whose refund authorization was executed
* @param partition Partition from which the tokens were transferred
* @param amount Amount of tokens transferred
* @param nonce Nonce of the original supply
*/
event SupplyRefund(
address indexed supplier,
bytes32 indexed partition,
uint256 amount,
uint256 indexed nonce
);
/**********************************************************************************************
* Direct Transfer Events
*********************************************************************************************/
/**
* @notice Emitted when tokens are directly transfered
* @param operator Address that executed the direct transfer
* @param from_partition Partition from which the tokens were transferred
* @param to_address Address to which the tokens were transferred
* @param to_partition Partition to which the tokens were transferred
* @param value Amount of tokens transferred
*/
event DirectTransfer(
address operator,
bytes32 indexed from_partition,
address indexed to_address,
bytes32 indexed to_partition,
uint256 value
);
/**********************************************************************************************
* Admin Configuration Events
*********************************************************************************************/
/**
* @notice Emitted when a partition is permitted for supply
* @param partition Partition added to the permitted set
*/
event PartitionAdded(bytes32 indexed partition);
/**
* @notice Emitted when a partition is removed from the set permitted for supply
* @param partition Partition removed from the permitted set
*/
event PartitionRemoved(bytes32 indexed partition);
/**********************************************************************************************
* Admin Withdrawal Management Events
*********************************************************************************************/
/**
* @notice Emitted when a new withdrawal root hash is added to the active set
* @param rootHash Merkle root hash.
* @param nonce Nonce of the Merkle root hash.
*/
event WithdrawalRootHashAddition(bytes32 indexed rootHash, uint256 indexed nonce);
/**
* @notice Emitted when a withdrawal root hash is removed from the active set
* @param rootHash Merkle root hash.
* @param nonce Nonce of the Merkle root hash.
*/
event WithdrawalRootHashRemoval(bytes32 indexed rootHash, uint256 indexed nonce);
/**
* @notice Emitted when the withdrawal limit is updated
* @param oldValue Old limit.
* @param newValue New limit.
*/
event WithdrawalLimitUpdate(uint256 indexed oldValue, uint256 indexed newValue);
/**********************************************************************************************
* Admin Fallback Management Events
*********************************************************************************************/
/**
* @notice Emitted when a new fallback withdrawal root hash is set
* @param rootHash Merkle root hash
* @param maxSupplyNonceIncluded Nonce of the last supply reflected in the tree data
* @param setDate Timestamp of when the root hash was set
*/
event FallbackRootHashSet(
bytes32 indexed rootHash,
uint256 indexed maxSupplyNonceIncluded,
uint256 setDate
);
/**
* @notice Emitted when the fallback root hash set date is reset
* @param newDate Timestamp of when the fallback reset date was set
*/
event FallbackMechanismDateReset(uint256 indexed newDate);
/**
* @notice Emitted when the fallback delay is updated
* @param oldValue Old delay
* @param newValue New delay
*/
event FallbackWithdrawalDelayUpdate(uint256 indexed oldValue, uint256 indexed newValue);
/**********************************************************************************************
* Role Management Events
*********************************************************************************************/
/**
* @notice Emitted when the Withdrawal Publisher is updated
* @param oldValue Old publisher
* @param newValue New publisher
*/
event WithdrawalPublisherUpdate(address indexed oldValue, address indexed newValue);
/**
* @notice Emitted when the Fallback Publisher is updated
* @param oldValue Old publisher
* @param newValue New publisher
*/
event FallbackPublisherUpdate(address indexed oldValue, address indexed newValue);
/**
* @notice Emitted when Withdrawal Limit Publisher is updated
* @param oldValue Old publisher
* @param newValue New publisher
*/
event WithdrawalLimitPublisherUpdate(address indexed oldValue, address indexed newValue);
/**
* @notice Emitted when the DirectTransferer address is updated
* @param oldValue Old DirectTransferer address
* @param newValue New DirectTransferer address
*/
event DirectTransfererUpdate(address indexed oldValue, address indexed newValue);
/**
* @notice Emitted when the Partition Manager address is updated
* @param oldValue Old Partition Manager address
* @param newValue New Partition Manager address
*/
event PartitionManagerUpdate(address indexed oldValue, address indexed newValue);
/**********************************************************************************************
* Constructor
*********************************************************************************************/
/**
* @notice FlexaCollateralManager constructor
* @param _amp Address of the Amp token contract
*/
constructor(address _amp) public {
amp = _amp;
ERC1820Client.setInterfaceImplementation(AMP_TOKENS_RECIPIENT, address(this));
ERC1820Client.setInterfaceImplementation(AMP_TOKENS_SENDER, address(this));
IAmp(amp).registerCollateralManager();
}
/**********************************************************************************************
* IAmpTokensRecipient Hooks
*********************************************************************************************/
/**
* @notice Validates where the supplied parameters are valid for a transfer of tokens to this
* contract
* @dev Implements IAmpTokensRecipient
* @param _partition Partition from which the tokens were transferred
* @param _to The destination address of the tokens. Must be this.
* @param _data Optional data sent with the transfer. Used to set the destination partition.
* @return true if the tokens can be received, otherwise false
*/
function canReceive(
bytes4, /* functionSig */
bytes32 _partition,
address, /* operator */
address, /* from */
address _to,
uint256, /* value */
bytes calldata _data,
bytes calldata /* operatorData */
) external override view returns (bool) {
if (msg.sender != amp || _to != address(this)) {
return false;
}
bytes32 _destinationPartition = PartitionUtils._getDestinationPartition(_data, _partition);
return partitions[_destinationPartition];
}
/**
* @notice Function called by the token contract after executing a transfer.
* @dev Implements IAmpTokensRecipient
* @param _partition Partition from which the tokens were transferred
* @param _operator Address which triggered the transfer. This address will be credited with
* the supply.
* @param _to The destination address of the tokens. Must be this.
* @param _value Number of tokens the token holder balance is decreased by.
* @param _data Optional data sent with the transfer. Used to set the destination partition.
*/
function tokensReceived(
bytes4, /* functionSig */
bytes32 _partition,
address _operator,
address, /* from */
address _to,
uint256 _value,
bytes calldata _data,
bytes calldata /* operatorData */
) external override {
require(msg.sender == amp, "Invalid sender");
require(_to == address(this), "Invalid to address");
bytes32 _destinationPartition = PartitionUtils._getDestinationPartition(_data, _partition);
require(partitions[_destinationPartition], "Invalid destination partition");
supplyNonce = SafeMath.add(supplyNonce, 1);
nonceToSupply[supplyNonce].supplier = _operator;
nonceToSupply[supplyNonce].partition = _destinationPartition;
nonceToSupply[supplyNonce].amount = _value;
emit SupplyReceipt(_operator, _destinationPartition, _value, supplyNonce);
}
/**********************************************************************************************
* IAmpTokensSender Hooks
*********************************************************************************************/
/**
* @notice Validates where the supplied parameters are valid for a transfer of tokens from this
* contract
* @dev Implements IAmpTokensSender
* @param _partition Source partition of the tokens
* @param _operator Address which triggered the transfer
* @param _from The source address of the tokens. Must be this.
* @param _value Amount of tokens to be transferred
* @param _operatorData Extra information attached by the operator. Must include the transfer
* operation flag and additional authorization data custom for each transfer operation type.
* @return true if the token transfer would succeed, otherwise false
*/
function canTransfer(
bytes4, /*functionSig*/
bytes32 _partition,
address _operator,
address _from,
address, /* to */
uint256 _value,
bytes calldata, /* data */
bytes calldata _operatorData
) external override view returns (bool) {
if (msg.sender != amp || _from != address(this)) {
return false;
}
bytes32 flag = _decodeOperatorDataFlag(_operatorData);
if (flag == WITHDRAWAL_FLAG) {
return _validateWithdrawal(_partition, _operator, _value, _operatorData);
}
if (flag == FALLBACK_WITHDRAWAL_FLAG) {
return _validateFallbackWithdrawal(_partition, _operator, _value, _operatorData);
}
if (flag == REFUND_FLAG) {
return _validateRefund(_partition, _operator, _value, _operatorData);
}
if (flag == DIRECT_TRANSFER_FLAG) {
return _validateDirectTransfer(_operator, _value);
}
return false;
}
/**
* @notice Function called by the token contract when executing a transfer
* @dev Implements IAmpTokensSender
* @param _partition Source partition of the tokens
* @param _operator Address which triggered the transfer
* @param _from The source address of the tokens. Must be this.
* @param _to The target address of the tokens.
* @param _value Amount of tokens to be transferred
* @param _data Data attached to the transfer. Typically includes partition change information.
* @param _operatorData Extra information attached by the operator. Must include the transfer
* operation flag and additional authorization data custom for each transfer operation type.
*/
function tokensToTransfer(
bytes4, /* functionSig */
bytes32 _partition,
address _operator,
address _from,
address _to,
uint256 _value,
bytes calldata _data,
bytes calldata _operatorData
) external override {
require(msg.sender == amp, "Invalid sender");
require(_from == address(this), "Invalid from address");
bytes32 flag = _decodeOperatorDataFlag(_operatorData);
if (flag == WITHDRAWAL_FLAG) {
_executeWithdrawal(_partition, _operator, _value, _operatorData);
} else if (flag == FALLBACK_WITHDRAWAL_FLAG) {
_executeFallbackWithdrawal(_partition, _operator, _value, _operatorData);
} else if (flag == REFUND_FLAG) {
_executeRefund(_partition, _operator, _value, _operatorData);
} else if (flag == DIRECT_TRANSFER_FLAG) {
_executeDirectTransfer(_partition, _operator, _to, _value, _data);
} else {
revert("invalid flag");
}
}
/**********************************************************************************************
* Withdrawals
*********************************************************************************************/
/**
* @notice Validates withdrawal data
* @param _partition Source partition of the withdrawal
* @param _operator Address that is invoking the transfer
* @param _value Number of tokens to be transferred
* @param _operatorData Contains the withdrawal authorization data
* @return true if the withdrawal data is valid, otherwise false
*/
function _validateWithdrawal(
bytes32 _partition,
address _operator,
uint256 _value,
bytes memory _operatorData
) internal view returns (bool) {
(
address supplier,
uint256 maxAuthorizedAccountNonce,
uint256 withdrawalRootNonce
) = _getWithdrawalData(_partition, _value, _operatorData);
return
_validateWithdrawalData(
_partition,
_operator,
_value,
supplier,
maxAuthorizedAccountNonce,
withdrawalRootNonce
);
}
/**
* @notice Validates the withdrawal data and updates state to reflect the transfer
* @param _partition Source partition of the withdrawal
* @param _operator Address that is invoking the transfer
* @param _value Number of tokens to be transferred
* @param _operatorData Contains the withdrawal authorization data
*/
function _executeWithdrawal(
bytes32 _partition,
address _operator,
uint256 _value,
bytes memory _operatorData
) internal {
(
address supplier,
uint256 maxAuthorizedAccountNonce,
uint256 withdrawalRootNonce
) = _getWithdrawalData(_partition, _value, _operatorData);
require(
_validateWithdrawalData(
_partition,
_operator,
_value,
supplier,
maxAuthorizedAccountNonce,
withdrawalRootNonce
),
"Transfer unauthorized"
);
addressToCumulativeAmountWithdrawn[_partition][supplier] = SafeMath.add(
_value,
addressToCumulativeAmountWithdrawn[_partition][supplier]
);
addressToWithdrawalNonce[_partition][supplier] = withdrawalRootNonce;
withdrawalLimit = SafeMath.sub(withdrawalLimit, _value);
emit Withdrawal(
supplier,
_partition,
_value,
withdrawalRootNonce,
maxAuthorizedAccountNonce
);
}
/**
* @notice Extracts withdrawal data from the supplied parameters
* @param _partition Source partition of the withdrawal
* @param _value Number of tokens to be transferred
* @param _operatorData Contains the withdrawal authorization data, including the withdrawal
* operation flag, supplier, maximum authorized account nonce, and Merkle proof.
* @return supplier, the address whose account is authorized
* @return maxAuthorizedAccountNonce, the maximum existing used withdrawal nonce for the
* supplier and partition
* @return withdrawalRootNonce, the active withdrawal root nonce found based on the supplied
* data and Merkle proof
*/
function _getWithdrawalData(
bytes32 _partition,
uint256 _value,
bytes memory _operatorData
)
internal
view
returns (
address, /* supplier */
uint256, /* maxAuthorizedAccountNonce */
uint256 /* withdrawalRootNonce */
)
{
(
address supplier,
uint256 maxAuthorizedAccountNonce,
bytes32[] memory merkleProof
) = _decodeWithdrawalOperatorData(_operatorData);
bytes32 leafDataHash = _calculateWithdrawalLeaf(
supplier,
_partition,
_value,
maxAuthorizedAccountNonce
);
bytes32 calculatedRoot = _calculateMerkleRoot(merkleProof, leafDataHash);
uint256 withdrawalRootNonce = withdrawalRootToNonce[calculatedRoot];
return (supplier, maxAuthorizedAccountNonce, withdrawalRootNonce);
}
/**
* @notice Validates that the parameters are valid for the requested withdrawal
* @param _partition Source partition of the tokens
* @param _operator Address that is executing the withdrawal
* @param _value Number of tokens to be transferred
* @param _supplier The address whose account is authorized
* @param _maxAuthorizedAccountNonce The maximum existing used withdrawal nonce for the
* supplier and partition
* @param _withdrawalRootNonce The active withdrawal root nonce found based on the supplied
* data and Merkle proof
* @return true if the withdrawal data is valid, otherwise false
*/
function _validateWithdrawalData(
bytes32 _partition,
address _operator,
uint256 _value,
address _supplier,
uint256 _maxAuthorizedAccountNonce,
uint256 _withdrawalRootNonce
) internal view returns (bool) {
return
// Only owner, withdrawal publisher or supplier can invoke withdrawals
(_operator == owner() || _operator == withdrawalPublisher || _operator == _supplier) &&
// Ensure maxAuthorizedAccountNonce has not been exceeded
(addressToWithdrawalNonce[_partition][_supplier] <= _maxAuthorizedAccountNonce) &&
// Ensure we are within the global withdrawal limit
(_value <= withdrawalLimit) &&
// Merkle tree proof is valid
(_withdrawalRootNonce > 0) &&
// Ensure the withdrawal root is more recent than the maxAuthorizedAccountNonce
(_withdrawalRootNonce > _maxAuthorizedAccountNonce);
}
/**********************************************************************************************
* Fallback Withdrawals
*********************************************************************************************/
/**
* @notice Validates fallback withdrawal data
* @param _partition Source partition of the withdrawal
* @param _operator Address that is invoking the transfer
* @param _value Number of tokens to be transferred
* @param _operatorData Contains the fallback withdrawal authorization data
* @return true if the fallback withdrawal data is valid, otherwise false
*/
function _validateFallbackWithdrawal(
bytes32 _partition,
address _operator,
uint256 _value,
bytes memory _operatorData
) internal view returns (bool) {
(
address supplier,
uint256 maxCumulativeWithdrawalAmount,
uint256 newCumulativeWithdrawalAmount,
bytes32 calculatedRoot
) = _getFallbackWithdrawalData(_partition, _value, _operatorData);
return
_validateFallbackWithdrawalData(
_operator,
maxCumulativeWithdrawalAmount,
newCumulativeWithdrawalAmount,
supplier,
calculatedRoot
);
}
/**
* @notice Validates the fallback withdrawal data and updates state to reflect the transfer
* @param _partition Source partition of the withdrawal
* @param _operator Address that is invoking the transfer
* @param _value Number of tokens to be transferred
* @param _operatorData Contains the fallback withdrawal authorization data
*/
function _executeFallbackWithdrawal(
bytes32 _partition,
address _operator,
uint256 _value,
bytes memory _operatorData
) internal {
(
address supplier,
uint256 maxCumulativeWithdrawalAmount,
uint256 newCumulativeWithdrawalAmount,
bytes32 calculatedRoot
) = _getFallbackWithdrawalData(_partition, _value, _operatorData);
require(
_validateFallbackWithdrawalData(
_operator,
maxCumulativeWithdrawalAmount,
newCumulativeWithdrawalAmount,
supplier,
calculatedRoot
),
"Transfer unauthorized"
);
addressToCumulativeAmountWithdrawn[_partition][supplier] = newCumulativeWithdrawalAmount;
addressToWithdrawalNonce[_partition][supplier] = maxWithdrawalRootNonce;
emit FallbackWithdrawal(supplier, _partition, _value);
}
/**
* @notice Extracts withdrawal data from the supplied parameters
* @param _partition Source partition of the withdrawal
* @param _value Number of tokens to be transferred
* @param _operatorData Contains the fallback withdrawal authorization data, including the
* fallback withdrawal operation flag, supplier, max cumulative withdrawal amount, and Merkle
* proof.
* @return supplier, the address whose account is authorized
* @return maxCumulativeWithdrawalAmount, the maximum amount of tokens that can be withdrawn
* for the supplier's account, including both withdrawals and fallback withdrawals
* @return newCumulativeWithdrawalAmount, the new total of all withdrawals include the
* current request
* @return calculatedRoot, the Merkle tree root calculated based on the supplied data and proof
*/
function _getFallbackWithdrawalData(
bytes32 _partition,
uint256 _value,
bytes memory _operatorData
)
internal
view
returns (
address, /* supplier */
uint256, /* maxCumulativeWithdrawalAmount */
uint256, /* newCumulativeWithdrawalAmount */
bytes32 /* calculatedRoot */
)
{
(
address supplier,
uint256 maxCumulativeWithdrawalAmount,
bytes32[] memory merkleProof
) = _decodeWithdrawalOperatorData(_operatorData);
uint256 newCumulativeWithdrawalAmount = SafeMath.add(
_value,
addressToCumulativeAmountWithdrawn[_partition][supplier]
);
bytes32 leafDataHash = _calculateFallbackLeaf(
supplier,
_partition,
maxCumulativeWithdrawalAmount
);
bytes32 calculatedRoot = _calculateMerkleRoot(merkleProof, leafDataHash);
return (
supplier,
maxCumulativeWithdrawalAmount,
newCumulativeWithdrawalAmount,
calculatedRoot
);
}
/**
* @notice Validates that the parameters are valid for the requested fallback withdrawal
* @param _operator Address that is executing the withdrawal
* @param _maxCumulativeWithdrawalAmount, the maximum amount of tokens that can be withdrawn
* for the supplier's account, including both withdrawals and fallback withdrawals
* @param _newCumulativeWithdrawalAmount, the new total of all withdrawals include the
* current request
* @param _supplier The address whose account is authorized
* @param _calculatedRoot The Merkle tree root calculated based on the supplied data and proof
* @return true if the fallback withdrawal data is valid, otherwise false
*/
function _validateFallbackWithdrawalData(
address _operator,
uint256 _maxCumulativeWithdrawalAmount,
uint256 _newCumulativeWithdrawalAmount,
address _supplier,
bytes32 _calculatedRoot
) internal view returns (bool) {
return
// Only owner or supplier can invoke the fallback withdrawal
(_operator == owner() || _operator == _supplier) &&
// Ensure we have entered fallback mode
(SafeMath.add(fallbackSetDate, fallbackWithdrawalDelaySeconds) <= block.timestamp) &&
// Check that the maximum allowable withdrawal for the supplier has not been exceeded
(_newCumulativeWithdrawalAmount <= _maxCumulativeWithdrawalAmount) &&
// Merkle tree proof is valid
(fallbackRoot == _calculatedRoot);
}
/**********************************************************************************************
* Supply Refunds
*********************************************************************************************/
/**
* @notice Validates refund data
* @param _partition Source partition of the refund
* @param _operator Address that is invoking the transfer
* @param _value Number of tokens to be transferred
* @param _operatorData Contains the refund authorization data
* @return true if the refund data is valid, otherwise false
*/
function _validateRefund(
bytes32 _partition,
address _operator,
uint256 _value,
bytes memory _operatorData
) internal view returns (bool) {
(uint256 _supplyNonce, Supply memory supply) = _getRefundData(_operatorData);
return _verifyRefundData(_partition, _operator, _value, _supplyNonce, supply);
}
/**
* @notice Validates the refund data and updates state to reflect the transfer
* @param _partition Source partition of the refund
* @param _operator Address that is invoking the transfer
* @param _value Number of tokens to be transferred
* @param _operatorData Contains the refund authorization data
*/
function _executeRefund(
bytes32 _partition,
address _operator,
uint256 _value,
bytes memory _operatorData
) internal {
(uint256 nonce, Supply memory supply) = _getRefundData(_operatorData);
require(
_verifyRefundData(_partition, _operator, _value, nonce, supply),
"Transfer unauthorized"
);
delete nonceToSupply[nonce];
emit SupplyRefund(supply.supplier, _partition, supply.amount, nonce);
}
/**
* @notice Extracts refund data from the supplied parameters
* @param _operatorData Contains the refund authorization data, including the refund
* operation flag and supply nonce.
* @return supplyNonce, nonce of the recorded supply
* @return supply, The supplier, partition and amount of tokens in the original supply
*/
function _getRefundData(bytes memory _operatorData)
internal
view
returns (uint256, Supply memory)
{
uint256 _supplyNonce = _decodeRefundOperatorData(_operatorData);
Supply memory supply = nonceToSupply[_supplyNonce];
return (_supplyNonce, supply);
}
/**
* @notice Validates that the parameters are valid for the requested refund
* @param _partition Source partition of the tokens
* @param _operator Address that is executing the refund
* @param _value Number of tokens to be transferred
* @param _supplyNonce nonce of the recorded supply
* @param _supply The supplier, partition and amount of tokens in the original supply
* @return true if the refund data is valid, otherwise false
*/
function _verifyRefundData(
bytes32 _partition,
address _operator,
uint256 _value,
uint256 _supplyNonce,
Supply memory _supply
) internal view returns (bool) {
return
// Supply record exists
(_supply.amount > 0) &&
// Only owner or supplier can invoke the refund
(_operator == owner() || _operator == _supply.supplier) &&
// Requested partition matches the Supply record
(_partition == _supply.partition) &&
// Requested value matches the Supply record
(_value == _supply.amount) &&
// Ensure we have entered fallback mode
(SafeMath.add(fallbackSetDate, fallbackWithdrawalDelaySeconds) <= block.timestamp) &&
// Supply has not already been included in the fallback withdrawal data
(_supplyNonce > fallbackMaxIncludedSupplyNonce);
}
/**********************************************************************************************
* Direct Transfers
*********************************************************************************************/
/**
* @notice Validates direct transfer data
* @param _operator Address that is invoking the transfer
* @param _value Number of tokens to be transferred
* @return true if the direct transfer data is valid, otherwise false
*/
function _validateDirectTransfer(address _operator, uint256 _value)
internal
view
returns (bool)
{
return
// Only owner and directTransferer can invoke withdrawals
(_operator == owner() || _operator == directTransferer) &&
// Ensure we are within the global withdrawal limit
(_value <= withdrawalLimit);
}
/**
* @notice Validates the direct transfer data and updates state to reflect the transfer
* @param _partition Source partition of the direct transfer
* @param _operator Address that is invoking the transfer
* @param _to The target address of the tokens.
* @param _value Number of tokens to be transferred
* @param _data Data attached to the transfer. Typically includes partition change information.
*/
function _executeDirectTransfer(
bytes32 _partition,
address _operator,
address _to,
uint256 _value,
bytes memory _data
) internal {
require(_validateDirectTransfer(_operator, _value), "Transfer unauthorized");
withdrawalLimit = SafeMath.sub(withdrawalLimit, _value);
bytes32 to_partition = PartitionUtils._getDestinationPartition(_data, _partition);
emit DirectTransfer(_operator, _partition, _to, to_partition, _value);
}
/**********************************************************************************************
* Release Request
*********************************************************************************************/
/**
* @notice Emits a release request event that can be used to trigger the release of tokens
* @param _partition Parition from which the tokens should be released
* @param _amount Number of tokens requested to be released
* @param _data Metadata to include with the release request
*/
function requestRelease(
bytes32 _partition,
uint256 _amount,
bytes memory _data
) external {
emit ReleaseRequest(msg.sender, _partition, _amount, _data);
}
/**********************************************************************************************
* Partition Management
*********************************************************************************************/
/**
* @notice Adds a partition to the set allowed to receive tokens
* @param _partition Parition to be permitted for incoming transfers
*/
function addPartition(bytes32 _partition) external {
require(msg.sender == owner() || msg.sender == partitionManager, "Invalid sender");
require(partitions[_partition] == false, "Partition already permitted");
(bytes4 prefix, , address partitionOwner) = PartitionUtils._splitPartition(_partition);
require(prefix == PARTITION_PREFIX, "Invalid partition prefix");
require(partitionOwner == address(this), "Invalid partition owner");
partitions[_partition] = true;
emit PartitionAdded(_partition);
}
/**
* @notice Removes a partition from the set allowed to receive tokens
* @param _partition Parition to be disallowed from incoming transfers
*/
function removePartition(bytes32 _partition) external {
require(msg.sender == owner() || msg.sender == partitionManager, "Invalid sender");
require(partitions[_partition], "Partition not permitted");
delete partitions[_partition];
emit PartitionRemoved(_partition);
}
/**********************************************************************************************
* Withdrawal Management
*********************************************************************************************/
/**
* @notice Modifies the withdrawal limit by the provided amount.
* @param _amount Limit delta
*/
function modifyWithdrawalLimit(int256 _amount) external {
require(msg.sender == owner() || msg.sender == withdrawalLimitPublisher, "Invalid sender");
uint256 oldLimit = withdrawalLimit;
if (_amount < 0) {
uint256 unsignedAmount = uint256(-_amount);
withdrawalLimit = SafeMath.sub(withdrawalLimit, unsignedAmount);
} else {
uint256 unsignedAmount = uint256(_amount);
withdrawalLimit = SafeMath.add(withdrawalLimit, unsignedAmount);
}
emit WithdrawalLimitUpdate(oldLimit, withdrawalLimit);
}
/**
* @notice Adds the root hash of a Merkle tree containing authorized token withdrawals to the
* active set
* @param _root The root hash to be added to the active set
* @param _nonce The nonce of the new root hash. Must be exactly one higher than the existing
* max nonce.
* @param _replacedRoots The root hashes to be removed from the repository.
*/
function addWithdrawalRoot(
bytes32 _root,
uint256 _nonce,
bytes32[] calldata _replacedRoots
) external {
require(msg.sender == owner() || msg.sender == withdrawalPublisher, "Invalid sender");
require(_root != 0, "Invalid root");
require(maxWithdrawalRootNonce + 1 == _nonce, "Nonce not current max plus one");
require(withdrawalRootToNonce[_root] == 0, "Nonce already used");
withdrawalRootToNonce[_root] = _nonce;
maxWithdrawalRootNonce = _nonce;
emit WithdrawalRootHashAddition(_root, _nonce);
for (uint256 i = 0; i < _replacedRoots.length; i++) {
deleteWithdrawalRoot(_replacedRoots[i]);
}
}
/**
* @notice Removes withdrawal root hashes from active set
* @param _roots The root hashes to be removed from the active set
*/
function removeWithdrawalRoots(bytes32[] calldata _roots) external {
require(msg.sender == owner() || msg.sender == withdrawalPublisher, "Invalid sender");
for (uint256 i = 0; i < _roots.length; i++) {
deleteWithdrawalRoot(_roots[i]);
}
}
/**
* @notice Removes a withdrawal root hash from active set
* @param _root The root hash to be removed from the active set
*/
function deleteWithdrawalRoot(bytes32 _root) private {
uint256 nonce = withdrawalRootToNonce[_root];
require(nonce > 0, "Root not found");
delete withdrawalRootToNonce[_root];
emit WithdrawalRootHashRemoval(_root, nonce);
}
/**********************************************************************************************
* Fallback Management
*********************************************************************************************/
/**
* @notice Sets the root hash of the Merkle tree containing fallback
* withdrawal authorizations.
* @param _root The root hash of a Merkle tree containing the fallback withdrawal
* authorizations
* @param _maxSupplyNonce The nonce of the latest supply whose value is reflected in the
* fallback withdrawal authorizations.
*/
function setFallbackRoot(bytes32 _root, uint256 _maxSupplyNonce) external {
require(msg.sender == owner() || msg.sender == fallbackPublisher, "Invalid sender");
require(_root != 0, "Invalid root");
require(
SafeMath.add(fallbackSetDate, fallbackWithdrawalDelaySeconds) > block.timestamp,
"Fallback is active"
);
require(
_maxSupplyNonce >= fallbackMaxIncludedSupplyNonce,
"Included supply nonce decreased"
);
require(_maxSupplyNonce <= supplyNonce, "Included supply nonce exceeds latest supply");
fallbackRoot = _root;
fallbackMaxIncludedSupplyNonce = _maxSupplyNonce;
fallbackSetDate = block.timestamp;
emit FallbackRootHashSet(_root, fallbackMaxIncludedSupplyNonce, block.timestamp);
}
/**
* @notice Resets the fallback set date to the current block's timestamp. This can be used to
* delay the start of the fallback period without publishing a new root, or to deactivate the
* fallback mechanism so a new fallback root may be published.
*/
function resetFallbackMechanismDate() external {
require(msg.sender == owner() || msg.sender == fallbackPublisher, "Invalid sender");
fallbackSetDate = block.timestamp;
emit FallbackMechanismDateReset(fallbackSetDate);
}
/**
* @notice Updates the time-lock period before the fallback mechanism is activated after the
* last fallback root was published.
* @param _newFallbackDelaySeconds The new delay period in seconds
*/
function setFallbackWithdrawalDelay(uint256 _newFallbackDelaySeconds) external {
require(msg.sender == owner(), "Invalid sender");
require(_newFallbackDelaySeconds != 0, "Invalid zero delay seconds");
require(_newFallbackDelaySeconds < 10 * 365 days, "Invalid delay over 10 years");
uint256 oldDelay = fallbackWithdrawalDelaySeconds;
fallbackWithdrawalDelaySeconds = _newFallbackDelaySeconds;
emit FallbackWithdrawalDelayUpdate(oldDelay, _newFallbackDelaySeconds);
}
/**********************************************************************************************
* Role Management
*********************************************************************************************/
/**
* @notice Updates the Withdrawal Publisher address, the only address other than the owner that
* can publish / remove withdrawal Merkle tree roots.
* @param _newWithdrawalPublisher The address of the new Withdrawal Publisher
* @dev Error invalid sender.
*/
function setWithdrawalPublisher(address _newWithdrawalPublisher) external {
require(msg.sender == owner(), "Invalid sender");
address oldValue = withdrawalPublisher;
withdrawalPublisher = _newWithdrawalPublisher;
emit WithdrawalPublisherUpdate(oldValue, withdrawalPublisher);
}
/**
* @notice Updates the Fallback Publisher address, the only address other than the owner that
* can publish / remove fallback withdrawal Merkle tree roots.
* @param _newFallbackPublisher The address of the new Fallback Publisher
* @dev Error invalid sender.
*/
function setFallbackPublisher(address _newFallbackPublisher) external {
require(msg.sender == owner(), "Invalid sender");
address oldValue = fallbackPublisher;
fallbackPublisher = _newFallbackPublisher;
emit FallbackPublisherUpdate(oldValue, fallbackPublisher);
}
/**
* @notice Updates the Withdrawal Limit Publisher address, the only address other than the
* owner that can set the withdrawal limit.
* @param _newWithdrawalLimitPublisher The address of the new Withdrawal Limit Publisher
* @dev Error invalid sender.
*/
function setWithdrawalLimitPublisher(address _newWithdrawalLimitPublisher) external {
require(msg.sender == owner(), "Invalid sender");
address oldValue = withdrawalLimitPublisher;
withdrawalLimitPublisher = _newWithdrawalLimitPublisher;
emit WithdrawalLimitPublisherUpdate(oldValue, withdrawalLimitPublisher);
}
/**
* @notice Updates the DirectTransferer address, the only address other than the owner that
* can execute direct transfers
* @param _newDirectTransferer The address of the new DirectTransferer
*/
function setDirectTransferer(address _newDirectTransferer) external {
require(msg.sender == owner(), "Invalid sender");
address oldValue = directTransferer;
directTransferer = _newDirectTransferer;
emit DirectTransfererUpdate(oldValue, directTransferer);
}
/**
* @notice Updates the Partition Manager address, the only address other than the owner that
* can add and remove permitted partitions
* @param _newPartitionManager The address of the new PartitionManager
*/
function setPartitionManager(address _newPartitionManager) external {
require(msg.sender == owner(), "Invalid sender");
address oldValue = partitionManager;
partitionManager = _newPartitionManager;
emit PartitionManagerUpdate(oldValue, partitionManager);
}
/**********************************************************************************************
* Operator Data Decoders
*********************************************************************************************/
/**
* @notice Extract flag from operatorData
* @param _operatorData The operator data to be decoded
* @return flag, the transfer operation type
*/
function _decodeOperatorDataFlag(bytes memory _operatorData) internal pure returns (bytes32) {
return abi.decode(_operatorData, (bytes32));
}
/**
* @notice Extracts the supplier, max authorized nonce, and Merkle proof from the operator data
* @param _operatorData The operator data to be decoded
* @return supplier, the address whose account is authorized
* @return For withdrawals: max authorized nonce, the last used withdrawal root nonce for the
* supplier and partition. For fallback withdrawals: max cumulative withdrawal amount, the
* maximum amount of tokens that can be withdrawn for the supplier's account, including both
* withdrawals and fallback withdrawals
* @return proof, the Merkle proof to be used for the authorization
*/
function _decodeWithdrawalOperatorData(bytes memory _operatorData)
internal
pure
returns (
address,
uint256,
bytes32[] memory
)
{
(, address supplier, uint256 nonce, bytes32[] memory proof) = abi.decode(
_operatorData,
(bytes32, address, uint256, bytes32[])
);
return (supplier, nonce, proof);
}
/**
* @notice Extracts the supply nonce from the operator data
* @param _operatorData The operator data to be decoded
* @return nonce, the nonce of the supply to be refunded
*/
function _decodeRefundOperatorData(bytes memory _operatorData) internal pure returns (uint256) {
(, uint256 nonce) = abi.decode(_operatorData, (bytes32, uint256));
return nonce;
}
/**********************************************************************************************
* Merkle Tree Verification
*********************************************************************************************/
/**
* @notice Hashes the supplied data and returns the hash to be used in conjunction with a proof
* to calculate the Merkle tree root
* @param _supplier The address whose account is authorized
* @param _partition Source partition of the tokens
* @param _value Number of tokens to be transferred
* @param _maxAuthorizedAccountNonce The maximum existing used withdrawal nonce for the
* supplier and partition
* @return leaf, the hash of the supplied data
*/
function _calculateWithdrawalLeaf(
address _supplier,
bytes32 _partition,
uint256 _value,
uint256 _maxAuthorizedAccountNonce
) internal pure returns (bytes32) {
return
keccak256(abi.encodePacked(_supplier, _partition, _value, _maxAuthorizedAccountNonce));
}
/**
* @notice Hashes the supplied data and returns the hash to be used in conjunction with a proof
* to calculate the Merkle tree root
* @param _supplier The address whose account is authorized
* @param _partition Source partition of the tokens
* @param _maxCumulativeWithdrawalAmount, the maximum amount of tokens that can be withdrawn
* for the supplier's account, including both withdrawals and fallback withdrawals
* @return leaf, the hash of the supplied data
*/
function _calculateFallbackLeaf(
address _supplier,
bytes32 _partition,
uint256 _maxCumulativeWithdrawalAmount
) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(_supplier, _partition, _maxCumulativeWithdrawalAmount));
}
/**
* @notice Calculates the Merkle root for the unique Merkle tree described by the provided
Merkle proof and leaf hash.
* @param _merkleProof The sibling node hashes at each level of the tree.
* @param _leafHash The hash of the leaf data for which merkleProof is an inclusion proof.
* @return The calculated Merkle root.
*/
function _calculateMerkleRoot(bytes32[] memory _merkleProof, bytes32 _leafHash)
private
pure
returns (bytes32)
{
bytes32 computedHash = _leafHash;
for (uint256 i = 0; i < _merkleProof.length; i++) {
bytes32 proofElement = _merkleProof[i];
if (computedHash < proofElement) {
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
} else {
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
}
}
return computedHash;
}
}File 3 of 4: ERC1820Registry
/* ERC1820 Pseudo-introspection Registry Contract
* This standard defines a universal registry smart contract where any address (contract or regular account) can
* register which interface it supports and which smart contract is responsible for its implementation.
*
* Written in 2019 by Jordi Baylina and Jacques Dafflon
*
* To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to
* this software to the public domain worldwide. This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*
* ███████╗██████╗ ██████╗ ██╗ █████╗ ██████╗ ██████╗
* ██╔════╝██╔══██╗██╔════╝███║██╔══██╗╚════██╗██╔═████╗
* █████╗ ██████╔╝██║ ╚██║╚█████╔╝ █████╔╝██║██╔██║
* ██╔══╝ ██╔══██╗██║ ██║██╔══██╗██╔═══╝ ████╔╝██║
* ███████╗██║ ██║╚██████╗ ██║╚█████╔╝███████╗╚██████╔╝
* ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚══════╝ ╚═════╝
*
* ██████╗ ███████╗ ██████╗ ██╗███████╗████████╗██████╗ ██╗ ██╗
* ██╔══██╗██╔════╝██╔════╝ ██║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝
* ██████╔╝█████╗ ██║ ███╗██║███████╗ ██║ ██████╔╝ ╚████╔╝
* ██╔══██╗██╔══╝ ██║ ██║██║╚════██║ ██║ ██╔══██╗ ╚██╔╝
* ██║ ██║███████╗╚██████╔╝██║███████║ ██║ ██║ ██║ ██║
* ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
*
*/
pragma solidity 0.5.3;
// IV is value needed to have a vanity address starting with '0x1820'.
// IV: 53759
/// @dev The interface a contract MUST implement if it is the implementer of
/// some (other) interface for any address other than itself.
interface ERC1820ImplementerInterface {
/// @notice Indicates whether the contract implements the interface 'interfaceHash' for the address 'addr' or not.
/// @param interfaceHash keccak256 hash of the name of the interface
/// @param addr Address for which the contract will implement the interface
/// @return ERC1820_ACCEPT_MAGIC only if the contract implements 'interfaceHash' for the address 'addr'.
function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32);
}
/// @title ERC1820 Pseudo-introspection Registry Contract
/// @author Jordi Baylina and Jacques Dafflon
/// @notice This contract is the official implementation of the ERC1820 Registry.
/// @notice For more details, see https://eips.ethereum.org/EIPS/eip-1820
contract ERC1820Registry {
/// @notice ERC165 Invalid ID.
bytes4 constant internal INVALID_ID = 0xffffffff;
/// @notice Method ID for the ERC165 supportsInterface method (= `bytes4(keccak256('supportsInterface(bytes4)'))`).
bytes4 constant internal ERC165ID = 0x01ffc9a7;
/// @notice Magic value which is returned if a contract implements an interface on behalf of some other address.
bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC"));
/// @notice mapping from addresses and interface hashes to their implementers.
mapping(address => mapping(bytes32 => address)) internal interfaces;
/// @notice mapping from addresses to their manager.
mapping(address => address) internal managers;
/// @notice flag for each address and erc165 interface to indicate if it is cached.
mapping(address => mapping(bytes4 => bool)) internal erc165Cached;
/// @notice Indicates a contract is the 'implementer' of 'interfaceHash' for 'addr'.
event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer);
/// @notice Indicates 'newManager' is the address of the new manager for 'addr'.
event ManagerChanged(address indexed addr, address indexed newManager);
/// @notice Query if an address implements an interface and through which contract.
/// @param _addr Address being queried for the implementer of an interface.
/// (If '_addr' is the zero address then 'msg.sender' is assumed.)
/// @param _interfaceHash Keccak256 hash of the name of the interface as a string.
/// E.g., 'web3.utils.keccak256("ERC777TokensRecipient")' for the 'ERC777TokensRecipient' interface.
/// @return The address of the contract which implements the interface '_interfaceHash' for '_addr'
/// or '0' if '_addr' did not register an implementer for this interface.
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) {
address addr = _addr == address(0) ? msg.sender : _addr;
if (isERC165Interface(_interfaceHash)) {
bytes4 erc165InterfaceHash = bytes4(_interfaceHash);
return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : address(0);
}
return interfaces[addr][_interfaceHash];
}
/// @notice Sets the contract which implements a specific interface for an address.
/// Only the manager defined for that address can set it.
/// (Each address is the manager for itself until it sets a new manager.)
/// @param _addr Address for which to set the interface.
/// (If '_addr' is the zero address then 'msg.sender' is assumed.)
/// @param _interfaceHash Keccak256 hash of the name of the interface as a string.
/// E.g., 'web3.utils.keccak256("ERC777TokensRecipient")' for the 'ERC777TokensRecipient' interface.
/// @param _implementer Contract address implementing '_interfaceHash' for '_addr'.
function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external {
address addr = _addr == address(0) ? msg.sender : _addr;
require(getManager(addr) == msg.sender, "Not the manager");
require(!isERC165Interface(_interfaceHash), "Must not be an ERC165 hash");
if (_implementer != address(0) && _implementer != msg.sender) {
require(
ERC1820ImplementerInterface(_implementer)
.canImplementInterfaceForAddress(_interfaceHash, addr) == ERC1820_ACCEPT_MAGIC,
"Does not implement the interface"
);
}
interfaces[addr][_interfaceHash] = _implementer;
emit InterfaceImplementerSet(addr, _interfaceHash, _implementer);
}
/// @notice Sets '_newManager' as manager for '_addr'.
/// The new manager will be able to call 'setInterfaceImplementer' for '_addr'.
/// @param _addr Address for which to set the new manager.
/// @param _newManager Address of the new manager for 'addr'. (Pass '0x0' to reset the manager to '_addr'.)
function setManager(address _addr, address _newManager) external {
require(getManager(_addr) == msg.sender, "Not the manager");
managers[_addr] = _newManager == _addr ? address(0) : _newManager;
emit ManagerChanged(_addr, _newManager);
}
/// @notice Get the manager of an address.
/// @param _addr Address for which to return the manager.
/// @return Address of the manager for a given address.
function getManager(address _addr) public view returns(address) {
// By default the manager of an address is the same address
if (managers[_addr] == address(0)) {
return _addr;
} else {
return managers[_addr];
}
}
/// @notice Compute the keccak256 hash of an interface given its name.
/// @param _interfaceName Name of the interface.
/// @return The keccak256 hash of an interface name.
function interfaceHash(string calldata _interfaceName) external pure returns(bytes32) {
return keccak256(abi.encodePacked(_interfaceName));
}
/* --- ERC165 Related Functions --- */
/* --- Developed in collaboration with William Entriken. --- */
/// @notice Updates the cache with whether the contract implements an ERC165 interface or not.
/// @param _contract Address of the contract for which to update the cache.
/// @param _interfaceId ERC165 interface for which to update the cache.
function updateERC165Cache(address _contract, bytes4 _interfaceId) external {
interfaces[_contract][_interfaceId] = implementsERC165InterfaceNoCache(
_contract, _interfaceId) ? _contract : address(0);
erc165Cached[_contract][_interfaceId] = true;
}
/// @notice Checks whether a contract implements an ERC165 interface or not.
// If the result is not cached a direct lookup on the contract address is performed.
// If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling
// 'updateERC165Cache' with the contract address.
/// @param _contract Address of the contract to check.
/// @param _interfaceId ERC165 interface to check.
/// @return True if '_contract' implements '_interfaceId', false otherwise.
function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool) {
if (!erc165Cached[_contract][_interfaceId]) {
return implementsERC165InterfaceNoCache(_contract, _interfaceId);
}
return interfaces[_contract][_interfaceId] == _contract;
}
/// @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache.
/// @param _contract Address of the contract to check.
/// @param _interfaceId ERC165 interface to check.
/// @return True if '_contract' implements '_interfaceId', false otherwise.
function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool) {
uint256 success;
uint256 result;
(success, result) = noThrowCall(_contract, ERC165ID);
if (success == 0 || result == 0) {
return false;
}
(success, result) = noThrowCall(_contract, INVALID_ID);
if (success == 0 || result != 0) {
return false;
}
(success, result) = noThrowCall(_contract, _interfaceId);
if (success == 1 && result == 1) {
return true;
}
return false;
}
/// @notice Checks whether the hash is a ERC165 interface (ending with 28 zeroes) or not.
/// @param _interfaceHash The hash to check.
/// @return True if '_interfaceHash' is an ERC165 interface (ending with 28 zeroes), false otherwise.
function isERC165Interface(bytes32 _interfaceHash) internal pure returns (bool) {
return _interfaceHash & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0;
}
/// @dev Make a call on a contract without throwing if the function does not exist.
function noThrowCall(address _contract, bytes4 _interfaceId)
internal view returns (uint256 success, uint256 result)
{
bytes4 erc165ID = ERC165ID;
assembly {
let x := mload(0x40) // Find empty storage location using "free memory pointer"
mstore(x, erc165ID) // Place signature at beginning of empty storage
mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature
success := staticcall(
30000, // 30k gas
_contract, // To addr
x, // Inputs are stored at location x
0x24, // Inputs are 36 (4 + 32) bytes long
x, // Store output over input (saves space)
0x20 // Outputs are 32 bytes long
)
result := mload(x) // Load the result
}
}
}File 4 of 4: CollateralPoolPartitionValidator
// SPDX-License-Identifier: MIT
pragma solidity 0.6.10;
abstract contract ERC1820Registry {
function setInterfaceImplementer(
address _addr,
bytes32 _interfaceHash,
address _implementer
) external virtual;
function getInterfaceImplementer(address _addr, bytes32 _interfaceHash)
external
virtual
view
returns (address);
function setManager(address _addr, address _newManager) external virtual;
function getManager(address _addr) public virtual view returns (address);
}
/// Base client to interact with the registry.
contract ERC1820Client {
ERC1820Registry constant ERC1820REGISTRY = ERC1820Registry(
0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
);
function setInterfaceImplementation(
string memory _interfaceLabel,
address _implementation
) internal {
bytes32 interfaceHash = keccak256(abi.encodePacked(_interfaceLabel));
ERC1820REGISTRY.setInterfaceImplementer(
address(this),
interfaceHash,
_implementation
);
}
function interfaceAddr(address addr, string memory _interfaceLabel)
internal
view
returns (address)
{
bytes32 interfaceHash = keccak256(abi.encodePacked(_interfaceLabel));
return ERC1820REGISTRY.getInterfaceImplementer(addr, interfaceHash);
}
function delegateManagement(address _newManager) internal {
ERC1820REGISTRY.setManager(address(this), _newManager);
}
}
contract ERC1820Implementer {
/**
* @dev ERC1820 well defined magic value indicating the contract has
* registered with the ERC1820Registry that it can implement an interface.
*/
bytes32 constant ERC1820_ACCEPT_MAGIC = keccak256(
abi.encodePacked("ERC1820_ACCEPT_MAGIC")
);
/**
* @dev Mapping of interface name keccak256 hashes for which this contract
* implements the interface.
* @dev Only settable internally.
*/
mapping(bytes32 => bool) internal _interfaceHashes;
/**
* @notice Indicates whether the contract implements the interface `_interfaceHash`
* for the address `_addr`.
* @param _interfaceHash keccak256 hash of the name of the interface.
* @return ERC1820_ACCEPT_MAGIC only if the contract implements `ìnterfaceHash`
* for the address `_addr`.
* @dev In this implementation, the `_addr` (the address for which the
* contract will implement the interface) is always `address(this)`.
*/
function canImplementInterfaceForAddress(
bytes32 _interfaceHash,
address // Comments to avoid compilation warnings for unused variables. /*addr*/
) external view returns (bytes32) {
if (_interfaceHashes[_interfaceHash]) {
return ERC1820_ACCEPT_MAGIC;
} else {
return "";
}
}
/**
* @notice Internally set the fact this contract implements the interface
* identified by `_interfaceLabel`
* @param _interfaceLabel String representation of the interface.
*/
function _setInterface(string memory _interfaceLabel) internal {
_interfaceHashes[keccak256(abi.encodePacked(_interfaceLabel))] = true;
}
}
/**
* @notice Partition strategy validator hooks for Amp
*/
interface IAmpPartitionStrategyValidator {
function tokensFromPartitionToValidate(
bytes4 _functionSig,
bytes32 _partition,
address _operator,
address _from,
address _to,
uint256 _value,
bytes calldata _data,
bytes calldata _operatorData
) external;
function tokensToPartitionToValidate(
bytes4 _functionSig,
bytes32 _partition,
address _operator,
address _from,
address _to,
uint256 _value,
bytes calldata _data,
bytes calldata _operatorData
) external;
function isOperatorForPartitionScope(
bytes32 _partition,
address _operator,
address _tokenHolder
) external view returns (bool);
}
/**
* @title PartitionUtils
* @notice Partition related helper functions.
*/
library PartitionUtils {
bytes32 public constant CHANGE_PARTITION_FLAG = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
/**
* @notice Retrieve the destination partition from the 'data' field.
* A partition change is requested ONLY when 'data' starts with the flag:
*
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
*
* When the flag is detected, the destination partition is extracted from the
* 32 bytes following the flag.
* @param _data Information attached to the transfer. Will contain the
* destination partition if a change is requested.
* @param _fallbackPartition Partition value to return if a partition change
* is not requested in the `_data`.
* @return toPartition Destination partition. If the `_data` does not contain
* the prefix and bytes32 partition in the first 64 bytes, the method will
* return the provided `_fromPartition`.
*/
function _getDestinationPartition(bytes memory _data, bytes32 _fallbackPartition)
internal
pure
returns (bytes32)
{
if (_data.length < 64) {
return _fallbackPartition;
}
(bytes32 flag, bytes32 toPartition) = abi.decode(_data, (bytes32, bytes32));
if (flag == CHANGE_PARTITION_FLAG) {
return toPartition;
}
return _fallbackPartition;
}
/**
* @notice Helper to get the strategy identifying prefix from the `_partition`.
* @param _partition Partition to get the prefix for.
* @return 4 byte partition strategy prefix.
*/
function _getPartitionPrefix(bytes32 _partition) internal pure returns (bytes4) {
return bytes4(_partition);
}
/**
* @notice Helper method to split the partition into the prefix, sub partition
* and partition owner components.
* @param _partition The partition to split into parts.
* @return The 4 byte partition prefix, 8 byte sub partition, and final 20
* bytes representing an address.
*/
function _splitPartition(bytes32 _partition)
internal
pure
returns (
bytes4,
bytes8,
address
)
{
bytes4 prefix = bytes4(_partition);
bytes8 subPartition = bytes8(_partition << 32);
address addressPart = address(uint160(uint256(_partition)));
return (prefix, subPartition, addressPart);
}
/**
* @notice Helper method to get a partition strategy ERC1820 interface name
* based on partition prefix.
* @param _prefix 4 byte partition prefix.
* @dev Each 4 byte prefix has a unique interface name so that an individual
* hook implementation can be set for each prefix.
*/
function _getPartitionStrategyValidatorIName(bytes4 _prefix)
internal
pure
returns (string memory)
{
return string(abi.encodePacked("AmpPartitionStrategyValidator", _prefix));
}
}
/**
* @title Base contract that satisfies the IAmpPartitionStrategyValidator
* interface
*/
contract AmpPartitionStrategyValidatorBase is
IAmpPartitionStrategyValidator,
ERC1820Client,
ERC1820Implementer
{
/**
* @notice Partition prefix the hooks are valid for.
* @dev Must to be set by the parent contract.
*/
bytes4 public partitionPrefix;
/**
* @notice Amp contract address.
*/
address public amp;
/**
* @notice Initialize the partition prefix and register the implementation
* with the ERC1820 registry for the dynamic interface name.
* @param _prefix Partition prefix the hooks are valid for.
* @param _amp The address of the Amp contract.
*/
constructor(bytes4 _prefix, address _amp) public {
partitionPrefix = _prefix;
string memory iname = PartitionUtils._getPartitionStrategyValidatorIName(
partitionPrefix
);
ERC1820Implementer._setInterface(iname);
amp = _amp;
}
/**
* @dev Placeholder to satisfy IAmpPartitionSpaceValidator interface that
* can be overridden by parent.
*/
function tokensFromPartitionToValidate(
bytes4, /* functionSig */
bytes32, /* fromPartition */
address, /* operator */
address, /* from */
address, /* to */
uint256, /* value */
bytes calldata, /* data */
bytes calldata /* operatorData */
) external virtual override {}
/**
* @dev Placeholder to satisfy IAmpPartitionSpaceValidator interface that
* can be overridden by parent.
*/
function tokensToPartitionToValidate(
bytes4, /* functionSig */
bytes32, /* fromPartition */
address, /* operator */
address, /* from */
address, /* to */
uint256, /* value */
bytes calldata, /* data */
bytes calldata /* operatorData */
) external virtual override {}
/**
* @notice Report if address is an operator for a partition based on the
* partition's strategy.
* @dev Placeholder that can be overridden by parent.
*/
function isOperatorForPartitionScope(
bytes32, /* partition */
address, /* operator */
address /* tokenHolder */
) external virtual override view returns (bool) {
return false;
}
}
interface IAmp {
function isCollateralManager(address) external view returns (bool);
}
/**
* @title CollateralPoolPartitionValidator
*/
contract CollateralPoolPartitionValidator is AmpPartitionStrategyValidatorBase {
bytes4 constant PARTITION_PREFIX = 0xCCCCCCCC;
constructor(address _amp)
public
AmpPartitionStrategyValidatorBase(PARTITION_PREFIX, _amp)
{}
/**
* @notice Reports if the token holder is an operator for the partition.
* @dev The `_operator` address param is unused. For this strategy, this will
* be being called on behalf of suppliers, as they have sent their tokens
* to the collateral manager address, and are now trying to execute a
* transfer from the pool. This implies that the pool sender hook
* MUST be implemented in such a way as to restrict any unauthorized
* transfers, as the partitions affected by this strategy will allow
* all callers to make an attempt to transfer from the collateral
* managers partition.
* @param _partition The partition to check.
* @param _tokenHolder The collateral manager holding the pool of tokens.
* @return The operator check for this strategy returns true if the partition
* owner (identified by the final 20 bytes of the partition) is the
* same as the token holder address, as in this case the token holder
* is the collateral manager address.
*/
function isOperatorForPartitionScope(
bytes32 _partition,
address, /* operator */
address _tokenHolder
) external override view returns (bool) {
require(msg.sender == address(amp), "Hook must be called by amp");
(, , address partitionOwner) = PartitionUtils._splitPartition(_partition);
if (!IAmp(amp).isCollateralManager(partitionOwner)) {
return false;
}
return _tokenHolder == partitionOwner;
}
/**
* @notice Validate the rules of the strategy when tokens are being sent to
* a partition under the purview of the strategy.
* @dev The `_toPartition` must be formatted with the PARTITION_PREFIX as the
* first 4 bytes, the `_to` value as the final 20 bytes. The 8 bytes in the
* middle can be used by the manager to create sub partitions within their
* impelemntation.
* @param _toPartition The partition the tokens are transferred to.
* @param _to The address of the collateral manager.
*/
function tokensToPartitionToValidate(
bytes4, /* functionSig */
bytes32 _toPartition,
address, /* operator */
address, /* from */
address _to,
uint256, /* value */
bytes calldata, /* _data */
bytes calldata /* operatorData */
) external override {
require(msg.sender == address(amp), "Hook must be called by amp");
(, , address toPartitionOwner) = PartitionUtils._splitPartition(_toPartition);
require(
_to == toPartitionOwner,
"Transfers to this partition must be to the partitionOwner"
);
require(
IAmp(amp).isCollateralManager(toPartitionOwner),
"Partition owner is not a registered collateral manager"
);
}
}