Skip to content

Instantly share code, notes, and snippets.

@patitonar
Last active April 15, 2020 22:07
Show Gist options
  • Select an option

  • Save patitonar/1f8a681457c7c4ae77e25682739d5ef9 to your computer and use it in GitHub Desktop.

Select an option

Save patitonar/1f8a681457c7c4ae77e25682739d5ef9 to your computer and use it in GitHub Desktop.
ForeignBridgeErcToNative implementation for xdai bridge that disables sai swap to dai
// File: contracts/upgradeability/EternalStorage.sol
pragma solidity 0.4.24;
/**
* @title EternalStorage
* @dev This contract holds all the necessary state variables to carry out the storage of any contract.
*/
contract EternalStorage {
mapping(bytes32 => uint256) internal uintStorage;
mapping(bytes32 => string) internal stringStorage;
mapping(bytes32 => address) internal addressStorage;
mapping(bytes32 => bytes) internal bytesStorage;
mapping(bytes32 => bool) internal boolStorage;
mapping(bytes32 => int256) internal intStorage;
}
// File: openzeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol
pragma solidity ^0.4.24;
/**
* @title ERC20Basic
* @dev Simpler version of ERC20 interface
* See https://github.com/ethereum/EIPs/issues/179
*/
contract ERC20Basic {
function totalSupply() public view returns (uint256);
function balanceOf(address _who) public view returns (uint256);
function transfer(address _to, uint256 _value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}
// File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol
pragma solidity ^0.4.24;
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
contract ERC20 is ERC20Basic {
function allowance(address _owner, address _spender)
public view returns (uint256);
function transferFrom(address _from, address _to, uint256 _value)
public returns (bool);
function approve(address _spender, uint256 _value) public returns (bool);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
// File: contracts/interfaces/IBridgeValidators.sol
pragma solidity 0.4.24;
interface IBridgeValidators {
function isValidator(address _validator) external view returns (bool);
function requiredSignatures() external view returns (uint256);
function owner() external view returns (address);
}
// File: contracts/upgradeable_contracts/ValidatorStorage.sol
pragma solidity 0.4.24;
contract ValidatorStorage {
bytes32 internal constant VALIDATOR_CONTRACT = 0x5a74bb7e202fb8e4bf311841c7d64ec19df195fee77d7e7ae749b27921b6ddfe; // keccak256(abi.encodePacked("validatorContract"))
}
// File: contracts/upgradeable_contracts/Validatable.sol
pragma solidity 0.4.24;
contract Validatable is EternalStorage, ValidatorStorage {
function validatorContract() public view returns (IBridgeValidators) {
return IBridgeValidators(addressStorage[VALIDATOR_CONTRACT]);
}
modifier onlyValidator() {
require(validatorContract().isValidator(msg.sender));
/* solcov ignore next */
_;
}
function requiredSignatures() public view returns (uint256) {
return validatorContract().requiredSignatures();
}
}
// File: contracts/libraries/Message.sol
pragma solidity 0.4.24;
library Message {
// function uintToString(uint256 inputValue) internal pure returns (string) {
// // figure out the length of the resulting string
// uint256 length = 0;
// uint256 currentValue = inputValue;
// do {
// length++;
// currentValue /= 10;
// } while (currentValue != 0);
// // allocate enough memory
// bytes memory result = new bytes(length);
// // construct the string backwards
// uint256 i = length - 1;
// currentValue = inputValue;
// do {
// result[i--] = byte(48 + currentValue % 10);
// currentValue /= 10;
// } while (currentValue != 0);
// return string(result);
// }
function addressArrayContains(address[] array, address value) internal pure returns (bool) {
for (uint256 i = 0; i < array.length; i++) {
if (array[i] == value) {
return true;
}
}
return false;
}
// layout of message :: bytes:
// offset 0: 32 bytes :: uint256 - message length
// offset 32: 20 bytes :: address - recipient address
// offset 52: 32 bytes :: uint256 - value
// offset 84: 32 bytes :: bytes32 - transaction hash
// offset 104: 20 bytes :: address - contract address to prevent double spending
// mload always reads 32 bytes.
// so we can and have to start reading recipient at offset 20 instead of 32.
// if we were to read at 32 the address would contain part of value and be corrupted.
// when reading from offset 20 mload will read 12 bytes (most of them zeros) followed
// by the 20 recipient address bytes and correctly convert it into an address.
// this saves some storage/gas over the alternative solution
// which is padding address to 32 bytes and reading recipient at offset 32.
// for more details see discussion in:
// https://github.com/paritytech/parity-bridge/issues/61
function parseMessage(bytes message)
internal
pure
returns (address recipient, uint256 amount, bytes32 txHash, address contractAddress)
{
require(isMessageValid(message));
assembly {
recipient := mload(add(message, 20))
amount := mload(add(message, 52))
txHash := mload(add(message, 84))
contractAddress := mload(add(message, 104))
}
}
function isMessageValid(bytes _msg) internal pure returns (bool) {
return _msg.length == requiredMessageLength();
}
function requiredMessageLength() internal pure returns (uint256) {
return 104;
}
function recoverAddressFromSignedMessage(bytes signature, bytes message, bool isAMBMessage)
internal
pure
returns (address)
{
require(signature.length == 65);
bytes32 r;
bytes32 s;
bytes1 v;
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := mload(add(signature, 0x60))
}
return ecrecover(hashMessage(message, isAMBMessage), uint8(v), r, s);
}
function hashMessage(bytes message, bool isAMBMessage) internal pure returns (bytes32) {
bytes memory prefix = "\x19Ethereum Signed Message:\n";
if (isAMBMessage) {
return keccak256(abi.encodePacked(prefix, uintToString(message.length), message));
} else {
string memory msgLength = "104";
return keccak256(abi.encodePacked(prefix, msgLength, message));
}
}
/**
* @dev Validates provided signatures, only first requiredSignatures() number
* of signatures are going to be validated, these signatures should be from different validators.
* @param _message bytes message used to generate signatures
* @param _signatures bytes blob with signatures to be validated.
* First byte X is a number of signatures in a blob,
* next X bytes are v components of signatures,
* next 32 * X bytes are r components of signatures,
* next 32 * X bytes are s components of signatures.
* @param _validatorContract contract, which conforms to the IBridgeValidators interface,
* where info about current validators and required signatures is stored.
* @param isAMBMessage true if _message is an AMB message with arbitrary length.
*/
function hasEnoughValidSignatures(
bytes _message,
bytes _signatures,
IBridgeValidators _validatorContract,
bool isAMBMessage
) internal view {
require(isAMBMessage || isMessageValid(_message));
uint256 requiredSignatures = _validatorContract.requiredSignatures();
uint256 amount;
assembly {
amount := and(mload(add(_signatures, 1)), 0xff)
}
require(amount >= requiredSignatures);
bytes32 hash = hashMessage(_message, isAMBMessage);
address[] memory encounteredAddresses = new address[](requiredSignatures);
for (uint256 i = 0; i < requiredSignatures; i++) {
uint8 v;
bytes32 r;
bytes32 s;
uint256 posr = 33 + amount + 32 * i;
uint256 poss = posr + 32 * amount;
assembly {
v := mload(add(_signatures, add(2, i)))
r := mload(add(_signatures, posr))
s := mload(add(_signatures, poss))
}
address recoveredAddress = ecrecover(hash, v, r, s);
require(_validatorContract.isValidator(recoveredAddress));
require(!addressArrayContains(encounteredAddresses, recoveredAddress));
encounteredAddresses[i] = recoveredAddress;
}
}
function uintToString(uint256 i) internal pure returns (string) {
if (i == 0) return "0";
uint256 j = i;
uint256 length;
while (j != 0) {
length++;
j /= 10;
}
bytes memory bstr = new bytes(length);
uint256 k = length - 1;
while (i != 0) {
bstr[k--] = bytes1(48 + (i % 10));
i /= 10;
}
return string(bstr);
}
}
// File: openzeppelin-solidity/contracts/math/SafeMath.sol
pragma solidity ^0.4.24;
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint256 _a, uint256 _b) internal pure returns (uint256 c) {
// Gas optimization: this is cheaper than asserting 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (_a == 0) {
return 0;
}
c = _a * _b;
assert(c / _a == _b);
return c;
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 _a, uint256 _b) internal pure returns (uint256) {
// assert(_b > 0); // Solidity automatically throws when dividing by 0
// uint256 c = _a / _b;
// assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold
return _a / _b;
}
/**
* @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 _a, uint256 _b) internal pure returns (uint256) {
assert(_b <= _a);
return _a - _b;
}
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 _a, uint256 _b) internal pure returns (uint256 c) {
c = _a + _b;
assert(c >= _a);
return c;
}
}
// File: contracts/interfaces/IUpgradeabilityOwnerStorage.sol
pragma solidity 0.4.24;
interface IUpgradeabilityOwnerStorage {
function upgradeabilityOwner() external view returns (address);
}
// File: contracts/upgradeable_contracts/Ownable.sol
pragma solidity 0.4.24;
/**
* @title Ownable
* @dev This contract has an owner address providing basic authorization control
*/
contract Ownable is EternalStorage {
bytes4 internal constant UPGRADEABILITY_OWNER = 0x6fde8202; // upgradeabilityOwner()
/**
* @dev Event to show ownership has been transferred
* @param previousOwner representing the address of the previous owner
* @param newOwner representing the address of the new owner
*/
event OwnershipTransferred(address previousOwner, address newOwner);
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner());
/* solcov ignore next */
_;
}
/**
* @dev Throws if called by any account other than contract itself or owner.
*/
modifier onlyRelevantSender() {
// proxy owner if used through proxy, address(0) otherwise
require(
!address(this).call(abi.encodeWithSelector(UPGRADEABILITY_OWNER)) || // covers usage without calling through storage proxy
msg.sender == IUpgradeabilityOwnerStorage(this).upgradeabilityOwner() || // covers usage through regular proxy calls
msg.sender == address(this) // covers calls through upgradeAndCall proxy method
);
/* solcov ignore next */
_;
}
bytes32 internal constant OWNER = 0x02016836a56b71f0d02689e69e326f4f4c1b9057164ef592671cf0d37c8040c0; // keccak256(abi.encodePacked("owner"))
/**
* @dev Tells the address of the owner
* @return the address of the owner
*/
function owner() public view returns (address) {
return addressStorage[OWNER];
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner the address to transfer ownership to.
*/
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0));
setOwner(newOwner);
}
/**
* @dev Sets a new owner address
*/
function setOwner(address newOwner) internal {
emit OwnershipTransferred(owner(), newOwner);
addressStorage[OWNER] = newOwner;
}
}
// File: contracts/upgradeable_contracts/BasicTokenBridge.sol
pragma solidity 0.4.24;
contract BasicTokenBridge is EternalStorage, Ownable {
using SafeMath for uint256;
event DailyLimitChanged(uint256 newLimit);
event ExecutionDailyLimitChanged(uint256 newLimit);
bytes32 internal constant MIN_PER_TX = 0xbbb088c505d18e049d114c7c91f11724e69c55ad6c5397e2b929e68b41fa05d1; // keccak256(abi.encodePacked("minPerTx"))
bytes32 internal constant MAX_PER_TX = 0x0f8803acad17c63ee38bf2de71e1888bc7a079a6f73658e274b08018bea4e29c; // keccak256(abi.encodePacked("maxPerTx"))
bytes32 internal constant DAILY_LIMIT = 0x4a6a899679f26b73530d8cf1001e83b6f7702e04b6fdb98f3c62dc7e47e041a5; // keccak256(abi.encodePacked("dailyLimit"))
bytes32 internal constant EXECUTION_MAX_PER_TX = 0xc0ed44c192c86d1cc1ba51340b032c2766b4a2b0041031de13c46dd7104888d5; // keccak256(abi.encodePacked("executionMaxPerTx"))
bytes32 internal constant EXECUTION_DAILY_LIMIT = 0x21dbcab260e413c20dc13c28b7db95e2b423d1135f42bb8b7d5214a92270d237; // keccak256(abi.encodePacked("executionDailyLimit"))
bytes32 internal constant DECIMAL_SHIFT = 0x1e8ecaafaddea96ed9ac6d2642dcdfe1bebe58a930b1085842d8fc122b371ee5; // keccak256(abi.encodePacked("decimalShift"))
function totalSpentPerDay(uint256 _day) public view returns (uint256) {
return uintStorage[keccak256(abi.encodePacked("totalSpentPerDay", _day))];
}
function totalExecutedPerDay(uint256 _day) public view returns (uint256) {
return uintStorage[keccak256(abi.encodePacked("totalExecutedPerDay", _day))];
}
function dailyLimit() public view returns (uint256) {
return uintStorage[DAILY_LIMIT];
}
function executionDailyLimit() public view returns (uint256) {
return uintStorage[EXECUTION_DAILY_LIMIT];
}
function maxPerTx() public view returns (uint256) {
return uintStorage[MAX_PER_TX];
}
function executionMaxPerTx() public view returns (uint256) {
return uintStorage[EXECUTION_MAX_PER_TX];
}
function minPerTx() public view returns (uint256) {
return uintStorage[MIN_PER_TX];
}
function decimalShift() public view returns (uint256) {
return uintStorage[DECIMAL_SHIFT];
}
function withinLimit(uint256 _amount) public view returns (bool) {
uint256 nextLimit = totalSpentPerDay(getCurrentDay()).add(_amount);
return dailyLimit() >= nextLimit && _amount <= maxPerTx() && _amount >= minPerTx();
}
function withinExecutionLimit(uint256 _amount) public view returns (bool) {
uint256 nextLimit = totalExecutedPerDay(getCurrentDay()).add(_amount);
return executionDailyLimit() >= nextLimit && _amount <= executionMaxPerTx();
}
function getCurrentDay() public view returns (uint256) {
// solhint-disable-next-line not-rely-on-time
return now / 1 days;
}
function setTotalSpentPerDay(uint256 _day, uint256 _value) internal {
uintStorage[keccak256(abi.encodePacked("totalSpentPerDay", _day))] = _value;
}
function setTotalExecutedPerDay(uint256 _day, uint256 _value) internal {
uintStorage[keccak256(abi.encodePacked("totalExecutedPerDay", _day))] = _value;
}
function setDailyLimit(uint256 _dailyLimit) external onlyOwner {
require(_dailyLimit > maxPerTx() || _dailyLimit == 0);
uintStorage[DAILY_LIMIT] = _dailyLimit;
emit DailyLimitChanged(_dailyLimit);
}
function setExecutionDailyLimit(uint256 _dailyLimit) external onlyOwner {
require(_dailyLimit > executionMaxPerTx() || _dailyLimit == 0);
uintStorage[EXECUTION_DAILY_LIMIT] = _dailyLimit;
emit ExecutionDailyLimitChanged(_dailyLimit);
}
function setExecutionMaxPerTx(uint256 _maxPerTx) external onlyOwner {
require(_maxPerTx < executionDailyLimit());
uintStorage[EXECUTION_MAX_PER_TX] = _maxPerTx;
}
function setMaxPerTx(uint256 _maxPerTx) external onlyOwner {
require(_maxPerTx == 0 || (_maxPerTx > minPerTx() && _maxPerTx < dailyLimit()));
uintStorage[MAX_PER_TX] = _maxPerTx;
}
function setMinPerTx(uint256 _minPerTx) external onlyOwner {
require(_minPerTx > 0 && _minPerTx < dailyLimit() && _minPerTx < maxPerTx());
uintStorage[MIN_PER_TX] = _minPerTx;
}
}
// File: contracts/upgradeable_contracts/MessageRelay.sol
pragma solidity 0.4.24;
contract MessageRelay is EternalStorage {
function relayedMessages(bytes32 _txHash) public view returns (bool) {
return boolStorage[keccak256(abi.encodePacked("relayedMessages", _txHash))];
}
function setRelayedMessages(bytes32 _txHash, bool _status) internal {
boolStorage[keccak256(abi.encodePacked("relayedMessages", _txHash))] = _status;
}
}
// File: contracts/upgradeable_contracts/Upgradeable.sol
pragma solidity 0.4.24;
contract Upgradeable {
// Avoid using onlyUpgradeabilityOwner name to prevent issues with implementation from proxy contract
modifier onlyIfUpgradeabilityOwner() {
require(msg.sender == IUpgradeabilityOwnerStorage(this).upgradeabilityOwner());
/* solcov ignore next */
_;
}
}
// File: contracts/upgradeable_contracts/Initializable.sol
pragma solidity 0.4.24;
contract Initializable is EternalStorage {
bytes32 internal constant INITIALIZED = 0x0a6f646cd611241d8073675e00d1a1ff700fbf1b53fcf473de56d1e6e4b714ba; // keccak256(abi.encodePacked("isInitialized"))
function setInitialize() internal {
boolStorage[INITIALIZED] = true;
}
function isInitialized() public view returns (bool) {
return boolStorage[INITIALIZED];
}
}
// File: contracts/upgradeable_contracts/InitializableBridge.sol
pragma solidity 0.4.24;
contract InitializableBridge is Initializable {
bytes32 internal constant DEPLOYED_AT_BLOCK = 0xb120ceec05576ad0c710bc6e85f1768535e27554458f05dcbb5c65b8c7a749b0; // keccak256(abi.encodePacked("deployedAtBlock"))
function deployedAtBlock() external view returns (uint256) {
return uintStorage[DEPLOYED_AT_BLOCK];
}
}
// File: openzeppelin-solidity/contracts/AddressUtils.sol
pragma solidity ^0.4.24;
/**
* Utility library of inline functions on addresses
*/
library AddressUtils {
/**
* Returns whether the target address is a contract
* @dev This function will return false if invoked during the constructor of a contract,
* as the code is not actually created until after the constructor finishes.
* @param _addr address to check
* @return whether the target address is a contract
*/
function isContract(address _addr) internal view returns (bool) {
uint256 size;
// XXX Currently there is no better way to check if there is a contract in an address
// than to check the size of the code at that address.
// See https://ethereum.stackexchange.com/a/14016/36603
// for more details about how this works.
// TODO Check this again before the Serenity release, because all addresses will be
// contracts then.
// solium-disable-next-line security/no-inline-assembly
assembly { size := extcodesize(_addr) }
return size > 0;
}
}
// File: contracts/upgradeable_contracts/Sacrifice.sol
pragma solidity 0.4.24;
contract Sacrifice {
constructor(address _recipient) public payable {
selfdestruct(_recipient);
}
}
// File: contracts/upgradeable_contracts/Claimable.sol
pragma solidity 0.4.24;
contract Claimable {
bytes4 internal constant TRANSFER = 0xa9059cbb; // transfer(address,uint256)
modifier validAddress(address _to) {
require(_to != address(0));
/* solcov ignore next */
_;
}
function claimValues(address _token, address _to) internal {
if (_token == address(0)) {
claimNativeCoins(_to);
} else {
claimErc20Tokens(_token, _to);
}
}
function claimNativeCoins(address _to) internal {
uint256 value = address(this).balance;
if (!_to.send(value)) {
(new Sacrifice).value(value)(_to);
}
}
function claimErc20Tokens(address _token, address _to) internal {
ERC20Basic token = ERC20Basic(_token);
uint256 balance = token.balanceOf(this);
safeTransfer(_token, _to, balance);
}
function safeTransfer(address _token, address _to, uint256 _value) internal {
bytes memory returnData;
bool returnDataResult;
bytes memory callData = abi.encodeWithSelector(TRANSFER, _to, _value);
assembly {
let result := call(gas, _token, 0x0, add(callData, 0x20), mload(callData), 0, 32)
returnData := mload(0)
returnDataResult := mload(0)
switch result
case 0 {
revert(0, 0)
}
}
// Return data is optional
if (returnData.length > 0) {
require(returnDataResult);
}
}
}
// File: contracts/upgradeable_contracts/VersionableBridge.sol
pragma solidity 0.4.24;
contract VersionableBridge {
function getBridgeInterfacesVersion() external pure returns (uint64 major, uint64 minor, uint64 patch) {
return (3, 0, 0);
}
/* solcov ignore next */
function getBridgeMode() external pure returns (bytes4);
}
// File: contracts/upgradeable_contracts/BasicBridge.sol
pragma solidity 0.4.24;
contract BasicBridge is InitializableBridge, Validatable, Ownable, Upgradeable, Claimable, VersionableBridge {
event GasPriceChanged(uint256 gasPrice);
event RequiredBlockConfirmationChanged(uint256 requiredBlockConfirmations);
bytes32 internal constant GAS_PRICE = 0x55b3774520b5993024893d303890baa4e84b1244a43c60034d1ced2d3cf2b04b; // keccak256(abi.encodePacked("gasPrice"))
bytes32 internal constant REQUIRED_BLOCK_CONFIRMATIONS = 0x916daedf6915000ff68ced2f0b6773fe6f2582237f92c3c95bb4d79407230071; // keccak256(abi.encodePacked("requiredBlockConfirmations"))
function setGasPrice(uint256 _gasPrice) external onlyOwner {
require(_gasPrice > 0);
uintStorage[GAS_PRICE] = _gasPrice;
emit GasPriceChanged(_gasPrice);
}
function gasPrice() external view returns (uint256) {
return uintStorage[GAS_PRICE];
}
function setRequiredBlockConfirmations(uint256 _blockConfirmations) external onlyOwner {
require(_blockConfirmations > 0);
uintStorage[REQUIRED_BLOCK_CONFIRMATIONS] = _blockConfirmations;
emit RequiredBlockConfirmationChanged(_blockConfirmations);
}
function requiredBlockConfirmations() external view returns (uint256) {
return uintStorage[REQUIRED_BLOCK_CONFIRMATIONS];
}
function claimTokens(address _token, address _to) public onlyIfUpgradeabilityOwner validAddress(_to) {
claimValues(_token, _to);
}
}
// File: contracts/upgradeable_contracts/BasicForeignBridge.sol
pragma solidity 0.4.24;
contract BasicForeignBridge is EternalStorage, Validatable, BasicBridge, BasicTokenBridge, MessageRelay {
/// triggered when relay of deposit from HomeBridge is complete
event RelayedMessage(address recipient, uint256 value, bytes32 transactionHash);
event UserRequestForAffirmation(address recipient, uint256 value);
/**
* @dev Validates provided signatures and relays a given message
* @param message bytes to be relayed
* @param signatures bytes blob with signatures to be validated
*/
function executeSignatures(bytes message, bytes signatures) external {
Message.hasEnoughValidSignatures(message, signatures, validatorContract(), false);
address recipient;
uint256 amount;
bytes32 txHash;
address contractAddress;
(recipient, amount, txHash, contractAddress) = Message.parseMessage(message);
if (withinExecutionLimit(amount)) {
require(contractAddress == address(this));
require(!relayedMessages(txHash));
setRelayedMessages(txHash, true);
require(onExecuteMessage(recipient, amount, txHash));
emit RelayedMessage(recipient, amount, txHash);
} else {
onFailedMessage(recipient, amount, txHash);
}
}
/* solcov ignore next */
function onExecuteMessage(address, uint256, bytes32) internal returns (bool);
/* solcov ignore next */
function onFailedMessage(address, uint256, bytes32) internal;
}
// File: contracts/upgradeable_contracts/ERC20Bridge.sol
pragma solidity 0.4.24;
contract ERC20Bridge is BasicForeignBridge {
bytes32 internal constant ERC20_TOKEN = 0x15d63b18dbc21bf4438b7972d80076747e1d93c4f87552fe498c90cbde51665e; // keccak256(abi.encodePacked("erc20token"))
function erc20token() public view returns (ERC20) {
return ERC20(addressStorage[ERC20_TOKEN]);
}
function setErc20token(address _token) internal {
require(AddressUtils.isContract(_token));
addressStorage[ERC20_TOKEN] = _token;
}
function _relayTokens(address _sender, address _receiver, uint256 _amount) internal {
require(_receiver != address(0));
require(_receiver != address(this));
require(_amount > 0);
require(withinLimit(_amount));
setTotalSpentPerDay(getCurrentDay(), totalSpentPerDay(getCurrentDay()).add(_amount));
erc20token().transferFrom(_sender, address(this), _amount);
emit UserRequestForAffirmation(_receiver, _amount);
}
function relayTokens(address _from, address _receiver, uint256 _amount) external {
require(_from == msg.sender || _from == _receiver);
_relayTokens(_from, _receiver, _amount);
}
function relayTokens(address _receiver, uint256 _amount) external {
_relayTokens(msg.sender, _receiver, _amount);
}
}
// File: contracts/upgradeable_contracts/OtherSideBridgeStorage.sol
pragma solidity 0.4.24;
contract OtherSideBridgeStorage is EternalStorage {
bytes32 internal constant BRIDGE_CONTRACT = 0x71483949fe7a14d16644d63320f24d10cf1d60abecc30cc677a340e82b699dd2; // keccak256(abi.encodePacked("bridgeOnOtherSide"))
function _setBridgeContractOnOtherSide(address _bridgeContract) internal {
addressStorage[BRIDGE_CONTRACT] = _bridgeContract;
}
function bridgeContractOnOtherSide() internal view returns (address) {
return addressStorage[BRIDGE_CONTRACT];
}
}
// File: contracts/interfaces/IScdMcdMigration.sol
pragma solidity 0.4.24;
interface IScdMcdMigration {
function swapSaiToDai(uint256 wad) external;
function daiJoin() external returns (address);
}
interface IDaiAdapter {
function dai() public returns (address);
}
interface ISaiTop {
function caged() public returns (uint256);
}
// File: contracts/interfaces/IPot.sol
pragma solidity 0.4.24;
interface IPot {
function chi() external view returns (uint256);
function rho() external view returns (uint256);
function drip() external returns (uint256);
}
// File: contracts/interfaces/IChai.sol
pragma solidity 0.4.24;
interface IChai {
function pot() external view returns (IPot);
function daiToken() external view returns (ERC20);
function balanceOf(address) external view returns (uint256);
function dai(address) external view returns (uint256);
function join(address, uint256) external;
function draw(address, uint256) external;
function exit(address, uint256) external;
function transfer(address, uint256) external;
}
// File: contracts/interfaces/ERC677Receiver.sol
pragma solidity 0.4.24;
contract ERC677Receiver {
function onTokenTransfer(address _from, uint256 _value, bytes _data) external returns (bool);
}
// File: contracts/upgradeable_contracts/TokenSwapper.sol
pragma solidity 0.4.24;
contract TokenSwapper {
// emitted when two tokens is swapped (e. g. Sai to Dai, Chai to Dai)
event TokensSwapped(address indexed from, address indexed to, uint256 value);
}
// File: contracts/upgradeable_contracts/ChaiConnector.sol
pragma solidity 0.4.24;
/**
* @title ChaiConnector
* @dev This logic allows to use Chai token (https://github.com/dapphub/chai)
*/
contract ChaiConnector is Ownable, ERC20Bridge, TokenSwapper {
using SafeMath for uint256;
// emitted when specified value of Chai tokens is transfered to interest receiver
event PaidInterest(address to, uint256 value);
bytes32 internal constant CHAI_TOKEN_ENABLED = 0x2ae87563606f93f71ad2adf4d62661ccdfb63f3f508f94700934d5877fb92278; // keccak256(abi.encodePacked("chaiTokenEnabled"))
bytes32 internal constant INTEREST_RECEIVER = 0xd88509eb1a8da5d5a2fc7b9bad1c72874c9818c788e81d0bc46b29bfaa83adf6; // keccak256(abi.encodePacked("interestReceiver"))
bytes32 internal constant INTEREST_COLLECTION_PERIOD = 0x68a6a652d193e5d6439c4309583048050a11a4cfb263a220f4cd798c61c3ad6e; // keccak256(abi.encodePacked("interestCollectionPeriod"))
bytes32 internal constant LAST_TIME_INTEREST_PAID = 0xcabd46177a706f95f4bb3e2c2ba45ac4aa1eac9c545425a19c62ab6de4aeea26; // keccak256(abi.encodePacked("lastTimeInterestPaid"))
bytes32 internal constant INVESTED_AMOUNT = 0xb6afb3323c9d7dc0e9dab5d34c3a1d1ae7739d2224c048d4ee7675d3c759dd1b; // keccak256(abi.encodePacked("investedAmount"))
bytes32 internal constant MIN_DAI_TOKEN_BALANCE = 0xce70e1dac97909c26a87aa4ada3d490673a153b3a75b22ea3364c4c7df7c551f; // keccak256(abi.encodePacked("minDaiTokenBalance"))
bytes4 internal constant ON_TOKEN_TRANSFER = 0xa4c0ed36; // onTokenTransfer(address,uint256,bytes)
uint256 internal constant ONE = 10**27;
/**
* @dev Throws if chai token is not enabled
*/
modifier chaiTokenEnabled {
require(isChaiTokenEnabled());
/* solcov ignore next */
_;
}
/**
* @dev Fixed point division
* @return Ceiled value of x / y
*/
function rdivup(uint256 x, uint256 y) internal pure returns (uint256) {
return x.mul(ONE).add(y.sub(1)) / y;
}
/**
* @return true, if chai token is enabled
*/
function isChaiTokenEnabled() public view returns (bool) {
return boolStorage[CHAI_TOKEN_ENABLED];
}
/**
* @return Chai token contract address
*/
function chaiToken() public view returns (IChai) {
return IChai(0x06AF07097C9Eeb7fD685c692751D5C66dB49c215);
}
/**
* @dev Initializes chai token
*/
function initializeChaiToken() public onlyOwner {
require(!isChaiTokenEnabled());
require(address(chaiToken().daiToken()) == address(erc20token()));
boolStorage[CHAI_TOKEN_ENABLED] = true;
uintStorage[MIN_DAI_TOKEN_BALANCE] = 100 ether;
uintStorage[INTEREST_COLLECTION_PERIOD] = 1 weeks;
}
/**
* @dev Initializes chai token, with interestReceiver
* @param _interestReceiver Receiver address
*/
function initializeChaiToken(address _interestReceiver) external {
require(_interestReceiver != address(0));
// onlyOwner condition is checked inside this call, so it can be excluded from function definition
initializeChaiToken();
addressStorage[INTEREST_RECEIVER] = _interestReceiver;
}
/**
* @dev Sets minimum DAI limit, needed for converting DAI into CHAI
*/
function setMinDaiTokenBalance(uint256 _minBalance) external onlyOwner {
uintStorage[MIN_DAI_TOKEN_BALANCE] = _minBalance;
}
/**
* @dev Evaluates edge DAI token balance, which has an impact on the invest amounts
* @return Value in DAI
*/
function minDaiTokenBalance() public view returns (uint256) {
return uintStorage[MIN_DAI_TOKEN_BALANCE];
}
/**
* @dev Withdraws all invested tokens, pays remaining interest, removes chai token from contract storage
*/
function removeChaiToken() external onlyOwner chaiTokenEnabled {
_convertChaiToDai(investedAmountInDai());
_payInterest();
delete boolStorage[CHAI_TOKEN_ENABLED];
}
/**
* @return Configured address of a receiver
*/
function interestReceiver() public view returns (ERC677Receiver) {
return ERC677Receiver(addressStorage[INTEREST_RECEIVER]);
}
/**
* Updates interest receiver address
* @param receiver New receiver address
*/
function setInterestReceiver(address receiver) external onlyOwner {
// the bridge account is not allowed to receive an interest by the following reason:
// during the Chai to Dai convertion, the Dai is minted to the receiver account,
// the Transfer(address(0), bridgeAddress, value) is emitted during this process,
// something can go wrong in the oracle logic, so that it will process this event as a request to the bridge
// Instead, the interest can be transfered to any other account, and then converted to Dai,
// which won't be related to the oracle logic anymore
require(receiver != address(this));
addressStorage[INTEREST_RECEIVER] = receiver;
}
/**
* @return Timestamp of last interest payment
*/
function lastInterestPayment() public view returns (uint256) {
return uintStorage[LAST_TIME_INTEREST_PAID];
}
/**
* @return Configured minimum interest collection period
*/
function interestCollectionPeriod() public view returns (uint256) {
return uintStorage[INTEREST_COLLECTION_PERIOD];
}
/**
* @dev Configures minimum interest collection period
* @param period collection period
*/
function setInterestCollectionPeriod(uint256 period) external onlyOwner {
uintStorage[INTEREST_COLLECTION_PERIOD] = period;
}
/**
* @dev Pays all available interest, in Dai tokens.
* Upgradeability owner can call this method without time restrictions,
* for others, the method can be called only once a specified period.
*/
function payInterest() external chaiTokenEnabled {
if (
// solhint-disable-next-line not-rely-on-time
lastInterestPayment() + interestCollectionPeriod() < now ||
IUpgradeabilityOwnerStorage(this).upgradeabilityOwner() == msg.sender
) {
_payInterest();
}
}
/**
* @dev Internal function for paying all available interest, in Dai tokens
*/
function _payInterest() internal {
address receiver = address(interestReceiver());
require(receiver != address(0));
// since investedAmountInChai() returns a ceiled value,
// the value of chaiBalance() - investedAmountInChai() will be floored,
// leading to excess remaining chai balance
// solhint-disable-next-line not-rely-on-time
uintStorage[LAST_TIME_INTEREST_PAID] = now;
uint256 interest = chaiBalance().sub(investedAmountInChai());
// interest is paid in Chai, paying interest directly in Dai can cause an unwanter Transfer event
// see a comment in setInterestReceiver describing why we cannot pay interest to the bridge directly
chaiToken().transfer(receiver, interest);
receiver.call(abi.encodeWithSelector(ON_TOKEN_TRANSFER, address(this), interest, ""));
// Additional constant to tolerate the DAI balance deposited to the Chai token is not needed here, since we allow to withdraw only extra part of chai balance,
// which is not necessary to cover 100% dai balance.
// It is guaranteed that the withdrawal of interest won't left the bridge balance uncovered.
require(dsrBalance() >= investedAmountInDai());
emit PaidInterest(receiver, interest);
}
/**
* @dev Evaluates bridge balance for tokens, holded in DSR
* @return Balance in dai, truncated
*/
function dsrBalance() public view returns (uint256) {
return chaiToken().dai(address(this));
}
/**
* @dev Evaluates bridge balance in Chai tokens
* @return Balance in chai, exact
*/
function chaiBalance() public view returns (uint256) {
return chaiToken().balanceOf(address(this));
}
/**
* @dev Evaluates bridge balance in Dai tokens
* @return Balance in Dai
*/
function daiBalance() internal view returns (uint256) {
return erc20token().balanceOf(address(this));
}
/**
* @dev Evaluates exact current invested amount, in DAI
* @return Value in DAI
*/
function investedAmountInDai() public view returns (uint256) {
return uintStorage[INVESTED_AMOUNT];
}
/**
* @dev Updates current invested amount, in DAI
* @return Value in DAI
*/
function setInvestedAmountInDai(uint256 amount) internal {
uintStorage[INVESTED_AMOUNT] = amount;
}
/**
* @dev Evaluates amount of chai tokens that is sufficent to cover 100% of the invested DAI
* @return Amount in chai, ceiled
*/
function investedAmountInChai() internal returns (uint256) {
IPot pot = chaiToken().pot();
// solhint-disable-next-line not-rely-on-time
uint256 chi = (now > pot.rho()) ? pot.drip() : pot.chi();
return rdivup(investedAmountInDai(), chi);
}
/**
* @dev Checks if DAI balance is high enough to be partially converted to Chai
* Twice limit is used in order to decrease frequency of convertDaiToChai calls,
* In case of high bridge utilization in DAI => xDAI direction,
* convertDaiToChai() will be called as soon as DAI balance reaches 2 * limit,
* limit DAI will be left as a buffer for future operations.
* @return true if convertDaiToChai() call is needed to be performed by the oracle
*/
function isDaiNeedsToBeInvested() public view returns (bool) {
// chai token needs to be initialized, DAI balance should be at least twice greater than minDaiTokenBalance
return isChaiTokenEnabled() && daiBalance() > 2 * minDaiTokenBalance();
}
/**
* @dev Converts all DAI into Chai tokens, keeping minDaiTokenBalance() DAI as a buffer
*/
function convertDaiToChai() public chaiTokenEnabled {
// there is not need to consider overflow when performing a + operation,
// since both values are controlled by the bridge and can't take extremely high values
uint256 amount = daiBalance().sub(minDaiTokenBalance());
require(amount > 0); // revert and save gas if there is nothing to convert
uint256 newInvestedAmountInDai = investedAmountInDai() + amount;
setInvestedAmountInDai(newInvestedAmountInDai);
erc20token().approve(chaiToken(), amount);
chaiToken().join(address(this), amount);
// When evaluating the amount of DAI kept in Chai using dsrBalance(), there are some fixed point truncations.
// The dependency between invested amount of DAI - value and returned value of dsrBalance() - res is the following:
// res = floor(floor(value / K) * K)), where K is the fixed-point coefficient
// from MakerDAO Pot contract (K = pot.chi() / 10**27).
// This can lead up to losses of ceil(K) DAI in this balance evaluation.
// The constant is needed here for making sure that everything works fine, and this error is small enough
// The 10000 constant is considered to be small enough when decimals = 18, however,
// it is not recommended to use it for smaller values of decimals, since it won't be negligible anymore
require(dsrBalance() + 10000 >= newInvestedAmountInDai);
emit TokensSwapped(erc20token(), chaiToken(), amount);
}
/**
* @dev Redeems DAI from Chai, the total redeemed amount will be at least equal to specified amount
* @param amount Amount of DAI to redeem
*/
function _convertChaiToDai(uint256 amount) internal {
if (amount == 0) return;
uint256 invested = investedAmountInDai();
uint256 initialDaiBalance = daiBalance();
// onExecuteMessage can call a convert operation with argument greater than the current invested amount,
// in this case bridge should withdraw all invested funds
uint256 withdrawal = amount >= invested ? invested : amount;
chaiToken().draw(address(this), withdrawal);
uint256 redeemed = daiBalance() - initialDaiBalance;
// Make sure that at least withdrawal amount was withdrawn
require(redeemed >= withdrawal);
uint256 newInvested = invested > redeemed ? invested - redeemed : 0;
setInvestedAmountInDai(newInvested);
// see comment in convertDaiToChai() for similar statement
require(dsrBalance() + 10000 >= newInvested);
emit TokensSwapped(chaiToken(), erc20token(), redeemed);
}
}
// File: contracts/upgradeable_contracts/erc20_to_native/ForeignBridgeErcToNative.sol
pragma solidity 0.4.24;
contract ForeignBridgeErcToNative is BasicForeignBridge, ERC20Bridge, OtherSideBridgeStorage, ChaiConnector {
bytes32 internal constant MIN_HDTOKEN_BALANCE = 0x48649cf195feb695632309f41e61252b09f537943654bde13eb7bb1bca06964e; // keccak256(abi.encodePacked("minHDTokenBalance"))
bytes4 internal constant SWAP_TOKENS = 0x73d00224; // swapTokens()
function initialize(
address _validatorContract,
address _erc20token,
uint256 _requiredBlockConfirmations,
uint256 _gasPrice,
uint256[] _dailyLimitMaxPerTxMinPerTxArray, // [ 0 = _dailyLimit, 1 = _maxPerTx, 2 = _minPerTx ]
uint256[] _homeDailyLimitHomeMaxPerTxArray, //[ 0 = _homeDailyLimit, 1 = _homeMaxPerTx ]
address _owner,
uint256 _decimalShift,
address _bridgeOnOtherSide
) external onlyRelevantSender returns (bool) {
require(!isInitialized());
require(AddressUtils.isContract(_validatorContract));
require(_requiredBlockConfirmations != 0);
require(_gasPrice > 0);
require(
_dailyLimitMaxPerTxMinPerTxArray[2] > 0 && // _minPerTx > 0
_dailyLimitMaxPerTxMinPerTxArray[1] > _dailyLimitMaxPerTxMinPerTxArray[2] && // _maxPerTx > _minPerTx
_dailyLimitMaxPerTxMinPerTxArray[0] > _dailyLimitMaxPerTxMinPerTxArray[1] // _dailyLimit > _maxPerTx
);
require(_homeDailyLimitHomeMaxPerTxArray[1] < _homeDailyLimitHomeMaxPerTxArray[0]); // _homeMaxPerTx < _homeDailyLimit
require(_owner != address(0));
require(_bridgeOnOtherSide != address(0));
addressStorage[VALIDATOR_CONTRACT] = _validatorContract;
setErc20token(_erc20token);
uintStorage[DEPLOYED_AT_BLOCK] = block.number;
uintStorage[REQUIRED_BLOCK_CONFIRMATIONS] = _requiredBlockConfirmations;
uintStorage[GAS_PRICE] = _gasPrice;
uintStorage[DAILY_LIMIT] = _dailyLimitMaxPerTxMinPerTxArray[0];
uintStorage[MAX_PER_TX] = _dailyLimitMaxPerTxMinPerTxArray[1];
uintStorage[MIN_PER_TX] = _dailyLimitMaxPerTxMinPerTxArray[2];
uintStorage[EXECUTION_DAILY_LIMIT] = _homeDailyLimitHomeMaxPerTxArray[0];
uintStorage[EXECUTION_MAX_PER_TX] = _homeDailyLimitHomeMaxPerTxArray[1];
uintStorage[DECIMAL_SHIFT] = _decimalShift;
setOwner(_owner);
_setBridgeContractOnOtherSide(_bridgeOnOtherSide);
setInitialize();
emit RequiredBlockConfirmationChanged(_requiredBlockConfirmations);
emit GasPriceChanged(_gasPrice);
emit DailyLimitChanged(_dailyLimitMaxPerTxMinPerTxArray[0]);
emit ExecutionDailyLimitChanged(_homeDailyLimitHomeMaxPerTxArray[0]);
return isInitialized();
}
function getBridgeMode() external pure returns (bytes4 _data) {
return 0x18762d46; // bytes4(keccak256(abi.encodePacked("erc-to-native-core")))
}
function claimTokens(address _token, address _to) public {
require(_token != address(erc20token()));
// Chai token is not claimable if investing into Chai is enabled
require(_token != address(chaiToken()) || !isChaiTokenEnabled());
if (_token == address(halfDuplexErc20token())) {
// SCD is not claimable if the bridge accepts deposits of this token
// solhint-disable-next-line not-rely-on-time
require(!isTokenSwapAllowed(now));
}
super.claimTokens(_token, _to);
}
function onExecuteMessage(
address _recipient,
uint256 _amount,
bytes32 /*_txHash*/
) internal returns (bool) {
setTotalExecutedPerDay(getCurrentDay(), totalExecutedPerDay(getCurrentDay()).add(_amount));
uint256 amount = _amount.div(10**decimalShift());
uint256 currentBalance = tokenBalance(erc20token());
// Convert part of Chai tokens back to DAI, if DAI balance is insufficient.
// If Chai token is disabled, bridge will keep all funds directly in DAI token,
// so it will have enough funds to cover any xDai => Dai transfer,
// and currentBalance >= amount will always hold.
if (currentBalance < amount) {
_convertChaiToDai(amount.sub(currentBalance).add(minDaiTokenBalance()));
}
bool res = erc20token().transfer(_recipient, amount);
if (tokenBalance(halfDuplexErc20token()) > 0) {
address(this).call(abi.encodeWithSelector(SWAP_TOKENS));
}
return res;
}
function onFailedMessage(address, uint256, bytes32) internal {
revert();
}
function saiTopContract() internal pure returns (ISaiTop) {
return ISaiTop(0x9b0ccf7C8994E19F39b2B4CF708e0A7DF65fA8a3);
}
function isTokenSwapAllowed(
uint256 /* _ts */
) public pure returns (bool) {
return false;
}
function halfDuplexErc20token() public pure returns (ERC20) {
return ERC20(0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359);
}
function setMinHDTokenBalance(uint256 _minBalance) external onlyOwner {
uintStorage[MIN_HDTOKEN_BALANCE] = _minBalance;
}
function minHDTokenBalance() public view returns (uint256) {
return uintStorage[MIN_HDTOKEN_BALANCE];
}
function isHDTokenBalanceAboveMinBalance() public view returns (bool) {
if (tokenBalance(halfDuplexErc20token()) > minHDTokenBalance()) {
return true;
}
return false;
}
function tokenBalance(ERC20 _token) internal view returns (uint256) {
return _token.balanceOf(address(this));
}
function migrationContract() internal pure returns (IScdMcdMigration) {
return IScdMcdMigration(0xc73e0383F3Aff3215E6f04B0331D58CeCf0Ab849);
}
function swapTokens() public {
// solhint-disable-next-line not-rely-on-time
require(isTokenSwapAllowed(now));
IScdMcdMigration mcdMigrationContract = migrationContract();
ERC20 hdToken = halfDuplexErc20token();
ERC20 fdToken = erc20token();
uint256 curHDTokenBalance = tokenBalance(hdToken);
require(curHDTokenBalance > 0);
uint256 curFDTokenBalance = tokenBalance(fdToken);
require(hdToken.approve(mcdMigrationContract, curHDTokenBalance));
mcdMigrationContract.swapSaiToDai(curHDTokenBalance);
require(tokenBalance(fdToken).sub(curFDTokenBalance) == curHDTokenBalance);
emit TokensSwapped(hdToken, fdToken, curHDTokenBalance);
}
function relayTokens(address _receiver, uint256 _amount) external {
_relayTokens(msg.sender, _receiver, _amount, erc20token());
}
function relayTokens(address _sender, address _receiver, uint256 _amount) external {
relayTokens(_sender, _receiver, _amount, erc20token());
}
function relayTokens(address _from, address _receiver, uint256 _amount, address _token) public {
require(_from == msg.sender || _from == _receiver);
_relayTokens(_from, _receiver, _amount, _token);
}
function relayTokens(address _receiver, uint256 _amount, address _token) external {
_relayTokens(msg.sender, _receiver, _amount, _token);
}
function _relayTokens(address _sender, address _receiver, uint256 _amount, address _token) internal {
require(_receiver != bridgeContractOnOtherSide());
require(_receiver != address(0));
require(_receiver != address(this));
require(_amount > 0);
require(withinLimit(_amount));
ERC20 tokenToOperate = ERC20(_token);
ERC20 hdToken = halfDuplexErc20token();
ERC20 fdToken = erc20token();
if (tokenToOperate == ERC20(0x0)) {
tokenToOperate = fdToken;
}
require(tokenToOperate == fdToken || tokenToOperate == hdToken);
setTotalSpentPerDay(getCurrentDay(), totalSpentPerDay(getCurrentDay()).add(_amount));
tokenToOperate.transferFrom(_sender, address(this), _amount);
emit UserRequestForAffirmation(_receiver, _amount);
if (tokenToOperate == hdToken) {
swapTokens();
}
if (isDaiNeedsToBeInvested()) {
convertDaiToChai();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment