-
Notifications
You must be signed in to change notification settings - Fork 163
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
feat: Valued express example #176
base: main
Are you sure you want to change the base?
Changes from all commits
4b0a2fc
5386308
f74fb71
9fef9a7
34c0291
0fb82b4
c383971
052d47c
f418c4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
//SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.9; | ||
|
||
import { AxelarExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol'; | ||
import { AxelarValuedExpressExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/express/AxelarValuedExpressExecutable.sol'; | ||
import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol'; | ||
import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol'; | ||
import { Upgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Upgradable.sol'; | ||
import { MockERC20 } from './mocks/MockERC20.sol'; | ||
|
||
contract CallContractWithValuedExpress is AxelarValuedExpressExecutable { | ||
IAxelarGasService public immutable gasService; | ||
|
||
constructor(address _gateway, address _gasReceiver) AxelarValuedExpressExecutable(_gateway) { | ||
gasService = IAxelarGasService(_gasReceiver); | ||
} | ||
|
||
function sendValuedMessage( | ||
string memory _destinationChain, | ||
string memory _destinationAddress, | ||
address _tokenAddr, | ||
uint256 _amount, | ||
address _receiver | ||
) external payable { | ||
require(msg.value > 0, 'insufficient funds'); | ||
|
||
bytes memory valuedMsg = _deriveMsgValueForNonGatewayTokenValueTransfer(_tokenAddr, _amount, _receiver); | ||
|
||
gasService.payNativeGasForContractCall{ value: msg.value }( | ||
address(this), | ||
_destinationChain, | ||
_destinationAddress, | ||
valuedMsg, | ||
msg.sender | ||
); | ||
gateway.callContract(_destinationChain, _destinationAddress, valuedMsg); | ||
} | ||
|
||
function _execute(string calldata, string calldata, bytes calldata _payload) internal override { | ||
(address tokenAddress, uint256 value, address reciever) = abi.decode(_payload, (address, uint256, address)); | ||
MockERC20(tokenAddress).mint(reciever, value); | ||
} | ||
|
||
function contractCallWithTokenValue( | ||
string calldata sourceChain, | ||
string calldata sourceAddress, | ||
bytes calldata payload, | ||
string calldata symbol, | ||
uint256 amount | ||
) public view override returns (address tokenAddress, uint256 value) {} | ||
|
||
function contractCallValue( | ||
string calldata, | ||
string calldata, | ||
bytes calldata payload | ||
) public view virtual override returns (address tokenAddress, uint256 value) { | ||
(tokenAddress, value) = abi.decode(payload, (address, uint256)); | ||
} | ||
|
||
function _deriveMsgValueForNonGatewayTokenValueTransfer( | ||
address _tokenAddr, | ||
uint256 _amount, | ||
address _receiver | ||
) internal pure returns (bytes memory valueToBeTransferred) { | ||
valueToBeTransferred = abi.encode(_tokenAddr, _amount, _receiver); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# Call Contract with Valued Express | ||
|
||
This is an example of sending an express transaction where the value is not a cross chain asset transfer using `callContractWithToken()`. Rather the value is in the `payload` of the transaction. Once the payload has been decoded and derived to a specific contract address that value can be transfered to the end receiver. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reverse this to make the first sentence of the readme what it IS rather than what it ISN'T. |
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add details about how it works at a high level? An overview? I hope we will also have corresponding updates in the docs? |
||
### Deployment | ||
|
||
To deploy the contract, run the following command: | ||
|
||
```bash | ||
npm run deploy evm/call-contract-with-valued-express [local|testnet] | ||
``` | ||
|
||
The aforementioned command pertains to specifying the intended environment for a project to execute on. It provides the option to choose between local and testnet environments by appending either `local` or `testnet` after the command. | ||
|
||
An example of its usage is demonstrated as follows: `npm run deploy evm/call-contract-with-valued-express local` or `npm run deploy evm/call-contract-with-valued-express testnet`. | ||
|
||
### Execution | ||
|
||
To execute the example, use the following command: | ||
|
||
```bash | ||
npm run execute evm/call-contract-with-valued-express [local|testnet] ${srcChain} ${destChain} | ||
``` | ||
|
||
**Note** | ||
The GMP Express feature is already lived on our testnet. However, the following conditions need to be met: | ||
|
||
- The contract address must be whitelisted by our executor service. | ||
|
||
Currently, our whitelisted contract addresses for this example are: | ||
|
||
- Avalanche: `0x4E3b6C3d284361Eb4fA9aDE5831eEfF85578b72c` | ||
- Polygon: `0xAb6dAb12aCCAe665A44E69d44bcfC6219A30Dd32` | ||
|
||
## Example | ||
|
||
```bash | ||
npm run deploy evm/call-contract-with-valued-express local | ||
npm run execute evm/call-contract-with-token local "Avalanche" "Fantom" 100 0xBa86A5719722B02a5D5e388999C25f3333c7A9fb | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this right? Deploying a different contract that we execute? |
||
|
||
### Output: | ||
|
||
``` | ||
|
||
--- Initially --- | ||
0xBa86A5719722B02a5D5e388999C25f3333c7A9fb has 100 tokens | ||
--- After --- | ||
0xBa86A5719722B02a5D5e388999C25f3333c7A9fb has 200 tokens | ||
|
||
``` | ||
|
||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
const { | ||
utils: { deployContract }, | ||
} = require('@axelar-network/axelar-local-dev'); | ||
const CallContractWithValuedExpress = rootRequire( | ||
'./artifacts/examples/evm/call-contract-with-valued-express/CallContractWithValuedExpress.sol/CallContractWithValuedExpress.json', | ||
); | ||
const MockERC20 = rootRequire('./artifacts/examples/evm/call-contract-with-valued-express/mocks/MockERC20.sol/MockERC20.json'); | ||
|
||
async function deploy(chain, wallet) { | ||
console.log(`Deploying Call Contract Valued Express for ${chain.name}.`); | ||
chain.callContractWithValuedExpress = await deployContract(wallet, CallContractWithValuedExpress, [chain.gateway, chain.gasService]); | ||
chain.mockToken = await deployContract(wallet, MockERC20); | ||
chain.wallet = wallet; | ||
console.log(`Deployed CallContractWithValuedExpress for ${chain.name} at ${chain.callContractWithValuedExpress.address}.`); | ||
} | ||
|
||
async function execute(chains, wallet, options) { | ||
const args = options.args || []; | ||
const { source, destination, calculateBridgeExpressFee } = options; | ||
|
||
// Get the accounts to send to. | ||
const accounts = args.slice(3).length ? args.slice(3) : [wallet.address]; | ||
|
||
// Calculate the express fee for the bridge. | ||
const expressFee = await calculateBridgeExpressFee(source, destination); | ||
|
||
// Get the balance of the first account. | ||
const initialBalance = await destination.mockToken.balanceOf(accounts[0]); | ||
|
||
// Get the amount to send. | ||
const amount = Math.floor(parseFloat(args[2])) * 1e6 || 10e6; | ||
|
||
async function logAccountBalances() { | ||
for (const account of accounts) { | ||
console.log(`${account} has ${(await destination.mockToken.balanceOf(account)) / 1e6} tokens on ${destination.name}`); | ||
} | ||
} | ||
|
||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); | ||
|
||
console.log('--- Initially ---'); | ||
|
||
// Log the balances of the accounts. | ||
await logAccountBalances(); | ||
|
||
// Send tokens to the distribution contract. | ||
const sendTx = await source.callContractWithValuedExpress.sendValuedMessage( | ||
destination.name, | ||
destination.callContractWithValuedExpress.address, | ||
destination.mockToken.address, | ||
amount, | ||
accounts[0], | ||
{ | ||
value: expressFee, | ||
}, | ||
); | ||
|
||
console.log('Sent valued msg to destination contract:', sendTx.hash); | ||
|
||
// Wait for the distribution to complete by checking the balance of the first account. | ||
while ((await destination.mockToken.balanceOf(accounts[0])).eq(initialBalance)) { | ||
await sleep(1000); | ||
} | ||
|
||
console.log('--- After ---'); | ||
// Log the balances of the accounts. | ||
await logAccountBalances(); | ||
} | ||
|
||
module.exports = { | ||
deploy, | ||
execute, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.9; | ||
|
||
import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; | ||
|
||
contract MockERC20 is ERC20 { | ||
constructor() ERC20("Mock Token", 'MTK') {} | ||
|
||
function mint(address to, uint256 amount) public { | ||
_mint(to, amount); | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo? Double comment?