Skip to content

Commit

Permalink
Add mediators contracts (#6)
Browse files Browse the repository at this point in the history
* Add mintable burnable kitty contract
* Add mediators contracts
* Update flatten script
* Add mediator deploy scripts
* Add poa-bridge-contracts submodule
  • Loading branch information
patitonar authored and akolotov committed Sep 27, 2019
1 parent af0bcb6 commit 138dd39
Show file tree
Hide file tree
Showing 34 changed files with 2,278 additions and 40 deletions.
11 changes: 11 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
DEPLOYMENT_ACCOUNT_PRIVATE_KEY=67..14
DEPLOYMENT_GAS_LIMIT_EXTRA=0.2

HOME_RPC_URL=https://sokol.poa.network
HOME_DEPLOYMENT_GAS_PRICE=1000000000
HOME_AMB_BRIDGE=0x0000000000000000000000000000000000000000
HOME_MEDIATOR_REQUEST_GAS_LIMIT=1000000
HOME_MEDIATOR_OWNER=0x0000000000000000000000000000000000000000
HOME_UPGRADEABLE_ADMIN=0x0000000000000000000000000000000000000000

FOREIGN_RPC_URL=https://sokol.poa.network
FOREIGN_DEPLOYMENT_GAS_PRICE=1000000000
FOREIGN_AMB_BRIDGE=0x0000000000000000000000000000000000000000
FOREIGN_MEDIATOR_REQUEST_GAS_LIMIT=1000000
FOREIGN_MEDIATOR_OWNER=0x0000000000000000000000000000000000000000
FOREIGN_UPGRADEABLE_ADMIN=0x0000000000000000000000000000000000000000

CRYPTOKITTIES_ADDRESS=0x0000000000000000000000000000000000000000
KITTIES_AMOUNT=1
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bridge-contracts
11 changes: 10 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
{
"extends": ["plugin:node/recommended", "airbnb-base", "plugin:prettier/recommended"],
"plugins": ["node"],
"env": {
"mocha": true
},
"globals": {
"artifacts": false,
"contract": false,
"web3": false
},
"rules": {
"no-await-in-loop": "off",
"consistent-return": "off",
"no-console": "off",
"no-plusplus": "off",
"no-use-before-define": ["error", { "functions": false }],
"node/no-unpublished-require": "off"
"node/no-unpublished-require": "off",
"func-names": "off"
}
}
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "bridge-contracts"]
path = bridge-contracts
url = https://github.com/poanetwork/poa-bridge-contracts.git
1 change: 1 addition & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"not-rely-on-time": "off",
"const-name-snakecase": "off",
"no-inline-assembly": "off",
"multiple-sends": "off",
"compiler-version": ["error", "0.4.24"]
}
}
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# cryptokitties-xdai-demo

#### Clone the repository
```bash
git clone --recursive https://github.com/poanetwork/cryptokitties-xdai-demo.git
```

#### Install dependencies
```bash
yarn
Expand All @@ -22,10 +27,32 @@ DEPLOYMENT_GAS_LIMIT_EXTRA=0.2
HOME_RPC_URL=https://sokol.poa.network
# The "gasPrice" parameter set in every deployment/configuration transaction on Home network (in Wei).
HOME_DEPLOYMENT_GAS_PRICE=1000000000
# The address of the existing AMB bridge in the Home network that will be used to pass messages
# to the Foreign network.
HOME_AMB_BRIDGE=0x0000000000000000000000000000000000000000
# The gas limit that will be used in the execution of the message passed to the mediator contract
# in the Foreign network.
HOME_MEDIATOR_REQUEST_GAS_LIMIT=1000000
# Address on Home network with permissions to change parameters of the mediator contract.
HOME_MEDIATOR_OWNER=0x0000000000000000000000000000000000000000
# Address on Home network with permissions to upgrade the mediator contract
HOME_UPGRADEABLE_ADMIN=0x0000000000000000000000000000000000000000
# The RPC channel to a Foreign node able to handle deployment/configuration transactions.
FOREIGN_RPC_URL=https://sokol.poa.network
# The "gasPrice" parameter set in every deployment/configuration transaction on Foreign network (in Wei).
FOREIGN_DEPLOYMENT_GAS_PRICE=1000000000
# The address of the existing AMB bridge in the Foreign network that will be used to pass messages
# to the Home network.
FOREIGN_AMB_BRIDGE=0x0000000000000000000000000000000000000000
# The gas limit that will be used in the execution of the message passed to the mediator contract
# in the Home network.
FOREIGN_MEDIATOR_REQUEST_GAS_LIMIT=1000000
# Address on Foreign network with permissions to change parameters of the mediator contract.
FOREIGN_MEDIATOR_OWNER=0x0000000000000000000000000000000000000000
# Address on Foreign network with permissions to upgrade the mediator contract
FOREIGN_UPGRADEABLE_ADMIN=0x0000000000000000000000000000000000000000
# Cryptokitties contract address on Foreign network. If not defined or set to address zero, the contract will be deployed on Foreign network.
CRYPTOKITTIES_ADDRESS=0x0000000000000000000000000000000000000000
# Amount of Kitties to Mint on Foreign network
Expand All @@ -37,6 +64,12 @@ Then
yarn deploy
```

#### Tests
```bash
yarn test
```


#### Flat contracts
```bash
yarn flatten
Expand Down
1 change: 1 addition & 0 deletions bridge-contracts
Submodule bridge-contracts added at 86b35f
5 changes: 5 additions & 0 deletions contracts/interfaces/IForeignMediator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma solidity 0.4.24;

interface IForeignMediator {
function handleBridgedTokens(address _recipient, uint256 _tokenId, bytes32 _nonce) external;
}
5 changes: 5 additions & 0 deletions contracts/interfaces/IHomeMediator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma solidity 0.4.24;

interface IHomeMediator {
function handleBridgedTokens(address _recipient, uint256 _tokenId, bytes _metadata, bytes32 _nonce) external;
}
18 changes: 18 additions & 0 deletions contracts/interfaces/ISimpleBridgeKitty.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
pragma solidity 0.4.24;

interface ISimpleBridgeKitty {
function mint(
uint256 _tokenId,
bool _isReady,
uint256 _cooldownIndex,
uint256 _nextActionAt,
uint256 _siringWithId,
uint256 _birthTime,
uint256 _matronId,
uint256 _sireId,
uint256 _generation,
uint256 _genes,
address _owner
) external;
function burn(uint256 _tokenId) external;
}
50 changes: 50 additions & 0 deletions contracts/mediator/AMBMediator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
pragma solidity 0.4.24;

import "../../bridge-contracts/contracts/upgradeability/EternalStorage.sol";
import "../../bridge-contracts/contracts/interfaces/IAMB.sol";
import "../../bridge-contracts/contracts/upgradeable_contracts/Ownable.sol";
import "openzeppelin-solidity/contracts/AddressUtils.sol";

contract AMBMediator is EternalStorage, Ownable {
bytes32 internal constant BRIDGE_CONTRACT = keccak256(abi.encodePacked("bridgeContract"));
bytes32 internal constant MEDIATOR_CONTRACT = keccak256(abi.encodePacked("mediatorContract"));
bytes32 internal constant REQUEST_GAS_LIMIT = keccak256(abi.encodePacked("requestGasLimit"));

function setBridgeContract(address _bridgeContract) external onlyOwner {
_setBridgeContract(_bridgeContract);
}

function _setBridgeContract(address _bridgeContract) internal {
require(AddressUtils.isContract(_bridgeContract));
addressStorage[BRIDGE_CONTRACT] = _bridgeContract;
}

function bridgeContract() public view returns (IAMB) {
return IAMB(addressStorage[BRIDGE_CONTRACT]);
}

function setMediatorContractOnOtherSide(address _mediatorContract) external onlyOwner {
_setMediatorContractOnOtherSide(_mediatorContract);
}

function _setMediatorContractOnOtherSide(address _mediatorContract) internal {
addressStorage[MEDIATOR_CONTRACT] = _mediatorContract;
}

function mediatorContractOnOtherSide() public view returns (address) {
return addressStorage[MEDIATOR_CONTRACT];
}

function setRequestGasLimit(uint256 _requestGasLimit) external onlyOwner {
_setRequestGasLimit(_requestGasLimit);
}

function _setRequestGasLimit(uint256 _requestGasLimit) internal {
require(_requestGasLimit <= bridgeContract().maxGasPerTx());
uintStorage[REQUEST_GAS_LIMIT] = _requestGasLimit;
}

function requestGasLimit() public view returns (uint256) {
return uintStorage[REQUEST_GAS_LIMIT];
}
}
130 changes: 130 additions & 0 deletions contracts/mediator/BasicMediator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
pragma solidity 0.4.24;

import "../../bridge-contracts/contracts/upgradeable_contracts/Initializable.sol";
import "../../bridge-contracts/contracts/upgradeable_contracts/Claimable.sol";
import "../../bridge-contracts/contracts/upgradeable_contracts/Upgradeable.sol";
import "../../bridge-contracts/contracts/libraries/Bytes.sol";
import "./AMBMediator.sol";
import "./ERC721Bridge.sol";

contract BasicMediator is Initializable, AMBMediator, ERC721Bridge, Upgradeable, Claimable {
event FailedMessageFixed(bytes32 indexed dataHash, address recipient, uint256 tokenId);

bytes32 internal constant NONCE = keccak256(abi.encodePacked("nonce"));
bytes4 internal constant GET_KITTY = 0xe98b7f4d; // getKitty(uint256)

function initialize(
address _bridgeContract,
address _mediatorContract,
address _erc721token,
uint256 _requestGasLimit,
address _owner
) external returns (bool) {
require(!isInitialized());

_setBridgeContract(_bridgeContract);
_setMediatorContractOnOtherSide(_mediatorContract);
setErc721token(_erc721token);
_setRequestGasLimit(_requestGasLimit);
setOwner(_owner);
setNonce(keccak256(abi.encodePacked(address(this))));
setInitialize();

return isInitialized();
}

function getBridgeInterfacesVersion() external pure returns (uint64 major, uint64 minor, uint64 patch) {
return (1, 0, 0);
}

function getBridgeMode() external pure returns (bytes4 _data) {
return bytes4(keccak256(abi.encodePacked("nft-to-nft-amb")));
}

function transferToken(address _from, uint256 _tokenId) external {
ERC721 token = erc721token();
address to = address(this);

token.transferFrom(_from, to, _tokenId);
bridgeSpecificActionsOnTokenTransfer(_from, _tokenId);
}

/**
* getKitty(uint256) returns:
* bool isGestating,
* bool isReady,
* uint256 cooldownIndex,
* uint256 nextActionAt,
* uint256 siringWithId,
* uint256 birthTime,
* uint256 matronId,
* uint256 sireId,
* uint256 generation,
* uint256 genes
**/
function getMetadata(uint256 _tokenId) internal view returns (bytes memory metadata) {
bytes memory callData = abi.encodeWithSelector(GET_KITTY, _tokenId);
address tokenAddress = erc721token();
metadata = new bytes(320);
assembly {
let result := call(gas, tokenAddress, 0x0, add(callData, 0x20), mload(callData), 0, 0)
returndatacopy(add(metadata, 0x20), 0, returndatasize)

switch result
case 0 {
revert(0, 0)
}
}
}

function nonce() internal view returns (bytes32) {
return Bytes.bytesToBytes32(bytesStorage[NONCE]);
}

function setNonce(bytes32 _hash) internal {
bytesStorage[NONCE] = abi.encodePacked(_hash);
}

function setMessageHashTokenId(bytes32 _hash, uint256 _tokenId) internal {
uintStorage[keccak256(abi.encodePacked("messageHashTokenId", _hash))] = _tokenId;
}

function messageHashTokenId(bytes32 _hash) internal view returns (uint256) {
return uintStorage[keccak256(abi.encodePacked("messageHashTokenId", _hash))];
}

function setMessageHashRecipient(bytes32 _hash, address _recipient) internal {
addressStorage[keccak256(abi.encodePacked("messageHashRecipient", _hash))] = _recipient;
}

function messageHashRecipient(bytes32 _hash) internal view returns (address) {
return addressStorage[keccak256(abi.encodePacked("messageHashRecipient", _hash))];
}

function setMessageHashFixed(bytes32 _hash) internal {
boolStorage[keccak256(abi.encodePacked("messageHashFixed", _hash))] = true;
}

function messageHashFixed(bytes32 _hash) public view returns (bool) {
return boolStorage[keccak256(abi.encodePacked("messageHashFixed", _hash))];
}

function requestFailedMessageFix(bytes32 _txHash) external {
require(!bridgeContract().messageCallStatus(_txHash));
require(bridgeContract().failedMessageReceiver(_txHash) == address(this));
require(bridgeContract().failedMessageSender(_txHash) == mediatorContractOnOtherSide());
bytes32 dataHash = bridgeContract().failedMessageDataHash(_txHash);

bytes4 methodSelector = this.fixFailedMessage.selector;
bytes memory data = abi.encodeWithSelector(methodSelector, dataHash);
bridgeContract().requireToPassMessage(mediatorContractOnOtherSide(), data, requestGasLimit());
}

function claimTokens(address _token, address _to) public onlyIfUpgradeabilityOwner validAddress(_to) {
claimValues(_token, _to);
}

function fixFailedMessage(bytes32 _dataHash) external;

function bridgeSpecificActionsOnTokenTransfer(address _from, uint256 _tokenId) internal;
}
18 changes: 18 additions & 0 deletions contracts/mediator/ERC721Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
pragma solidity 0.4.24;

import "../kitty/ERC721.sol";
import "openzeppelin-solidity/contracts/AddressUtils.sol";
import "../../bridge-contracts/contracts/upgradeability/EternalStorage.sol";

contract ERC721Bridge is EternalStorage {
bytes32 internal constant ERC721_TOKEN = keccak256(abi.encodePacked("erc721token"));

function erc721token() public view returns (ERC721) {
return ERC721(addressStorage[ERC721_TOKEN]);
}

function setErc721token(address _token) internal {
require(AddressUtils.isContract(_token));
addressStorage[ERC721_TOKEN] = _token;
}
}
48 changes: 48 additions & 0 deletions contracts/mediator/ForeignMediator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
pragma solidity 0.4.24;

import "./BasicMediator.sol";
import "../interfaces/IHomeMediator.sol";

contract ForeignMediator is BasicMediator {
function passMessage(address _from, uint256 _tokenId) internal {
bytes memory metadata = getMetadata(_tokenId);

bytes4 methodSelector = IHomeMediator(0).handleBridgedTokens.selector;
bytes memory data = abi.encodeWithSelector(methodSelector, _from, _tokenId, metadata, nonce());

bytes32 dataHash = keccak256(data);
setMessageHashTokenId(dataHash, _tokenId);
setMessageHashRecipient(dataHash, _from);
setNonce(dataHash);

bridgeContract().requireToPassMessage(mediatorContractOnOtherSide(), data, requestGasLimit());
}

function handleBridgedTokens(
address _recipient,
uint256 _tokenId,
bytes32 /* _nonce */
) external {
require(msg.sender == address(bridgeContract()));
require(bridgeContract().messageSender() == mediatorContractOnOtherSide());
erc721token().transfer(_recipient, _tokenId);
}

function bridgeSpecificActionsOnTokenTransfer(address _from, uint256 _tokenId) internal {
passMessage(_from, _tokenId);
}

function fixFailedMessage(bytes32 _dataHash) external {
require(msg.sender == address(bridgeContract()));
require(bridgeContract().messageSender() == mediatorContractOnOtherSide());
require(!messageHashFixed(_dataHash));

address recipient = messageHashRecipient(_dataHash);
uint256 tokenId = messageHashTokenId(_dataHash);

setMessageHashFixed(_dataHash);
erc721token().transfer(recipient, tokenId);

emit FailedMessageFixed(_dataHash, recipient, tokenId);
}
}
Loading

0 comments on commit 138dd39

Please sign in to comment.