-
Notifications
You must be signed in to change notification settings - Fork 229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Arbitrary Message Bridging in the subsidized mode #73
Comments
Great job @akolotov , the approach looks really good. I only want to mention one trade-off regarding the name of functions and events. On Javascript, normally to call a method you would do something like Given:
The second and third functions have the same amount of parameters but with different types.
which is not very straightforward. Something similar happens to Events names. Given:
To listen to the events on the contracts you won't be able to use the name of the events, you would have to get the signature hash and use it to filter by topic which also is not very straightforward. I think it would be a good idea to consider adding specific names for each event and method to avoid this complexity. |
With respect to compensation, would it make sense to use Example: Let's say I use AMB to send a transaction to the foreign network. I set some gas limit but for some reason the method ends up using a lot less than that. With this implementation, the balance will be reduced by the whole amount, which may be unfair to the user. The problem is that In code, this would be something like: uint256 fee = (PASS_MESSAGE_GAS + _gas) * tx.gasprice;
require(balanceOf[_sender] >= fee);
require(address(this).balance >= fee);
require(_contract.call.gas(_gas)(_data));
fee = fee - (gasleft() * tx.gasprice)
balanceOf[_sender] = balanceOf[_sender] - fee;
msg.sender.transfer(fee); Does this make sense? |
@patitonar thanks for comment. The main reason to use the same name for different events is to simplify watcher on the bridge application side. I would avoid listening bunch of events event if they are for the same action - to relay data to another side of the bridge. contract TestContract0 {
event TestEvent(bytes data);
function EmitEvent() public {
address someAddress = msg.sender;
bytes32 someArbitraryData = keccak256(abi.encodePacked(someAddress));
uint256 someInt = block.timestamp;
bytes1 someByte = bytes1(keccak256(abi.encodePacked(someArbitraryData)));
//data: 0x000000000000000000000000ca35b7d915458ef540ade6068dfe2f44e8fa733cb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03
emit TestEvent(abi.encode(someAddress, someArbitraryData));
//data: 0x000000000000000000000000ca35b7d915458ef540ade6068dfe2f44e8fa733cb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03000000000000000000000000000000000000000000000000000000005b877705
emit TestEvent(abi.encode(someAddress, someArbitraryData, someInt));
//data: 0x000000000000000000000000ca35b7d915458ef540ade6068dfe2f44e8fa733cb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03cd00000000000000000000000000000000000000000000000000000000000000
emit TestEvent(abi.encode(someAddress, someArbitraryData, someByte));
//data: 0xca35b7d915458ef540ade6068dfe2f44e8fa733cb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03
emit TestEvent(abi.encodePacked(someAddress, someArbitraryData));
//data: 0xca35b7d915458ef540ade6068dfe2f44e8fa733cb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03000000000000000000000000000000000000000000000000000000005b877705
emit TestEvent(abi.encodePacked(someAddress, someArbitraryData, someInt));
//data: 0xca35b7d915458ef540ade6068dfe2f44e8fa733cb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03cd
emit TestEvent(abi.encodePacked(someAddress, someArbitraryData, someByte));
}
} This approach will increase gas usage of the corresponding methods but not significantly: look at three methods below and corresponding comments how much they consume. We see there that usage contract TestContract1 {
event TestEvent01(address, bytes32, uint256, bytes1);
event TestEvent02(bytes data);
//Consumes 24281 gas on Sokol-testnet
function EmitEvent01() public {
address someAddress = msg.sender;
bytes32 someArbitraryData = keccak256(abi.encodePacked(someAddress));
uint256 someInt = block.timestamp;
bytes1 someByte = bytes1(keccak256(abi.encodePacked(someArbitraryData)));
emit TestEvent01(someAddress, someArbitraryData, someInt, someByte);
}
//Consumes 25293 gas on Sokol-testnet
function EmitEvent02() public {
address someAddress = msg.sender;
bytes32 someArbitraryData = keccak256(abi.encodePacked(someAddress));
uint256 someInt = block.timestamp;
bytes1 someByte = bytes1(keccak256(abi.encodePacked(someArbitraryData)));
emit TestEvent02(abi.encode(someAddress, someArbitraryData, someInt, someByte));
}
//Consumes 25102 gas on Sokol-testnet
function EmitEvent02WithPackedData() public {
address someAddress = msg.sender;
bytes32 someArbitraryData = keccak256(abi.encodePacked(someAddress));
uint256 someInt = block.timestamp;
bytes1 someByte = bytes1(keccak256(abi.encodePacked(someArbitraryData)));
emit TestEvent02(abi.encodePacked(someAddress, someArbitraryData, someInt, someByte));
}
} The main reason to have the same names for methods is similar: it should simplify the logic of the bridge:
contract TestContract2 {
event TestEvent(address, bytes32, uint256, bytes1);
function parseMessage(bytes _data)
internal
pure
returns(address someAddress, bytes32 someArbitraryData, uint256 someInt, bytes1 someByte)
{
assembly {
someAddress := and(mload(add(_data, 20)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
someArbitraryData := mload(add(_data, 52))
someInt := mload(add(_data, 84))
someByte := and(mload(add(_data, 116)), 0xFF00000000000000000000000000000000000000000000000000000000000000)
}
}
//Passed: "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c", "0xb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03", 1535604485, "0xcd"
//Consumes 27686 gas on Sokol-testnet
function getParameters(address someAddress, bytes32 someArbitraryData, uint256 someInt, bytes1 someByte) public {
emit TestEvent(someAddress, someArbitraryData, someInt, someByte);
}
//Passed: "0xca35b7d915458ef540ade6068dfe2f44e8fa733cb1591967aed668a4b27645ff40c444892d91bf5951b382995d4d4f6ee3a2ce03000000000000000000000000000000000000000000000000000000005b877705cd"
//Consumes 28224 gas on Sokol-testnet
function getParametersFromData(bytes _data) public {
address someAddress;
bytes32 someArbitraryData;
uint256 someInt;
bytes1 someByte;
(someAddress, someArbitraryData, someInt, someByte) = parseMessage(_data);
emit TestEvent(someAddress, someArbitraryData, someInt, someByte);
}
} |
Bearing in mind described in the previous comment here is the next version for Home and Foreign bridge contracts: pragma solidity ^0.4.24;
contract ContractOnHome {
HomeBridge homeBridge;
ForeignBridge foreignBridge;
ContractOnForeign darkSide;
uint256 constant WITHDRAW_GAS_USAGE = 100000; // must be provided by the bridge operator
uint256 constant SETVALUE_GAS_USAGE = 90000; // must be provided by the application contract
uint256 constant FOREIGN_GAS_PRICE = 1; //1 wei - gasprice in JS VM
constructor(HomeBridge _bridgeAtHome, ForeignBridge _bridgeAtForeign, ContractOnForeign _contract) public {
homeBridge = _bridgeAtHome;
foreignBridge = _bridgeAtForeign;
darkSide = _contract;
}
function depositClearanceOnForeign(address _depostRecipient) public {
require(_depostRecipient != address(0));
bytes4 methodSelector = ForeignBridge(0).withdrawFromDeposit.selector;
bytes memory encodedData = abi.encodeWithSelector(methodSelector, _depostRecipient);
homeBridge.requireToPassMessage(foreignBridge,
encodedData,
WITHDRAW_GAS_USAGE,
FOREIGN_GAS_PRICE);
}
function setValueOnForeign(uint256 _i) public {
bytes4 methodSelector = ContractOnForeign(0).setValue.selector;
bytes memory encodedData = abi.encodeWithSelector(methodSelector, _i);
homeBridge.requireToPassMessage(darkSide,
encodedData,
SETVALUE_GAS_USAGE,
FOREIGN_GAS_PRICE);
}
}
contract HomeBridge {
bytes4 constant SUBSIDIZED_MODE = bytes4(keccak256("AMB-subsidized-mode"));
bytes4 constant DEFRAYAL_MODE = bytes4(keccak256("AMB-defrayal-mode"));
bytes4 public foreignBridgeMode = DEFRAYAL_MODE;
function setSubsidizedModeForForeign() public {
foreignBridgeMode = SUBSIDIZED_MODE;
}
function setDefrayalModeForForeign() public {
foreignBridgeMode = DEFRAYAL_MODE;
}
event RequestForSignature(bytes encodedData);
function requireToPassMessage(address _contract, bytes _data, uint256 _gas) public {
require(foreignBridgeMode == SUBSIDIZED_MODE);
emit RequestForSignature(abi.encodePacked(msg.sender, _contract, _gas, uint8(0x00), _data));
}
function requireToPassMessage(address _contract, bytes _data, uint256 _gas, uint256 _gasPrice) public {
if (foreignBridgeMode == SUBSIDIZED_MODE)
emit RequestForSignature(abi.encodePacked(msg.sender, _contract, _gas, uint8(0x00), _data));
else
emit RequestForSignature(abi.encodePacked(msg.sender, _contract, _gas, uint8(0x01), _gasPrice, _data));
}
function requireToPassMessage(address _contract, bytes _data, uint256 _gas, bytes1 _oracleGasPriceSpeed) public {
if (foreignBridgeMode == SUBSIDIZED_MODE)
emit RequestForSignature(abi.encodePacked(msg.sender, _contract, _gas, uint8(0x00), _data));
else
emit RequestForSignature(abi.encodePacked(msg.sender, _contract, _gas, uint8(0x02), _oracleGasPriceSpeed, _data));
}
}
contract ForeignBridge {
bytes4 constant SUBSIDIZED_MODE = bytes4(keccak256("AMB-subsidized-mode"));
bytes4 constant DEFRAYAL_MODE = bytes4(keccak256("AMB-defrayal-mode"));
uint256 constant PASS_MESSAGE_GAS = 100000;
mapping (address => uint256) public balanceOf;
bytes4 public foreignBridgeMode = DEFRAYAL_MODE;
address accountForAction = address(0);
function setSubsidizedModeForForeign() public {
foreignBridgeMode = SUBSIDIZED_MODE;
}
function setDefrayalModeForForeign() public {
foreignBridgeMode = DEFRAYAL_MODE;
}
function withdrawFromDeposit(address _recipient) public {
require(msg.sender == address(this));
require(accountForAction != address(0));
require(balanceOf[accountForAction] > 0);
uint256 withdrawValue = balanceOf[accountForAction];
balanceOf[accountForAction] = 0;
_recipient.transfer(withdrawValue);
accountForAction = address(0);
}
function depositForContractSender(address _contract) public payable {
require(_contract != address(0));
balanceOf[_contract] = balanceOf[_contract] + msg.value;
}
function isWithdrawFromDepositSelector(bytes _data) internal pure returns(bool _retval) {
_retval = false;
bytes4 withdrawFromDepositSelector = this.withdrawFromDeposit.selector;
if ((_data[0] == withdrawFromDepositSelector[0]) &&
(_data[1] == withdrawFromDepositSelector[1]) &&
(_data[2] == withdrawFromDepositSelector[2]) &&
(_data[3] == withdrawFromDepositSelector[3])) {
_retval = true;
}
}
function _passMessage(address _sender, address _contract, bytes _data, uint256 _gas) internal {
if (_contract == address(this)) {
//Special check to handle invocation of withdrawFromDeposit
if (isWithdrawFromDepositSelector(_data)) {
accountForAction = _sender;
}
}
require(_contract.call.gas(_gas)(_data));
}
function _defrayAndPassMessage(address _sender, address _contract, bytes _data, uint256 _gas) internal {
uint256 fee = (PASS_MESSAGE_GAS + _gas) * tx.gasprice;
require(balanceOf[_sender] >= fee);
require(address(this).balance >= fee);
balanceOf[_sender] = balanceOf[_sender] - fee;
_passMessage(_sender, _contract, _data, _gas);
msg.sender.transfer(fee);
}
function unpackData(bytes _data)
internal
pure
returns(address sender, address executor, uint256 gasLimit, bytes1 dataType, uint256 gasPrice, bytes1 oracleGasPriceSpeed, bytes memory data)
{
uint256 datasize;
uint256 srcdataptr = 20 + 20 + 32 + 1; //20 (sender) + 20 (executor) + 32 (gasPrice) + 1 (dataType)
assembly {
sender := and(mload(add(_data, 20)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
executor := and(mload(add(_data, 40)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
gasLimit := mload(add(_data, 72))
dataType := and(mload(add(_data, 104)), 0xFF00000000000000000000000000000000000000000000000000000000000000)
switch dataType
case 0x0000000000000000000000000000000000000000000000000000000000000000 {
gasPrice := 0
oracleGasPriceSpeed := 0x00
}
case 0x0100000000000000000000000000000000000000000000000000000000000000 {
gasPrice := mload(add(_data, 105))
oracleGasPriceSpeed := 0x00
srcdataptr := add(srcdataptr, 0x20)
}
case 0x0200000000000000000000000000000000000000000000000000000000000000 {
gasPrice := 0
oracleGasPriceSpeed := and(mload(add(_data, 105)), 0xFF00000000000000000000000000000000000000000000000000000000000000)
srcdataptr := add(srcdataptr, 0x01)
}
default {
revert(0, 1)
}
datasize := sub(mload(_data), srcdataptr)
}
data = new bytes(datasize);
assembly {
let dataptr := add(data, /*BYTES_HEADER_SIZE*/32)
calldatacopy(dataptr, add(68, srcdataptr), datasize) //68 = 4 (selector) + 32 (bytes header) + 32 (bytes length)
}
}
function executeSignatures(bytes _data) public {
address sender;
address executor;
uint256 gasLimit;
bytes1 dataType;
uint256 gasPrice;
bytes1 oracleGasPriceSpeed;
bytes memory data;
(sender, executor, gasLimit, dataType, gasPrice, oracleGasPriceSpeed, data) = unpackData(_data);
if (dataType == 0x00) {
require(foreignBridgeMode == SUBSIDIZED_MODE);
_passMessage(sender, executor, data, gasLimit);
} else if (dataType == 0x01) {
require(foreignBridgeMode == DEFRAYAL_MODE);
require(gasPrice == tx.gasprice);
_defrayAndPassMessage(sender, executor, data, gasLimit);
} else if (dataType == 0x02) {
require(foreignBridgeMode == DEFRAYAL_MODE);
_defrayAndPassMessage(sender, executor, data, gasLimit);
} else {
revert();
}
}
}
contract ContractOnForeign {
uint256 public i;
function setValue(uint256 _i) public {
i = _i;
}
} |
@fvictorio thanks for the suggestion. Yes, it could be implemented. But there are two things we need keep in mind here:
But if you could suggest some other ideas how validators could get reward and it will consistent with another bridge functionality, I am open for any suggestions! |
@akolotov Here it is mentioned that the logic to send/receive messages is going to be implemented on both sides of the bridge.
An other question about this, Are both sides going to work with the same mode? Or for example is there a possibility that passing messages from Home to Foreign could work on |
@patitonar thanks for asking |
@akolotov here are some comments that came out:
|
@patitonar thanks for questions.
|
It is suggested to enhance check for data requested to be sent to another side of the bridge. In the sketches described previously the simple attack is possible - a contract could call For example, the method below will be executed successfully if function requireToPassMessage(address _contract, bytes _data, uint256 _gas) public {
require(foreignBridgeMode == SUBSIDIZED_MODE);
emit RequestForSignature(abi.encodePacked(msg.sender, _contract, _gas, uint8(0x00), _data));
} And It does not make sense to allow such kind of requests. It is worth to check that gas provided is enough at least to send transaction. As per Ethereum Yellow Paper 68 gas paid for every non-zero byte of data or code for a transaction. It means that the contracts should declare gas value which is greater or equal 68 multiplied by the length of data: function getMinimumGasUsage(bytes _data) pure public returns(uint256 gas) {
//From Ethereum Yellow Paper
// 68 gas is paid for every non-zero byte of data or code for a transaction
return _data.length * 68;
}
function requireToPassMessage(address _contract, bytes _data, uint256 _gas) public {
require(foreignBridgeMode == SUBSIDIZED_MODE);
bytes memory messageToRelay=abi.encodePacked(msg.sender, _contract, _gas, uint8(0x00), _data);
require(_gas >= getMinimumGasUsage(messageToRelay));
emit RequestForSignature(messageToRelay);
} |
The idea wouldn't be to convert value in home to value in foreign, but to send part of the value that is already deposited in the foreign contract. For example, let's say I have a contract in foreign that checks that |
Is this added to the minimum gas that is used for a transaction (21000, I think)? If that's the case, would it make sense to do |
No, it is assumed that 21000 is included in In this case I think the approach could be refined to: function getMinimumGasUsage(bytes _data) pure public returns(uint256 gas) {
//From Ethereum Yellow Paper
// 68 gas is paid for every non-zero byte of data or code for a transaction
return _data.length * 68;
}
function requireToPassMessage(address _contract, bytes _data, uint256 _gas) public {
require(foreignBridgeMode == SUBSIDIZED_MODE);
require(_gas >= getMinimumGasUsage(_data));
emit RequestForSignature(abi.encodePacked(msg.sender, _contract, _gas, uint8(0x00), _data));
} where gas to handle |
Right. This is the intention for the first version of the bridge. The behavior you are describing could be implemented if the contract we invoke through the bridge ( contract ContractOnForeign {
TargetContract someAddress;
constructor (TargetContract _addr) public {
someAddress = _addr;
}
function setValue(uint256 _i) public {
require(address(this).balance >= 1 finney);
someAddress.setValue.value(1 finney)(_i);
}
}
contract TargetContract {
uint256 public i;
function setValue(uint256 _i) payable public {
require(msg.value >= 1 finney);
i = _i;
}
} |
I think it's a good idea to keep the different names because they are processed in a different way and they are easily identified inside the bridge code |
hey guys, how's the progress going on this? |
@perissology so far we are busy with the launch of the |
thanks for the update 👍 |
Hey all! is this still in the roadmap? |
@perissology it was postponed for a while. We are currently implementing distributing rewards between validators for |
nice Work Have You consider little different mechanics, where there is HomeBridge and ForeignBridge and only keccak256(msg.data,_validSender, foreignContractAddress,_declaredValue) is send from one contract to another by validator when enaught confirmations is made by validators anyone can call method to ForeignContract
I believe this approach has seeveral advantages
I was thinking about Replay attacks, but they can be easily imtigated on foreign Bridge by invalidation of _requestId after single use. |
@adamskrodzki please note that the code you are referring is still on the code review
this is exactly how it is going to work.
Since in a special case when the |
@akolotov |
It is not. The code review was postponed due to other high priority development activities |
ok, I will then take a look and try to deploy it myself on testnets and try to implement some example contracts using it |
# Conflicts: # deploy/.env.example # deploy/src/loadEnv.js # package-lock.json
Here is a situation that we need to think how to handle in order to avoid getting stuck on processing the events from the validator oracle perspective. On an example of We need to properly handle those kind of failures on the contracts and/or on the oracle side so if call to The same should be applied for executing the message on Home side. |
# Conflicts: # contracts/upgradeable_contracts/erc20_to_erc20/HomeBridgeErcToErc.sol # contracts/upgradeable_contracts/erc20_to_native/HomeBridgeErcToNative.sol # contracts/upgradeable_contracts/native_to_erc20/ForeignBridgeNativeToErc.sol # contracts/upgradeable_contracts/native_to_erc20/HomeBridgeNativeToErc.sol # deploy/src/loadEnv.js # flatten.sh
# Conflicts: # deploy/package-lock.json
@patitonar thanks for question! So far I have no generic answer on your question. So, let's consider solutions case by case:
|
@akolotov thanks for the answers!
|
OK. It could indeed reduce the number of RPC calls in the good case. But we will need to change
Sure. I based on the sketch presented above and txHash` was not introduced there for simplification of the concept demonstration. |
@patitonar during the discussion on the POA Network forum So, it was suggested to implement in AMB a functionality similar to contract AMB {
address messageSender;
function getMessageSender() external view returns (address) {
return messageSender;
}
function _passMessage(address _sender, address _contract, bytes _data, uint256 _gas) internal {
if (_contract == address(this)) {
//Special check to handle invocation of withdrawFromDeposit
if (isWithdrawFromDepositSelector(_data)) {
accountForAction = _sender;
}
}
messageSender = _sender;
bool status = _contract.call.gas(_gas)(_data);
messageSender = address(0);
emit MessageRelayed(status);
}
}
contract targetContract {
address public pairedContract;
AMB public bridge;
bytes32 data;
function targetMethod(bytes32 _data) external returns (bool) {
require(msg.sender == address(bridge));
require(bridge.getMessageSender() == pairedContract);
data = _data;
return true;
}
} What do you think? |
@akolotov Looks good to me, I'll update the implementation with this feature |
# Conflicts: # contracts/upgradeable_contracts/BasicBridge.sol # contracts/upgradeable_contracts/ERC677Bridge.sol # contracts/upgradeable_contracts/erc20_to_erc20/BasicForeignBridgeErcToErc.sol # contracts/upgradeable_contracts/erc20_to_erc20/HomeBridgeErcToErc.sol # contracts/upgradeable_contracts/erc20_to_native/ForeignBridgeErcToNative.sol # contracts/upgradeable_contracts/erc20_to_native/HomeBridgeErcToNative.sol # contracts/upgradeable_contracts/native_to_erc20/ForeignBridgeNativeToErc.sol # contracts/upgradeable_contracts/native_to_erc20/HomeBridgeNativeToErc.sol # package-lock.json # package.json
# Conflicts: # deploy/src/loadEnv.js # package-lock.json
# Conflicts: # contracts/upgradeable_contracts/BasicBridge.sol
# Conflicts: # contracts/upgradeable_contracts/BasicBridge.sol # contracts/upgradeable_contracts/BasicHomeBridge.sol # contracts/upgradeable_contracts/ERC677Bridge.sol # package-lock.json
# Conflicts: # contracts/upgradeable_contracts/BasicBridge.sol # contracts/upgradeable_contracts/BasicForeignBridge.sol # contracts/upgradeable_contracts/ERC677Bridge.sol # package-lock.json # package.json
# Conflicts: # deploy/src/loadEnv.js
# Conflicts: # contracts/interfaces/IAMB.sol
# Conflicts: # contracts/upgradeable_contracts/BasicBridge.sol
The issue is being closed since the feature was implemented for the release |
Based on feature description available here: https://hackmd.io/61fw0kf8T2C9bvDTOpzyVQ
POA network projects provides a set of bridges working in EVM-based
networks which allows to transfer assets from one blockchain to another.
At this moment the POA bridge supports two modes:
native-to-erc20
(used in production)erc20-to-erc20
(in development stage)Intorduction of every new bridge mode requires changes as on both the contract side and server side. It increase efforts needed for development and testing. That's why it is good idea to think of generic bridging approach which would allow transfer arbitrary data between two blockchains as so any depending on receiver the data could be interpreted as transfer of digital assets or an arbitrary contract method invocation.
Basic bridge functionality
Let's consider that such kind of bridge has several components:
Home
network which sends a message to another blockchain -HomeSender
;Foreign
to receive the message fromHome
blockchain -ForeignReceiver
;Home
network which is responsible for transferring the message fromHome
-HomeBridge
;Foreign
network which is responsible for delivering the message toForeignReceiver
.Home
andForeign
blockchains which is responsible for listening events onHome
side and creating transactions inForeign
side.Having this we could describe the process of transferring the message as the set of the following steps:
HomeSender
by invoking directly or indirectly its method as soHomeSender
needs to call the methodAction1
fromForeignReceiver
contract with some integer parameterP
.HomeSender
invokes the methodrequireToPassMessage
fromHomeBridge
and pass the following parameters:contractAddress
- address ofForeignReceiver
;data
- set of bytes where the selector ofAction1
and value of parameterP
encoded;gas
- estimated value of gas which is expected to be spent forAction1
execution.Example of way to encode
data
:which equals to
requireToTransfer
raises the eventRequestForSignature
with the following parameters:where
msg.sender
is an address of the contract that callsrequireToTransfer
.In order to secure the bridge operation it also could be worth to set a maximum value of estimated gas can be requested in one transaction. A daily limit for gas could be introduced as well.
RequestForSignature
event and initiates procedure to sign the request to transfer data.CollectedSignatures
event which contains a hash of the transaction raised initialRequestForSignature
:CollectedSignatures
event and callsexecuteSignatures
of the bridge contractForeignBridge
on Foreign side with the list of collected signatures.A validator generates her signature combining the set of parameters:
Gas value specified for the transaction that invokes
executeSignatures
is calculated as amount of gas needed for execution ofexecuteSignatures
itself plusgas
specified byHomeSender
.executeSignatures
after signatures verification invokes a method specified indata
fromcontractAddress
:A simple example demostrating minimal functionality of the bridge:
Bridge operations compensation
In cases when a method that is going to be invoked on
Foreign
network side could consume lots of gas or the bridge is going to be used intensively it makes sense to introduce gaining fees to compensate bridge operations.In the approach described below it is assumed that there is a registration of
HomeSender
contract address inForeignBridge
. Funds to cover bridge expenses must be deposited as part of the contract registration.In this case
HomeBridge
could provide interface to callrequireToPassMessage
with different set of arguments:Where
gasPrice
- fee to cover expenses of the bridge to execute the methodAction1
for every gas unit.oracleGasPriceSpeed
- an integer which defines the gas price speed used by the gas price oracle (0x01
- instant,0x02
- fast,0x03
- standard,0x04
- slow).Therefore if
gasPrice
is specified as part of the methodrequireToPassMessage
invocation it is necessary to check that it is greater than some minimum value.RequestForSignature
also allows to be emitted in several ways:executeSignatures
of the bridge contractForeignBridge
will have three implementation:Depending on the form of the method the following combination of parameters is used to verify the validators signatures:
If the signatures vaildation is correct the deposit of
contractSender
account is reduced by(executeSignaturesGas + gas) * gasPrice
value, whereexecuteSignaturesGas
is a constant that present amount of gas needed to executeexecuteSignatures
method. IforacleGasPriceSpeed
is specified instead ofgasPrice
as per the method call,gasPrice
should be equaltx.gasprice
.The contract must also force the transaction sender use
gasPrice
initially specified by theHomeSender
through the check thatgasPrice
is equaltx.gasprice
.The corresponding amount of coins is sent to the address that invokes
executeSignatures
method.The main idea of the bridge contracts with compensation of bridge operations demostrated below:
Configurable mode
Two approaches to work with expenses were presented above. Both of them could be used depending on validator abilities and owner of the bridge. It makes sense to provide a configuration parameter to define the mode of the bridge as so the same source code could be used for different types of bridge:
This will allow to configure different sides of the bridge differently. For example, the bridge on the side of a chain with low gas price could be configured to not require cover gas used by the validator, at the same time another side of the bridge could still expect payment for handling transactions.
The text was updated successfully, but these errors were encountered: