fix: release FX fixed and unit tests added

This commit is contained in:
PedroCailleret 2022-12-09 01:45:23 -03:00
parent 932b2a03b4
commit d2b4e21241
22 changed files with 467 additions and 89 deletions

View File

@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/8b1c16bb48b706a49fa1987390ca36fd.json" "buildInfo": "../../build-info/246d885849ebe73a8f97a386ac3822ea.json"
} }

View File

@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/8b1c16bb48b706a49fa1987390ca36fd.json" "buildInfo": "../../build-info/10314198dd1b1c93bdb3afe5190d4fa3.json"
} }

View File

@ -38,6 +38,11 @@
"name": "LengthMismatch", "name": "LengthMismatch",
"type": "error" "type": "error"
}, },
{
"inputs": [],
"name": "LockExpired",
"type": "error"
},
{ {
"inputs": [], "inputs": [],
"name": "LoopOverflow", "name": "LoopOverflow",

View File

@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/8b1c16bb48b706a49fa1987390ca36fd.json" "buildInfo": "../../build-info/246d885849ebe73a8f97a386ac3822ea.json"
} }

View File

@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/8b1c16bb48b706a49fa1987390ca36fd.json" "buildInfo": "../../../../build-info/246d885849ebe73a8f97a386ac3822ea.json"
} }

View File

@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/8b1c16bb48b706a49fa1987390ca36fd.json" "buildInfo": "../../../../build-info/246d885849ebe73a8f97a386ac3822ea.json"
} }

View File

@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/8b1c16bb48b706a49fa1987390ca36fd.json" "buildInfo": "../../../../build-info/246d885849ebe73a8f97a386ac3822ea.json"
} }

View File

@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/8b1c16bb48b706a49fa1987390ca36fd.json" "buildInfo": "../../../../build-info/246d885849ebe73a8f97a386ac3822ea.json"
} }

View File

@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/8b1c16bb48b706a49fa1987390ca36fd.json" "buildInfo": "../../../../build-info/246d885849ebe73a8f97a386ac3822ea.json"
} }

View File

@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/8b1c16bb48b706a49fa1987390ca36fd.json" "buildInfo": "../../../../build-info/246d885849ebe73a8f97a386ac3822ea.json"
} }

View File

@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/8b1c16bb48b706a49fa1987390ca36fd.json" "buildInfo": "../../../../build-info/246d885849ebe73a8f97a386ac3822ea.json"
} }

View File

@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/8b1c16bb48b706a49fa1987390ca36fd.json" "buildInfo": "../../../../build-info/246d885849ebe73a8f97a386ac3822ea.json"
} }

View File

@ -1,4 +1,4 @@
{ {
"_format": "hh-sol-dbg-1", "_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/43c90b4df62900d52ce9b2809e9d7089.json" "buildInfo": "../../build-info/a7ece494b4784ac2499b0ced3d2c9c0a.json"
} }

File diff suppressed because one or more lines are too long

View File

@ -100,4 +100,7 @@ interface EventAndErrors {
/// @dev Reverts when success return value returns false. /// @dev Reverts when success return value returns false.
/// @dev 0xe10bf1cc /// @dev 0xe10bf1cc
error StaticCallFailed(); error StaticCallFailed();
/// @dev Reverts on an expired lock.
/// @dev 0xf6fafba0
error LockExpired();
} }

View File

@ -46,7 +46,7 @@ contract P2PIX is
/// @dev List of Locks. /// @dev List of Locks.
mapping(bytes32 => DT.Lock) public mapLocks; mapping(bytes32 => DT.Lock) public mapLocks;
/// @dev List of Pix transactions already signed. /// @dev List of Pix transactions already signed.
mapping(bytes32 => bool) private usedTransactions; mapping(bytes32 => bool) public usedTransactions;
/// @dev Seller casted to key => Seller's allowlist merkleroot. /// @dev Seller casted to key => Seller's allowlist merkleroot.
mapping(uint256 => bytes32) public sellerAllowList; mapping(uint256 => bytes32) public sellerAllowList;
/// @dev Tokens allowed to serve as the underlying amount of a deposit. /// @dev Tokens allowed to serve as the underlying amount of a deposit.
@ -243,9 +243,12 @@ contract P2PIX is
) public nonReentrant { ) public nonReentrant {
DT.Lock storage l = mapLocks[lockID]; DT.Lock storage l = mapLocks[lockID];
if ( // if (
l.expirationBlock <= block.number || l.amount <= 0 // l.expirationBlock <= block.number || l.amount <= 0
) revert AlreadyReleased(); // ) revert AlreadyReleased();
if (l.amount == 0) revert AlreadyReleased();
if (l.expirationBlock < block.number)
revert LockExpired();
DT.Deposit storage d = mapDeposits[l.depositID]; DT.Deposit storage d = mapDeposits[l.depositID];
bytes32 message = keccak256( bytes32 message = keccak256(
@ -275,7 +278,8 @@ contract P2PIX is
ERC20 t = ERC20(d.token); ERC20 t = ERC20(d.token);
// We cache values before zeroing them out. // We cache values before zeroing them out.
uint256 totalAmount = (l.amount - l.relayerPremium); uint256 lockAmount = l.amount;
uint256 totalAmount = (lockAmount - l.relayerPremium);
l.amount = 0; l.amount = 0;
l.expirationBlock = 0; l.expirationBlock = 0;
@ -284,11 +288,12 @@ contract P2PIX is
if (msg.sender != l.relayerAddress) { if (msg.sender != l.relayerAddress) {
userRecord[_castAddrToKey(msg.sender)] += l userRecord[_castAddrToKey(msg.sender)] += l
.relayerPremium; .relayerPremium;
userRecord[_castAddrToKey(l.relayerAddress)] += l userRecord[
.amount; _castAddrToKey(l.relayerAddress)
] += lockAmount;
} else { } else {
userRecord[_castAddrToKey(msg.sender)] += (l userRecord[_castAddrToKey(msg.sender)] += (l
.relayerPremium + l.amount); .relayerPremium + lockAmount);
} }
SafeTransferLib.safeTransfer( SafeTransferLib.safeTransfer(

View File

@ -5,4 +5,4 @@
], ],
"p2pix": "0x37c856F4d5bC2597da60f607b1335738468453F3", "p2pix": "0x37c856F4d5bC2597da60f607b1335738468453F3",
"token": "0x294003F602c321627152c6b7DED3EAb5bEa853Ee" "token": "0x294003F602c321627152c6b7DED3EAb5bEa853Ee"
} }

View File

@ -45,6 +45,11 @@ const _abi = [
name: "LengthMismatch", name: "LengthMismatch",
type: "error", type: "error",
}, },
{
inputs: [],
name: "LockExpired",
type: "error",
},
{ {
inputs: [], inputs: [],
name: "LoopOverflow", name: "LoopOverflow",

File diff suppressed because one or more lines are too long

View File

@ -49,6 +49,7 @@ export interface P2PIXInterface extends utils.Interface {
"setValidSigners(address[])": FunctionFragment; "setValidSigners(address[])": FunctionFragment;
"tokenSettings(address[],bool[])": FunctionFragment; "tokenSettings(address[],bool[])": FunctionFragment;
"unlockExpired(bytes32[])": FunctionFragment; "unlockExpired(bytes32[])": FunctionFragment;
"usedTransactions(bytes32)": FunctionFragment;
"userRecord(uint256)": FunctionFragment; "userRecord(uint256)": FunctionFragment;
"validBacenSigners(uint256)": FunctionFragment; "validBacenSigners(uint256)": FunctionFragment;
"withdraw(uint256,bytes32[])": FunctionFragment; "withdraw(uint256,bytes32[])": FunctionFragment;
@ -77,6 +78,7 @@ export interface P2PIXInterface extends utils.Interface {
| "setValidSigners" | "setValidSigners"
| "tokenSettings" | "tokenSettings"
| "unlockExpired" | "unlockExpired"
| "usedTransactions"
| "userRecord" | "userRecord"
| "validBacenSigners" | "validBacenSigners"
| "withdraw" | "withdraw"
@ -180,6 +182,10 @@ export interface P2PIXInterface extends utils.Interface {
functionFragment: "unlockExpired", functionFragment: "unlockExpired",
values: [PromiseOrValue<BytesLike>[]] values: [PromiseOrValue<BytesLike>[]]
): string; ): string;
encodeFunctionData(
functionFragment: "usedTransactions",
values: [PromiseOrValue<BytesLike>]
): string;
encodeFunctionData( encodeFunctionData(
functionFragment: "userRecord", functionFragment: "userRecord",
values: [PromiseOrValue<BigNumberish>] values: [PromiseOrValue<BigNumberish>]
@ -253,6 +259,10 @@ export interface P2PIXInterface extends utils.Interface {
functionFragment: "unlockExpired", functionFragment: "unlockExpired",
data: BytesLike data: BytesLike
): Result; ): Result;
decodeFunctionResult(
functionFragment: "usedTransactions",
data: BytesLike
): Result;
decodeFunctionResult(functionFragment: "userRecord", data: BytesLike): Result; decodeFunctionResult(functionFragment: "userRecord", data: BytesLike): Result;
decodeFunctionResult( decodeFunctionResult(
functionFragment: "validBacenSigners", functionFragment: "validBacenSigners",
@ -596,6 +606,11 @@ export interface P2PIX extends BaseContract {
overrides?: Overrides & { from?: PromiseOrValue<string> } overrides?: Overrides & { from?: PromiseOrValue<string> }
): Promise<ContractTransaction>; ): Promise<ContractTransaction>;
usedTransactions(
arg0: PromiseOrValue<BytesLike>,
overrides?: CallOverrides
): Promise<[boolean]>;
userRecord( userRecord(
arg0: PromiseOrValue<BigNumberish>, arg0: PromiseOrValue<BigNumberish>,
overrides?: CallOverrides overrides?: CallOverrides
@ -739,6 +754,11 @@ export interface P2PIX extends BaseContract {
overrides?: Overrides & { from?: PromiseOrValue<string> } overrides?: Overrides & { from?: PromiseOrValue<string> }
): Promise<ContractTransaction>; ): Promise<ContractTransaction>;
usedTransactions(
arg0: PromiseOrValue<BytesLike>,
overrides?: CallOverrides
): Promise<boolean>;
userRecord( userRecord(
arg0: PromiseOrValue<BigNumberish>, arg0: PromiseOrValue<BigNumberish>,
overrides?: CallOverrides overrides?: CallOverrides
@ -882,6 +902,11 @@ export interface P2PIX extends BaseContract {
overrides?: CallOverrides overrides?: CallOverrides
): Promise<void>; ): Promise<void>;
usedTransactions(
arg0: PromiseOrValue<BytesLike>,
overrides?: CallOverrides
): Promise<boolean>;
userRecord( userRecord(
arg0: PromiseOrValue<BigNumberish>, arg0: PromiseOrValue<BigNumberish>,
overrides?: CallOverrides overrides?: CallOverrides
@ -1115,6 +1140,11 @@ export interface P2PIX extends BaseContract {
overrides?: Overrides & { from?: PromiseOrValue<string> } overrides?: Overrides & { from?: PromiseOrValue<string> }
): Promise<BigNumber>; ): Promise<BigNumber>;
usedTransactions(
arg0: PromiseOrValue<BytesLike>,
overrides?: CallOverrides
): Promise<BigNumber>;
userRecord( userRecord(
arg0: PromiseOrValue<BigNumberish>, arg0: PromiseOrValue<BigNumberish>,
overrides?: CallOverrides overrides?: CallOverrides
@ -1241,6 +1271,11 @@ export interface P2PIX extends BaseContract {
overrides?: Overrides & { from?: PromiseOrValue<string> } overrides?: Overrides & { from?: PromiseOrValue<string> }
): Promise<PopulatedTransaction>; ): Promise<PopulatedTransaction>;
usedTransactions(
arg0: PromiseOrValue<BytesLike>,
overrides?: CallOverrides
): Promise<PopulatedTransaction>;
userRecord( userRecord(
arg0: PromiseOrValue<BigNumberish>, arg0: PromiseOrValue<BigNumberish>,
overrides?: CallOverrides overrides?: CallOverrides

View File

@ -944,69 +944,345 @@ describe("P2PIX", () => {
expect(newState3.valid).to.be.false; expect(newState3.valid).to.be.false;
}); });
}); });
// describe("Release", async () => { describe("Release", async () => {
// // it("should revert if lock has expired or has already been released") it("should revert if lock has expired", async () => {
// // it("should revert if signed message has already been used") const messageToSign = ethers.utils.solidityKeccak256(
// // it("should revert if ecrecovered signer is invalid") ["string", "uint256", "uint256"],
// // // @todo Finish storage and event checks ["pixTarget", 100, "1337"],
// // it("should release lock, update storage and emit events", async () => { );
// // const endtoendID = "124"; const flatSig = await acc01.signMessage(
// // const pixTarget = "pixTarget"; ethers.utils.arrayify(messageToSign),
// // const messageToSign = ethers.utils.solidityKeccak256( );
// // ["string", "uint256", "uint256"], const sig = ethers.utils.splitSignature(flatSig);
// // [pixTarget, 100, endtoendID], await erc20.approve(p2pix.address, price);
// // ); await p2pix.deposit(
// // const messageHashBytes = erc20.address,
// // ethers.utils.arrayify(messageToSign); price,
// // const flatSig = await acc01.signMessage( "pixTarget",
// // messageHashBytes, merkleRoot,
// // ); );
// // const sig = ethers.utils.splitSignature(flatSig); await p2pix
// // const root = ethers.constants.HashZero; .connect(acc03)
.lock(
0,
acc02.address,
acc03.address,
6,
100,
[],
[],
);
const lockID = ethers.utils.solidityKeccak256(
["uint256", "uint256", "address"],
[0, 100, acc02.address],
);
await mine(13);
const fail = p2pix.release(
lockID,
acc03.address,
"1337",
sig.r,
sig.s,
sig.v,
);
// // await erc20.approve(p2pix.address, price); await expect(fail).to.be.revertedWithCustomError(
// // await p2pix.deposit( p2pix,
// // erc20.address, P2PixErrors.LockExpired,
// // price, );
// // pixTarget, });
// // root, it("should revert if lock has already been released", async () => {
// // ); const messageToSign = ethers.utils.solidityKeccak256(
// // await p2pix ["string", "uint256", "uint256"],
// // .connect(acc01) ["pixTarget", 100, "1337"],
// // .lock( );
// // 0, const flatSig = await acc01.signMessage(
// // acc02.address, ethers.utils.arrayify(messageToSign),
// // acc03.address, );
// // 0, const sig = ethers.utils.splitSignature(flatSig);
// // 100, await erc20.approve(p2pix.address, price);
// // [], await p2pix.deposit(
// // [], erc20.address,
// // ); price,
// // const lockID = ethers.utils.solidityKeccak256( "pixTarget",
// // ["uint256", "uint256", "address"], merkleRoot,
// // [0, 100, acc02.address], );
// // ); await p2pix
// // const storage1: Lock = await p2pix.callStatic.mapLocks( .connect(acc03)
// // lockID, .lock(
// // ); 0,
// // const tx = await p2pix acc02.address,
// // .connect(acc01) acc03.address,
// // .release( 6,
// // lockID, 100,
// // acc03.address, [],
// // endtoendID, [],
// // sig.r, );
// // sig.s, const lockID = ethers.utils.solidityKeccak256(
// // sig.v, ["uint256", "uint256", "address"],
// // ); [0, 100, acc02.address],
// // }); );
// // it("should release multiple locks") - EDGE CASE TEST { await p2pix.release(
// // TEST 3 CASES ( lockID,
// // EMPTY PREMIUM, acc03.address,
// // LOCK RELAYER != RELEASE RELAYER, (check userRecord storage update) "1337",
// // LOCK RELAYER == RELEASE RELAYER (check userRecord storage update) sig.r,
// // )} sig.s,
// }); sig.v,
);
const fail = p2pix.release(
lockID,
acc03.address,
"1337",
sig.r,
sig.s,
sig.v,
);
await expect(fail).to.be.revertedWithCustomError(
p2pix,
P2PixErrors.AlreadyReleased,
);
});
it("should revert if signed message has already been used", async () => {
const messageToSign = ethers.utils.solidityKeccak256(
["string", "uint256", "uint256"],
["pixTarget", 100, "1337"],
);
const flatSig = await owner.signMessage(
ethers.utils.arrayify(messageToSign),
);
const sig = ethers.utils.splitSignature(flatSig);
await erc20.approve(p2pix.address, price);
await p2pix.deposit(
erc20.address,
price,
"pixTarget",
ethers.constants.HashZero,
);
await p2pix
.connect(acc03)
.lock(
0,
acc02.address,
acc03.address,
6,
100,
[],
[],
);
const lockID = ethers.utils.solidityKeccak256(
["uint256", "uint256", "address"],
[0, 100, acc02.address],
);
await p2pix
.connect(acc01)
.release(
lockID,
acc02.address,
"1337",
sig.r,
sig.s,
sig.v,
);
await p2pix
.connect(acc03)
.lock(
0,
acc02.address,
acc03.address,
6,
100,
[],
[],
);
const lockID2 = ethers.utils.solidityKeccak256(
["uint256", "uint256", "address"],
[0, 100, acc02.address],
);
const fail = p2pix
.connect(acc01)
.release(
lockID2,
acc02.address,
"1337",
sig.r,
sig.s,
sig.v,
);
await expect(fail).to.be.revertedWithCustomError(
p2pix,
P2PixErrors.TxAlreadyUsed,
);
});
it("should revert if ecrecovered signer is invalid", async () => {
const messageToSign = ethers.utils.solidityKeccak256(
["string", "uint256", "uint256"],
["pixTarget", 100, "1337"],
);
const flatSig = await acc03.signMessage(
ethers.utils.arrayify(messageToSign),
);
const sig = ethers.utils.splitSignature(flatSig);
await erc20.approve(p2pix.address, price);
await p2pix.deposit(
erc20.address,
price,
"pixTarget",
ethers.constants.HashZero,
);
await p2pix
.connect(acc03)
.lock(
0,
acc02.address,
acc03.address,
6,
100,
[],
[],
);
const lockID = ethers.utils.solidityKeccak256(
["uint256", "uint256", "address"],
[0, 100, acc02.address],
);
const fail = p2pix
.connect(acc01)
.release(
lockID,
acc02.address,
"1337",
sig.r,
sig.s,
sig.v,
);
await expect(fail).to.be.revertedWithCustomError(
p2pix,
P2PixErrors.InvalidSigner,
);
});
it("should release lock, update storage and emit events", async () => {
const endtoendID = "124";
const pixTarget = "pixTarget";
const messageToSign = ethers.utils.solidityKeccak256(
["string", "uint256", "uint256"],
[pixTarget, 100, endtoendID],
);
// Note: messageToSign is a string, that is 66-bytes long, to sign the
// binary value, we must convert it to the 32 byte Array that
// the string represents
//
// i.e.,
// 66-byte string
// "0x592fa743889fc7f92ac2a37bb1f5ba1daf2a5c84741ca0e0061d243a2e6707ba"
// ... vs ...
// 32 entry Uint8Array
// [ 89, 47, 167, 67, 136, 159, ... 103, 7, 186]
const messageHashBytes =
ethers.utils.arrayify(messageToSign);
const flatSig = await acc01.signMessage(
messageHashBytes,
);
const sig = ethers.utils.splitSignature(flatSig);
const root = ethers.constants.HashZero;
await erc20.approve(p2pix.address, price);
await p2pix.deposit(
erc20.address,
price,
pixTarget,
root,
);
await p2pix
.connect(acc03)
.lock(
0,
acc02.address,
acc03.address,
6,
100,
[],
[],
);
const lockID = ethers.utils.solidityKeccak256(
["uint256", "uint256", "address"],
[0, 100, acc02.address],
);
const acc01Key = await p2pix.callStatic._castAddrToKey(
acc01.address,
);
const acc03Key = await p2pix.callStatic._castAddrToKey(
acc03.address,
);
const userRecordA = await p2pix.callStatic.userRecord(
acc01Key,
);
const userRecord1 = await p2pix.callStatic.userRecord(
acc03Key,
);
const storage1: Lock = await p2pix.callStatic.mapLocks(
lockID,
);
const tx = await p2pix
.connect(acc01)
.release(
lockID,
acc02.address,
endtoendID,
sig.r,
sig.s,
sig.v,
);
const storage2: Lock = await p2pix.callStatic.mapLocks(
lockID,
);
const userRecordB = await p2pix.callStatic.userRecord(
acc01Key,
);
const userRecord2 = await p2pix.callStatic.userRecord(
acc03Key,
);
const used = await p2pix.callStatic.usedTransactions(
messageHashBytes,
);
expect(tx).to.be.ok;
await expect(tx)
.to.emit(p2pix, "LockReleased")
.withArgs(acc02.address, lockID);
expect(storage1.expirationBlock).to.eq(
ethers.BigNumber.from(16),
);
expect(storage1.amount).to.eq(
ethers.BigNumber.from(100),
);
expect(storage2.expirationBlock).to.eq(
ethers.BigNumber.from(0),
);
expect(storage2.amount).to.eq(ethers.BigNumber.from(0));
expect(used).to.eq(true);
expect(userRecordA).to.eq(ethers.constants.Zero);
expect(userRecord1).to.eq(ethers.constants.Zero);
expect(userRecordB).to.eq(ethers.BigNumber.from(6));
expect(userRecord2).to.eq(ethers.BigNumber.from(100));
await expect(tx).to.changeTokenBalances(
erc20,
[acc03.address, acc02.address],
[3, 97],
// acc02 is acting both as buyer and relayerTarget
// (i.e., 94 + 3 = 97)
);
});
/// @todo
// it("should release multiple locks") - EDGE CASE TEST {
// TEST 3 CASES (
// EMPTY PREMIUM,
// LOCK RELAYER != RELEASE RELAYER, (check userRecord storage update)
// LOCK RELAYER == RELEASE RELAYER (check userRecord storage update)
// )}
});
describe("Unexpire Locks", async () => { describe("Unexpire Locks", async () => {
it("should revert if lock isn't expired", async () => { it("should revert if lock isn't expired", async () => {
await erc20.approve(p2pix.address, price); await erc20.approve(p2pix.address, price);
@ -1059,8 +1335,8 @@ describe("P2PIX", () => {
); );
// await mine(10); // await mine(10);
await p2pix.release( await p2pix.release(
lockID, lockID,
acc03.address, acc03.address,
endtoendID, endtoendID,
sig.r, sig.r,
sig.s, sig.s,
@ -1281,7 +1557,7 @@ describe("P2PIX", () => {
}); });
}); });
describe("Allowlist Settings", async () => { describe("Allowlist Settings", async () => {
it(" should revert if the msg.sender differs from deposit's seller", async () => { it("should revert if the msg.sender differs from deposit's seller", async () => {
const root = ethers.utils.keccak256( const root = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes("root"), ethers.utils.toUtf8Bytes("root"),
); );

View File

@ -14,4 +14,5 @@ export enum P2PixErrors {
LengthMismatch = "LengthMismatch", LengthMismatch = "LengthMismatch",
AddressDenied = "AddressDenied", AddressDenied = "AddressDenied",
AmountNotAllowed = "AmountNotAllowed", AmountNotAllowed = "AmountNotAllowed",
LockExpired = "LockExpired",
} }