Skip to content

Commit

Permalink
feat(Express): new ExpressProxyDeployer, Create3Deployer and FinalPro…
Browse files Browse the repository at this point in the history
…xy (#20)

* feat(Express): ExpressProxyDeployer, Create3Deployer and FinalProxy

* feat(FinalProxy): isFinal

* feat(ExpressProxy): excluding registry bytecode from the proxy

* revert(ConstAddressDeployer): keeping the legacy deployer

* feature(Proxy): proxy of every kind

* feature(ExpressExecutable): enableExpressCallWithToken to opt out

* refactor(FinalProxy): calling super method

* chore(README): copy update
  • Loading branch information
re1ro authored Feb 14, 2023
1 parent f0222fe commit aaba6c6
Show file tree
Hide file tree
Showing 40 changed files with 988 additions and 326 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ $ npm install @axelar-network/axelar-gmp-sdk-solidity
### AxelarExecutable
Base interface for validating and executing GMP contract calls.

### AxelarForecallable
### ExpressProxy and ExpressExecutable
Interface that allows to expedite GMP calls by lending assets and performing execution
before it fully propagates through the Axelar network.

### ConstAddressDeployer
This contract is used to deploy your Executable to have the same address on different EVM chains.
### Create3Deployer and ConstAddressDeployer
These contracts are used to deploy your Executable to have the same address on different EVM chains.
This simplifies message validation from peer Executables. You can learn more in the
[documentation](https://docs.axelar.dev/dev/build/solidity-utilities).

### Proxy and Upgradable
Base implementation of upgradable contracts designed to be deployed with `ConstAddressDeployer`
Base implementation of upgradable contracts designed to be deployed with `Create3Deployer`
and to have the same Proxy address on different EVM chains.

### TokenLinker and NTFLinker
Expand Down
File renamed without changes.
46 changes: 46 additions & 0 deletions contracts/deploy/Create3.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

error AlreadyDeployed();
error EmptyBytecode();
error DeployFailed();

contract CreateDeployer {
function deploy(bytes memory bytecode) external {
// solhint-disable-next-line no-inline-assembly
assembly {
pop(create(0, add(bytecode, 32), mload(bytecode)))
}

selfdestruct(payable(msg.sender));
}
}

library Create3 {
bytes32 internal constant DEPLOYER_BYTECODE_HASH = keccak256(type(CreateDeployer).creationCode);

function deploy(bytes32 salt, bytes memory bytecode) internal returns (address deployed) {
deployed = deployedAddress(salt, address(this));

if (deployed.code.length != 0) revert AlreadyDeployed();
if (bytecode.length == 0) revert EmptyBytecode();

// CREATE2
CreateDeployer deployer = new CreateDeployer{ salt: salt }();

if (address(deployer) == address(0)) revert DeployFailed();

deployer.deploy(bytecode);

if (deployed.code.length == 0) revert DeployFailed();
}

function deployedAddress(bytes32 salt, address host) internal pure returns (address deployed) {
address deployer = address(
uint160(uint256(keccak256(abi.encodePacked(hex'ff', host, salt, DEPLOYER_BYTECODE_HASH))))
);

deployed = address(uint160(uint256(keccak256(abi.encodePacked(hex'd6_94', deployer, hex'01')))));
}
}
65 changes: 65 additions & 0 deletions contracts/deploy/Create3Deployer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { Create3 } from './Create3.sol';

contract Create3Deployer {
error FailedInit();

event Deployed(bytes32 indexed bytecodeHash, bytes32 indexed salt, address indexed deployedAddress);

/**
* @dev Deploys a contract using `CREATE3`. The address where the contract
* will be deployed can be known in advance via {deployedAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already by the same `msg.sender`.
*/
function deploy(bytes calldata bytecode, bytes32 salt) external returns (address deployedAddress_) {
bytes32 deploySalt = keccak256(abi.encode(msg.sender, salt));
deployedAddress_ = Create3.deploy(deploySalt, bytecode);

emit Deployed(keccak256(bytecode), salt, deployedAddress_);
}

/**
* @dev Deploys a contract using `CREATE3` and initialize it. The address where the contract
* will be deployed can be known in advance via {deployedAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already by the same `msg.sender`.
* - `init` is used to initialize the deployed contract
*/
function deployAndInit(
bytes memory bytecode,
bytes32 salt,
bytes calldata init
) external returns (address deployedAddress_) {
bytes32 deploySalt = keccak256(abi.encode(msg.sender, salt));
deployedAddress_ = Create3.deploy(deploySalt, bytecode);

// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = deployedAddress_.call(init);
if (!success) revert FailedInit();
}

/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} or {deployAndInit} by `sender`.
* Any change in the `bytecode`, `sender`, or `salt` will result in a new destination address.
*/
function deployedAddress(address sender, bytes32 salt) external view returns (address) {
bytes32 deploySalt = keccak256(abi.encode(sender, salt));
return Create3.deployedAddress(deploySalt, address(this));
}
}
8 changes: 6 additions & 2 deletions contracts/express/ExpressExecutable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ pragma solidity ^0.8.0;

import { IAxelarGateway } from '../interfaces/IAxelarGateway.sol';
import { IERC20 } from '../interfaces/IERC20.sol';
import { IAxelarExecutable } from '../interfaces/IAxelarExecutable.sol';
import { IExpressExecutable } from '../interfaces/IExpressExecutable.sol';

abstract contract ExpressExecutable is IAxelarExecutable {
abstract contract ExpressExecutable is IExpressExecutable {
error NotSelf();

IAxelarGateway public immutable gateway;
Expand All @@ -17,6 +17,10 @@ abstract contract ExpressExecutable is IAxelarExecutable {
gateway = IAxelarGateway(gateway_);
}

function enableExpressCallWithToken() external virtual returns (bool) {
return true;
}

/// @notice this function is shadowed by the proxy and can be called only internally
function execute(
bytes32,
Expand Down
73 changes: 30 additions & 43 deletions contracts/express/ExpressProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,33 @@

pragma solidity ^0.8.0;

import { IAxelarGateway } from '../interfaces/IAxelarGateway.sol';
import { IERC20 } from '../interfaces/IERC20.sol';
import { IExpressExecutable } from '../interfaces/IExpressExecutable.sol';
import { IAxelarGateway } from '../interfaces/IAxelarGateway.sol';
import { IExpressProxy } from '../interfaces/IExpressProxy.sol';
import { IGMPExpressService } from '../interfaces/IGMPExpressService.sol';
import { IExpressRegistry } from '../interfaces/IExpressRegistry.sol';
import { Proxy } from '../upgradable/Proxy.sol';
import { ExpressRegistry } from './ExpressRegistry.sol';
import { IExpressExecutable } from '../interfaces/IExpressExecutable.sol';
import { FinalProxy } from '../upgradable/FinalProxy.sol';
import { SafeTokenTransfer, SafeTokenTransferFrom } from '../utils/SafeTransfer.sol';
import { Create3 } from '../deploy/Create3.sol';

contract ExpressProxy is Proxy, IExpressExecutable {
contract ExpressProxy is FinalProxy, IExpressProxy {
using SafeTokenTransfer for IERC20;
using SafeTokenTransferFrom for IERC20;

IAxelarGateway public immutable gateway;
bytes32 internal constant REGISTRY_SALT = keccak256('express-registry');

constructor(address gateway_, address gmpExpressService_) {
if (gateway_ == address(0) && gmpExpressService_ == address(0)) revert InvalidAddress();
IAxelarGateway public immutable gateway;

IAxelarGateway resolvedGateway;
constructor(
address implementationAddress,
address owner,
bytes memory setupParams,
address gateway_
) FinalProxy(implementationAddress, owner, setupParams) {
if (gateway_ == address(0)) revert InvalidAddress();

// Providing gateway_ as address(0) allows having the same address across chains
// assuming condition that gmpExpressService_ address is the same
// and gateway address is different across chains.
if (gateway_ == address(0)) {
resolvedGateway = IGMPExpressService(gmpExpressService_).gateway();
} else {
resolvedGateway = IAxelarGateway(gateway_);
}

gateway = resolvedGateway;
gateway = IAxelarGateway(gateway_);
}

modifier onlyRegistry() {
Expand All @@ -40,31 +37,18 @@ contract ExpressProxy is Proxy, IExpressExecutable {
_;
}

function deployRegistry() external {
if (address(registry()).code.length == 0) new ExpressRegistry(address(gateway));
}

function registry() public view returns (IExpressRegistry) {
// Computing address is cheaper than using storage
// Can't use immutable storage as it will alter the codehash for each instance
return
IExpressRegistry(
address(
uint160(
uint256(
keccak256(
abi.encodePacked(
// 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01)
// 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex)
hex'd6_94',
address(this),
hex'01' // Nonce of the registry contract deployment
)
)
)
)
)
);
// Can't use immutable storage as it will alter the codehash for each proxy instance
return IExpressRegistry(Create3.deployedAddress(REGISTRY_SALT, address(this)));
}

// @notice should be called right after the proxy is deployed
function deployRegistry(bytes calldata registryCreationCode) external {
Create3.deploy(
REGISTRY_SALT,
abi.encodePacked(registryCreationCode, abi.encode(address(gateway), address(this)))
);
}

function execute(
Expand All @@ -91,6 +75,8 @@ contract ExpressProxy is Proxy, IExpressExecutable {
bytes32 payloadHash = keccak256(payload);
address token = gateway.tokenAddresses(tokenSymbol);

if (IExpressExecutable(implementation()).enableExpressCallWithToken() == false) revert ExpressCallNotEnabled();

if (token == address(0)) revert InvalidTokenSymbol();

registry().registerExpressCallWithToken(
Expand All @@ -115,9 +101,10 @@ contract ExpressProxy is Proxy, IExpressExecutable {
string calldata tokenSymbol,
uint256 amount
) external override {
registry().processCallWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount);
registry().processExecuteWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount);
}

/// @notice callback to complete the GMP call
function completeExecuteWithToken(
address expressCaller,
bytes32 commandId,
Expand Down
62 changes: 62 additions & 0 deletions contracts/express/ExpressProxyDeployer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IExpressProxyDeployer } from '../interfaces/IExpressProxyDeployer.sol';
import { IExpressProxy } from '../interfaces/IExpressProxy.sol';
import { Create3 } from '../deploy/Create3.sol';
import { ExpressProxy } from './ExpressProxy.sol';
import { ExpressRegistry } from './ExpressRegistry.sol';

contract ExpressProxyDeployer is IExpressProxyDeployer {
address public immutable gateway;
bytes32 public immutable proxyCodeHash;
bytes32 public immutable registryCodeHash;

constructor(address gateway_) {
if (gateway_ == address(0)) revert InvalidAddress();

gateway = gateway_;

ExpressProxy proxy = new ExpressProxy(address(this), address(this), '', gateway_);
proxy.deployRegistry(type(ExpressRegistry).creationCode);

proxyCodeHash = address(proxy).codehash;
registryCodeHash = address(proxy.registry()).codehash;
}

function isExpressProxy(address proxyAddress) external view returns (bool) {
address expressRegistry = address(IExpressProxy(proxyAddress).registry());

return proxyAddress.codehash == proxyCodeHash && expressRegistry.codehash == registryCodeHash;
}

/// @param host is delegating call to this contract
function deployedProxyAddress(
bytes32 salt,
address sender,
address host
) external pure returns (address) {
bytes32 deploySalt = keccak256(abi.encode(sender, salt));
return Create3.deployedAddress(deploySalt, host);
}

/// @dev delegatecall to this function to deploy a proxy from a host contract
function deployExpressProxy(
bytes32 deploySalt,
address implementationAddress,
address owner,
bytes memory setupParams
) external returns (address proxyAddress) {
proxyAddress = Create3.deploy(
deploySalt,
abi.encodePacked(
type(ExpressProxy).creationCode,
abi.encode(gateway, implementationAddress, owner, setupParams)
)
);

ExpressProxy proxy = ExpressProxy(payable(proxyAddress));
proxy.deployRegistry(type(ExpressRegistry).creationCode);
}
}
Loading

0 comments on commit aaba6c6

Please sign in to comment.