Skip to content

Commit 426c3fc

Browse files
committed
add/move files that are no longer planned to be in the main repo
1 parent 86caeeb commit 426c3fc

File tree

11 files changed

+323
-11
lines changed

11 files changed

+323
-11
lines changed

contracts/crosschain/axelar/AxelarGatewayDestination.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contract
66
import {CAIP2} from "@openzeppelin/contracts@master/utils/CAIP2.sol";
77
import {CAIP10} from "@openzeppelin/contracts@master/utils/CAIP10.sol";
88
import {Strings} from "@openzeppelin/contracts@master/utils/Strings.sol";
9+
import {IERC7786GatewayDestinationPassive, IERC7786Receiver} from "../interfaces/draft-IERC7786.sol";
910
import {AxelarGatewayBase} from "./AxelarGatewayBase.sol";
10-
// import {IERC7786GatewayDestinationPassive, IERC7786Receiver} from "@openzeppelin/contracts@master/interfaces/IERC7786.sol";
11-
import {IERC7786GatewayDestinationPassive, IERC7786Receiver} from "../vendor/draft-IERC7786.sol";
1211

1312
abstract contract AxelarGatewayDestination is IERC7786GatewayDestinationPassive, AxelarGatewayBase, AxelarExecutable {
1413
using Strings for address;

contracts/crosschain/axelar/AxelarGatewaySource.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import {CAIP2} from "@openzeppelin/contracts@master/utils/CAIP2.sol";
66
import {CAIP10} from "@openzeppelin/contracts@master/utils/CAIP10.sol";
77
import {Strings} from "@openzeppelin/contracts@master/utils/Strings.sol";
88
import {AxelarGatewayBase} from "./AxelarGatewayBase.sol";
9-
// import {IERC7786GatewaySource} from "@openzeppelin/contracts@master/interfaces/IERC7786.sol";
10-
import {IERC7786GatewaySource} from "../vendor/draft-IERC7786.sol";
9+
import {IERC7786GatewaySource} from "../interfaces/draft-IERC7786.sol";
1110

1211
abstract contract AxelarGatewaySource is IERC7786GatewaySource, AxelarGatewayBase {
1312
using Strings for address;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.24;
4+
5+
import {BitMaps} from "@openzeppelin/contracts@master/utils/structs/BitMaps.sol";
6+
import {Strings} from "@openzeppelin/contracts@master/utils/Strings.sol";
7+
import {CAIP2} from "@openzeppelin/contracts@master/utils/CAIP2.sol";
8+
import {CAIP10} from "@openzeppelin/contracts@master/utils/CAIP10.sol";
9+
import {IERC7786GatewaySource, IERC7786GatewayDestinationPassive, IERC7786Receiver} from "../interfaces/draft-IERC7786.sol";
10+
11+
contract ERC7786GatewayMock is IERC7786GatewaySource, IERC7786GatewayDestinationPassive {
12+
using BitMaps for BitMaps.BitMap;
13+
using Strings for *;
14+
15+
BitMaps.BitMap private _outbox;
16+
bool private _activeMode;
17+
18+
function _setActive(bool newActiveMode) internal {
19+
_activeMode = newActiveMode;
20+
}
21+
22+
function supportsAttribute(bytes4 /*selector*/) public pure returns (bool) {
23+
return false;
24+
}
25+
26+
function sendMessage(
27+
string calldata destination, // CAIP-2 chain ID
28+
string calldata receiver, // CAIP-10 account ID
29+
bytes calldata payload,
30+
bytes[] calldata attributes
31+
) public payable returns (bytes32) {
32+
string memory source = CAIP2.local();
33+
string memory sender = msg.sender.toChecksumHexString();
34+
35+
require(destination.equal(source), "This mock only supports local messages");
36+
for (uint256 i = 0; i < attributes.length; ++i) {
37+
bytes4 selector = bytes4(attributes[i][0:4]);
38+
if (!supportsAttribute(selector)) revert UnsupportedAttribute(selector);
39+
}
40+
41+
if (_activeMode) {
42+
address target = Strings.parseAddress(receiver);
43+
IERC7786Receiver(target).receiveMessage(address(this), new bytes(0), source, sender, payload, attributes);
44+
} else {
45+
_outbox.set(uint256(keccak256(abi.encode(source, sender, receiver, payload, attributes))));
46+
}
47+
48+
emit MessageCreated(0, CAIP10.format(source, sender), CAIP10.format(source, receiver), payload, attributes);
49+
return 0;
50+
}
51+
52+
function validateReceivedMessage(
53+
bytes calldata /*messageKey*/, // this mock doesn't use a messageKey
54+
string calldata source,
55+
string calldata sender,
56+
bytes calldata payload,
57+
bytes[] calldata attributes
58+
) public {
59+
uint256 digest = uint256(
60+
keccak256(abi.encode(source, sender, msg.sender.toChecksumHexString(), payload, attributes))
61+
);
62+
require(_outbox.get(digest), "invalid message");
63+
_outbox.unset(digest);
64+
}
65+
}

contracts/crosschain/mocks/ERC7786ReceiverMock.sol

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
// SPDX-License-Identifier: MIT
22

3-
pragma solidity ^0.8.27;
3+
pragma solidity ^0.8.0;
44

5-
import {ERC7786Receiver} from "../vendor/draft-ERC7786Receiver.sol";
5+
import {ERC7786Receiver} from "../utils/draft-ERC7786Receiver.sol";
66

77
contract ERC7786ReceiverMock is ERC7786Receiver {
8-
address public immutable GATEWAY;
8+
address private immutable _gateway;
99

1010
event MessageReceived(address gateway, string source, string sender, bytes payload, bytes[] attributes);
1111

12-
constructor(address _gateway) {
13-
GATEWAY = _gateway;
12+
constructor(address gateway_) {
13+
_gateway = gateway_;
1414
}
1515

1616
function _isKnownGateway(address instance) internal view virtual override returns (bool) {
17-
return instance == GATEWAY;
17+
return instance == _gateway;
1818
}
1919

2020
function _processMessage(

contracts/crosschain/vendor/draft-ERC7786Receiver.sol renamed to contracts/crosschain/utils/draft-ERC7786Receiver.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
pragma solidity ^0.8.0;
44

5-
import {IERC7786GatewayDestinationPassive, IERC7786Receiver} from "./draft-IERC7786.sol";
5+
import {IERC7786GatewayDestinationPassive, IERC7786Receiver} from "../interfaces/draft-IERC7786.sol";
66

77
/**
88
* @dev Base implementation of an ERC-7786 compliant cross-chain message receiver.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
const { ethers } = require('hardhat');
2+
const { expect } = require('chai');
3+
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
4+
5+
const { getLocalCAIP } = require('../helpers/chains');
6+
const payload = require('../helpers/random').generators.hexBytes(128);
7+
const attributes = [];
8+
9+
const getAddress = account => ethers.getAddress(account.target ?? account.address ?? account);
10+
11+
async function fixture() {
12+
const [sender, notAGateway] = await ethers.getSigners();
13+
const { caip2, toCaip10 } = await getLocalCAIP();
14+
15+
const gateway = await ethers.deployContract('$ERC7786GatewayMock');
16+
const receiver = await ethers.deployContract('$ERC7786ReceiverMock', [gateway]);
17+
18+
return { sender, notAGateway, gateway, receiver, caip2, toCaip10 };
19+
}
20+
21+
// NOTE: here we are only testing the receiver. Failures of the gateway itself (invalid attributes, ...) are out of scope.
22+
describe('ERC7786Receiver', function () {
23+
beforeEach(async function () {
24+
Object.assign(this, await loadFixture(fixture));
25+
});
26+
27+
describe('active mode', function () {
28+
beforeEach(async function () {
29+
await this.gateway.$_setActive(true);
30+
});
31+
32+
it('nominal workflow', async function () {
33+
await expect(this.gateway.connect(this.sender).sendMessage(this.caip2, getAddress(this.receiver), payload, attributes))
34+
.to.emit(this.gateway, 'MessageCreated')
35+
.withArgs(ethers.ZeroHash, this.toCaip10(this.sender), this.toCaip10(this.receiver), payload, attributes)
36+
.to.emit(this.receiver, 'MessageReceived')
37+
.withArgs(this.gateway, this.caip2, getAddress(this.sender), payload, attributes);
38+
});
39+
});
40+
41+
describe('passive mode', function () {
42+
beforeEach(async function () {
43+
await this.gateway.$_setActive(false);
44+
});
45+
46+
it('nominal workflow', async function () {
47+
await expect(this.gateway.connect(this.sender).sendMessage(this.caip2, this.receiver.target, payload, attributes))
48+
.to.emit(this.gateway, 'MessageCreated')
49+
.withArgs(ethers.ZeroHash, this.toCaip10(this.sender), this.toCaip10(this.receiver), payload, attributes)
50+
.to.not.emit(this.receiver, 'MessageReceived');
51+
52+
await expect(
53+
this.receiver.receiveMessage(this.gateway, '0x', this.caip2, getAddress(this.sender), payload, attributes),
54+
)
55+
.to.emit(this.receiver, 'MessageReceived')
56+
.withArgs(this.gateway, this.caip2, getAddress(this.sender), payload, attributes);
57+
});
58+
59+
it('invalid message', async function () {
60+
await this.gateway.connect(this.sender).sendMessage(this.caip2, this.receiver.target, payload, attributes);
61+
62+
// Altering the message (in this case, changing the sender's address)
63+
// Here the error is actually triggered by the gateway itself.
64+
await expect(
65+
this.receiver.receiveMessage(this.gateway, '0x', this.caip2, getAddress(this.notAGateway), payload, attributes),
66+
).to.be.revertedWith('invalid message');
67+
});
68+
69+
it('invalid gateway', async function () {
70+
await expect(
71+
this.receiver.receiveMessage(this.notAGateway, '0x', this.caip2, getAddress(this.sender), payload, attributes),
72+
)
73+
.to.be.revertedWithCustomError(this.receiver, 'ERC7786ReceiverInvalidGateway')
74+
.withArgs(this.notAGateway);
75+
});
76+
77+
it('with value', async function () {
78+
await expect(
79+
this.receiver.receiveMessage(this.gateway, '0x', this.caip2, getAddress(this.sender), payload, attributes, {
80+
value: 1n,
81+
}),
82+
).to.be.revertedWithCustomError(this.receiver, 'ERC7786ReceivePassiveModeValue');
83+
});
84+
});
85+
});

test/helpers/chains.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// NOTE: this file defines some examples of CAIP-2 and CAIP-10 identifiers.
2+
// The following listing does not pretend to be exhaustive or even accurate. It SHOULD NOT be used in production.
3+
4+
const { ethers } = require('hardhat');
5+
const { mapValues } = require('./iterate');
6+
7+
// EVM (https://axelarscan.io/resources/chains?type=evm)
8+
const ethereum = {
9+
Ethereum: '1',
10+
optimism: '10',
11+
binance: '56',
12+
Polygon: '137',
13+
Fantom: '250',
14+
fraxtal: '252',
15+
filecoin: '314',
16+
Moonbeam: '1284',
17+
centrifuge: '2031',
18+
kava: '2222',
19+
mantle: '5000',
20+
base: '8453',
21+
immutable: '13371',
22+
arbitrum: '42161',
23+
celo: '42220',
24+
Avalanche: '43114',
25+
linea: '59144',
26+
blast: '81457',
27+
scroll: '534352',
28+
aurora: '1313161554',
29+
};
30+
31+
// Cosmos (https://axelarscan.io/resources/chains?type=cosmos)
32+
const cosmos = {
33+
Axelarnet: 'axelar-dojo-1',
34+
osmosis: 'osmosis-1',
35+
cosmoshub: 'cosmoshub-4',
36+
juno: 'juno-1',
37+
'e-money': 'emoney-3',
38+
injective: 'injective-1',
39+
crescent: 'crescent-1',
40+
kujira: 'kaiyo-1',
41+
'secret-snip': 'secret-4',
42+
secret: 'secret-4',
43+
sei: 'pacific-1',
44+
stargaze: 'stargaze-1',
45+
assetmantle: 'mantle-1',
46+
fetch: 'fetchhub-4',
47+
ki: 'kichain-2',
48+
evmos: 'evmos_9001-2',
49+
aura: 'xstaxy-1',
50+
comdex: 'comdex-1',
51+
persistence: 'core-1',
52+
regen: 'regen-1',
53+
umee: 'umee-1',
54+
agoric: 'agoric-3',
55+
xpla: 'dimension_37-1',
56+
acre: 'acre_9052-1',
57+
stride: 'stride-1',
58+
carbon: 'carbon-1',
59+
sommelier: 'sommelier-3',
60+
neutron: 'neutron-1',
61+
rebus: 'reb_1111-1',
62+
archway: 'archway-1',
63+
provenance: 'pio-mainnet-1',
64+
ixo: 'ixo-5',
65+
migaloo: 'migaloo-1',
66+
teritori: 'teritori-1',
67+
haqq: 'haqq_11235-1',
68+
celestia: 'celestia',
69+
ojo: 'agamotto',
70+
chihuahua: 'chihuahua-1',
71+
saga: 'ssc-1',
72+
dymension: 'dymension_1100-1',
73+
fxcore: 'fxcore',
74+
c4e: 'perun-1',
75+
bitsong: 'bitsong-2b',
76+
nolus: 'pirin-1',
77+
lava: 'lava-mainnet-1',
78+
'terra-2': 'phoenix-1',
79+
terra: 'columbus-5',
80+
};
81+
82+
const makeCAIP = ({ namespace, reference, account }) => ({
83+
namespace,
84+
reference,
85+
account,
86+
caip2: `${namespace}:${reference}`,
87+
caip10: `${namespace}:${reference}:${account}`,
88+
toCaip10: other => `${namespace}:${reference}:${ethers.getAddress(other.target ?? other.address ?? other)}`,
89+
});
90+
91+
module.exports = {
92+
CHAINS: mapValues(
93+
Object.assign(
94+
mapValues(ethereum, reference => ({
95+
namespace: 'eip155',
96+
reference,
97+
account: ethers.Wallet.createRandom().address,
98+
})),
99+
mapValues(cosmos, reference => ({
100+
namespace: 'cosmos',
101+
reference,
102+
account: ethers.encodeBase58(ethers.randomBytes(32)),
103+
})),
104+
),
105+
makeCAIP,
106+
),
107+
getLocalCAIP: account =>
108+
ethers.provider.getNetwork().then(({ chainId }) => makeCAIP({ namespace: 'eip155', reference: chainId, account })),
109+
};

test/helpers/iterate.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
module.exports = {
2+
// ================================================= Array helpers =================================================
3+
4+
// Cut an array into an array of sized-length arrays
5+
// Example: chunk([1,2,3,4,5,6,7,8], 3) → [[1,2,3],[4,5,6],[7,8]]
6+
chunk: (array, size = 1) =>
7+
Array.from({ length: Math.ceil(array.length / size) }, (_, i) => array.slice(i * size, i * size + size)),
8+
9+
// Cartesian cross product of an array of arrays
10+
// Example: product([1,2],[a,b,c],[true]) → [[1,a,true],[1,b,true],[1,c,true],[2,a,true],[2,b,true],[2,c,true]]
11+
product: (...arrays) => arrays.reduce((a, b) => a.flatMap(ai => b.map(bi => [...ai, bi])), [[]]),
12+
13+
// Range from start to end in increment
14+
// Example: range(17,42,7) → [17,24,31,38]
15+
range: (start, stop = undefined, step = 1) => {
16+
if (stop == undefined) {
17+
stop = start;
18+
start = 0;
19+
}
20+
return start < stop ? Array.from({ length: (stop - start + step - 1) / step }, (_, i) => start + i * step) : [];
21+
},
22+
23+
// Unique elements, with an optional getter function
24+
// Example: unique([1,1,2,3,4,8,1,3,8,13,42]) → [1,2,3,4,8,13,42]
25+
unique: (array, op = x => x) => array.filter((obj, i) => array.findIndex(entry => op(obj) === op(entry)) === i),
26+
27+
// Zip arrays together. If some arrays are smaller, undefined is used as a filler.
28+
// Example: zip([1,2],[a,b,c],[true]) → [[1,a,true],[2,b,undefined],[undefined,c,undefined]]
29+
zip: (...args) => Array.from({ length: Math.max(...args.map(arg => arg.length)) }, (_, i) => args.map(arg => arg[i])),
30+
31+
// ================================================ Object helpers =================================================
32+
33+
// Create a new object by mapping the values through a function, keeping the keys
34+
// Example: mapValues({a:1,b:2,c:3}, x => x**2) → {a:1,b:4,c:9}
35+
mapValues: (obj, fn) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)])),
36+
};

0 commit comments

Comments
 (0)