Skip to content
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: add nft and token linker contracts #144

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
"options": {
"trailingComma": "all"
}
},
{
"files": "*.json",
"options": {
"tabWidth": 2
}
}
]
}
3 changes: 2 additions & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"rules": {
"quotes": ["error", "single"],
"compiler-version": ["off"],
"func-visibility": ["warn", {"ignoreConstructors": true}]
"func-visibility": ["warn", { "ignoreConstructors": true }],
"avoid-low-level-calls": "off"
}
}
1 change: 0 additions & 1 deletion examples/evm/cross-chain-lending/CompoundInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ contract CompoundInterface is AxelarExecutable {
revert('Invalid function name');
}

// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory result) = address(this).call(
abi.encodeWithSelector(commandSelector, sourceChain, sourceAddress, tokenSymbol, amount, params)
);
Expand Down
11 changes: 11 additions & 0 deletions examples/evm/nft-linker/IERC721MintableBurnable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IERC721 } from '@openzeppelin/contracts/token/ERC721/IERC721.sol';

interface IERC721MintableBurnable is IERC721 {
function mint(address to, uint256 tokenId) external;

function burn(uint256 tokenId) external;
}
29 changes: 6 additions & 23 deletions examples/evm/nft-linker/NftLinker.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import { ERC721 } from '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import { IERC721 } from '@openzeppelin/contracts/interfaces/IERC721.sol';
import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol';
import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol';
import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol';
Expand Down Expand Up @@ -30,12 +31,7 @@ contract NftLinker is ERC721, AxelarExecutable, Upgradable {
}

//The main function users will interract with.
function sendNFT(
address operator,
uint256 tokenId,
string memory destinationChain,
address destinationAddress
) external payable {
function sendNFT(address operator, uint256 tokenId, string memory destinationChain, address destinationAddress) external payable {
//If we are the operator then this is a minted token that lives remotely.
if (operator == address(this)) {
require(ownerOf(tokenId) == _msgSender(), 'NOT_YOUR_TOKEN');
Expand All @@ -47,11 +43,7 @@ contract NftLinker is ERC721, AxelarExecutable, Upgradable {
}

//Burns and sends a token.
function _sendMintedToken(
uint256 tokenId,
string memory destinationChain,
address destinationAddress
) internal {
function _sendMintedToken(uint256 tokenId, string memory destinationChain, address destinationAddress) internal {
_burn(tokenId);
//Get the original information.
(string memory originalChain, address operator, uint256 originalTokenId) = abi.decode(
Expand All @@ -68,12 +60,7 @@ contract NftLinker is ERC721, AxelarExecutable, Upgradable {
}

//Locks and sends a token.
function _sendNativeToken(
address operator,
uint256 tokenId,
string memory destinationChain,
address destinationAddress
) internal {
function _sendNativeToken(address operator, uint256 tokenId, string memory destinationChain, address destinationAddress) internal {
//Create the payload.
bytes memory payload = abi.encode(chainName, operator, tokenId, destinationAddress);
string memory stringAddress = address(this).toString();
Expand All @@ -84,11 +71,7 @@ contract NftLinker is ERC721, AxelarExecutable, Upgradable {
}

//This is automatically executed by Axelar Microservices since gas was payed for.
function _execute(
string calldata, /*sourceChain*/
string calldata sourceAddress,
bytes calldata payload
) internal override {
function _execute(string calldata /*sourceChain*/, string calldata sourceAddress, bytes calldata payload) internal override {
//Check that the sender is another token linker.
require(sourceAddress.toAddress() == address(this), 'NOT_A_LINKER');
//Decode the payload.
Expand Down
51 changes: 51 additions & 0 deletions examples/evm/nft-linker/NftLinkerBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

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 { AxelarExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol';
import { AddressToString, StringToAddress } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/AddressString.sol';
import { Upgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Upgradable.sol';

abstract contract NftLinkerBase is AxelarExecutable, Upgradable {
using StringToAddress for string;
using AddressToString for address;

bytes32 internal constant CONTRACT_ID = keccak256('nft-linker');
IAxelarGasService public immutable gasService;

constructor(address gatewayAddress, address gasServiceAddress_) AxelarExecutable(gatewayAddress) Upgradable() {
gasService = IAxelarGasService(gasServiceAddress_);
}

function contractId() external pure override returns (bytes32) {
return CONTRACT_ID;
}

function sendNft(string memory destinationChain, address to, uint256 tokenId, address refundAddress) external payable virtual {
string memory thisAddress = address(this).toString();
_takeNft(msg.sender, tokenId);
bytes memory payload = abi.encode(to, tokenId);
if (msg.value > 0) {
gasService.payNativeGasForContractCall{ value: msg.value }(
address(this),
destinationChain,
thisAddress,
payload,
refundAddress
);
}
gateway.callContract(destinationChain, thisAddress, payload);
}

function _execute(string calldata /*sourceChain*/, string calldata sourceAddress, bytes calldata payload) internal override {
if (sourceAddress.toAddress() != address(this)) return;
(address recipient, uint256 tokenId) = abi.decode(payload, (address, uint256));
_giveNft(recipient, tokenId);
}

function _giveNft(address to, uint256 tokenId) internal virtual;

function _takeNft(address from, uint256 tokenId) internal virtual;
}
39 changes: 39 additions & 0 deletions examples/evm/nft-linker/NftLinkerLockUnlock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IERC721 } from '@openzeppelin/contracts/interfaces/IERC721.sol';
import { NftLinkerBase } from './NftLinkerBase.sol';

contract NftLinkerLockUnlock is NftLinkerBase {
error TransferFailed();
error TransferFromFailed();

address public immutable operatorAddress;

constructor(
address gatewayAddress_,
address gasServiceAddress_,
address operatorAddress_
) NftLinkerBase(gatewayAddress_, gasServiceAddress_) {
operatorAddress = operatorAddress_;
}

function _giveNft(address to, uint256 tokenId) internal override {
(bool success, bytes memory returnData) = operatorAddress.call(
abi.encodeWithSelector(IERC721.transferFrom.selector, address(this), to, tokenId)
);
bool transferred = success && (returnData.length == uint256(0) || abi.decode(returnData, (bool)));

if (!transferred || operatorAddress.code.length == 0) revert TransferFailed();
}

function _takeNft(address from, uint256 tokenId) internal override {
(bool success, bytes memory returnData) = operatorAddress.call(
abi.encodeWithSelector(IERC721.transferFrom.selector, from, address(this), tokenId)
);
bool transferred = success && (returnData.length == uint256(0) || abi.decode(returnData, (bool)));

if (!transferred || operatorAddress.code.length == 0) revert TransferFromFailed();
}
}
39 changes: 39 additions & 0 deletions examples/evm/nft-linker/NftLinkerMintBurn.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IERC721MintableBurnable } from './IERC721MintableBurnable.sol';
import { NftLinkerBase } from './NftLinkerBase.sol';

contract NftLinkerMintBurn is NftLinkerBase {
error TransferFailed();
error TransferFromFailed();

address public immutable operatorAddress;

constructor(
address gatewayAddress_,
address gasServiceAddress_,
address operatorAddress_
) NftLinkerBase(gatewayAddress_, gasServiceAddress_) {
operatorAddress = operatorAddress_;
}

function _giveNft(address to, uint256 tokenId) internal override {
(bool success, bytes memory returnData) = operatorAddress.call(
abi.encodeWithSelector(IERC721MintableBurnable.mint.selector, to, tokenId)
);
bool transferred = success && (returnData.length == uint256(0) || abi.decode(returnData, (bool)));

if (!transferred || operatorAddress.code.length == 0) revert TransferFailed();
}

function _takeNft(address /*from*/, uint256 tokenId) internal override {
(bool success, bytes memory returnData) = operatorAddress.call(
abi.encodeWithSelector(IERC721MintableBurnable.burn.selector, tokenId)
);
bool transferred = success && (returnData.length == uint256(0) || abi.decode(returnData, (bool)));

if (!transferred || operatorAddress.code.length == 0) revert TransferFromFailed();
}
}
17 changes: 17 additions & 0 deletions examples/evm/nft-linker/NftLinkerProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { Proxy } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Proxy.sol';

contract NftLinkerProxy is Proxy {
bytes32 internal constant CONTRACT_ID = keccak256('nft-linker');

constructor(address implementationAddress, address owner, bytes memory setupParams) Proxy(implementationAddress, owner, setupParams) {}

function contractId() internal pure override returns (bytes32) {
return CONTRACT_ID;
}

receive() external payable override {}
}
Loading