Omnibridge usage of manual/oracle-driven lanes (#547)

This commit is contained in:
Kirill Fedoseev 2020-11-05 18:22:56 +03:00 committed by GitHub
parent 1748f94757
commit 09d228fa72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 232 additions and 26 deletions

View File

@ -42,6 +42,14 @@ contract AMBMock {
}
function requireToPassMessage(address _contract, bytes _data, uint256 _gas) external returns (bytes32) {
return _sendMessage(_contract, _data, _gas, 0x00);
}
function requireToConfirmMessage(address _contract, bytes _data, uint256 _gas) external returns (bytes32) {
return _sendMessage(_contract, _data, _gas, 0xf0);
}
function _sendMessage(address _contract, bytes _data, uint256 _gas, uint256 _dataType) internal returns (bytes32) {
require(messageId == bytes32(0));
bytes32 bridgeId = keccak256(abi.encodePacked(uint16(1337), address(this))) &
0x00000000ffffffffffffffffffffffffffffffffffffffff0000000000000000;
@ -55,7 +63,7 @@ contract AMBMock {
uint32(_gas),
uint8(2),
uint8(2),
uint8(0x00),
uint8(_dataType),
uint16(1337),
uint16(1338),
_data

View File

@ -61,8 +61,8 @@ contract HomeAMBErc20ToNative is BasicAMBErc20ToNative, BlockRewardBridge, HomeF
* @param _decimalShift number of decimals shift required to adjust the amount of tokens bridged.
* @param _owner address of the owner of the mediator contract
* @param _blockReward address of the block reward contract
* @param _rewardAddreses list of reward addresses, between whom fees will be distributed
* @param _fees array with initial fees for both bridge firections
* @param _rewardAddresses list of reward addresses, between whom fees will be distributed
* @param _fees array with initial fees for both bridge directions
* [ 0 = homeToForeignFee, 1 = foreignToHomeFee ]
*/
function rewardableInitialize(
@ -74,10 +74,10 @@ contract HomeAMBErc20ToNative is BasicAMBErc20ToNative, BlockRewardBridge, HomeF
int256 _decimalShift,
address _owner,
address _blockReward,
address[] _rewardAddreses,
address[] _rewardAddresses,
uint256[2] _fees // [ 0 = homeToForeignFee, 1 = foreignToHomeFee ]
) external returns (bool) {
_setRewardAddressList(_rewardAddreses);
_setRewardAddressList(_rewardAddresses);
_setFee(HOME_TO_FOREIGN_FEE, _fees[0]);
_setFee(FOREIGN_TO_HOME_FEE, _fees[1]);
return

View File

@ -58,7 +58,7 @@ contract BasicMultiAMBErc20ToErc677 is
* @return patch value of the version
*/
function getBridgeInterfacesVersion() external pure returns (uint64 major, uint64 minor, uint64 patch) {
return (1, 1, 1);
return (1, 2, 0);
}
/**

View File

@ -4,13 +4,18 @@ import "./BasicMultiAMBErc20ToErc677.sol";
import "./TokenProxy.sol";
import "./HomeFeeManagerMultiAMBErc20ToErc677.sol";
import "../../interfaces/IBurnableMintableERC677Token.sol";
import "./MultiTokenForwardingRules.sol";
/**
* @title HomeMultiAMBErc20ToErc677
* @dev Home side implementation for multi-erc20-to-erc677 mediator intended to work on top of AMB bridge.
* It is designed to be used as an implementation contract of EternalStorageProxy contract.
*/
contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, HomeFeeManagerMultiAMBErc20ToErc677 {
contract HomeMultiAMBErc20ToErc677 is
BasicMultiAMBErc20ToErc677,
HomeFeeManagerMultiAMBErc20ToErc677,
MultiTokenForwardingRules
{
bytes32 internal constant TOKEN_IMAGE_CONTRACT = 0x20b8ca26cc94f39fab299954184cf3a9bd04f69543e4f454fab299f015b8130f; // keccak256(abi.encodePacked("tokenImageContract"))
event NewTokenRegistered(address indexed foreignToken, address indexed homeToken);
@ -26,8 +31,8 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, HomeFeeManager
* @param _requestGasLimit the gas limit for the message execution.
* @param _owner address of the owner of the mediator contract.
* @param _tokenImage address of the PermittableToken contract that will be used for deploying of new tokens.
* @param _rewardAddreses list of reward addresses, between whom fees will be distributed.
* @param _fees array with initial fees for both bridge firections.
* @param _rewardAddresses list of reward addresses, between whom fees will be distributed.
* @param _fees array with initial fees for both bridge directions.
* [ 0 = homeToForeignFee, 1 = foreignToHomeFee ]
*/
function initialize(
@ -38,7 +43,7 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, HomeFeeManager
uint256 _requestGasLimit,
address _owner,
address _tokenImage,
address[] _rewardAddreses,
address[] _rewardAddresses,
uint256[2] _fees // [ 0 = homeToForeignFee, 1 = foreignToHomeFee ]
) external onlyRelevantSender returns (bool) {
require(!isInitialized());
@ -50,8 +55,8 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, HomeFeeManager
_setRequestGasLimit(_requestGasLimit);
_setOwner(_owner);
_setTokenImage(_tokenImage);
if (_rewardAddreses.length > 0) {
_setRewardAddressList(_rewardAddreses);
if (_rewardAddresses.length > 0) {
_setRewardAddressList(_rewardAddresses);
}
_setFee(HOME_TO_FOREIGN_FEE, address(0), _fees[0]);
_setFee(FOREIGN_TO_HOME_FEE, address(0), _fees[1]);
@ -242,26 +247,26 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, HomeFeeManager
* @dev Executes action on withdrawal of bridged tokens
* @param _token address of token contract
* @param _from address of tokens sender
* @param _value requsted amount of bridged tokens
* @param _value requested amount of bridged tokens
* @param _data alternative receiver, if specified
*/
function bridgeSpecificActionsOnTokenTransfer(ERC677 _token, address _from, uint256 _value, bytes _data) internal {
if (!lock()) {
bytes32 _messageId = messageId();
uint256 valueToBridge = _value;
uint256 fee = 0;
// Next line disables fee collection in case sender is one of the reward addresses.
// It is needed to allow a 100% withdrawal of tokens from the home side.
// If fees are not disabled for reward receivers, small fraction of tokens will always
// be redistributed between the same set of reward addresses, which is not the desired behaviour.
if (!isRewardAddress(_from)) {
uint256 fee = _distributeFee(HOME_TO_FOREIGN_FEE, _token, valueToBridge);
if (fee > 0) {
valueToBridge = valueToBridge.sub(fee);
emit FeeDistributed(fee, _token, _messageId);
}
fee = _distributeFee(HOME_TO_FOREIGN_FEE, _token, valueToBridge);
valueToBridge = valueToBridge.sub(fee);
}
IBurnableMintableERC677Token(_token).burn(valueToBridge);
passMessage(_token, _from, chooseReceiver(_from, _data), valueToBridge);
bytes32 _messageId = passMessage(_token, _from, chooseReceiver(_from, _data), valueToBridge);
if (fee > 0) {
emit FeeDistributed(fee, _token, _messageId);
}
}
}
@ -273,20 +278,27 @@ contract HomeMultiAMBErc20ToErc677 is BasicMultiAMBErc20ToErc677, HomeFeeManager
* @param _from address of sender, if bridge operation fails, tokens will be returned to this address
* @param _receiver address of receiver on the other side, will eventually receive bridged tokens
* @param _value bridged amount of tokens
* @return id of the created and passed message
*/
function passMessage(ERC677 _token, address _from, address _receiver, uint256 _value) internal {
function passMessage(ERC677 _token, address _from, address _receiver, uint256 _value) internal returns (bytes32) {
bytes4 methodSelector = this.handleBridgedTokens.selector;
address foreignToken = foreignTokenAddress(_token);
bytes memory data = abi.encodeWithSelector(methodSelector, foreignToken, _receiver, _value);
bytes32 _messageId = bridgeContract().requireToPassMessage(
mediatorContractOnOtherSide(),
data,
requestGasLimit()
);
address executor = mediatorContractOnOtherSide();
uint256 gasLimit = requestGasLimit();
IAMB bridge = bridgeContract();
// Address of the foreign token is used here for determining lane permissions.
// Such decision makes it possible to set rules for tokens that are not bridged yet.
bytes32 _messageId = destinationLane(foreignToken, _from, _receiver) >= 0
? bridge.requireToPassMessage(executor, data, gasLimit)
: bridge.requireToConfirmMessage(executor, data, gasLimit);
setMessageToken(_messageId, _token);
setMessageValue(_messageId, _value);
setMessageRecipient(_messageId, _from);
return _messageId;
}
}

View File

@ -0,0 +1,130 @@
pragma solidity 0.4.24;
import "../../upgradeable_contracts/Ownable.sol";
/**
* @title MultiTokenForwardingRules
* @dev Multi token mediator functionality for managing destination AMB lanes permissions.
*/
contract MultiTokenForwardingRules is Ownable {
address internal constant ANY_ADDRESS = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF;
event ForwardingRuleUpdated(address token, address sender, address receiver, int256 lane);
/**
* @dev Tells the destination lane for a particular bridge operation by checking several wildcard forwarding rules.
* @param _token address of the token contract on the foreign side of the bridge.
* @param _sender address of the tokens sender on the home side of the bridge.
* @param _receiver address of the tokens receiver on the foreign side of the bridge.
* @return destination lane identifier, where the message should be forwarded to.
* 1 - oracle-driven-lane should be used.
* 0 - default behaviour should be applied.
* -1 - manual lane should be used.
*/
function destinationLane(address _token, address _sender, address _receiver) public view returns (int256) {
int256 defaultLane = forwardingRule(_token, ANY_ADDRESS, ANY_ADDRESS); // specific token for all senders and receivers
int256 lane;
if (defaultLane < 0) {
lane = forwardingRule(_token, _sender, ANY_ADDRESS); // specific token for specific sender
if (lane != 0) return lane;
lane = forwardingRule(_token, ANY_ADDRESS, _receiver); // specific token for specific receiver
if (lane != 0) return lane;
return defaultLane;
}
lane = forwardingRule(ANY_ADDRESS, _sender, ANY_ADDRESS); // all tokens for specific sender
if (lane != 0) return lane;
return forwardingRule(ANY_ADDRESS, ANY_ADDRESS, _receiver); // all tokens for specific receiver
}
/**
* Updates the forwarding rule for bridging specific token.
* Only owner can call this method.
* @param _token address of the token contract on the foreign side.
* @param _enable true, if bridge operations for a given token should be forwarded to the manual lane.
*/
function setTokenForwardingRule(address _token, bool _enable) external {
require(_token != ANY_ADDRESS);
_setForwardingRule(_token, ANY_ADDRESS, ANY_ADDRESS, _enable ? int256(-1) : int256(0));
}
/**
* Allows a particular address to send bridge requests to the oracle-driven lane for a particular token.
* Only owner can call this method.
* @param _token address of the token contract on the foreign side.
* @param _sender address of the tokens sender on the home side of the bridge.
* @param _enable true, if bridge operations for a given token and sender should be forwarded to the oracle-driven lane.
*/
function setSenderExceptionForTokenForwardingRule(address _token, address _sender, bool _enable) external {
require(_token != ANY_ADDRESS);
require(_sender != ANY_ADDRESS);
_setForwardingRule(_token, _sender, ANY_ADDRESS, _enable ? int256(1) : int256(0));
}
/**
* Allows a particular address to receive bridged tokens from the oracle-driven lane for a particular token.
* Only owner can call this method.
* @param _token address of the token contract on the foreign side.
* @param _receiver address of the tokens receiver on the foreign side of the bridge.
* @param _enable true, if bridge operations for a given token and receiver should be forwarded to the oracle-driven lane.
*/
function setReceiverExceptionForTokenForwardingRule(address _token, address _receiver, bool _enable) external {
require(_token != ANY_ADDRESS);
require(_receiver != ANY_ADDRESS);
_setForwardingRule(_token, ANY_ADDRESS, _receiver, _enable ? int256(1) : int256(0));
}
/**
* Updates the forwarding rule for the specific sender.
* Only owner can call this method.
* @param _sender address of the tokens sender on the home side.
* @param _enable true, if all bridge operations from a given sender should be forwarded to the manual lane.
*/
function setSenderForwardingRule(address _sender, bool _enable) external {
require(_sender != ANY_ADDRESS);
_setForwardingRule(ANY_ADDRESS, _sender, ANY_ADDRESS, _enable ? int256(-1) : int256(0));
}
/**
* Updates the forwarding rule for the specific receiver.
* Only owner can call this method.
* @param _receiver address of the tokens receiver on the foreign side.
* @param _enable true, if all bridge operations to a given receiver should be forwarded to the manual lane.
*/
function setReceiverForwardingRule(address _receiver, bool _enable) external {
require(_receiver != ANY_ADDRESS);
_setForwardingRule(ANY_ADDRESS, ANY_ADDRESS, _receiver, _enable ? int256(-1) : int256(0));
}
/**
* @dev Tells forwarding rule set up for a particular bridge operation.
* @param _token address of the token contract on the foreign side of the bridge.
* @param _sender address of the tokens sender on the home side of the bridge.
* @param _receiver address of the tokens receiver on the foreign side of the bridge.
* @return preferred destination lane for the particular bridge operation.
*/
function forwardingRule(address _token, address _sender, address _receiver) public view returns (int256) {
return intStorage[keccak256(abi.encodePacked("forwardTo", _token, _sender, _receiver))];
}
/**
* @dev Internal function for updating the preferred destination lane for the specific wildcard pattern.
* Only owner can call this method.
* Examples:
* _setForwardingRule(tokenA, ANY_ADDRESS, ANY_ADDRESS, -1) - forward all operations on tokenA to the manual lane
* _setForwardingRule(tokenA, Alice, ANY_ADDRESS, 1) - allow Alice to use the oracle-driven lane for bridging tokenA
* _setForwardingRule(tokenA, ANY_ADDRESS, Bob, 1) - forward all tokenA bridge operations, where Bob is the receiver, to the oracle-driven lane
* _setForwardingRule(ANY_ADDRESS, Mallory, ANY_ADDRESS, -1) - forward all bridge operations from Mallory to the manual lane
* @param _token address of the token contract on the foreign side of the bridge.
* @param _sender address of the tokens sender on the home side of the bridge.
* @param _receiver address of the tokens receiver on the foreign side of the bridge.
* @param _lane preferred destination lane for the particular sender.
* 1 - forward to the oracle-driven lane.
* 0 - behaviour is unset, proceed by checking other less-specific rules.
* -1 - manual lane should be used.
*/
function _setForwardingRule(address _token, address _sender, address _receiver, int256 _lane) internal onlyOwner {
intStorage[keccak256(abi.encodePacked("forwardTo", _token, _sender, _receiver))] = _lane;
emit ForwardingRuleUpdated(_token, _sender, _receiver, _lane);
}
}

View File

@ -995,6 +995,62 @@ contract('HomeMultiAMBErc20ToErc677', async accounts => {
})
}
})
describe('oracle driven lane permissions', () => {
it('should allow to set/update lane permissions', async () => {
expect(await contract.destinationLane(token.address, user, user2)).to.be.bignumber.equal('0')
await contract.setTokenForwardingRule(token.address, true, { from: user }).should.be.rejected
await contract.setTokenForwardingRule(token.address, true, { from: owner }).should.be.fulfilled
expect(await contract.destinationLane(token.address, user, user2)).to.be.bignumber.equal('-1')
await contract.setSenderExceptionForTokenForwardingRule(token.address, user, true, { from: user }).should.be
.rejected
await contract.setSenderExceptionForTokenForwardingRule(token.address, user, true, { from: owner }).should.be
.fulfilled
expect(await contract.destinationLane(token.address, user, user2)).to.be.bignumber.equal('1')
expect(await contract.destinationLane(token.address, user2, user2)).to.be.bignumber.equal('-1')
await contract.setSenderExceptionForTokenForwardingRule(token.address, user, false, { from: owner }).should.be
.fulfilled
await contract.setReceiverExceptionForTokenForwardingRule(token.address, user, true, { from: user }).should.be
.rejected
await contract.setReceiverExceptionForTokenForwardingRule(token.address, user, true, { from: owner }).should.be
.fulfilled
expect(await contract.destinationLane(token.address, user, user)).to.be.bignumber.equal('1')
expect(await contract.destinationLane(token.address, user, user2)).to.be.bignumber.equal('-1')
await contract.setTokenForwardingRule(token.address, false, { from: owner }).should.be.fulfilled
expect(await contract.destinationLane(token.address, user2, user2)).to.be.bignumber.equal('0')
await contract.setSenderForwardingRule(user2, true, { from: user }).should.be.rejected
await contract.setSenderForwardingRule(user2, true, { from: owner }).should.be.fulfilled
expect(await contract.destinationLane(token.address, user2, user2)).to.be.bignumber.equal('-1')
await contract.setReceiverForwardingRule(user2, true, { from: user }).should.be.rejected
await contract.setReceiverForwardingRule(user2, true, { from: owner }).should.be.fulfilled
expect(await contract.destinationLane(token.address, user, user2)).to.be.bignumber.equal('-1')
})
it('should send a message to the manual lane', async () => {
homeToken = await bridgeToken(token)
await homeToken.transferAndCall(contract.address, ether('0.1'), '0x', { from: user }).should.be.fulfilled
await contract.setTokenForwardingRule(token.address, true, { from: owner }).should.be.fulfilled
await homeToken.transferAndCall(contract.address, ether('0.1'), '0x', { from: user }).should.be.fulfilled
const events = await getEvents(ambBridgeContract, { event: 'MockedEvent' })
expect(events.length).to.be.equal(2)
expect(strip0x(events[0].returnValues.encodedData).slice(156, 158)).to.be.equal('00')
expect(strip0x(events[1].returnValues.encodedData).slice(156, 158)).to.be.equal('f0')
})
})
})
describe('fees management', () => {